Шаблон дизайна списка атрибутов продукта

9

Я работаю над обновлением базы данных продуктов нашего сайта. Он построен в MySQL, но это больше вопрос шаблона проектирования базы данных.

Я планирую перейти на шаблон Supertype / Subtype. Наша текущая / предыдущая база данных в основном представляет собой одну таблицу, в которой есть данные об одном типе продукта. Мы рассчитываем расширить ассортимент нашей продукции, чтобы включить в нее различные продукты.

Этот новый черновой вариант выглядит так:

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

У меня вопрос по поводу таблиц атрибутов товара. Идея здесь заключается в том, что продукт может иметь список заданных атрибутов, таких как цвет: красный, зеленый, синий или материал: пластик, дерево, хром, алюминий и т. Д.

Этот список будет храниться в таблице, и первичный ключ (PK) для этого элемента атрибута будет использоваться в таблице конкретного продукта в качестве внешнего ключа (FK).

(Книга Мартина Фаулера « Шаблоны архитектуры корпоративных приложений» называется « Сопоставление внешнего ключа »)

Это позволяет интерфейсу веб-сайта извлекать список атрибутов для данного типа атрибута и выплевывать его в раскрывающемся меню выбора или в каком-либо другом элементе пользовательского интерфейса. Этот список можно считать «разрешенным» списком значений атрибутов.

Количество соединений, которое в конечном итоге происходит при извлечении определенного продукта, кажется мне чрезмерным. Вы должны присоединить каждую таблицу атрибутов продукта к продукту, чтобы получить поля этого атрибута. Обычно это поле может быть просто строкой (varchar) для его имени.

Этот шаблон проектирования в конечном итоге создает большое количество таблиц, а также вы получаете таблицу для каждого атрибута. Одной из идей противодействия этому было бы создание чего-то большего из таблицы «мешок для захвата» для всех атрибутов продукта. Что-то вроде этого:

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

Таким образом, ваша таблица может выглядеть так:

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

Это может помочь уменьшить проскальзывание таблицы, но это не уменьшает количество объединений, и он чувствует себя немного неправильно, объединяя так много разных типов в одну таблицу. Но вы сможете получить все доступные атрибуты «цвета» довольно легко.

Однако может существовать атрибут, который имеет больше полей, чем просто «имя», например значение RGB цвета. Это потребует, чтобы этот конкретный атрибут мог иметь другую таблицу или иметь одно поле для пары имя: значение (что имеет свои недостатки).

Последний шаблон проектирования, о котором я могу подумать, - это сохранение фактического значения атрибута в таблице конкретного продукта и отсутствие «таблицы атрибутов» вообще. Что-то вроде этого:

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

Вместо внешнего ключа к другой таблице он будет содержать фактическое значение, например:

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

Это устранит соединения и предотвратит ползучесть таблиц (может быть?). Однако это предотвращает наличие «авторизованного списка» атрибутов. Вы можете вернуть все введенные в данный момент значения для данного поля (т. Е. Цвет), но это также устраняет идею наличия «разрешенного списка» значений для данного атрибута.

Чтобы получить этот список, вам все равно придется создать таблицу атрибутов «grab bag» или иметь несколько таблиц (таблица ползучести) для каждого атрибута.

Это создает больший недостаток (и почему я никогда не использовал этот подход), так как теперь название продукта находится в нескольких местах.

Если у вас есть значение цвета «красный» в «таблице основных атрибутов», а также вы сохранили его в таблице «product_ [type]», обновление таблицы «master» вызовет потенциальную проблему целостности данных, если приложение не Не обновляйте все записи со старым значением в таблице «product_type».

Итак, после моего многословного объяснения и анализа этого сценария я понял, что это не может быть необычным сценарием и даже может быть название для такого типа ситуации.

Существуют ли общепринятые решения этой проблемы дизайна? Приемлемо ли потенциально большое количество объединений, если таблицы относительно малы? Допустимо ли хранение имени атрибута вместо атрибута PK в некоторых ситуациях? Есть ли другое решение, о котором я не думаю?

Несколько заметок об этой базе данных / приложении:

  • Продукты не часто обновляются / добавляются / удаляются
  • Атрибуты не часто обновляются / добавляются / удаляются
  • Таблица чаще всего запрашивается для чтения / возврата информации
  • Кэширование на стороне сервера позволяет кэшировать результат данного запроса / результата
  • Я планирую начать только с одного типа продукта и расширять / добавлять другие с течением времени, и потенциально будет иметь более 10 различных типов
jmbertucci
источник
1
Сколько типов продуктов у вас будет?
Дезсо
1
Хороший вопрос. Он начнется с малого 3-4, но
потенциально
Что вы подразумеваете под "Авторизованным списком атрибутов"?
NoChance
Извините, это должно быть "значение атрибута". Идея в том, что у вас есть таблица, в которой перечислены все значения, разрешенные для атрибута. То есть. Вот список из 10 цветов, которые могут быть этого типа продукта. Эти 10 являются «авторизованными» значениями, которые можно выбрать.
jmbertucci
Мне интересно, было бы хорошо, если бы все эти значения атрибутов были объединены с таблицей типов продуктов, если бы я в конечном итоге создал «представление» поверх него?
jmbertucci

Ответы:

17

Я лично использовал бы модель, подобную следующей:

Таблица продуктов будет довольно простой, ваши основные данные продукта:

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

Вторая таблица атрибутов для хранения каждого из различных атрибутов.

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

Наконец, создайте таблицу product_attribute как таблицу JOIN между каждым продуктом и его атрибутами, связанными с ним.

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

В зависимости от того, как вы хотите использовать данные, вы просматриваете два объединения:

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;

Смотрите SQL Fiddle с демонстрацией . Это возвращает данные в формате:

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

Но если вы хотите вернуть данные в PIVOTформате, где у вас есть одна строка со всеми атрибутами в виде столбцов, вы можете использовать CASEоператоры с агрегатом:

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;

Смотрите SQL Fiddle с демонстрацией . Данные возвращаются в формате:

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

Как вы видите, данные могут быть в лучшем формате для вас, но если у вас есть неизвестное количество атрибутов, они легко станут несостоятельными из-за жестко кодируемых имен атрибутов, поэтому в MySQL вы можете использовать подготовленные операторы для создания динамических сводок , Ваш код будет выглядеть следующим образом (см. 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;

Это приводит к тому же результату, что и вторая версия, без необходимости что-либо кодировать. Хотя есть много способов смоделировать это, я думаю, что этот дизайн базы данных является наиболее гибким.

Тарын
источник
+1 - фантастически написанный ответ. Я все еще беру несколько минут, чтобы перечитать и переварить этот ответ, прежде чем принять. Это выглядит как хорошее решение моего вопроса о соединениях и атрибутах продукта и даже выходит за рамки примеров с основными точками и подготовленными утверждениями. Итак, я начну с +1 за это. =)
jmbertucci
@jmbertucci вы, похоже, беспокоились о запросах к таблицам, поэтому я решил, что предоставлю вам несколько образцов. :)
Тарын
Верно. Я собираюсь сделать "до", что я не видел, чтобы сделать кросс-таблицу продукта для атрибута. Вероятно, это случай переосмысления, особенно после погружения шаблонов проектирования и теорий. Кроме того, мой опыт работы с базой данных является базовым, и мне нужно больше делать с подготовленными утверждениями, поэтому ваше включение наиболее полезно. И этот ответ помог разрушить тот «блок писателей», который у меня был, чтобы я мог продолжить этот проект, который делает мой день. =)
jmbertucci
хорошо, один вопрос ... это медленно? Я упал, как будто вам понадобится более 30 секунд, чтобы запросить только 10 тыс.
Продуктов
@ZenithS Вам нужно протестировать его, чтобы увидеть и, возможно, добавить индексы для столбцов, которые вы запрашиваете. У меня нет экземпляра MySQL для тестирования.
Тарын
0

Я бы расширил ответ Тэрина и изменил бы таблицу атрибутов, чтобы иметь столбец fk_attribute_type_id, который будет вместо столбца attribute_name и указывает на новую таблицу attribute_type.

Таким образом, у вас есть структурированные типы атрибутов в одной таблице, и вы можете изменить их в любое время в одном месте.

На мой взгляд, лучше работать с вещами типа "dial" (таблица с возможными типами), чем с типом enum (например, в столбце attribute_name (и, кроме того, на самом деле это не name, а его тип атрибута)).

Алесь
источник