Лично я не люблю использовать для этой цели схему из нескольких таблиц.
- Трудно обеспечить целостность.
- Это трудно поддерживать.
- Сложно фильтровать результаты.
Я установил образец dbfiddle .
Моя предложенная схема таблицы:
CREATE TABLE #Brands
(
BrandId int NOT NULL PRIMARY KEY,
BrandName nvarchar(100) NOT NULL
);
CREATE TABLE #Clothes
(
ClothesId int NOT NULL PRIMARY KEY,
ClothesName nvarchar(100) NOT NULL
);
-- Lookup table for known attributes
--
CREATE TABLE #Attributes
(
AttrId int NOT NULL PRIMARY KEY,
AttrName nvarchar(100) NOT NULL
);
-- holds common propeties, url, price, etc.
--
CREATE TABLE #BrandsClothes
(
BrandId int NOT NULL REFERENCES #Brands(BrandId),
ClothesId int NOT NULL REFERENCES #Clothes(ClothesId),
VievingUrl nvarchar(300) NOT NULL,
Price money NOT NULL,
PRIMARY KEY CLUSTERED (BrandId, ClothesId),
INDEX IX_BrandsClothes NONCLUSTERED (ClothesId, BrandId)
);
-- holds specific and unlimited attributes
--
CREATE TABLE #BCAttributes
(
BrandId int NOT NULL REFERENCES #Brands(BrandId),
ClothesId int NOT NULL REFERENCES #Clothes(ClothesId),
AttrId int NOT NULL REFERENCES #Attributes(AttrId),
AttrValue nvarchar(300) NOT NULL,
PRIMARY KEY CLUSTERED (BrandId, ClothesId, AttrId),
INDEX IX_BCAttributes NONCLUSTERED (ClothesId, BrandId, AttrId)
);
Позвольте мне вставить некоторые данные:
INSERT INTO #Brands VALUES
(1, 'Brand1'), (2, 'Brand2');
INSERT INTO #Clothes VALUES
(1, 'Pants'), (2, 'T-Shirt');
INSERT INTO #Attributes VALUES
(1, 'Color'), (2, 'Size'), (3, 'Shape'), (4, 'Provider'), (0, 'Custom');
INSERT INTO #BrandsClothes VALUES
(1, 1, 'http://mysite.com?B=1&C=1', 123.99),
(1, 2, 'http://mysite.com?B=1&C=2', 110.99),
(2, 1, 'http://mysite.com?B=2&C=1', 75.99),
(2, 2, 'http://mysite.com?B=2&C=2', 85.99);
INSERT INTO #BCAttributes VALUES
(1, 1, 1, 'Blue, Red, White'),
(1, 1, 2, '32, 33, 34'),
(1, 2, 1, 'Pearl, Black widow'),
(1, 2, 2, 'M, L, XL'),
(2, 1, 4, 'Levis, G-Star, Armani'),
(2, 1, 3, 'Slim fit, Regular fit, Custom fit'),
(2, 2, 4, 'G-Star, Armani'),
(2, 2, 3, 'Slim fit, Regular fit'),
(2, 2, 0, '15% Discount');
Если вам нужно получить общие атрибуты:
SELECT b.BrandName, c.ClothesName, bc.VievingUrl, bc.Price
FROM #BrandsClothes bc
INNER JOIN #Brands b
ON b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON c.ClothesId = bc.ClothesId
ORDER BY bc.BrandId, bc.ClothesId;
BrandName ClothesName VievingUrl Price
--------- ----------- ------------------------- ------
Brand1 Pants http://mysite.com?B=1&C=1 123.99
Brand1 T-Shirt http://mysite.com?B=1&C=2 110.99
Brand2 Pants http://mysite.com?B=2&C=1 75.99
Brand2 T-Shirt http://mysite.com?B=2&C=2 85.99
Или вы можете легко получить одежду по брендам:
Дай мне всю одежду Brand2
SELECT c.ClothesName, b.BrandName, a.AttrName, bca.AttrValue
FROM #BCAttributes bca
INNER JOIN #BrandsClothes bc
ON bc.BrandId = bca.BrandId
AND bc.ClothesId = bca.ClothesId
INNER JOIN #Brands b
ON b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON c.ClothesId = bc.ClothesId
INNER JOIN #Attributes a
ON a.AttrId = bca.AttrId
WHERE bca.ClothesId = 2
ORDER BY bca.ClothesId, bca.BrandId, bca.AttrId;
ClothesName BrandName AttrName AttrValue
----------- --------- -------- ---------------------
T-Shirt Brand1 Color Pearl, Black widow
T-Shirt Brand1 Size M, L, XL
T-Shirt Brand2 Custom 15% Discount
T-Shirt Brand2 Shape Slim fit, Regular fit
T-Shirt Brand2 Provider G-Star, Armani
Но для меня одна из лучших в этой схеме - это то, что вы можете фильтровать по Attibutes:
Дай мне всю одежду, которая имеет атрибут: Размер
SELECT c.ClothesName, b.BrandName, a.AttrName, bca.AttrValue
FROM #BCAttributes bca
INNER JOIN #BrandsClothes bc
ON bc.BrandId = bca.BrandId
AND bc.ClothesId = bca.ClothesId
INNER JOIN #Brands b
ON b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON c.ClothesId = bc.ClothesId
INNER JOIN #Attributes a
ON a.AttrId = bca.AttrId
WHERE bca.AttrId = 2
ORDER BY bca.ClothesId, bca.BrandId, bca.AttrId;
ClothesName BrandName AttrName AttrValue
----------- --------- -------- ----------
Pants Brand1 Size 32, 33, 34
T-Shirt Brand1 Size M, L, XL
При использовании схемы с несколькими таблицами любой из предыдущих запросов потребует работы с неограниченным количеством таблиц или с полями XML или JSON.
Еще один вариант с этой схемой, это то, что вы можете определять шаблоны, например, вы можете добавить новую таблицу BrandAttrTemplates. Каждый раз, когда вы добавляете новую запись, вы можете использовать триггер или SP, чтобы сгенерировать набор предопределенных атрибутов для этой ветви.
Извините, я хотел бы расширить мои объяснения, я думаю, что это более понятно, чем мой английский.
Обновить
Мой текущий ответ должен работать независимо от того, какая СУБД. Согласно вашим комментариям, если вам нужно отфильтровать значения атрибутов, я бы предложил небольшие изменения.
Поскольку MS-Sql не допускает массивы, я настроил новый образец, поддерживающий ту же схему таблицы, но меняющий AttrValue на тип поля ARRAY.
Фактически, используя POSTGRES, вы можете использовать Advantatge для этого массива, используя индекс GIN.
(Позвольте мне сказать, что @EvanCarrol хорошо знает Postgres, конечно, лучше меня. Но позвольте мне добавить немного.)
CREATE TABLE BCAttributes
(
BrandId int NOT NULL REFERENCES Brands(BrandId),
ClothesId int NOT NULL REFERENCES Clothes(ClothesId),
AttrId int NOT NULL REFERENCES Attrib(AttrId),
AttrValue text[],
PRIMARY KEY (BrandId, ClothesId, AttrId)
);
CREATE INDEX ix_attributes on BCAttributes(ClothesId, BrandId, AttrId);
CREATE INDEX ix_gin_attributes on BCAttributes using GIN (AttrValue);
INSERT INTO BCAttributes VALUES
(1, 1, 1, '{Blue, Red, White}'),
(1, 1, 2, '{32, 33, 34}'),
(1, 2, 1, '{Pearl, Black widow}'),
(1, 2, 2, '{M, L, XL}'),
(2, 1, 4, '{Levis, G-Star, Armani}'),
(2, 1, 3, '{Slim fit, Regular fit, Custom fit}'),
(2, 2, 4, '{G-Star, Armani}'),
(2, 2, 3, '{Slim fit, Regular fit}'),
(2, 2, 0, '{15% Discount}');
Теперь вы можете дополнительно запрашивать значения отдельных атрибутов, такие как:
Дайте мне список всех штанов Размер: 33
AttribId = 2 AND ARRAY['33'] && bca.AttrValue
SELECT c.ClothesName, b.BrandName, a.AttrName, array_to_string(bca.AttrValue, ', ')
FROM BCAttributes bca
INNER JOIN BrandsClothes bc
ON bc.BrandId = bca.BrandId
AND bc.ClothesId = bca.ClothesId
INNER JOIN Brands b
ON b.BrandId = bc.BrandId
INNER JOIN Clothes c
ON c.ClothesId = bc.ClothesId
INNER JOIN Attrib a
ON a.AttrId = bca.AttrId
WHERE bca.AttrId = 2
AND ARRAY['33'] && bca.AttrValue
ORDER BY bca.ClothesId, bca.BrandId, bca.AttrId;
Это результат:
clothes name | brand name | attribute | values
------------- ------------ ---------- ----------------
Pants Brand1 Size 32, 33, 34
То, что вы описываете, является, по крайней мере частично, каталогом продукции. У вас есть несколько атрибутов, которые являются общими для всех продуктов. Они относятся к хорошо нормализованной таблице.
Кроме того, у вас есть ряд атрибутов, которые являются специфическими для бренда (и я ожидаю, что они могут быть специфическими для продукта). Что ваша система должна делать с этими конкретными атрибутами? У вас есть бизнес-логика, которая зависит от схемы этих атрибутов, или вы просто перечисляете их в серии пар "метка": "значение"?
Другие ответы предлагают использовать то, что по сути является CSV-подходом (будь
JSON
тоARRAY
или нет). Эти подходы требуют регулярной обработки реляционной схемы, перемещая схему из метаданных в сами данные.Для этого существует портативный шаблон проектирования, который очень хорошо подходит для реляционных баз данных. Это EAV (сущность-атрибут-значение). Я уверен, что вы читали во многих, многих местах, что «EAV is Evil» (и это так). Однако есть одно конкретное приложение, в котором проблемы с EAV не важны, это каталоги атрибутов продукта.
Все обычные аргументы против EAV не применимы к каталогу функций продукта, поскольку значения характеристик продукта, как правило, превращаются только в список, а в худшем случае - в таблицу сравнения.
Использование
JSON
типа столбца лишает вас возможности навязывать любые ограничения данных из базы данных и вводить их в логику вашего приложения. Кроме того, использование одной таблицы атрибутов для каждого бренда имеет следующие недостатки:Не особенно сложно получить данные о продукте с особенностями бренда. Возможно, проще создать динамический SQL с использованием модели EAV, чем с использованием модели таблицы на категорию. В таблице на категорию вам нужно размышление (или ваше
JSON
), чтобы узнать, как называются имена столбцов объектов. Затем вы можете создать список элементов для предложения where. В модели EAV запросWHERE X AND Y AND Z
становитсяINNER JOIN X INNER JOIN Y INNER JOIN Z
, так что запрос немного сложнее, но логика построения запроса по-прежнему полностью основана на таблицах, и он будет более чем достаточно масштабируемым, если вы построите правильные индексы.Есть много причин, чтобы не использовать EAV в качестве общего подхода. Эти причины не относятся к каталогу функций продукта, поэтому в этом конкретном приложении нет ничего плохого в использовании EAV.
Конечно, это короткий ответ на сложную и противоречивую тему. Я уже отвечал на подобные вопросы раньше и более подробно рассказал об общем отвращении к EAV. Например:
Я бы сказал, что в последнее время EAV используется реже, чем раньше, по большей части по уважительным причинам. Тем не менее, я думаю, что это также не совсем понятно.
источник
Использование JSON и PostgreSQL
Я думаю, ты делаешь это сложнее, чем нужно, и ты будешь укушен этим позже. Вам не нужна модель Entity-attribute-value, если вам не нужен EAV.
В этой схеме нет ничего плохого.
Теперь вы можете запросить его с помощью простого соединения
И любой из операторов JSON работает в предложении where.
Как примечание, не помещайте URL в базу данных. Они меняются со временем. Просто создайте функцию, которая принимает их.
или что угодно. Если вы используете PostgreSQL, вы можете даже использовать хеш-коды .
Также следует отметить, что
jsonb
он хранится в двоичном виде (таким образом, -'b ') и также может индексироваться, или SARGable, или как там еще крутые дети называют его в эти дни:CREATE INDEX ON brands USING gin ( attributes );
Разница здесь в простоте запроса.
Как насчет другого ..
источник
Одно простое решение - включить все возможные атрибуты в виде столбцов на основной таблице одежды и сделать все столбцы, относящиеся к бренду, обнуляемыми. Это решение нарушает нормализацию базы данных, но его очень легко реализовать.
источник