そこで、1つの製品を複数のカテゴリに接続できるデータベースを設計しようとしています。私が考え出したこの部分。しかし、私が解決できないのは、さまざまな種類の製品の詳細を保持するという問題です。
たとえば、商品は本(この場合、isbn、authorなどのようなその本を参照するメタデータが必要です)またはビジネスリスティング(異なるメタデータを持つ)である可能性があります。
どのように取り組むべきですか?
これを観測パターンと呼びます。
たとえば、3つのオブジェクト
Book
Title = 'Gone with the Wind'
Author = 'Margaret Mitchell'
ISBN = '978-1416548898'
Cat
Name = 'Phoebe'
Color = 'Gray'
TailLength = 9 'inch'
Beer Bottle
Volume = 500 'ml'
Color = 'Green'
テーブルは次のようになります。
Entity
EntityID Name Description
1 'Book' 'To read'
2 'Cat' 'Fury cat'
3 'Beer Bottle' 'To ship beer in'
。
PropertyType
PropertyTypeID Name IsTrait Description
1 'Height' 'NO' 'For anything that has height'
2 'Width' 'NO' 'For anything that has width'
3 'Volume' 'NO' 'For things that can have volume'
4 'Title' 'YES' 'Some stuff has title'
5 'Author' 'YES' 'Things can be authored'
6 'Color' 'YES' 'Color of things'
7 'ISBN' 'YES' 'Books would need this'
8 'TailLength' 'NO' 'For stuff that has long tails'
9 'Name' 'YES' 'Name of things'
。
Property
PropertyID EntityID PropertyTypeID
1 1 4 -- book, title
2 1 5 -- book, author
3 1 7 -- book, isbn
4 2 9 -- cat, name
5 2 6 -- cat, color
6 2 8 -- cat, tail length
7 3 3 -- beer bottle, volume
8 3 6 -- beer bottle, color
。
Measurement
PropertyID Unit Value
6 'inch' 9 -- cat, tail length
7 'ml' 500 -- beer bottle, volume
。
Trait
PropertyID Value
1 'Gone with the Wind' -- book, title
2 'Margaret Mitchell' -- book, author
3 '978-1416548898' -- book, isbn
4 'Phoebe' -- cat, name
5 'Gray' -- cat, color
8 'Green' -- beer bottle, color
編集:
ジェフリーは有効なポイントを上げたので(コメントを参照)、答えを拡大します。
このモデルでは、スキーマを変更せずに、任意のタイプのプロパティを使用して、任意の数のエンティティを動的に(オンフライで)作成できます。しかし、この柔軟性には代償があります。保存と検索は、通常のテーブルデザインよりも遅く、複雑です。
例を挙げましょう。ただし、最初に、作業を簡単にするために、モデルをフラット化してビューにします。
create view vModel as
select
e.EntityId
, x.Name as PropertyName
, m.Value as MeasurementValue
, m.Unit
, t.Value as TraitValue
from Entity as e
join Property as p on p.EntityID = p.EntityID
join PropertyType as x on x.PropertyTypeId = p.PropertyTypeId
left join Measurement as m on m.PropertyId = p.PropertyId
left join Trait as t on t.PropertyId = p.PropertyId
;
コメントからジェフリーの例を使用するには
with
q_00 as ( -- all books
select EntityID
from vModel
where PropertyName = 'object type'
and TraitValue = 'book'
),
q_01 as ( -- all US books
select EntityID
from vModel as a
join q_00 as b on b.EntityID = a.EntityID
where PropertyName = 'publisher country'
and TraitValue = 'US'
),
q_02 as ( -- all US books published in 2008
select EntityID
from vModel as a
join q_01 as b on b.EntityID = a.EntityID
where PropertyName = 'year published'
and MeasurementValue = 2008
),
q_03 as ( -- all US books published in 2008 not discontinued
select EntityID
from vModel as a
join q_02 as b on b.EntityID = a.EntityID
where PropertyName = 'is discontinued'
and TraitValue = 'no'
),
q_04 as ( -- all US books published in 2008 not discontinued that cost less than $50
select EntityID
from vModel as a
join q_03 as b on b.EntityID = a.EntityID
where PropertyName = 'price'
and MeasurementValue < 50
and MeasurementUnit = 'USD'
)
select
EntityID
, max(case PropertyName when 'title' than TraitValue else null end) as Title
, max(case PropertyName when 'ISBN' than TraitValue else null end) as ISBN
from vModel as a
join q_04 as b on b.EntityID = a.EntityID
group by EntityID ;
これは書くのが複雑に見えますが、詳しく調べると、CTEのパターンに気付く場合があります。
ここで、各オブジェクトプロパティに独自の列がある標準の固定スキーマ設計があるとします。クエリは次のようになります。
select EntityID, Title, ISBN
from vModel
WHERE ObjectType = 'book'
and PublisherCountry = 'US'
and YearPublished = 2008
and IsDiscontinued = 'no'
and Price < 50
and Currency = 'USD'
;
私は答えるつもりはありませんでしたが、今のところ受け入れられた答えは非常に悪い考えを持っています。単純な属性と値のペアを格納するためにリレーショナルデータベースを使用しないでください。それは将来的に多くの問題を引き起こすでしょう。
これに対処する最善の方法は、タイプごとに個別のテーブルを作成することです。
Product
-------
ProductId
Description
Price
(other attributes common to all products)
Book
----
ProductId (foreign key to Product.ProductId)
ISBN
Author
(other attributes related to books)
Electronics
-----------
ProductId (foreign key to Product.ProductId)
BatteriesRequired
etc.
各テーブルの各行は、現実世界に関する命題を表す必要があり、テーブルの構造とその制約は、表現されている現実を反映している必要があります。この理想に近づくほど、データはよりクリーンになり、レポート作成や他の方法でのシステムの拡張が容易になります。また、より効率的に実行されます。
スキーマレスアプローチを採用できます。
メタデータをTEXT列にJSONオブジェクトとして保持します(または他のシリアル化ですが、すぐに説明する理由からJSONの方が適しています)。
このテクニックの利点:
クエリの削減:1つのクエリですべての情報を取得し、「方向性のある」クエリ(メタメタデータを取得するため)や結合は必要ありません。
テーブルを変更する必要はなく、いつでも必要な属性を追加/削除できます(これは、一部のデータベースでは問題があります。たとえば、Mysqlはテーブルをロックし、巨大なテーブルでは時間がかかります)
JSONであるため、バックエンドで追加の処理を行う必要はありません。あなたのウェブページ(私はそれがウェブアプリケーションだと思います)はあなたのウェブサービスからJSONをそのまま読みます、そしてそれはそれです、あなたは好きなようにjavascriptでJSONオブジェクトを使うことができます。
問題:
スペースが無駄になる可能性があります。同じ著者の本が100冊ある場合、すべての本にauthor_idだけが含まれる著者テーブルの方がスペース的に経済的です。
インデックスを実装する必要があります。メタデータはJSONオブジェクトであるため、すぐにインデックスを作成することはできません。ただし、必要な特定のメタデータに特定のインデックスを実装するのはかなり簡単です。たとえば、作成者ごとにインデックスを作成する場合、author_idとitem_idを使用してauthor_idxテーブルを作成します。誰かが作成者を検索すると、このテーブルとアイテム自体を検索できます。
規模によっては、これはやり過ぎかもしれません。小規模な結合では問題なく機能します。
この種の問題では、3つの選択肢があります。
これは、実際のエンティティがすべて同じ方法で処理されている場合に機能します。少なくともほとんどの場合、「同じ」データではないにしても、少なくとも類似のデータが必要です。これは、実際の機能の違いがある場合に機能しなくなります。トースターの場合、ワット=ボルト*アンペアを計算している場合と同様に、本に対応する計算がない可能性があります。本のページ数とトースターの電圧を含むpages_voltsフィールドの作成を開始すると、物事が制御不能になります。
Damirが提案するようなプロパティ/値スキームを使用します。そこの賛否両論については、彼の投稿に対する私のコメントを参照してください。
私が通常提案するのは、タイプ/サブタイプスキームです。タイプコードと汎用フィールドを含む「product」のテーブルを作成します。次に、本、トースター、猫など、実際のタイプごとに、製品テーブルに接続された個別のテーブルを作成します。次に、本固有の処理を行う必要がある場合は、本のテーブルを処理します。一般的な処理を行う必要がある場合は、productテーブルを処理します。
製品を入力する必要があります。例えば製品テーブルにtype_idを含めます。これは、サポートする製品のカテゴリを指し、適切な関連属性についてクエリする他のテーブルを通知します。
これはあなたが探している種類の答えではないかもしれないことを理解していますが、残念ながら、リレーショナルデータベース(SQL)は構造化された事前定義スキーマのアイデアに基づいて構築されています。構造化されていないスキーマレスデータを、そのために構築されていないモデルに格納しようとしています。はい、技術的に無限の量のメタデータを保存できるようにファッジすることができますが、これはすぐに多くの問題を引き起こし、すぐに手に負えなくなります。 Wordpressと、このアプローチで発生した問題の量を見るだけで、なぜそれが良い考えではないのかが簡単にわかります。
幸いなことに、これはリレーショナルデータベースの長年の問題でした。そのため、ドキュメントアプローチを使用するNoSQLスキーマレスデータベースが開発され、過去10年間で非常に人気が高まっています。これは、フォーチュン500のすべてのテクノロジー企業が、変化し続けるユーザーデータを保存するために使用するものです。これにより、個々のレコードに、同じコレクション(テーブル)に残りながら、必要な数のフィールド(列)を含めることができます。
したがって、MongoDBなどのNoSQLデータベースを調べて、それらに変換するか、リレーショナルデータベースと組み合わせて使用することをお勧めします。それらを表す同じ量の列を持つ必要があることがわかっているすべてのタイプのデータはSQLに格納する必要があり、レコード間で異なることがわかっているすべてのタイプのデータはNoSQLデータベースに格納する必要があります。