Лучший дизайн схемы для табличных отношений, который обеспечивает целостность - PullRequest
6 голосов
/ 17 марта 2009

Учитывая таблицу моделей «A», которая может иметь несколько дочерних моделей «B», из которых «B» будет иметь одну или несколько дочерних моделей «C» ... это звучит просто, однако мне нужно применить это для каждый 'A', любой 'B' должен иметь уникальную коллекцию 'C' .. например, C не может быть дочерним для двух 'B, которые являются частью одного и того же родителя' A '.. но' C 'может быть дочерним для нескольких «B», если каждый «родительский» B «A» различен ..

Имеет ли это смысл, или я должен отразить свой сценарий? ура заранее!

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

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

Просто чтобы прояснить ситуацию, я объясню сценарий, но вот некоторые примечания:

«A» имеет ноль или более «B», «B» неявно связано с «A», и, как таковое, всегда является потомком только одного «A». «C» является своего рода корневым объектом, который связан со многими «B», а также с другими элементами в базе данных.


Вот настоящая история:

Это веб-сайт, который содержит много сводок (A) и множество членов (C), в записке может быть много отправлений (B), из которых в представлении всегда будет один или несколько связанных участников. Идея состоит в том, что на самом деле представление может быть совместной работой, и каждый участник обладает не большей «властью», чем кто-либо другой, но будет существовать другая система для проверки политики совместной работы участников.

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

Надеюсь, это поможет, но я думаю, вы уже оказали мне большую помощь!

Стив.

Ответы [ 5 ]

3 голосов
/ 17 марта 2009

Я думаю, что вам нужны стандартные утверждения 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 (второй вариант). Вы также можете получить дополнительную информацию (краткое название, заголовок представления, имя участника, различные даты и т. Д.), Но суть проблемы заключается в том, что показанный запрос не должен возвращать никаких данных.

3 голосов
/ 17 марта 2009

Я думаю, у меня здесь запечатлена ваша модель отношений; Если нет, то я голосую за необоснованность:

  • A [{AID}, ...]
  • B [{BID}, AID, ...]
  • C [{CID}, ...]
  • B_C_Link [{BID, CID}, AID]
    • Дополнительный уникальный индекс включен (AID, CID)

В нотации используется индикатор первичного ключа {}. Так как может иметь несколько B (путем помещения AID в B), B может иметь C (используя таблицу B_C_Link "многие ко многим"), и несколько C не могут принадлежать одному и тому же A (путем добавления AID ко многим уникальность таблиц и принудительных (AID, CID) таблиц to-many.

0 голосов
/ 17 марта 2009

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

0 голосов
/ 17 марта 2009

Что у вас есть троичные отношения . Вам нужно иметь таблицу, которая связывает A и B и C в своем первичном ключе. Поскольку первичные ключи не могут быть дублированы, это приведет к тому, что будет только один C для каждого A, а также каждого B. Это создаст уникальную коллекцию, которую вы искали.

Вы получаете следующую структуру таблицы:

A's({A_ID}, ...)
B's({B_ID}, ...)
C's({C_ID}, ...)
A_B_C_Relation({[A_ID], [B_ID], [C_ID]}, ...)

Первичные ключи в скобках, внешние ключи в скобках.

Смотрите здесь для получения дополнительной информации.

0 голосов
/ 17 марта 2009

Добавьте идентификатор TableA в TableB, добавьте его к первичному ключу и сделайте то же самое для TableB и TableC.

редактирование:

Я полагаю, что первая часть этого ответа будет работать для ограничения от А до Б. Тем не менее, я бы тогда поставил таблицу связей между B и C, в которой также содержался PK. таким образом, вы получаете 1: N между A: B, и ваши ограничения затем применяются.

...