правильный способ смоделировать это с помощью таблицы ассоциации :
+-------+ +--------+ +--------+
| Group |--------------| Member |-----------------| Person |
+-------+ 1 * +--------+ 1 1 +--------+
| 1 | 1
| |
| |
| 0..1 |
+--------+ |
| Leader |--------------------------------------------+
+--------+ 0..1
Я притворяюсь, что "лидер" - точное описание того, кто«особенный» в группе.Вы должны попытаться использовать более описательное имя, чем «selected».
Схема выглядит следующим образом:
CREATE TABLE Group
(
Id int NOT NULL PRIMARY KEY,
...
)
CREATE TABLE Person
(
Id int NOT NULL PRIMARY KEY,
...
)
CREATE TABLE Member
(
PersonId int NOT NULL PRIMARY KEY
CONSTRAINT FK_Member_Person FOREIGN KEY REFERENCES Person (Id)
ON UPDATE CASCADE ON DELETE CASCADE,
GroupId int NOT NULL
CONSTRAINT FK_Member_Group FOREIGN KEY REFERENCES Group (Id)
ON UPDATE CASCADE ON DELETE CASCADE
)
CREATE INDEX IX_Member_Group ON Member (GroupId)
CREATE TABLE Leader
(
PersonId int NOT NULL PRIMARY KEY
CONSTRAINT FK_Leader_Person FOREIGN KEY REFERENCES Person (Id)
ON UPDATE CASCADE ON DELETE CASCADE,
GroupId int NOT NULL
CONSTRAINT FK_Leader_Group FOREIGN KEY REFERENCES Group (Id)
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT U_Member_Group UNIQUE (GroupId)
)
Она выражает следующую информацию об отношениях:
Группа существует, период .Это может иметь или не иметь членов.Если у него нет членов, то по определению у него также нет лидера.Он все еще существует, потому что новые члены могут быть добавлены позже.
Человек существует, период .Человек не перестанет существовать просто потому, что его группа существует.
Человек может быть членом одной и только одной группы.
Человек также может быть лидером группы.Группа может иметь только одного лидера одновременно.Лидер группы может или не считаться участником .
Вы можете подумать, что ограничения, налагаемые этим реляционным дизайном,значительно слабее, чем те, о которых спрашивают в вашем вопросе.И ты был бы прав.Это потому, что ваш вопрос связывает модель данных с требованиями бизнеса / домена.
В дополнение к этой модели у вас также должно быть несколько бизнес-правил , которые применяются вашим приложение , например:
Если в группе нет участников, она удаляется / деактивируется / скрывается.
Если деактивированная / скрытая группа получает участников, она реактивируется / отображается.
Человек должен быть членом какой-либо группы.Эта информация должна быть предоставлена при добавлении нового лица (это не обязательно должна быть существующая группа, это может быть новая группа).Если членская группа человека удалена, это должно вызвать процесс исключения;альтернативно, не разрешайте удалять группу, если в ней все еще есть участники.
Группа, в которой есть участники, должна иметь лидера.Если новый человек добавляется в пустую группу, он становится лидером.Если лидер (человек) удален, то новый лидер должен быть автоматически выбран на основе некоторых критериев или должен быть запущен процесс исключения.
Почему это "Правильный «дизайн»
Прежде всего потому, что он точно отображает независимость сущностей и их отношения .Группы и люди не на самом деле зависят друг от друга;это просто ваши бизнес-правила, предписывающие, что вы не заинтересованы в людях без членства в группе или группах без каких-либо членов или лидеров.
Что еще более важно, потому что индексирование и ограничения намного чище:
- Быстро опрашивать членов группы.
- Быстро опрашивать членов группы.
- Быстро опрашивать лидера группы.
- Опрос лиц, которые также являются лидерами, выполняется быстро.
- Удаление группы автоматически удаляет все членства / лидеров группы.
- Удаление человека автоматически удаляет все членство / лидерство в группе.
- Изменение членства - это все еще один оператор
UPDATE
. - Смена руководства - это все еще один оператор
UPDATE
. - SQL Server не будет жаловаться на множественные каскадные пути.
- Каждая таблица имеет не более 2 индексов для столбцов, которые вы ожидаете индексировать.
- Вы можете легко расширить этот дизайн, т.е.mmodate различные типы членства.
- Изменения в членстве / руководстве никогда не будут мешать простым запросам (таким как поиск человека по имени).
- Каждый ORM может справиться с этимбез проблем на всех.Обычно вы относитесь к нему как ко многим, но вы можете реализовать его как nullable-one-to-one.
Все других решений имеют серьезный, фатальный недостаток:
Помещение GroupId
в Person
и LeaderId
в Group
приводит к циклу, который не может быть разрешен, за исключением случаев, когда хотя бы один из столбцов обнуляется. Вы также не сможете CASCADE
одно из отношений.
Помещение GroupId
в Person
и дополнительное IsLeader
в Person
не позволяет вам применять верхнюю границу (1 лидер на группу) без триггера. На самом деле, технически вы можете сделать это с отфильтрованным индексом (только для SQL '08), но он все еще ошибочен, поскольку бит IsLeader
фактически не обозначает отношение, и если вы случайно обновите GroupId
, но забудете о IsLeader
тогда вы вдруг сделали этого человека лидером совершенно другой группы и, вероятно, нарушили ограничение не более одного.
Некоторые люди решат добавить GroupId
к Person
, но при этом сохранят таблицу ассоциации Leader
. Концептуально это лучший дизайн, но, поскольку у вас, скорее всего, будет CASCADE
от группы к человеку, вы не сможете также поставить двустороннюю CASCADE
на лидера (вы получите "каскадные пути" ошибка при попытке).
Да, я знаю, что это немного больше работы, и вам нужно немного подумать о том, каковы ваши бизнес-правила, но поверьте мне, это то, что вы хотите сделать. Все остальное приведет только к боли.