Modèle de conception de liste d'attributs de produit

9

Je travaille sur la mise à jour de la base de données des produits de notre site Web. Il est construit dans MySQL, mais il s'agit plus d'une question générale de modèle de conception de base de données.

Je prévois de passer à un modèle Supertype / Subtype. Notre base de données actuelle / précédente est principalement une table unique qui contient des données sur un seul type de produit. Nous envisageons d'élargir notre offre de produits pour inclure des produits différents.

Ce nouveau projet de conception est comme ceci:

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

J'ai une question concernant les tableaux d'attributs du produit. L'idée ici est qu'un produit peut avoir une liste d'attributs donnés tels que la couleur: rouge, vert, bleu ou le matériau: plastique, bois, chrome, aluminium, etc.

Cette liste serait stockée dans une table et la clé primaire (PK) pour cet élément d'attribut sera utilisée dans la table de produit spécifique en tant que clé étrangère (FK).

(Le livre de Martin Fowler intitulé Patterns of Enterprise Application Architecture appelle ceci " Foreign Key Mapping ")

Cela permet à une interface de site Web d'extraire la liste des attributs pour un type d'attribut donné et de la recracher dans un menu de sélection déroulant ou un autre élément d'interface utilisateur. Cette liste peut être considérée comme une liste "autorisée" de valeurs d'attribut.

Le nombre de jointures qui finissent par se produire lors de l'extraction d'un produit spécifique me semble excessif. Vous devez joindre chaque table d'attributs de produit au produit pour pouvoir obtenir les champs de cet attribut. Généralement, ce champ peut simplement n'être rien de plus qu'une chaîne (varchar) pour son nom.

Ce modèle de conception finit par créer un grand nombre de tables et vous vous retrouvez avec une table pour chaque attribut. Une idée pour contrecarrer cela serait de créer quelque chose de plus d'une table de «sac de saisie» pour tous les attributs de produit. Quelque chose comme ça:

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

De cette façon, votre table pourrait ressembler à ceci:

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

Cela pourrait aider à réduire le fluage de la table, mais cela ne réduit pas le nombre de jointures et il semble un peu mal de combiner autant de types différents en une seule table. Mais vous pourriez obtenir assez facilement tous les attributs de «couleur» disponibles.

Cependant, il peut y avoir un attribut qui a plus de champs que simplement "nom" comme la valeur RVB d'une couleur. Cela nécessiterait que cet attribut spécifique ait éventuellement une autre table ou un seul champ pour la paire nom: valeur (qui a ses propres inconvénients).

Le dernier modèle de conception auquel je peux penser est de stocker la valeur d'attribut réelle dans la table de produit spécifique et de ne pas avoir de «table d'attribut» du tout. Quelque chose comme ça:

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

Au lieu d'une clé étrangère vers une autre table, elle contiendrait la valeur réelle telle que:

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

Cela éliminerait les jointures et empêcherait le fluage de la table (peut-être?). Cependant, cela empêche d'avoir une «liste autorisée» d'attributs. Vous pouvez renvoyer toutes les valeurs actuellement saisies pour un champ donné (c'est-à-dire: couleur) mais cela élimine également l'idée d'avoir une «liste autorisée» de valeurs pour un attribut donné.

Pour avoir cette liste, vous devez toujours créer une table d'attributs «grab bag» ou avoir plusieurs tables (fluage de table) pour chaque attribut.

Cela crée le plus gros inconvénient (et pourquoi je n'ai jamais utilisé cette approche) d'avoir maintenant le nom du produit à plusieurs endroits.

Si vous avez la valeur de couleur «rouge» dans la «table d'attributs principale» et que vous la stockez également dans la table «produit_ [type]», une mise à jour de la table «principale» entraînera un problème potentiel d'intégrité des données si l'application ne fonctionne pas. 'ne mettez pas non plus à jour tous les enregistrements avec l'ancienne valeur dans la table "product_type".

Donc, après mes longues explications et analyses de ce scénario, je me rends compte que cela ne peut pas être un scénario inhabituel et il pourrait même y avoir un nom pour ce type de situation.

Existe-t-il des solutions généralement acceptées à ce défi de conception? Le nombre potentiellement important de jointures est-il acceptable si les tables sont relativement petites? Le stockage du nom d'attribut, au lieu d'un PK d'attribut est-il acceptable dans certaines situations? Y a-t-il une autre solution à laquelle je ne pense pas?

Quelques notes sur cette base de données / application de produit:

  • Les produits ne sont pas fréquemment mis à jour / ajoutés / supprimés
  • Les attributs ne sont pas fréquemment mis à jour / ajoutés / supprimés
  • Le tableau est le plus fréquemment interrogé pour lire / renvoyer des informations
  • La mise en cache côté serveur est activée pour mettre en cache le résultat d'une requête / résultat donné
  • Je prévois de commencer avec un seul type de produit et d'étendre / ajouter d'autres au fil du temps et j'aurai potentiellement plus de 10 types différents
jmbertucci
la source
1
Combien de types de produits aurez-vous?
dezso
1
Bonne question. Il commencera petit 3-4 mais grossira
volontairement
Qu'entendez-vous par «liste d'attributs autorisés»?
NoChance
Désolé, cela devrait être "valeur d'attribut". L'idée que vous avez un tableau répertoriant toutes les valeurs autorisées pour un attribut. C'est à dire. voici une liste de 10 couleurs que ce type de produit peut être. Ces 10 sont les valeurs "autoriser" que quelqu'un pourrait choisir.
jmbertucci
Je me demande si ce serait bien d'avoir toutes ces valeurs d'attribut jointes à la table des types de produits si je finissais par créer une "vue" dessus?
jmbertucci

Réponses:

17

Personnellement, j'utiliserais un modèle similaire au suivant:

Le tableau des produits serait assez basique, les principaux détails de votre produit:

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

Deuxièmement, la table d'attributs pour stocker chacun des différents attributs.

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

Enfin, créez la table product_attribute en tant que table JOIN entre chaque produit et ses attributs qui lui sont associés.

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

Selon la façon dont vous souhaitez utiliser les données, vous examinez deux jointures:

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;

Voir SQL Fiddle avec démo . Cela renvoie des données au format:

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

Mais si vous souhaitez renvoyer les données dans un PIVOTformat où vous avez une ligne avec tous les attributs sous forme de colonnes, vous pouvez utiliser des CASEinstructions avec un agrégat:

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;

Voir SQL Fiddle avec démo . Les données sont retournées au format:

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

Comme vous le voyez, les données peuvent être dans un meilleur format pour vous, mais si vous avez un nombre inconnu d'attributs, cela deviendra facilement intenable en raison des noms d'attributs codés en dur, donc dans MySQL vous pouvez utiliser des instructions préparées pour créer des pivots dynamiques . Votre code serait le suivant (voir 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;

Cela génère le même résultat que la deuxième version sans avoir besoin de coder en dur quoi que ce soit. Bien qu'il existe de nombreuses façons de modéliser cela, je pense que cette conception de base de données est la plus flexible.

Taryn
la source
+1 - Une réponse incroyablement écrite. Je prends encore quelques instants pour relire et digérer cette réponse avant d'accepter. Cela ressemble à une bonne solution à ma question sur les jointures et les attributs de produit et va même au-delà avec des exemples de pivots et d'instructions préparées. Donc, je vais commencer avec un +1 pour cela. =)
jmbertucci
@jmbertucci, vous sembliez préoccupé par l'interrogation des tables, alors j'ai pensé que je vous fournirais quelques exemples. :)
Taryn
En effet. Je vais "doh" que je n'ai pas vu faire un tableau croisé de produits à attribuer. Probablement un cas de réflexion excessive, surtout après avoir immergé les modèles de conception et les théories. De plus, mon expérience DBA est basique et faire plus avec les instructions préparées est quelque chose dont j'ai besoin, donc votre inclusion est très utile. Et cette réponse a aidé à briser ce «bloc d'écrivains» que j'avais pour que je puisse passer à autre chose avec ce projet, ce qui fait ma journée. =)
jmbertucci
eh bien, une question ... est-ce lent? Je suis tombé comme si vous
mettiez
@ZenithS Vous devrez le tester pour voir et éventuellement ajouter des index sur les colonnes que vous interrogez. Je n'ai pas d'instance MySQL pour faire des tests.
Taryn
0

Je développerais la réponse de Taryn et modifierais la table d'attributs pour avoir la colonne fk_attribute_type_id qui sera à la place de la colonne attribute_name et pointe vers une nouvelle table attribute_type.

Vous avez donc des types d'attributs structurés dans une table et vous pouvez les modifier à tout moment en un seul endroit.

À mon avis, il vaut mieux travailler avec le type de "numérotation" (table avec les types possibles) qu'avec le type enum (comme sa colonne dans l'attribut_name (et en plus de cela en fait son pas nom, son type d'attribut)).

Ales
la source