Как вы моделируете пользовательские атрибуты сущностей? - PullRequest
5 голосов
/ 19 мая 2009

Допустим, у нас есть приложение, которое должно быть в состоянии хранить все виды продуктов. Каждый продукт имеет как минимум ID и Name, но все остальные атрибуты могут быть определены самим пользователем.

  1. например. Он мог бы создать группу продуктов Ipods , которая бы содержала атрибуты емкость и поколение
  2. например. Он мог создать группу товаров Футболки с атрибутами Размер и Цвет
  3. Нам нужно сохранить определение продукта и сами конкретные продукты.
  4. Мы хотим обеспечить легкую агрегацию (GROUP BY) по атрибутам продукта. Например. выберите общую сумму емкости для каждого поколения ipods
  5. Решение не должно требовать изменения схемы (добавлено требование благодаря вкладу Билла Карвина - см. Также его ответ!)

Как бы вы смоделировали вашу схему с учетом вышеуказанных требований?

Примечание: Требование 4. важно!

Спасибо всем за участие и обсуждение подхода. Я видел некоторые решения этой проблемы в прошлом, но ни одно из них не облегчало группировку: (

Ответы [ 5 ]

11 голосов
/ 19 мая 2009

Я бы рекомендовал либо Наследование бетонных таблиц , либо Наследование таблиц классов . Оба дизайна удовлетворяют всем четырем вашим критериям.

Наследование бетонного стола:

  1. Ipods хранятся в таблице product_ipods со столбцами ID, Name, Capacity, Generation.
  2. Футболки хранятся в таблице product_tshirts с колонками ID, Name, Size, Color.
  3. Определение конкретных типов продуктов содержится в метаданных (определениях таблиц) product_ipods и product_tshirts.
  4. SELECT SUM(Capacity) FROM product_ipods GROUP BY Generation;

В таблице наследования классов:

  1. Общие атрибуты продукта хранятся в таблице Products со столбцами ID, Name.

    Ipods хранятся в таблице product_ipods со столбцами product_id (внешний ключ к Products.ID), Capacity, Generation.

  2. Футболки хранятся в таблице product_tshirts с колонками product_id (внешний ключ к Products.ID), Size, Color.
  3. Определение конкретных типов продуктов содержится в метаданных (определениях таблиц) products, product_ipods и product_tshirts.
  4. SELECT SUM(Capacity) FROM product_ipods GROUP BY Generation;

См. Также мой ответ на " Таблица продуктов, много видов продуктов, каждый продукт имеет много параметров ", где я опишу несколько решений для типа проблемы, которую вы описываете. Я также подробно расскажу о , почему EAV - это неправильный дизайн. Комментарий от @dcolumbus:

С CTI, будет ли каждая строка product_ipods вариацией со своей собственной ценой?

Я ожидаю, что столбец цены появится в таблице products, если цена для каждого типа товара есть. В CTI таблицы типов продуктов обычно содержат столбцы для атрибутов, которые относятся только к этому типу продуктов. Любые атрибуты, общие для всех типов продуктов, получают столбцы в родительской таблице.

Кроме того, при хранении позиций заказа вы бы затем сохранили строку из product_ipods как позицию?

В таблице позиций храните идентификатор продукта, который должен иметь одинаковое значение как в таблице products, так и в таблице product_ipods.


Комментарии от @dcolumbus:

Мне кажется, что это излишне ... в этом сценарии я не вижу смысла в дополнительной таблице. Но даже если подстолбок имеет смысл, что связывает id?

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

Идентификатором подключения может быть число с автоматическим приращением. Таблице подтипов не нужно автоматически увеличивать свой собственный идентификатор, потому что она может просто использовать значение, сгенерированное супер-таблицей.

CREATE TABLE products (
  product_id INT AUTO_INCREMENT PRIMARY KEY,
  sku VARCHAR(30) NOT NULL,
  name VARCHAR(100) NOT NULL,
  price NUMERIC(9,2) NOT NULL
);

CREATE TABLE product_ipods (
  product_id INT PRIMARY KEY,
  size TINYINT DEFAULT 16,
  color VARCHAR(10) DEFAULT 'silver',
  FOREIGN KEY (product_id) REFERENCES products(product_id)
);

INSERT INTO products (sku, name, price) VALUES ('IPODS1C1', 'iPod Touch', 229.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 16, 'silver');
INSERT INTO products (sku, name, price) VALUES ('IPODS1C2', 'iPod Touch', 229.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 16, 'black');
INSERT INTO products (sku, name, price) VALUES ('IPODS1C3', 'iPod Touch', 229.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 16, 'red');
INSERT INTO products (sku, name, price) VALUES ('IPODS2C1', 'iPod Touch', 299.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 32, 'silver');
INSERT INTO products (sku, name, price) VALUES ('IPODS2C2', 'iPod Touch', 299.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 32, 'silver');
INSERT INTO products (sku, name, price) VALUES ('IPODS2C3', 'iPod Touch', 299.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 32, 'red');
3 голосов
/ 19 мая 2009

Группировка не будет легкой, потому что какой оператор агрегирования вы собираетесь использовать для «цвета»? Обратите внимание, что невозможно использовать ваше требование 4 в случае 2.

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

Это классическая модель EAV, и она имеет место в тщательно продуманных базах данных. Чтобы сделать его более безопасным для типов, я видел случаи, когда значения хранятся в безопасных типах таблицах, а не в одном столбце varchar свободной формы.

Вместо значений:

EntityID int
,AttributeID int
,Value varchar(255)

У вас есть несколько таблиц:

EntityID int
,AttributeID int
,ValueMoney money

EntityID int
,AttributeID int
,ValueInt int

etc.

Затем, чтобы получить емкость iPod в каждом поколении:

SELECT vG.ValueVarChar AS Generation, SUM(vC.ValueDecimal) AS TotalCapacity
FROM Products AS p
INNER JOIN Attributes AS aG
    ON aG.AttributeName = 'generation'
INNER JOIN ValueVarChar AS vG
    ON vG.EntityID = p.ProductID
    AND vG.AttributeID = aG.AttributeID
INNER JOIN Attributes AS aC
    ON aC.AttributeName = 'capacity'
INNER JOIN ValueDecimal AS vC
    ON vC.EntityID = p.ProductID
    AND vC.AttributeID = aC.AttributeID
GROUP BY vG.ValueVarChar
0 голосов
/ 16 ноября 2010

Интересно, как преодолеть проблемы с использованием шаблона BLOB в качестве альтернативы EAV. Давайте предположим, что мы можем хранить все настраиваемые поля сущности в одном поле как пример строки в JSON, что-то вроде tihis: {customField1: значение1, customField2: значение2,…, customFieldN: valueN}

Как преодолеть следующие проблемы: 1. Как выполнить поиск по отдельным настраиваемым полям, например, чтобы найти сущности с условиями custField1 = value1 AND customField2 = value2? 2. Как обеспечить целостность данных, например, если мы удаляем настраиваемое поле для сущности, как удалять все значения этих настраиваемых полей в сущности.

0 голосов
/ 19 мая 2009

Звучит так, будто вы хотите создать базу данных каталога продуктов.

Я рекомендую этот подход. http://edocs.bea.com/wlp/docs40/catalog/schemcat.htm

0 голосов
/ 19 мая 2009

CREATE TABLE для новых продуктов и ALTER TABLE, добавляя / удаляя столбцы, когда пользователь выполняет операции. Используйте схему, чтобы узнать, какими свойствами обладает каждый продукт. Это удовлетворяет всем четырем вашим требованиям.

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

select [name] from sysobjects where [name] like 'product_%' AND xtype='U'
...