Entwurfsmuster der Produktattributliste

9

Ich arbeite an der Aktualisierung der Produktdatenbank unserer Website. Es ist in MySQL integriert, aber dies ist eher eine allgemeine Frage zum Datenbankdesignmuster.

Ich plane, zu einem Supertype / Subtype-Muster zu wechseln. Unsere aktuelle / vorherige Datenbank besteht hauptsächlich aus einer einzelnen Tabelle, die Daten zu einem einzelnen Produkttyp enthält. Wir möchten unser Produktangebot um unterschiedliche Produkte erweitern.

Dieser neue Entwurfsentwurf ist wie folgt:

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

Ich habe eine Frage zu den Produktattributtabellen. Die Idee hier ist, dass ein Produkt eine Liste gegebener Attribute wie Farbe haben kann: Rot, Grün, Blau oder Material: Kunststoff, Holz, Chrom, Aluminium usw.

Diese Liste wird in einer Tabelle gespeichert und der Primärschlüssel (PK) für dieses Attributelement wird in der spezifischen Produkttabelle als Fremdschlüssel (FK) verwendet.

(Martin Fowlers Buch Patterns of Enterprise Application Architecture nennt dies " Foreign Key Mapping ")

Auf diese Weise kann eine Website-Oberfläche die Liste der Attribute für einen bestimmten Attributtyp abrufen und in einem Dropdown-Auswahlmenü oder einem anderen UI-Element ausspucken. Diese Liste kann als "autorisierte" Liste von Attributwerten betrachtet werden.

Die Anzahl der Verknüpfungen, die beim Ziehen eines bestimmten Produkts auftreten, erscheint mir zu hoch. Sie müssen jede Produktattributtabelle mit dem Produkt verknüpfen, damit Sie die Felder dieses Attributs abrufen können. Normalerweise ist dieses Feld einfach nichts anderes als eine Zeichenfolge (varchar) für seinen Namen.

Dieses Entwurfsmuster erstellt eine große Anzahl von Tabellen, und Sie erhalten eine Tabelle für jedes Attribut. Eine Idee, dem entgegenzuwirken, wäre, für alle Produktattribute eher eine „Grab-Bag“ -Tabelle zu erstellen. Etwas wie das:

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

Auf diese Weise könnte Ihr Tisch folgendermaßen aussehen:

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

Dies könnte dazu beitragen, das Kriechen von Tabellen zu verringern, verringert jedoch nicht die Anzahl der Verknüpfungen und es fühlt sich ein wenig falsch an, so viele verschiedene Typen in einer einzigen Tabelle zu kombinieren. Sie können jedoch ganz einfach alle verfügbaren Farbattribute abrufen.

Möglicherweise gibt es jedoch ein Attribut, das mehr Felder als nur "Name" enthält, z. B. den RGB-Wert einer Farbe. Dies würde erfordern, dass dieses bestimmte Attribut möglicherweise eine andere Tabelle oder ein einzelnes Feld für das Name: Wert-Paar hat (was seine eigenen Nachteile hat).

Das letzte Entwurfsmuster, an das ich denken kann, ist das Speichern des tatsächlichen Attributwerts in der spezifischen Produkttabelle und überhaupt keine „Attributtabelle“. Etwas wie das:

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

Anstelle eines Fremdschlüssels für eine andere Tabelle würde er den tatsächlichen Wert enthalten, z.

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

Dies würde Verknüpfungen eliminieren und ein Kriechen der Tabelle verhindern (vielleicht?). Dies verhindert jedoch, dass eine „autorisierte Liste“ von Attributen vorhanden ist. Sie können alle aktuell eingegebenen Werte für ein bestimmtes Feld (dh Farbe) zurückgeben, dies beseitigt jedoch auch die Idee, eine „autorisierte Liste“ von Werten für ein bestimmtes Attribut zu haben.

Um diese Liste zu haben, müssten Sie immer noch eine Attributtabelle "Grab Bag" erstellen oder für jedes Attribut mehrere Tabellen (Table Creep) haben.

Dies schafft den größeren Nachteil (und warum ich diesen Ansatz noch nie verwendet habe), den Produktnamen jetzt an mehreren Stellen zu haben.

Wenn Sie den Farbwert "rot" in der Tabelle "Hauptattribut" haben und ihn auch in der Tabelle "Produkt_ [Typ]" speichern, führt eine Aktualisierung der Tabelle "Master" zu einem potenziellen Datenintegritätsproblem, wenn die Anwendung dies nicht tut Aktualisieren Sie nicht alle Datensätze mit dem alten Wert in der Tabelle "product_type".

Nach meiner langwierigen Erklärung und Analyse dieses Szenarios ist mir klar geworden, dass dies kein ungewöhnliches Szenario sein kann und es möglicherweise sogar einen Namen für diese Art von Situation gibt.

Gibt es allgemein akzeptierte Lösungen für diese Designherausforderung? Ist die potenziell große Anzahl von Joins akzeptabel, wenn die Tabellen relativ klein sind? Ist das Speichern des Attributnamens anstelle einer Attribut-PK unter bestimmten Umständen akzeptabel? Gibt es eine andere Lösung, über die ich nicht nachdenke?

Einige Hinweise zu dieser Produktdatenbank / Anwendung:

  • Produkte werden nicht häufig aktualisiert / hinzugefügt / entfernt
  • Attribute werden nicht häufig aktualisiert / hinzugefügt / entfernt
  • Die Tabelle wird am häufigsten zum Lesen / Zurückgeben von Informationen abgefragt
  • Das serverseitige Caching ist aktiviert, um das Ergebnis einer bestimmten Abfrage / eines bestimmten Ergebnisses zwischenzuspeichern
  • Ich plane, mit nur einem Produkttyp zu beginnen und andere im Laufe der Zeit zu erweitern / hinzuzufügen, und werde möglicherweise mehr als 10 verschiedene Typen haben
jmbertucci
quelle
1
Wie viele Produkttypen haben Sie?
Dekso
1
Gute Frage. Es wird klein 3-4 beginnen, aber
potenziell
Was meinst du mit "Autorisierte Liste von Attributen"?
NoChance
Entschuldigung, es sollte "Attributwert" sein. Die Idee, dass Sie eine Tabelle haben, in der alle für ein Attribut zulässigen Werte aufgelistet sind. Dh. Hier ist eine Liste von 10 Farben, die dieser Produkttyp sein kann. Diese 10 sind die "Autorisierungs" -Werte, die jemand wählen könnte.
jmbertucci
Ich frage mich, ob es in Ordnung wäre, all diese Attributwerte mit der Produkttyp-Tabelle zu verknüpfen, wenn ich letztendlich eine "Ansicht" darüber erstelle.
jmbertucci

Antworten:

17

Ich persönlich würde ein Modell verwenden, das dem folgenden ähnlich ist:

Die Produkttabelle wäre ziemlich einfach, Ihre wichtigsten Produktdetails:

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

Zweitens die Attributtabelle zum Speichern der verschiedenen Attribute.

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');

Erstellen Sie schließlich die Tabelle product_attribute als JOIN-Tabelle zwischen jedem Produkt und den damit verbundenen Attributen.

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);

Je nachdem, wie Sie die Daten verwenden möchten, sehen Sie sich zwei Verknüpfungen an:

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;

Siehe SQL Fiddle mit Demo . Dies gibt Daten im Format zurück:

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

Wenn Sie die Daten jedoch in einem PIVOTFormat zurückgeben möchten, in dem Sie eine Zeile mit allen Attributen als Spalten haben, können Sie CASEAnweisungen mit einem Aggregat verwenden:

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;

Siehe SQL Fiddle mit Demo . Die Daten werden im folgenden Format zurückgegeben:

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

Wie Sie sehen, haben die Daten möglicherweise ein besseres Format für Sie. Wenn Sie jedoch eine unbekannte Anzahl von Attributen haben, werden sie aufgrund hartcodierter Attributnamen leicht unhaltbar. In MySQL können Sie vorbereitete Anweisungen verwenden, um dynamische Pivots zu erstellen . Ihr Code wäre wie folgt (siehe 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;

Dies führt zum gleichen Ergebnis wie die zweite Version, ohne dass ein Hardcode erforderlich ist. Obwohl es viele Möglichkeiten gibt, dies zu modellieren, denke ich, dass dieses Datenbankdesign das flexibelste ist.

Taryn
quelle
+1 - Eine fantastisch geschriebene Antwort. Ich nehme mir noch einige Momente Zeit, um diese Antwort erneut zu lesen und zu verarbeiten, bevor ich sie akzeptiere. Es scheint eine gute Lösung für meine Frage nach Verknüpfungen und Produktattributen zu sein und geht sogar mit Beispielen für Drehpunkte und vorbereitete Aussagen darüber hinaus. Also werde ich mit einer +1 dafür beginnen. =)
jmbertucci
@jmbertucci Sie schienen besorgt über das Abfragen der Tabellen zu sein, also dachte ich mir, ich würde Ihnen einige Beispiele zur Verfügung stellen. :)
Taryn
Tatsächlich. Ich gehe "doh", dass ich nicht gesehen habe, wie eine Kreuztabelle von Produkten zugeordnet wurde. Wahrscheinlich ein Fall von Überdenken, insbesondere nach dem Eintauchen in Designmuster und Theorien. Außerdem ist meine DBA-Erfahrung grundlegend und ich brauche mehr, um mehr mit vorbereiteten Aussagen zu tun. Daher ist Ihre Einbeziehung am hilfreichsten. Und diese Antwort hat dazu beigetragen, die "Schreibblockade", die ich hatte, aufzubrechen, damit ich mit diesem Projekt weitermachen kann, das meinen Tag ausmacht. =)
jmbertucci
Nun, eine Frage ... ist es langsam? Ich bin der Meinung, dass Sie mehr als 30 Sekunden
brauchen werden
@ZenithS Sie müssten es testen, um Indizes für die von Ihnen abgefragten Spalten anzuzeigen und möglicherweise hinzuzufügen. Ich habe keine MySQL-Instanz, an der ich Tests durchführen kann.
Taryn
0

Ich würde Taryns Antwort erweitern und die Attributtabelle so ändern, dass sie die Spalte fk_attribute_type_id enthält , die anstelle der Spalte attribute_name steht und auf die neue Tabelle attribute_type verweist.

Sie haben also Attributtypen in einer Tabelle strukturiert und können diese jederzeit an einer Stelle ändern.

Meiner Meinung nach ist es besser, mit "dial" zu arbeiten (Tabelle mit möglichen Typen) als mit enum type (wie in der Spalte attribute_name (und obendrein ist es eigentlich nicht name, sein Attributtyp)).

Ales
quelle