Я думаю, что вам нужны стандартные утверждения SQL, которые (к сожалению) в значительной степени не реализуются реальной СУБД.
Все ответы согласны с тем, что есть три первичные таблицы, называемые TableA, TableB и TableC, каждая из которых содержит свой собственный столбец ID:
TableA (A_ID PRIMARY KEY, ...)
TableB (B_ID PRIMARY KEY, ...)
TableC (C_ID PRIMARY KEY, ...)
Из описания проблемы не ясно, может ли одно значение B иметь несколько родительских записей A. Понятно, что один C может иметь несколько родительских записей B. Если B привязан к одному A, дизайн таблицы B может быть изменен на:
TableB (B_ID, ..., A_ID REFERENCES TableA)
Если B можно связать с несколькими различными A, то соединение лучше всего представить объединяющей таблицей:
A_and_B (A_ID REFERENCES TableA,
B_ID REFERENCES TableB,
PRIMARY KEY (A_ID, B_ID)
)
Из описания также не ясно, должны ли C, связанные с B, быть одинаковыми для каждого A, с которым связан B, или могут ли разные A ссылаться на один и тот же B, и набор C, связанный с B для A1 может отличаться от набора C, связанных с B для A2. (Конечно, если один B может быть связан только с одним А, этот вопрос является спорным.)
Для целей этого ответа я собираюсь предположить, что любой B связан с одним A, поэтому структура TableB включает A_ID в качестве внешнего ключа. Поскольку один C может быть связан с несколькими B, соответствующей структурой является новая объединяющая таблица:
B_and_C (B_ID REFERENCES TableB,
C_ID REFERENCES TableC,
PRIMARY KEY (B_ID, C_ID)
)
Упрощение (исключая правила об отсрочке и немедленности) утверждение выглядит следующим образом:
CREATE ASSERTION assertion_name CHECK ( <search_condition> )
Итак, когда у нас есть набор проектных решений, мы можем написать утверждение для проверки данных. Для заданных таблиц TableA, TableB (с внешним ключом A_ID), TableC и B_and_C требуется, чтобы количество вхождений данного C_ID в полный A составляло 1.
CREATE ASSERTION only_one_instance_of_c_per_a CHECK
(
NOT EXISTS (
SELECT A_ID, COUNT(C_ID)
FROM TableB JOIN B_and_C USING (C_ID)
GROUP BY A_ID
HAVING COUNT(C_ID) > 1
)
)
[ Изменено : Я думаю, что это более точно:
CREATE ASSERTION only_one_instance_of_c_per_a CHECK
(
NOT EXISTS (
SELECT A_ID, C_ID, COUNT(*)
FROM TableB JOIN B_and_C USING (C_ID)
GROUP BY A_ID, C_ID
HAVING COUNT(*) > 1
)
)
]
Набор условий соединения варьируется в зависимости от других правил соединения таблиц, но общая структура ограничений остается неизменной - не должно существовать более одной ссылки на данный C_ID для конкретного A_ID.
В комментариях ниже примечания meandmycode:
У меня такое ощущение, что в моем дизайне есть изъян. Моя логика в реальном мире заключается в том, что у «В» всегда есть хотя бы один дочерний «С». Это не имеет смысла, учитывая, что «B» должен существовать до того, как его ребенок может быть присоединен.
База данных в настоящее время позволяет присоединять букву «B» к букве «A», не имея по крайней мере ОДНОГО «C»… ребенка, поэтому я собираюсь пересмотреть «B», чтобы в нем было поле, которое относится первичный дочерний элемент «C», а также наличие дочерней коллекции дополнительных «C», но теперь у меня есть коллекция, которая также может включать первичный «C», указанный в «B», что было бы ... неправильно.
Существует ли шаблон БД, который выведет правило "один или несколько детей", против нуля или более?
Я думаю, у вас есть проблемы с вашей моделью. Трудно создать B, если уже должен существовать C, который ссылается на вновь созданный B, особенно если C должен ссылаться только на существующие B. Фраза «курица и яйцо» приходит на ум. Итак, обычно вы позволяете B иметь ноль или более C в контексте, подобном этому.
Вы до сих пор не указали, есть ли у TableB внешний ключ A_ID или есть ли у вас таблица связывания, такая как A_and_B. Если у него есть внешний ключ, то, вероятно, вы не сможете создать B, пока не создадите A, к которому он относится.
Я не думаю, что включение одного идентификатора C в таблицу B является хорошей идеей - оно способствует асимметричной обработке (более сложный SQL). Это также означает, что если вам нужно удалить этот один C, вы должны обновить его так, чтобы одна из других ссылок C была удалена из таблицы, в которой он находится в данный момент, а затем обновить значение в B-записи. Это грязно, чтобы быть вежливым об этом.
Я думаю, вам нужно изменить свой вопрос, чтобы определить фактическую структуру таблицы, на которую вы смотрите - в соответствии с линиями, показанными в различных ответах; Вы можете использовать тройные точки для представления других, но не относящихся к делу столбцов. Предложенное мною утверждение, вероятно, должно быть реализовано как своего рода триггер, который попадает в специфичные для СУБД нотации.
Из исправленного описания брифов (A), представлений (B) и членов (C) ясно, что одно представление относится только к одному брифу, так что представления могут иметь простой внешний ключ, который идентифицирует бриф это представление для. И участник может сотрудничать только в одном представлении для конкретного брифинга. Будет таблица 'submission_collaborators' со столбцами для идентификации представления и члена, комбинация - это первичный ключ, а каждый столбец - внешний ключ.
Briefs(Brief_ID, ...)
Submissions(Submission_ID, Brief_ID REFERENCES Briefs, ...)
Members(Member_ID, ...)
Submission_Collaborators(Submission_ID REFERENCES Submissions,
Member_ID REFERENCES Members,
PRIMARY KEY (Submission_ID, Member_ID)
)
Следовательно, требование заключается в том, чтобы следующий запрос не возвращал строк:
SELECT s.brief_id, c.member_id, COUNT(*)
FROM submissions AS s JOIN submission_collaborators AS c
ON s.submission_id = c.submission_id
GROUP BY s.brief_id, c.member_id
HAVING COUNT(*) > 1
Это тот же запрос, который я встроил в CREATE ASSERTION (второй вариант). Вы также можете получить дополнительную информацию (краткое название, заголовок представления, имя участника, различные даты и т. Д.), Но суть проблемы заключается в том, что показанный запрос не должен возвращать никаких данных.