製品属性リストのデザインパターン


9

ウェブサイトの製品データベースの更新に取り組んでいます。MySQLに組み込まれていますが、これは一般的なデータベース設計パターンの問題です。

Supertype / Subtypeパターンへの切り替えを計画しています。現在/以前のデータベースは、主に単一のタイプの製品に関するデータを含む単一のテーブルです。私たちは、異なる製品を含めるために製品提供を拡大することを検討しています。

この新しいドラフトデザインは次のようになります。

Product             product_[type]          product_attribute_[name]
----------------    ----------------        ----------------------------
part_number (PK)    part_number (FK)        attributeId (PK)
UPC                 specific_attr1 (FK)     attribute_name
price               specific_attr2 (FK)
...                 ...

製品の属性表について質問があります。ここでのアイデアは、色:赤、緑、青、または材料:プラスチック、木材、クロム、アルミニウムなどの特定の属性のリストを持つことができる製品です。

このリストはテーブルに格納され、その属性項目の主キー(PK)は特定の製品テーブルで外部キー(FK)として使用されます。

(Martin Fowler氏の著書「Patterns of Enterprise Application Architecture」では、これを「外部キーマッピング」と呼んでいます)

これにより、Webサイトインターフェースは、指定された属性タイプの属性のリストをプルし、ドロップダウン選択メニューまたはその他のUI要素にそれを吐き出すことができます。このリストは、属性値の「許可された」リストと考えることができます。

特定の製品をプルするときに発生する結合の数が多すぎるように見えます。すべての製品属性テーブルを製品に結合して、その属性のフィールドを取得できるようにする必要があります。一般的に、そのフィールドは、単にその名前の文字列(varchar)にすぎません。

この設計パターンでは、多数のテーブルが作成されるだけでなく、属性ごとにテーブルが作成されます。これに対抗する1つのアイデアは、すべての製品属性に対して「グラブバッグ」テーブルのようなものを作成することです。このようなもの:

product_attribute
----------------
attributeId (PK) 
name
field_name

このようにすると、テーブルは次のようになります。

1  red     color
2  blue    color
3  chrome  material
4  plastic material
5  yellow  color
6  x-large size

これはテーブルのクリープを減らすのに役立ちますが、結合の数を減らすことはなく、非常に多くの異なるタイプを1つのテーブルに結合することは少し間違っているように感じます。ただし、使用可能なすべての「色」属性をかなり簡単に取得できます。

ただし、色のRGB値など、単なる「名前」よりも多くのフィールドを持つ属性がある場合があります。これには、特定の属性が別のテーブルを持っているか、名前と値のペアの単一のフィールドを持っている必要があります(これには独自の欠点があります)。

私が考えることができる最後のデザインパターンは、実際の属性値を特定の製品テーブルに格納し、「属性テーブル」をまったく持たないことです。このようなもの:

Product             product_[type] 
----------------    ----------------
part_number (PK)    part_number (FK) 
UPC                 specific_attr1 
price               specific_attr2 
...                 ...

別のテーブルへの外部キーの代わりに、次のような実際の値が含まれます。

part_number    color    material
-----------    -----    --------
1234           red      plastic

これは結合を排除し、テーブルのクリープを防止します(たぶん?)。ただし、これにより、属性の「承認済みリスト」を作成できなくなります。特定のフィールド(つまり、色)に現在入力されているすべての値を返すこともできますが、これにより、特定の属性の値の「承認済みリスト」を作成する必要がなくなります。

そのリストを作成するには、「グラブバッグ」属性テーブルを作成するか、各属性に複数のテーブル(テーブルクリープ)を用意する必要があります。

これにより、製品名が複数の場所に配置されるという大きな欠点(そして私がこのアプローチを使用したことがない理由)が作成されます。

「マスター属性テーブル」に「赤」のカラー値があり、それを「product_ [type]」テーブルにも格納している場合、「マスター」テーブルを更新すると、アプリケーションがデータ整合性の問題を引き起こす可能性があります「product_type」テーブルの古い値ですべてのレコードも更新しません。

したがって、このシナリオの長い説明と分析の後で、私の認識は、これは珍しいシナリオではない可能性があり、このタイプの状況に名前が付けられることさえあるということです。

この設計課題に対する一般的に受け入れられている解決策はありますか?テーブルが比較的小さい場合、潜在的に多数の結合が許容されますか?状況によっては、属性PKの代わりに属性名を格納できますか?私が考えていない別の解決策はありますか?

この製品データベース/アプリケーションに関する注意事項:

  • 製品が頻繁に更新/追加/削除されない
  • 属性が頻繁に更新/追加/削除されない
  • テーブルは、情報の読み取り/返却のために最も頻繁に照会されます
  • サーバー側のキャッシュは、特定のクエリ/結果の結果をキャッシュするために有効になっています
  • 私は1つの製品タイプから始めて、時間の経過とともに他の製品タイプを拡張/追加することを計画しており、潜在的に10以上の異なるタイプがあるでしょう

1
いくつの製品タイプがありますか?
dezso 2012

1
良い質問。3〜4の小ささから始まりますが、
潜在的

「許可された属性のリスト」とはどういう意味ですか?
NoChance 2012

申し訳ありませんが、「属性値」である必要があります。属性に許可されているすべての値をリストしたテーブルがあるという考え。つまり。こちらは、この商品タイプで使用可能な10色のリストです。これらの10は、誰かが選択できる「許可」値です。
ジャンベルトゥッチ

最終的にその上に「ビュー」を作成した場合、これらのすべての属性値を製品タイプテーブルに結合しても問題ないでしょうか。
jmbertucci 2012

回答:


17

個人的には、次のようなモデルを使用します。

製品表はかなり基本的なもので、主な製品の詳細は次のとおりです。

create table product
(
  part_number int, (PK)
  name varchar(10),
  price int
);
insert into product values
(1, 'product1', 50),
(2, 'product2', 95.99);

次に、さまざまな属性をそれぞれ格納する属性テーブル。

create table attribute
(
  attributeid int, (PK)
  attribute_name varchar(10),
  attribute_value varchar(50)
);
insert into attribute values
(1, 'color', 'red'),
(2, 'color', 'blue'),
(3, 'material', 'chrome'),
(4, 'material', 'plastic'),
(5, 'color', 'yellow'),
(6, 'size', 'x-large');

最後に、product_attributeテーブルを、各製品とそれに関連付けられたその属性の間のJOINテーブルとして作成します。

create table product_attribute
(
  part_number int, (FK)
  attributeid int  (FK) 
);
insert into product_attribute values
(1,  1),
(1,  3),
(2,  6),
(2,  2),
(2,  6);

2つの結合で調べているデータの使用方法に応じて、次のようにします。

select *
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid;

SQL Fiddle with Demoを参照してください。次の形式でデータを返します。

PART_NUMBER | NAME       | PRICE | ATTRIBUTEID | ATTRIBUTE_NAME | ATTRIBUTE_VALUE
___________________________________________________________________________
1           | product1   | 50    | 1           | color          | red
1           | product1   | 50    | 3           | material       | chrome
2           | product2   | 96    | 6           | size           | x-large
2           | product2   | 96    | 2           | color          | blue
2           | product2   | 96    | 6           | size           | x-large

ただし、PIVOTすべての属性を列とする1つの行がある形式でデータを返す場合はCASE、集計を含むステートメントを使用できます。

SELECT p.part_number,
  p.name,
  p.price,
  MAX(IF(a.ATTRIBUTE_NAME = 'color', a.ATTRIBUTE_VALUE, null)) as color,
  MAX(IF(a.ATTRIBUTE_NAME = 'material', a.ATTRIBUTE_VALUE, null)) as material,
  MAX(IF(a.ATTRIBUTE_NAME = 'size', a.ATTRIBUTE_VALUE, null)) as size
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid
group by p.part_number, p.name, p.price;

SQL Fiddle with Demoを参照してください。データは次の形式で返されます:

PART_NUMBER | NAME       | PRICE | COLOR | MATERIAL | SIZE
_________________________________________________________________
1           | product1   | 50    | red   | chrome   | null
2           | product2   | 96    | blue  | null     | x-large

ご覧のように、データの方が適切な形式である可能性がありますが、属性の数が不明な場合は、属性名がハードコーディングされているため、簡単に使用できなくなります。そのため、MySQLでは、準備されたステートメントを使用して動的なピボットを作成できます。。コードは次のようになります(SQL Fiddle With Demoを参照)。

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(IF(a.attribute_name = ''',
      attribute_name,
      ''', a.attribute_value, NULL)) AS ',
      attribute_name
    )
  ) INTO @sql
FROM attribute;

SET @sql = CONCAT('SELECT p.part_number
                    , p.name
                    , ', @sql, ' 
                   from product p
                   left join product_attribute t
                     on p.part_number = t.part_number
                   left join attribute a
                     on t.attributeid = a.attributeid
                   GROUP BY p.part_number
                    , p.name');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

これにより、2番目のバージョンと同じ結果が生成されます。ハードコードする必要はありません。これをモデル化する方法はたくさんありますが、このデータベース設計が最も柔軟だと思います。


+1-素晴らしい答え。受け入れる前に、この回答をもう一度読んで消化するまで、少し時間がかかります。これは、結合と製品属性に関する私の質問に対する優れた解決策のように見え、ピボットと準備されたステートメントの例だけでなく、それを超えています。だから、私はそのための+1から始めます。=)
jmbertucci 2012

@jmbertucciはテーブルのクエリについて心配しているようだったので、サンプルをいくつか提供すると思いました。:)
タリン

確かに。私は、属性への製品のクロステーブルを行うことを見なかった「doh」に行きます。おそらく、デザインパターンや理論に没頭した後で、考えすぎたケースでしょう。また、私のDBAの経験は基本的なものであり、準備されたステートメントでより多くのことを行う必要があるので、あなたのインクルージョンが最も役に立ちます。そして、この答えは、私が持っていた「作家のブロック」を打破するのに役立ちました。それで、私はこのプロジェクトを進めることができます。=)
jmbertucci 2012

さて、1つの質問...それは遅いですか?私はあなたが... 10個の属性を持つクエリにのみ10K製品を30秒以上かかりますように落ちた
ZenithS

@ZenithSクエリを実行する列にインデックスを表示し、場合によってはインデックスを追加するためにテストする必要があります。テストを実行するMySQLインスタンスがありません。
タリン

0

Tarynの答えを拡張し、attribute_name列の代わりに新しいテーブルattribute_typeを指すfk_attribute_type_id列が含まれるように属性テーブルを変更します。

つまり、1つのテーブルに構造化された属性タイプがあり、いつでも1か所で変更できます。

私の意見では、列挙型(attribute_name列内(およびその上に実際には名前ではなく、その属性型)など)よりも、「ダイヤル」の種類(可能な型のテーブル)で作業する方が良いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.