У меня есть таблица, containers
которая может иметь отношение многие ко многим к нескольким таблицам, скажем plants
, так animals
и есть bacteria
. Каждый контейнер может содержать произвольное количество растений, животных или бактерий, и каждое растение, животное или бактерия могут находиться в произвольном количестве контейнеров.
Пока это очень просто, но у меня возникла проблема с тем, что каждый контейнер должен содержать только элементы одного типа. Смешанные контейнеры, которые, например, содержат как растения, так и животных, должны быть нарушением ограничений в базе данных.
Моя оригинальная схема для этого была следующей:
containers
----------
id
...
...
containers_plants
-----------------
container_id
plant_id
containers_animals
------------------
container_id
animal_id
containers_bacteria
-------------------
container_id
bacterium_id
Но с этой схемой я не могу придумать, как реализовать ограничение, что контейнеры должны быть однородными.
Есть ли способ реализовать это со ссылочной целостностью и обеспечить на уровне базы данных однородность контейнеров?
Я использую Postgres 9.6 для этого.
источник
Ответы:
Существует способ реализовать это декларативно, только не сильно меняя текущие настройки, если вы согласны ввести некоторую избыточность. Последующее может считаться развитием предложения RDFozz , хотя эта идея полностью сформировалась в моей голове, прежде чем я прочитал его ответ (и в любом случае он достаточно отличается, чтобы оправдать свой собственный ответный пост).
Реализация
Вот что вы делаете, шаг за шагом:
Создайте
containerTypes
таблицу в соответствии с предложенной в ответе RDFozz:Заполните его предварительно определенными идентификаторами для каждого типа. В целях этого ответа, пусть они соответствуют примеру RDFozz: 1 для растений, 2 для животных, 3 для бактерий.
Добавьте
containerType_id
столбецcontainers
и сделайте его ненулевым и внешним ключом.Предполагая, что
id
столбец уже является первичным ключомcontainers
, создайте уникальное ограничение для(id, containerType_id)
.Вот где начинаются увольнения. Если
id
объявлен первичным ключом, мы можем быть уверены, что он уникален. Если он уникален, любая комбинацияid
и другого столбца обязательно должна быть уникальной без дополнительного объявления уникальности - так какой смысл? Дело в том, что формально объявляя уникальную пару столбцов, мы позволяем им ссылаться , то есть быть целью ограничения внешнего ключа, о чем эта часть.Добавить
containerType_id
столбец в каждой из таблиц перехода (containers_animals
,containers_plants
,containers_bacteria
). Сделать его внешним ключом совершенно необязательно. Важно убедиться, что столбец имеет одинаковое значение для всех строк, различное для каждой таблицы: 1 дляcontainers_plants
, 2 дляcontainers_animals
, 3 дляcontainers_bacteria
, в соответствии с описаниями вcontainerTypes
. В каждом случае вы также можете сделать это значение по умолчанию, чтобы упростить ваши операторы вставки:В каждой из соединительных таблиц сделайте пару столбцов
(container_id, containerType_id)
ссылкой на ограничение внешнего ключаcontainers
.Если
container_id
оно уже определено как ссылкаcontainers
, не стесняйтесь удалять это ограничение из каждой таблицы, так как больше не нужно.Как это работает
Добавив столбец типа контейнера и включив его в ограничения внешнего ключа, вы подготовите механизм, предотвращающий изменение типа контейнера. Изменение типа в
containers
типе было бы возможно только в том случае, если внешние ключи были определены с помощьюDEFERRABLE
предложения, чего не должно быть в этой реализации.Даже если бы они были отложенными, изменение типа все равно было бы невозможным из-за проверочного ограничения на другой стороне
containers
взаимосвязи таблицы соединений. Каждая соединительная таблица допускает только один конкретный тип контейнера. Это не только предотвращает изменение типа существующими ссылками, но также предотвращает добавление неправильных ссылок на типы. То есть, если у вас есть контейнер типа 2 (животные), вы можете добавлять в него элементы только с помощью таблицы, где разрешен тип 2, то естьcontainers_animals
, и вы не сможете добавить строки, ссылающиеся на него, скажемcontainers_bacteria
, который принимает только контейнеры типа 3.Наконец, ваше собственное решение иметь разные таблицы для
plants
,animals
иbacteria
и разные соединительные таблицы для каждого типа сущности уже делает невозможным для контейнера иметь элементы более одного типа.Итак, все эти факторы в совокупности гарантируют, чисто декларативным образом, что все ваши контейнеры будут однородными.
источник
Один из вариантов - добавить
containertype_id
вContainer
таблицу. Сделайте столбец NOT NULL и внешний ключContainerType
таблицы, в котором будут записи для каждого типа элемента, который может помещаться в контейнер:Чтобы убедиться, что тип контейнера не может быть изменен, создайте триггер обновления, который проверяет, было ли
containertype_id
обновлено, и откатывает изменения в этом случае.Затем в триггерах вставки и обновления в таблицах ссылок контейнеров проверьте containertype_id по типу объекта в этой таблице, чтобы убедиться, что они совпадают.
Если все, что вы помещаете в контейнер, должно соответствовать типу, и тип не может быть изменен, тогда все в контейнере будет того же типа.
ПРИМЕЧАНИЕ. Поскольку триггер в таблицах ссылок определяет, что будет соответствовать, если вам необходимо иметь тип контейнера, в котором могут быть растения и животные, вы можете создать этот тип, назначить его контейнеру и проверить его. , Таким образом, вы сохраняете гибкость, если что-то изменится в какой-то момент (скажем, вы получите типы «журналы» и «книги» ...).
ПРИМЕЧАНИЕ второе: если большая часть того, что происходит с контейнерами, одинакова, независимо от того, что в них, то это имеет смысл. Если у вас происходят совершенно разные вещи (в системе, а не в нашей физической реальности) в зависимости от содержимого контейнера, то идея Эвана Кэрролла о наличии отдельных таблиц для отдельных типов контейнеров имеет вполне разумный смысл. Это решение устанавливает, что контейнеры имеют разные типы при создании, но сохраняет их в одной таблице. Если вам приходится проверять тип каждый раз, когда вы выполняете действие над контейнером, и если выполняемое вами действие зависит от типа, отдельные таблицы на самом деле могут быть быстрее и проще.
источник
Если вам нужны только 2 или 3 категории (растения / метазоа / бактерии) и вы хотите смоделировать отношения XOR, возможно, «дуга» - это решение для вас. Преимущество: нет необходимости в триггерах. Примеры диаграмм можно найти [здесь] [1]. В вашей ситуации таблица «контейнеры» будет иметь 3 столбца с ограничением CHECK, что позволяет использовать либо растение, либо животное, либо бактерию.
Это, вероятно, не подходит, если в будущем будет необходимо различать множество категорий (например, роды, виды, подвиды). Однако для 2-3 групп / категорий это может помочь.
ОБНОВЛЕНИЕ: Вдохновленное предложениями и комментариями участника, другое решение, которое допускает множество таксонов (групп родственных организмов, классифицированных биологом) и избегает «определенных» имен таблиц (PostgreSQL 9.5).
Код DDL:
Тестовые данные:
Тестирование:
Спасибо @RDFozz и @Evan Carroll и @ypercube за их вклад и терпение (чтение / исправление моих ответов).
источник
Во-первых, я согласен с @RDFozz в прочтении вопроса. Однако он высказывает некоторые опасения по поводу ответа Стефана :
Чтобы решить его проблемы, просто
PRIMARY KEY
UNIQUE
ограничения для защиты от повторяющихся записей.EXCLUSION
ограничения, чтобы гарантировать, что контейнеры "однородны"c_id
чтобы обеспечить достойную производительность.Вот как это выглядит,
Теперь вы можете иметь один контейнер с несколькими вещами, но только один тип вещи в контейнере.
И все это реализовано в индексах GIST.
Великая пирамида Гизы не имеет ничего о PostgreSQL.
источник
Это плохая идея.
И теперь ты знаешь почему. знак равно
Я считаю, что вы застряли на идее наследования от объектно-ориентированного программирования (ОО). OO Inheritance решает проблему с повторным использованием кода. В SQL избыточный код является наименьшей из наших проблем. Честность - это прежде всего. Производительность часто вторая. Мы будем наслаждаться болью для первых двух. У нас нет «времени компиляции», которое могло бы устранить затраты.
Так что просто воздержитесь от одержимости повторным использованием кода. Контейнеры для растений, животных и бактерий принципиально различны в любом месте в реальном мире. Компонент повторного использования кода "содержит вещи" просто не сделает это за вас. Разбей их на части. Мало того, что это даст вам больше целостности и производительности, но в будущем вам будет проще расширять вашу схему: в конце концов, в вашей схеме вам уже приходилось разбивать содержащиеся в ней элементы (растения, животные и т. Д.) По крайней мере, кажется, что вам придется разбить контейнеры. Вы не будете хотеть перепроектировать всю свою схему тогда.
источник
plant_containers
и так далее. Вещи, для которых нужен только контейнер для растений, выбирают только изplant_containers
таблицы. Вещи, которые нуждаются в любом контейнере (то есть поиск во всех типах контейнеров), могут выполнятьUNION ALL
на всех трех таблицах с контейнерами.