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

146

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

  • Поддержка многих видов продукции (телевизор, телефон, ПК, ...). У каждого вида товаров свой набор параметров, например:

    • Телефон будет иметь цвет, размер, вес, ОС ...

    • ПК будет иметь CPU, HDD, RAM ...

  • Набор параметров должен быть динамическим. Вы можете добавить или отредактировать любой понравившийся параметр.

Как я могу удовлетворить эти требования без отдельной таблицы для каждого вида товаров?

Каменное сердце
источник

Ответы:

240

У вас есть как минимум пять вариантов моделирования описываемой иерархии типов:

  • Единая таблица наследования : одна таблица для всех типов продуктов с достаточным количеством столбцов для хранения всех атрибутов всех типов. Это означает множество столбцов, большинство из которых имеют NULL в любой данной строке.

  • Наследование таблицы классов : одна таблица для продуктов, в которой хранятся атрибуты, общие для всех типов продуктов. Затем по одной таблице для каждого типа продукта, в которой хранятся атрибуты, относящиеся к этому типу продукта.

  • Наследование конкретной таблицы : нет таблицы для общих атрибутов продуктов. Вместо этого одна таблица для каждого типа продукта, в которой хранятся как общие атрибуты продукта, так и атрибуты, специфичные для продукта.

  • Сериализованный LOB : одна таблица для продуктов, в которой хранятся атрибуты, общие для всех типов продуктов. В одном дополнительном столбце хранится большой двоичный объект частично структурированных данных в XML, YAML, JSON или другом формате. Этот большой двоичный объект позволяет хранить атрибуты, специфичные для каждого типа продукта. Вы можете использовать причудливые шаблоны дизайна, чтобы описать это, например фасад и сувенир. Но независимо от того, есть ли у вас множество атрибутов, которые нелегко запросить в SQL; вам нужно вернуть весь BLOB-объект в приложение и отсортировать его там.

  • Entity-Attribute-Value : одна таблица для продуктов и одна таблица, которая объединяет атрибуты в строки, а не столбцы. EAV не является правильным дизайном с точки зрения реляционной парадигмы, но многие люди все равно его используют. Это «Шаблон свойств», упомянутый в другом ответе. Посмотрите другие вопросы с тегом eav на StackOverflow, чтобы узнать о некоторых подводных камнях.

Подробнее об этом я написал в презентации Extensible Data Modeling .


Дополнительные мысли о EAV: Хотя многие люди, кажется, предпочитают EAV, я нет. Это кажется наиболее гибким решением, а значит, и лучшим. Однако имейте в виду пословицу ТАНСТААФЛ . Вот некоторые из недостатков EAV:

  • Невозможно сделать столбец обязательным (эквивалент NOT NULL).
  • Невозможно использовать типы данных SQL для проверки записей.
  • Невозможно обеспечить единообразное написание имен атрибутов.
  • Невозможно поместить внешний ключ в значения любого заданного атрибута, например, для таблицы поиска.
  • Получение результатов в обычном табличном макете сложно и дорого, потому что для получения атрибутов из нескольких строк вам нужно делать это JOINдля каждого атрибута.

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

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

Я бы использовал EAV только в том случае, если каждой строке должно быть разрешено потенциально иметь отдельный набор атрибутов. Когда у вас есть конечный набор типов продуктов, EAV будет излишним. Первым моим выбором было бы наследование таблицы классов.


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

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

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

Билл Карвин
источник
11
@HimalayaGarg Параметр "4.5" на самом деле противоположен сути сообщения Билла.
user3308043
2
В отличие от MySQL, SQL Server имеет обширную поддержку XML, XPath и XQuery. Поэтому для пользователей SQL Server лучшим вариантом будет сохранение дополнительных атрибутов в столбце типа XML (вариант 4). Таким образом, вам НЕ нужно «возвращать весь BLOB-объект обратно в приложение и сортировать его там». Вы даже можете создавать индексы для столбцов XML в SQL Server.
Delphi.Boy 02
2
В моем случае я предпочитаю сериализованный LOB. Но подходит ли он для ORM? Я использую EF.
Махмуд Дженами,
@ user2741577, конечно, но вам, вероятно, придется написать собственный код для распаковки полей неструктурированных данных из LOB и применения их к каждому полю сущности вашего объекта ORM. Я не знаю EF, но я полагаю, вы могли бы создать базовый класс ORM, который делает это. Вам необходимо отслеживать, какие поля взяты из конкретных полей строки базы данных, а какие - из полей LOB, чтобы вы могли повторно сформировать LOB, когда пришло время сохранить объект.
Билл Карвин,
12

@Каменное сердце

Я бы пошел сюда с EAV и MVC полностью.

@ Билл Карвин

Вот некоторые из недостатков EAV:

  • Невозможно сделать столбец обязательным (эквивалент NOT NULL).
  • Невозможно использовать типы данных SQL для проверки записей.
  • Невозможно обеспечить единообразное написание имен атрибутов.
  • Невозможно поместить внешний ключ в значения любого заданного атрибута, например, для таблицы поиска.

Все, что вы здесь упомянули:

  • проверка данных
  • проверка правописания имен атрибутов
  • обязательные столбцы / поля
  • обработка уничтожения зависимых атрибутов

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

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

Получение результатов в обычном табличном макете сложно и дорого, потому что для получения атрибутов из нескольких строк вам нужно выполнить JOIN для каждого атрибута.

Эту проблему можно решить, сделав несколько запросов к частичным данным и преобразовав их в табличную структуру с помощью вашего приложения. Даже если у вас есть 600 ГБ данных о товарах, вы можете обрабатывать их партиями, если вам нужны данные из каждой отдельной строки в этой таблице.

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

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

Если вас по-прежнему беспокоит производительность операций, выполняемых приложением, вы всегда можете использовать Erlang, C ++, Go Language для предварительной обработки данных, а затем просто обрабатывать оптимизированные данные в своем основном приложении.

Павел Барчик
источник
you can always use Erlang, C++, Go Language to pre-process the dataЧто ты имел в виду? Вместо БД использовать Go lang? Не могли бы вы подробнее рассказать об этом?
Green
1
Я абсолютно согласен. EAV - это подходящий вариант, особенно если вам нужен уровень гибкости, который позволил бы вам добавлять новые виды продуктов и параметров без изменений схемы БД, я имею в виду живое производство через ваше приложение. Был там, сделал это. Работал у меня. О медленных запросах ... Кто-нибудь здесь слышал про кеши? ;)
pawel.kalisz 01
@Green Я отредактировал последний абзац, чтобы сделать его более ясным, но он касается передачи ваших сырых данных EAV в процесс на языке, который может обрабатывать преобразования данных, поиск в древовидной структуре или любую базовую карту, сокращает операции очень быстро и с эффективным использованием памяти. Специфика здесь будет зависеть от того, что необходимо оптимизировать
Павел Барчик
6

Если я использую Class Table Inheritanceзначение:

одна таблица для продуктов, в которой хранятся атрибуты, общие для всех типов продуктов. Затем по одной таблице для каждого типа продукта, в которой хранятся атрибуты, относящиеся к этому типу продукта. -Билл Карвин

Что мне больше всего нравится из предложений Билла Карвина ... Я могу предвидеть один недостаток, который я попытаюсь объяснить, как не допустить превращения в проблему.

Какой план действий на случай непредвиденных обстоятельств я должен иметь, когда атрибут, который является общим только для 1 типа, затем становится общим для 2, затем 3 и т. Д.?

Например: (это всего лишь пример, а не моя настоящая проблема)

Если мы продаем мебель, мы можем продавать стулья, лампы, диваны, телевизоры и т. Д. Тип телевизора может быть единственным типом, который мы продаем, с потребляемой мощностью. Поэтому я бы поставил power_consumptionатрибут на tv_type_table. Но потом мы начали заниматься домашними кинотеатрами, у которых тоже есть power_consumptionсобственность. Хорошо, это всего лишь еще один продукт, поэтому я добавлю это поле к нему, stereo_type_tableтак как это, вероятно, проще всего на данном этапе. Но со временем, по мере того как мы начинаем поставлять все больше и больше электроники, мы понимаем, что power_consumptionэто достаточно широко, чтобы быть в main_product_table. Что мне теперь делать?

Добавьте поле в main_product_table. Напишите сценарий, чтобы перебрать электронику и поместить правильное значение из каждого type_tableв main_product_table. Затем отбросьте этот столбец из каждого type_table.

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

Джей Ди Айзекс
источник
3

У вас может быть таблица Product и отдельная таблица ProductAdditionInfo с 3 столбцами: идентификатор продукта, название дополнительной информации, значение дополнительной информации. Если цвет используется многими, но не всеми видами товаров, вы можете сделать его столбцом, допускающим значение NULL, в таблице товаров или просто поместить его в ProductAdditionalInfo.

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

Стив Йегге назвал это шаблоном свойств и написал длинный пост об его использовании.

Росс
источник
4
Шаблон свойств - это просто значение-сущность-атрибут-значение под другим именем. Он широко используется, но его хранение в реляционной базе данных нарушает правила нормализации.
Билл Карвин,
2
Если честно, когда я прочитал описание EAV в ответе @Bills, я не совсем понял, что он объясняет. Но когда вы сказали, 3 columns: product ID, additional info name, additional info valueя понял концепцию. И я действительно делал это раньше и сталкивался с проблемами. Однако на данный момент я не помню, что это были за проблемы.
JD Isaacks
1
@JDIsaacks В этом шаблоне общая проблема состоит в том, что мы не знаем, сколько JOIN нам нужно для получения всех атрибутов.
Омид