Внешний ключ для нескольких таблиц - PullRequest
106 голосов
/ 21 октября 2011

В моей базе данных 3 соответствующие таблицы.

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner int NOT NULL,
    Subject varchar(50) NULL
)

Пользователи принадлежат нескольким группам.Это делается через отношения многие ко многим, но в данном случае не имеет значения.Билет может принадлежать как группе, так и пользователю через поле dbo.Ticket.Owner.

Каким образом MOST CORRECT может описать эту связь между билетом и, возможно, пользователем или группой?

Я думаю, что мне нужно добавить флаг втаблица тикетов, в которой указано, кому принадлежит этот тип.

Ответы [ 4 ]

134 голосов
/ 21 октября 2011

У вас есть несколько вариантов, различающихся по «правильности» и простоте использования.Как всегда, правильный дизайн зависит от ваших потребностей.

  • Вы можете просто создать два столбца в Ticket, OwnedByUserId и OwnedByGroupId и иметь обнуляемые внешние ключи для каждой таблицы.

  • Вы можете создать справочные таблицы M: M, включающие отношения ticket: user и ticket: group.Возможно, в будущем вы захотите разрешить владение одним билетом нескольким пользователям или группам?Этот дизайн не предусматривает, что билет должен принадлежать только одному объекту.

  • Вы можете создать группу по умолчанию для каждого пользователя и иметь билеты, которыми просто владеетлибо настоящая группа, либо пользовательская группа по умолчанию.

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

Вот грубый пример использования вашей опубликованной схемы:

create table dbo.PartyType
(   
    PartyTypeId tinyint primary key,
    PartyTypeName varchar(10)
)

insert into dbo.PartyType
    values(1, 'User'), (2, 'Group');


create table dbo.Party
(
    PartyId int identity(1,1) primary key,
    PartyTypeId tinyint references dbo.PartyType(PartyTypeId),
    unique (PartyId, PartyTypeId)
)

CREATE TABLE dbo.[Group]
(
    ID int primary key,
    Name varchar(50) NOT NULL,
    PartyTypeId as cast(2 as tinyint) persisted,
    foreign key (ID, PartyTypeId) references Party(PartyId, PartyTypeID)
)  

CREATE TABLE dbo.[User]
(
    ID int primary key,
    Name varchar(50) NOT NULL,
    PartyTypeId as cast(1 as tinyint) persisted,
    foreign key (ID, PartyTypeId) references Party(PartyID, PartyTypeID)
)

CREATE TABLE dbo.Ticket
(
    ID int primary key,
    [Owner] int NOT NULL references dbo.Party(PartyId),
    [Subject] varchar(50) NULL
)
27 голосов
/ 21 октября 2011

Первая опция в списке @ Nathan Skerl - это то, что было реализовано в проекте, с которым я когда-то работал, где между тремя таблицами была установлена ​​аналогичная связь.(Один из них ссылался на два других, по одному за раз.)

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

Вот как это может выглядеть применительно к вашим таблицам:

CREATE TABLE dbo.[Group]
(
    ID int NOT NULL CONSTRAINT PK_Group PRIMARY KEY,
    Name varchar(50) NOT NULL
);

CREATE TABLE dbo.[User]
(
    ID int NOT NULL CONSTRAINT PK_User PRIMARY KEY,
    Name varchar(50) NOT NULL
);

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL CONSTRAINT PK_Ticket PRIMARY KEY,
    OwnerGroup int NULL
      CONSTRAINT FK_Ticket_Group FOREIGN KEY REFERENCES dbo.[Group] (ID),
    OwnerUser int NULL
      CONSTRAINT FK_Ticket_User  FOREIGN KEY REFERENCES dbo.[User]  (ID),
    Subject varchar(50) NULL,
    CONSTRAINT CK_Ticket_GroupUser CHECK (
      CASE WHEN OwnerGroup IS NULL THEN 0 ELSE 1 END +
      CASE WHEN OwnerUser  IS NULL THEN 0 ELSE 1 END = 1
    )
);

Как видите, таблица Ticket имеет два столбца, OwnerGroupи OwnerUser, оба из которых являются обнуляемыми внешними ключами.(Соответствующие столбцы в двух других таблицах соответственно являются первичными ключами.) Проверочное ограничение CK_Ticket_GroupUser гарантирует, что только один из двух столбцов внешнего ключа содержит ссылку (другой - NULL, поэтому обе должны иметь значение NULL).

(Первичный ключ на Ticket.ID не является необходимым для этой конкретной реализации, но определенно не повредит иметь такой ключ в такой таблице.)

0 голосов
/ 21 июня 2019

Еще один вариант - иметь в Ticket один столбец, указывающий тип объекта-владельца (User или Group), второй столбец с указанным идентификатором User или Group и НЕ использовать внешние ключи.но вместо этого полагайтесь на Trigger для обеспечения ссылочной целостности.

Два преимущества, которые я вижу здесь, перед превосходной моделью Натана (выше):

  • Более непосредственная ясность и простота.
  • Упрощенные запросы для записи.
0 голосов
/ 21 октября 2011
CREATE TABLE dbo.OwnerType
(
    ID int NOT NULL,
    Name varchar(50) NULL
)

insert into OwnerType (Name) values ('User');
insert into OwnerType (Name) values ('Group');

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

...