Можно сохранять полную связь, сохраняя намерение хранить данные параметризованным способом. Ниже приведена очень упрощенная демонстрация, но ее должно быть достаточно, чтобы показать основные приемы, которые необходимы - вкратце, дополнительные уровни абстракции, некоторые суррогатные первичные ключи и некоторые таблицы с составными первичными ключами. Я опущу точное описание ограничений внешнего ключа, предполагая, что читатель может понять очевидные отношения между таблицами ниже.
Ваша первая таблица предназначена только для определения сущностей, о которых вы хотите хранить информацию, и ключа для поиска, какие виды информации будут храниться:
entity_id | entity_type
---------------------------
1 | lawn mower
2 | toothbrush
3 | bicycle
4 | restaurant
5 | person
Следующая таблица связывает тип сущности с полями, которые вы хотите сохранить для каждого типа сущности:
entity_type | attribute
------------------------
lawn mower | horsepower
lawn mower | retail price
lawn mower | gas_or_electric
lawn mower | ...etc
toothbrush | bristle stiffness
toothbrush | weight
toothbrush | head size
toothbrush | retail price
toothbrush | ...etc
person | name
person | email
person | birth date
person | ...etc
Это можно расширить до любого количества полей для каждого типа объекта. Это все еще реляционный; эта таблица имеет первичный ключ, это просто составной ключ, состоящий из обоих столбцов.
Этот пример упрощен для краткости; на практике вам приходится сталкиваться с проблемами пространства имен с помощью атрибутов, и вы, вероятно, хотите, чтобы определенные имена атрибутов были для каждого типа объекта в случае, если одно и то же имя означает что-то другое для совершенно другого типа объекта. Используйте суррогатный первичный ключ для атрибутов, чтобы решить проблему с пространством имен, если вы не возражаете против снижения читабельности при взгляде непосредственно на таблицы.
Между тем, в противоположность предыдущему пункту, полезно сделать общие и однозначные атрибуты (такие как «вес в граммах» или «розничная цена в долларах США» доступными для повторного использования для нескольких типов объектов. Чтобы справиться с этим, добавьте уровень абстракции между атрибутами и типами сущностей. Составьте таблицу «наборов атрибутов», каждый из которых связан с атрибутами 1..n. Тогда каждый тип сущности в приведенной выше таблице будет связан не непосредственно с атрибутами, а с одним или несколькими атрибутами. наборы.
Вам нужно будет либо гарантировать, что наборы атрибутов не перекрываются в том, на какие атрибуты они указывают, либо создать средство разрешения конфликтов по иерархии, составу, объединению наборов или тому, что соответствует вашим потребностям.
Итак, на этом этапе поиск конкретной сущности происходит следующим образом. Из идентификатора объекта мы получаем тип объекта. Из типа сущности мы получаем наборы атрибутов 1..n, которые дают результирующий набор атрибутов, который содержится в сущности. Наконец, есть большая таблица с фактическими данными в ней:
entity_id | attribute_id | value
---------------------------------------
923 | 1049272 | green
923 | 1049273 | 206.55
924 | 1049274 | 843-219-2862
924 | 1049275 | Smith
929 | 1049276 | soft
929 | 1049277 | ...etc
Как и во всех этих таблицах, эта имеет первичный ключ, в данном случае состоящий из столбцов entity_id и attribute_id. Значения хранятся в текстовом столбце без единиц измерения. Единицы хранятся в отдельной таблице, связывающей атрибуты с единицами. Можно создать больше таблиц, если вам нужно более детально это определить; Вы можете установить дополнительные уровни абстракции, чтобы создать систему «типа атрибута», аналогичную системе типов сущностей, описанной выше.
При необходимости вы можете зайти так далеко, чтобы сохранить отношения, такие как «атрибут X численно преобразуется в атрибут Y по следующей формуле», для числовых атрибутов. Или для нечисловых атрибутов вы можете создать таблицы эквивалентности для управления альтернативным написанием или форматами для допустимых значений атрибута.
Как вы можете себе представить, чем дальше вы идете с вашей системой "типов атрибутов и единиц", и чем больше вы используете этот дополнительный механизм в вычислениях, тем медленнее будет все это. В худшем случае вы смотрите на много соединений. Но эту проблему можно решить с помощью кэширования и представлений, если ваша ситуация позволяет сделать компромиссы, такие как замедление скорости записи, для значительного увеличения скорости чтения. Кроме того, многие из ваших запросов к базе данных будут в ситуациях, когда вы уже знаете, с каким типом сущности вы работаете в данный момент, каковы его результирующие атрибуты и их типы; так что вам нужно только извлечь буквальные значения из таблицы сущности / атрибута / значения, и это очень быстро.
В заключение, надеюсь, я показал, как вы можете получить столько параметров, сколько пожелаете, оставаясь полностью реляционным. Для большего количества уровней абстракции требуется больше таблиц, чем для некоторых более простых подходов; все же это избегает недостатков стиля "одного большого стола". Этот стиль хранения сущностей> тип> атрибут> значений является мощным, гибким и может быть расширен по мере необходимости.
А благодаря настройке реляционной / нормализованной таблицы вы можете выполнять любые виды реорганизации по мере развития вашей схемы сущности без потери данных. Дополнительные уровни абстракции позволяют вам переопределять атрибуты из одного набора атрибутов в другой, менять их имена при необходимости и изменять наборы атрибутов, которые использует тип сущности, без потери сохраненных значений, пока вы пишете соответствующие миграции , На днях я понял, что мне нужно хранить определенный атрибут продукта для каждого бренда, а не для каждого продукта, и смог изменить схему за пять минут, сохранив только пару обновленных строк в базе данных. Во многих других настройках, особенно в настройке с одной большой таблицей, это могло бы быть намного больше работы, требующей до одной или нескольких обновленных строк на сущность, затронутую изменением.