Я создаю веб-сайт электронной коммерции для развлечения с тремя основными компонентами: внешним интерфейсом в Angular, базой данных MySQL и внутренним компонентом PHP, который будет функционировать как REST API.
База данных инвентаризации состоит из продуктов, у которых есть много вариантов (да, я видел много вопросов на StackExchange, обсуждающих подобные вопросы, но пока не нашел удовлетворительного решения). Прочитав несколько страшных историй об использовании подхода EAV, я избегаю этого.
Я думаю, что сделал решение для базы данных, которое работает, но я не уверен, как мне следует запрашивать данные. Поскольку существует несколько отношений «многие ко многим», запрос для одного продукта и всех его вариантов может возвращать несколько строк, в которых только пара столбцов содержит «новые» данные.
(TL: DR) Главный вопрос: что наиболее эффективно при запросе продукта для всех его вариантов (цвета, размеры доступных цветов и количество вариантов цвета / размера):
Выполнить 1 запрос к базе данных, возвращая много строк, где мне нужно было бы построить ассоциативный массив в PHP, отбрасывая «дубликаты» данных?
Выполнить несколько запросов при получении всех вариантов продукта?
- Или в структуре базы данных есть недостаток, который является причиной этих проблем?
Схема базы данных:
CREATE TABLE IF NOT EXISTS `products` (
`productID` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`longDescription` TEXT,
`shortDescription` VARCHAR(1000),
PRIMARY KEY (`productID`)
);
INSERT INTO `products` (`productID`, `name`, `longDescription`, `shortDescription`) VALUES
(1, 'A shirt', 'Long description of this product', 'shortDesc of shirt');
CREATE TABLE IF NOT EXISTS `productpricing` (
`productID` INT(11) NOT NULL,
`startDate` TIMESTAMP NOT NULL DEFAULT '1971-01-01 00:00:00',
`endDate` TIMESTAMP NOT NULL DEFAULT '2099-01-01 00:00:00',
`price` DECIMAL(10, 2) NOT NULL,
PRIMARY KEY(`productID`, `endDate`),
FOREIGN KEY (`productID`) REFERENCES products(`productID`)
);
INSERT INTO `productpricing` (`productID`, `startDate`, `endDate`, `price`) VALUES
(1, '1971-01-01 00:00:00', '2099-01-01 00:00:00', 309.99);
CREATE TABLE IF NOT EXISTS `categories` (
`categoryID` INT(11) NOT NULL AUTO_INCREMENT,
`categoryName` VARCHAR(100) NOT NULL,
PRIMARY KEY (`categoryID`)
);
INSERT INTO `categories` (`categoryID`, `categoryName`) VALUES
(1, 'Test Category');
CREATE TABLE IF NOT EXISTS `sizes` (
`sizeID` INT(11) NOT NULL,
`size` INT NOT NULL,
PRIMARY KEY (`sizeID`)
);
INSERT INTO `sizes` (`sizeID`, `size`) VALUES
(1, 50),
(2, 56),
(3, 62),
(4, 68),
(5, 74),
(6, 80),
(7, 86);
CREATE TABLE IF NOT EXISTS `colors` (
`colorID` INT(11) NOT NULL,
`color` VARCHAR(100) NOT NULL,
PRIMARY KEY (`colorID`)
);
INSERT INTO `colors` (`colorID`, `color`) VALUES
(1, "Red"),
(2, "White"),
(3, "Blue"),
(4, "Purple");
CREATE TABLE IF NOT EXISTS `product_variants` (
`productvariantID` INT(11) NOT NULL,
`productID` INT(11) NOT NULL,
`categoryID` INT(11) NOT NULL,
`colorID` INT(11) NOT NULL,
`sizeID` INT(11) NOT NULL,
`sku` VARCHAR(50) NOT NULL UNIQUE,
`quantity` INT(11) NOT NULL,
`isActive` BOOLEAN NOT NULL DEFAULT 0,
PRIMARY KEY (`productvariantID`),
UNIQUE (`productID`, `colorID`, `sizeID`),
FOREIGN KEY (`productID`) REFERENCES products(`productID`),
FOREIGN KEY (`categoryID`) REFERENCES categories(`categoryID`),
FOREIGN KEY (`colorID`) REFERENCES colors(`colorID`),
FOREIGN KEY (`sizeID`) REFERENCES sizes(`sizeID`)
);
INSERT INTO `product_variants` (`productvariantID`, `productID`, `categoryID`, `colorID`, `sizeID`, `sku`, `quantity`, `isActive`) VALUES
(1, 1, 1, 2, 1, 'clalb121', 2, 1),
(2, 1, 1, 3, 2, 'clalb132', 1, 1),
(3, 1, 1, 2, 2, 'clalb122', 5, 1);
CREATE TABLE IF NOT EXISTS `images` (
`imageID` INT(11) NOT NULL AUTO_INCREMENT,
`imageFilename` VARCHAR(100) NOT NULL,
PRIMARY KEY(`imageID`)
);
INSERT INTO `images` (`imageID`, `imageFilename`) VALUES
(1, 'shirtwhite1.jpg'),
(2, 'shirtwhite2.jpg'),
(3, 'shirtblue1.jpg'),
(4, 'shirtblue2.jpg');
CREATE TABLE IF NOT EXISTS `product_variant_images` (
`productvariantID` INT(11) NOT NULL,
`imageID` INT(11) NOT NULL,
FOREIGN KEY(`productvariantID`) REFERENCES product_variants(`productvariantID`),
FOREIGN KEY(`imageID`) REFERENCES images(`imageID`)
);
INSERT INTO `product_variant_images` (`productvariantID`, `imageID`) VALUES
(1, 1),
(1, 2),
(2, 3),
(2, 4),
(3, 1),
(3, 2);
(я собираюсь удалить categoryID из таблицы product_variants , так как разные варианты одного и того же продукта не имеют разных категорий.)
Пример запроса 1 продукта для всех его вариантов (в одном запросе):
SELECT
p.productID,
p.name,
p.longDescription,
p.shortDescription,
colors.color,
sizes.size,
pvar.quantity,
pprice.price,
images.imageFilename
FROM products as p
JOIN product_variants as pvar
ON p.productID = pvar.productID
JOIN productpricing as pprice
ON pprice.productID = p.productID
JOIN colors
ON colors.colorID = pvar.colorID
JOIN sizes
ON sizes.sizeID = pvar.sizeID
JOIN product_variant_images as pvari
ON pvari.productvariantID = pvar.productvariantID
JOIN images
ON images.imageID = pvari.imageID
WHERE p.productID = 1 AND pvar.isActive = 1 AND NOW() BETWEEN pprice.startDate AND pprice.endDate;
Пример возвращаемого набора:
# productID, name, longDescription, shortDescription, color, size, quantity, price, imageFilename
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', 'White', '50', '2', '309.99', 'shirtwhite1.jpg'
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', 'White', '50', '2', '309.99', 'shirtwhite2.jpg'
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', 'White', '56', '5', '309.99', 'shirtwhite1.jpg'
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', 'White', '56', '5', '309.99', 'shirtwhite2.jpg'
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', 'Blue', '56', '1', '309.99', 'shirtblue1.jpg'
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', 'Blue', '56', '1', '309.99', 'shirtblue2.jpg'
Как видно из набора результатов, белый вариант (одного размера) рубашки связан с двумя изображениями, поэтому есть две записи, в которых только imageFilename содержит «новые данные».
Пример использования нескольких запросов:
SELECT
p.productID,
p.name,
p.longDescription,
p.shortDescription,
pprice.price
FROM products as p
JOIN productpricing as pprice
ON pprice.productID = p.productID
WHERE p.productID = 1 AND NOW() BETWEEN pprice.startDate AND pprice.endDate;
SELECT
c.color,
s.size,
pvar.quantity
FROM product_variants as pvar
JOIN colors AS c
ON c.colorID = pvar.colorID
JOIN sizes as s
ON s.sizeID = pvar.sizeID
WHERE pvar.productID = 1;
SELECT DISTINCT
c.color,
i.imageFilename
FROM images AS i
JOIN product_variant_images as pvari
ON pvari.imageID = i.imageID
JOIN product_variants as pvar
ON pvar.productvariantID = pvari.productvariantID
JOIN colors AS c
ON c.colorID = pvar.colorID
WHERE pvar.productID = 1;
Наборы результатов нескольких запросов:
# productID, name, longDescription, shortDescription, price
'1', 'A shirt', 'Long description of this product', 'shortDesc of shirt', '309.99'
# color, size, quantity
'White', '50', '2'
'Blue', '56', '1'
'White', '56', '5'
# color, imageFilename
'White', 'shirtwhite1.jpg'
'White', 'shirtwhite2.jpg'
'Blue', 'shirtblue1.jpg'
'Blue', 'shirtblue2.jpg'
Любые советы относительно дизайна / запросов к базе данных приветствуются!