J'ai essayé de modéliser des variantes de produits et j'ai pensé que je pourrais avoir besoin d'utiliser EAV. J'aurais peut-être pu le faire sans EAV, mais je crains d'avoir raté quelque chose. Voici ma conception:
Voici ce que j'essaie de représenter:
product
peut avoir 0 ou plus product variants
(Par exemple, un produit de t-shirt peut avoir des variantes de taille et de couleur).product variant
Peut avoir 1 ou plusieurs product variant options
(Par exemple, la variante de taille peut être petite, moyenne, grande).SKU
comprend 1 ou plusieurs product variant options
(Le tableau product_variant_option_combination
Contiendrait toutes les combinaisons possibles de `product_variant_options. Donc, s'il y avait 3 tailles et 3 couleurs, il y avait serait 3 * 3 = 9 combinaisons - et chaque combinaison aurait son propre SKU et son propre prix).product
peut avoir 1 ou plusieurs SKUs
.Si le produit n'a pas de variantes, ignorez simplement product_variants
, product_variant_options
Et product_variant_option_combinations
.
Cette conception est-elle saine? Vais-je finir par avoir des problèmes à l'interroger? Va-t-il évoluer? Est-ce normalisé?
MISE À JOUR 1
@Edper:
Si un produit peut avoir 0 ou plusieurs variantes de produit (mode optionnel) (par exemple taille, couleur, etc.). S'ensuit-il qu'une variante de produit peut également avoir 0 ou plusieurs produits ayant cette variante?
Je ne pense pas. Il est possible qu'un produit comme un "t-shirt" puisse avoir une variante de "taille" et un autre produit comme un "pantalon" puisse également avoir une variante de "taille", mais je pense que ce n'est qu'un hasard. Il n'est pas nécessaire de faire apparaître "size" comme un seul enregistrement car "size" peut avoir un contexte différent.
Les produits avec lesquels je traite varient considérablement et ils ont forcément des variantes nommées de manière similaire.
MISE À JOUR 2:
Voici un exemple de la façon dont je vois mes données:
J'ai encadré la variante Size
et ses valeurs associées. Je tiens à préciser que ces données ne sont pas considérées comme des données en double. La variante Size
pour les 3 produits est un hasard. Il n'est pas nécessaire de normaliser cela, je pense. Chaque produit peut avoir 0 ou plusieurs variantes - et elles me sont inconnues. J'attends des "doublons" (bien qu'ils ne soient pas vraiment des doublons comme ils le sont toujours dans le contexte d'un produit particulier - donc, la variante "Taille" du Widget 1 n'est pas la même que la variante "Taille" du Widget 2).
MISE À JOUR 3:
Je vois maintenant que, dans ma conception, il est possible pour un product
d'avoir plusieurs product_variants
Identiques. Je pense que cela peut être résolu en faisant de product_variants
. product_id
Et product_variants
.name
une clé composite. Cela signifie que le widget 1 ne peut avoir qu'une seule fois la variante "Taille".
product_variant_options
. product_variant_id
product_variant_options
.name
devrait également être une clé composite.
MISE À JOUR 4:
En mettant à jour mon product_variant_option_combinations
Pour inclure product_variant_id
(FK à product_variants
.id
) et en appliquant une contrainte UNIQUE avec product_variant_option_combinations
. sku_id
Et product_variant_option_combinations
. product_variant_id
, Je pense que j'ai pu éviter le problème d'avoir un SKU à la fois "Petit" et "Grand". Est-ce correct?
-- phpMyAdmin SQL Dump
-- version 4.1.14
-- http://www.phpmyadmin.net
--
-- Host: 127.0.0.1
-- Generation Time: Jul 30, 2014 at 03:35 AM
-- Server version: 5.6.17
-- PHP Version: 5.5.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Database: `mydb`
--
-- --------------------------------------------------------
--
-- Table structure for table `products`
--
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
--
-- Dumping data for table `products`
--
INSERT INTO `products` (`id`, `name`) VALUES
(1, 'Widget 1');
-- --------------------------------------------------------
--
-- Table structure for table `product_variants`
--
CREATE TABLE IF NOT EXISTS `product_variants` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQUE_product_id_name` (`product_id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `product_variants`
--
INSERT INTO `product_variants` (`id`, `product_id`, `name`) VALUES
(2, 1, 'Color'),
(1, 1, 'Size');
-- --------------------------------------------------------
--
-- Table structure for table `product_variant_options`
--
CREATE TABLE IF NOT EXISTS `product_variant_options` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_variant_id` int(11) NOT NULL,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQUE_product_variant_id_name` (`product_variant_id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
--
-- Dumping data for table `product_variant_options`
--
INSERT INTO `product_variant_options` (`id`, `product_variant_id`, `name`) VALUES
(2, 1, 'Large'),
(1, 1, 'Small'),
(4, 2, 'Black'),
(3, 2, 'White');
-- --------------------------------------------------------
--
-- Table structure for table `skus`
--
CREATE TABLE IF NOT EXISTS `skus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`sku` varchar(45) NOT NULL,
`price` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
KEY `skus_product_id_products_id_idx` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
--
-- Dumping data for table `skus`
--
INSERT INTO `skus` (`id`, `product_id`, `sku`, `price`) VALUES
(1, 1, 'W1SSCW', '10.00'),
(2, 1, 'W1SSCB', '10.00'),
(3, 1, 'W1SLCW', '12.00'),
(4, 1, 'W1SLCB', '15.00');
-- --------------------------------------------------------
--
-- Table structure for table `skus_product_variant_options`
--
CREATE TABLE IF NOT EXISTS `skus_product_variant_options` (
`sku_id` int(11) NOT NULL,
`product_variant_id` int(11) NOT NULL,
`product_variant_options_id` int(11) NOT NULL,
PRIMARY KEY (`sku_id`,`product_variant_options_id`,`product_variant_id`),
UNIQUE KEY `UNIQUE_sku_id_product_variant_id` (`sku_id`,`product_variant_id`),
KEY `spvo_product_variant_options_id_pro_idx` (`product_variant_options_id`),
KEY `spvo_product_variant_id_product_var_idx` (`product_variant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `skus_product_variant_options`
--
INSERT INTO `skus_product_variant_options` (`sku_id`, `product_variant_id`, `product_variant_options_id`) VALUES
(1, 1, 1),
(2, 1, 1),
(3, 1, 2),
(4, 1, 2),
(1, 2, 3),
(3, 2, 3),
(2, 2, 4),
(4, 2, 4);
--
-- Constraints for dumped tables
--
--
-- Constraints for table `product_variants`
--
ALTER TABLE `product_variants`
ADD CONSTRAINT `product_variants_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
--
-- Constraints for table `product_variant_options`
--
ALTER TABLE `product_variant_options`
ADD CONSTRAINT `product_variant_options_product_variant_id_product_variants_id` FOREIGN KEY (`product_variant_id`) REFERENCES `product_variants` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
--
-- Constraints for table `skus`
--
ALTER TABLE `skus`
ADD CONSTRAINT `skus_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
--
-- Constraints for table `skus_product_variant_options`
--
ALTER TABLE `skus_product_variant_options`
ADD CONSTRAINT `skus_product_variant_options_sku_id_skus_id` FOREIGN KEY (`sku_id`) REFERENCES `skus` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
ADD CONSTRAINT `spvo_product_variant_options_id_product_variant_options_id` FOREIGN KEY (`product_variant_options_id`) REFERENCES `product_variant_options` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
ADD CONSTRAINT `spvo_product_variant_id_product_variants_id` FOREIGN KEY (`product_variant_id`) REFERENCES `product_variants` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
Vous pourriez avoir un design comme:
+---------------+ +-------------------+
| PRODUCTS |-----< PRODUCT_VARIANTS |
+---------------+ +-------------------+
| #product_id | | #product_id |
| product_name | | #variant_id |
+---------------+ | sku_id |
| +-------------------+
| |
+--------^--------+ +--------^--------+
| PRODUCT_OPTIONS |-----< VARIANT_VALUES |
+-----------------+ +-----------------+
| #product_id | | #product_id |
| #option_id | | #variant_id |
+--------v--------+ | #option_id |
| | value_id |
+-----------------+ +--------v--------+
| OPTIONS | |
+-----------------+ |
| #option_id | |
| option_name | |
+-----------------+ |
| |
+-------^-------+ |
| OPTION_VALUES |---------------+
+---------------+
| #option_id |
| #value_id |
| value_name |
+---------------+
Avec les clés primaires, uniques et étrangères:
Vous avez:
Vous devez ensuite créer un tableau à n dimensions, le nombre de dimensions étant égal au nombre d'options pour le produit. Chaque élément du tableau correspond à une variante de produit. Il y aura toujours au moins une variante de produit pour chaque produit; car il y a toujours la pseudo option du produit "tel quel"
Vous pouvez souhaiter avoir une validation pour vous assurer qu'aucun SKU n'est attribué, sauf si des valeurs ont été spécifiées pour toutes les options associées à un produit.
Sur la base de la feuille de calcul de la façon dont vous voyez vos données, vous pouvez entrer des données dans vos tableaux comme suit:
PRODUCTS
========
id name
--- --------
1 Widget 1
2 Widget 2
3 Widget 3
PRODUCT_VARIANTS
================
id product_id name
--- ---------- ------
1 1 Size (Widget 1)
2 1 Color (Widget 1)
3 2 Size (Widget 2)
4 3 Class (Widget 3)
5 3 Size (Widget 3)
PRODUCT_VARIANT_OPTIONS
=======================
id product_variant_id name
--- ------------------ -------------
1 1 Small (Widget 1; Size)
2 1 Large (Widget 1; Size)
3 2 White (Widget 1; Color)
4 2 Black (Widget 1; Color)
5 3 Small (Widget 2; Size)
6 3 Medium (Widget 2; Size)
7 4 Amateur (Widget 3; Class)
8 4 Professional (Widget 3; Class)
9 5 Medium (Widget 3; Size)
10 5 Large (Widget 3; Size)
SKUS
====
id product_id sku price
--- ---------- ------ -----
1 1 W1SSCW 10 (Widget 1)
2 1 W1SSCB 10 (Widget 1)
3 1 W1SLCW 12 (Widget 1)
4 1 W1SLCB 15 (Widget 1)
5 2 W2SS 100 (Widget 2)
6 2 W2SM 100 (Widget 2)
7 3 W3CASM 50 (Widget 3)
8 3 W3CASL 50 (Widget 3)
9 3 W3CPSM 150 (Widget 3)
10 3 W3CPSL 160 (Widget 3)
PRODUCT_VARIANT_OPTION_COMBINATIONS
===================================
product_variant_option_id sku_id
------------------------- ------
1 1 (W1SSCW; Size; Small)
3 1 (W1SSCW; Color; White)
1 2 (W1SSCB; Size; Small)
4 2 (W1SSCB; Color; Black)
2 3 (W1SLCW; Size; Large)
3 3 (W1SLCW; Color; White)
2 4 (W1SLCB; Size; Large)
4 4 (W1SLCB; Color; Black)
5 5 (W2SS; Size; Small)
6 6 (W2SM; Size; Medium)
7 7 (W3CASM; Class; Amateur)
9 7 (W3CASM; Size; Medium)
7 8 (W3CASL; Class; Amateur)
10 8 (W3CASL; Size; Large)
8 9 (W3CPSM; Class; Professional)
9 9 (W3CPSM; Size; Medium)
8 10 (W3CPSL; Class; Professional)
10 10 (W3CPSL; Size; Large)
Il ne semble rien dans votre conception d'empêcher l'ajout de l'entrée de l'enregistrement (product_variant_option_id: 2; sku_id 1), de sorte que le SKU W1SSCW dispose désormais des deux options, Small et Large. Il n'y a rien pour arrêter l'entrée de l'enregistrement (product_variant_option_id: 7; sku_id: 1) de sorte que le SKU W1SSCW a également l'option Amateur.
Sur la base de la feuille de calcul de la façon dont vous voyez vos données, vous pouvez entrer des données dans mes tableaux comme suit:
PRODUCTS
========
product_id product_name
---------- ------------
1 Widget 1
2 Widget 2
3 Widget 3
OPTIONS
=======
option_id option_name
--------- -----------
1 Size SL
2 Color
3 Size SM
4 Class
5 Size ML
OPTION_VALUES
=============
option_id value_id value_name
--------- -------- ------------
1 1 Small (Size SL)
1 2 Large (Size SL)
2 1 White (Color)
2 2 Black (Color)
3 1 Small (Size SM)
3 2 Medium (Size SM)
4 1 Amateur (Class)
4 2 Professional (Class)
5 1 Medium (Size ML)
5 2 Large (Size ML)
PRODUCT_OPTIONS
===============
product_id option_id
---------- ---------
1 1 (Widget 1; Size SL)
1 2 (Widget 1; Color)
2 3 (Widget 2; Size SM)
3 4 (Widget 3; Class)
3 5 (Widget 4; Size ML)
PRODUCT_VARIANTS
================
product_id variant_id sku_id
---------- ---------- ------
1 1 W1SSCW (Widget 1)
1 2 W1SSCB (Widget 1)
1 3 W1SLCW (Widget 1)
1 4 W1SLCB (Widget 1)
2 1 W2SS (Widget 2)
2 2 W2SM (Widget 2)
3 1 W3CASM (Widget 3)
3 2 W3CASL (Widget 3)
3 3 W3CPSM (Widget 3)
3 4 W3CPSL (Widget 3)
VARIANT_VALUES
==============
product_id variant_id option_id value_id
---------- ---------- --------- --------
1 1 1 1 (W1SSCW; Size SL; Small)
1 1 2 1 (W1SSCW; Color; White)
1 2 1 1 (W1SSCB; Size SL; Small)
1 2 2 2 (W1SSCB; Color; Black)
1 3 1 2 (W1SLCW; Size SL; Large)
1 3 2 1 (W1SLCW; Color; White)
1 4 1 2 (W1SLCB; Size SL; Large)
1 4 2 2 (W1SLCB; Color; Black)
2 1 3 1 (W2SS; Size SM; Small)
2 2 3 2 (W2SM; Size SM; Medium)
3 1 4 1 (W3CASM; Class; Amateur)
3 1 5 1 (W3CASM; Size ML; Medium)
3 2 4 1 (W3CASL; Class; Amateur)
3 2 5 2 (W3CASL; Size ML; Large)
3 3 4 2 (W3CPSM; Class; Professional)
3 3 5 1 (W3CPSM; Size ML; Medium)
3 4 4 2 (W3CPSL; Class; Professional)
3 4 5 2 (W3CPSL; Size ML; Large)
Dans ma conception, vous ne pouviez pas entrer l'enregistrement VARIANT_VALUES supplémentaire (product_id: 1; variant_id: 1; option_id: 1; value_id: 2) - de sorte que SKU W1SSCW a maintenant les deux options Small et Large - en raison de la clé primaire sur VARIANT_VALUES et l'enregistrement VARIANT_VALUES existant (product_id: 1; variant_id: 1; option_id: 1; value_id: 1). Dans ma conception, vous ne pouviez pas entrer l'enregistrement VARIANT_VALUES (product_id: 1; variant_id: 1; option_id: 4; value_id: 1) - de sorte que le SKU W1SSCW a également l'option Amateur - en raison de la clé étrangère référençant PRODUCT_OPTIONS et de l'absence d'un enregistrement dans ce tableau de (product_id: 1; option_id: 4) indiquant que la classe est une option valide pour le produit Widget 1.
MODIFIER : Conception sans table PRODUCT_OPTIONS
Vous pourriez avoir un design comme:
+---------------+ +---------------+
| PRODUCTS |-----< PRODUCT_SKUS |
+---------------+ +---------------+
| #product_id | | #product_id |
| product_name | | #sku_id |
+---------------+ | sku |
| | price |
| +---------------+
| |
+-------^-------+ +------^------+
| OPTIONS |------< SKU_VALUES |
+---------------+ +-------------+
| #product_id | | #product_id |
| #option_id | | #sku_id |
| option_name | | #option_id |
+---------------+ | value_id |
| +------v------+
+-------^-------+ |
| OPTION_VALUES |-------------+
+---------------+
| #product_id |
| #option_id |
| #value_id |
| value_name |
+---------------+
Avec les clés primaires, uniques et étrangères:
Sur la base de la feuille de calcul de la façon dont vous voyez vos données, vous pouvez entrer des données dans ces tableaux comme suit:
PRODUCTS
========
product_id product_name
---------- ------------
1 Widget 1
2 Widget 2
3 Widget 3
OPTIONS
=======
product_id option_id option_name
---------- --------- -----------
1 1 Size (Widget 1)
1 2 Color (Widget 1)
2 1 Size (Widget 2)
3 1 Class (Widget 3)
3 2 Size (Widget 3)
OPTION_VALUES
=============
product_id option_id value_id value_name
---------- --------- -------- ------------
1 1 1 Small (Widget1; Size)
1 1 2 Large (Widget1; Size)
1 2 1 White (Widget1; Color)
1 2 2 Black (Widget1; Color)
2 1 1 Small (Widget2; Size)
2 1 2 Medium (Widget2; Size)
3 1 1 Amateur (Widget3; Class)
3 1 2 Professional (Widget3; Class)
3 2 1 Medium (Widget3; Size)
3 2 2 Large (Widget3; Size)
PRODUCT_SKUS
============
product_id sku_id sku
---------- ------ ------
1 1 W1SSCW (Widget 1)
1 2 W1SSCB (Widget 1)
1 3 W1SLCW (Widget 1)
1 4 W1SLCB (Widget 1)
2 1 W2SS (Widget 2)
2 2 W2SM (Widget 2)
3 1 W3CASM (Widget 3)
3 2 W3CASL (Widget 3)
3 3 W3CPSM (Widget 3)
3 4 W3CPSL (Widget 3)
SKU_VALUES
==========
product_id sku_id option_id value_id
---------- ------ --------- --------
1 1 1 1 (W1SSCW; Size; Small)
1 1 2 1 (W1SSCW; Color; White)
1 2 1 1 (W1SSCB; Size; Small)
1 2 2 2 (W1SSCB; Color; Black)
1 3 1 2 (W1SLCW; Size; Large)
1 3 2 1 (W1SLCW; Color; White)
1 4 1 2 (W1SLCB; Size; Large)
1 4 2 2 (W1SLCB; Color; Black)
2 1 1 1 (W2SS; Size; Small)
2 2 1 2 (W2SM; Size; Medium)
3 1 1 1 (W3CASM; Class; Amateur)
3 1 2 1 (W3CASM; Size; Medium)
3 2 1 1 (W3CASL; Class; Amateur)
3 2 2 2 (W3CASL; Size; Large)
3 3 1 2 (W3CPSM; Class; Professional)
3 3 2 1 (W3CPSM; Size; Medium)
3 4 1 2 (W3CPSL; Class; Professional)
3 4 2 2 (W3CPSL; Size; Large)