web-dev-qa-db-ja.com

キー値のこのデータベーススキーマに名前はありますか?

使い慣れたフォーム(エンティティごとに1行、属性ごとに1列)から見慣れないフォーム(属性ごとにエンティティごとに1行)にデータベースをリファクタリングしたクライアントからのルーチンデータフィードを処理します。

前:属性ごとに1列

ID   Ht_cm   wt_kg   Age_yr  ... 
1      190      82     43    ...
2      170      60     22    ...
3      205      90     51    ...

後:すべての属性に1列

ID    Metric   Value
 1     Ht_cm     190
 1     Wt_kg     82
 1     Age_yr    43
 1      ...
 2     Ht_cm     170
 2     Wt_kg     60
 2     Age_yr    22
 2     ...
 3     Ht_cm     205
 3     Wt_kg     90
 3     Age_yr    51
 3     ...

このデータベース構造に名前はありますか?相対的な利点は何ですか?古い方法では、特定の属性(null以外、negative以外など)に有効性制約を設定する方が簡単で、平均の計算も簡単です。しかし、データベースをリファクタリングせずに新しい属性を追加する方が簡単な場合があることがわかります。これはデータを構造化する標準的な方法ですか?

70
prototype

これはEntity-Attribute-Value(「名前と値のペア」と呼ばれることもあります)と呼ばれ、リレーショナルデータベースでEAVパターンを使用する場合の「四角い穴の丸いペグ」の典型的なケースです。

あなたがEAVを使用してはならない理由のリストは次のとおりです:

  • データ型は使用できません。値が日付、数値、または金額(10進数)であるかどうかは関係ありません。常にvarcharに強制変換されます。これは、マイナーなパフォーマンスの問題から大規模なガタガタ(月例のロールアップレポートで1セントの変動を追跡する必要があったことはありますか?).
  • 制約を(簡単に)強制することはできません。 「すべての人が0から3メートルの高さを持っている必要がある」または「年齢はnullでなく、== 0である必要があります」を強制するために、これらの制約のそれぞれの1-2行とは対照的に、とんでもない量のコードが必要です。適切にモデル化されたシステムで。
  • 上記に関連して、各クライアントに必要な情報を取得することを簡単に保証することはできません(年齢がクライアントから欠落している可能性があり、次のクライアントは高さが欠落している可能性があります)。あなたはそれを行うことができますが、それはSELECT height, weight, age FROM Client where height is null or weight is nullよりもはるかに難しいことです。
  • 再度関連しますが、重複データは検出が非常に困難です(1つのクライアントで2つの年齢が与えられた場合はどうなりますか?以下のようにデータを非EAV化すると、1つの属性が2つある場合、2行の結果が得られます。1つのクライアントの場合2つの属性に2つの個別のエントリがある場合、以下のクエリからfour行が取得されます)。
  • 属性名の一貫性を保証することもできません。 「Age_yr」は「AGE_IN_YEARS」または「age」になる可能性があります。 (確かに、これは、人々がデータを挿入しているときよりも、抽出を受け取っているときの方が問題ではありません。)
  • あらゆる種類の重要なクエリは、完全な災害です。 3つの属性を持つEAVシステムを関係付けて合理的にクエリできるようにするには、EAVテーブルの3つの結合が必要です。

比較:

SELECT cID.ID AS [ID], cH.Value AS [Height], cW.Value AS [Weight], cA.Value AS [Age]
FROM (SELECT DISTINCT ID FROM Client) cID 
      LEFT OUTER JOIN 
    Client cW ON cID.ID = cW.ID AND cW.Metric = "Wt_kg" 
      LEFT OUTER JOIN 
    Client cH ON cID.ID = cH.ID AND cW.Metric = "Ht_cm" 
      LEFT OUTER JOIN 
    Client cA ON cID.ID = cA.ID AND cW.Metric = "Age_yr"

に:

SELECT c.ID, c.Ht_cm, c.Wt_kg, c.Age_yr
FROM Client c

EAVを使用する必要がある場合の(非常に短い)リストは次のとおりです:

  • 絶対にありません回避策がなく、データベースでスキーマのないデータをサポートする必要がある場合。
  • 「もの」を保存する必要があるだけで、より構造化された形式でそれを必要とする必要がない場合。ただし、「要件の変更」と呼ばれるモンスターに注意してください。

私がこの投稿全体を費やして、EAVがほとんどの場合ひどい考えである理由を詳しく説明したことはわかっていますが、それが必要/不可避な場合がいくつかあります。ただし、ほとんどの場合(上記の例を含む)、それは価値があるよりもはるかに面倒になります。 EAVタイプのデータ入力を幅広くサポートする必要がある場合は、Key-Valueシステムにそれらを格納することを検討する必要があります。 Hadoop/HBase、CouchDB、MongoDB、Cassandra、BerkeleyDB。

95
Simon Righarts

エンティティ属性値 (EAV)

私を含む多くの人からアンチパターンと見なされています。

ここにあなたの選択肢があります:

  1. データベースを使用 テーブル継承

  2. xMLデータと SQLXML関数 を使用する

  3. hBaseのようなnosqlデータベースを使用する

19
Neil McGuigan

PostgreSQLでは、EAV構造を処理する非常に良い方法の1つは、追加モジュール hstore です。バージョン8.4以降。私はマニュアルを引用します:

このモジュールは、単一のPostgreSQL値内にキーと値のペアのセットを格納するためのhstoreデータ型を実装します。これは、ほとんど検査されない多くの属性を持つ行や半構造化データなど、さまざまなシナリオで役立ちます。キーと値は単なるテキスト文字列です。

Postgres 9.2以降、 json タイプとそれに伴う機能のホスト(- ほとんどは9.3で追加されました )。

Postgres 9.4は(大いに優れています!)「バイナリJSON」データ型 jsonb のリストに追加しますオプション。高度なインデックスオプション。

16

EAV dbモデルがどのように批判され、「アンチパターン」と見なされているかを見るのはおかしいです。

私に関する限り、主な欠点は次のとおりです。

  • 学習曲線が急勾配すでにEAVの使用を開始したプロジェクトに参加した場合。確かに、結合(およびテーブル)の数を大幅に増やすため、クエリは難しいなので、理解するのにより多くの時間が必要になります。 Magentoプロジェクトを見て、プロジェクトの外部の開発者がDBでの作業にどのように苦労しているのかを確認してください。ただし、ドキュメントは十分に維持されています。
  • レポートには適していません、「M」で始まる名前などの人数を取得する必要がある場合...

ただし、このソリューションは絶対に破棄しないでください。理由は次のとおりです。

  • サイモンは「要件の変更」と呼ばれるモンスターについて話しました。私はこの表現が好きです:)そして、これがEAVが良い候補である理由です。これは「変更」に適していますなので、必要なだけ多くの属性を簡単に追加できます。もちろん、変更する要件によって異なります。まったく新しいビジネスについて話している場合は、もちろんdataModelを確認する必要がありますが、EAVには多くの柔軟性があります。厳密さが求められるからといって、これがあまり面白くないという意味ではありません。
  • 「データ型は使えない」とも言われた。 :これは間違っています複数の値テーブルがある場合があります、各dataTypeに1つ。次に、属性テーブルで、どの種類のdataTypeが属性であるかを指定する必要があります。実際、古典的なリレーショナル/ EAVとクラスの関係を組み合わせると、データベース設計に多くの興味深い可能性がもたらされます。
11

EAV構造を使用しているデータベースがある場合、さまざまな方法でデータを照会できます。

@ Simonの回答 は、複数の結合を使用してクエリを実行する方法をすでに示しています。

使用されるサンプルデータ:

CREATE TABLE yourtable ([ID] int, [Metric] varchar(6), [Value] int);

INSERT INTO yourtable ([ID], [Metric], [Value])
VALUES (1, 'Ht_cm', 190),
    (1, 'Wt_kg', 82),
    (1, 'Age_yr', 43),
    (2, 'Ht_cm', 170),
    (2, 'Wt_kg', 60),
    (2, 'Age_yr', 22),
    (3, 'Ht_cm', 205),
    (3, 'Wt_kg', 90),
    (3, 'Age_yr', 51);

PIVOT関数( SQL Server 2005 + / Oracle 11g + )を持つRDBMSを使用している場合は、次の方法でデータをクエリできます。

select id, Ht_cm, Wt_kg, Age_yr
from
(
  select id, metric, value
  from yourtable
) src
pivot
(
  max(value)
  for metric in (Ht_cm, Wt_kg, Age_yr)
) piv;

参照 SQL Fiddleデモあり

PIVOT関数にアクセスできない場合は、CASEステートメントで集計関数を使用してデータを返すことができます。

select id,
  max(case when metric ='Ht_cm' then value else null end) Ht_cm,
  max(case when metric ='Wt_kg' then value else null end) Wt_kg,
  max(case when metric ='Age_yr' then value else null end) Age_yr
from yourtable
group by id

参照 SQL Fiddleデモあり

これらのクエリはどちらも結果にデータを返します。

| ID | HT_CM | WT_KG | AGE_YR |
-------------------------------
|  1 |   190 |    82 |     43 |
|  2 |   170 |    60 |     22 |
|  3 |   205 |    90 |     51 |
10
Taryn