多个表的外键

时间:2011-10-21 02:51:45

标签: sql-server relational-database

我的数据库中有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 方式描述故障单与可选的用户或组之间的这种关系?

我想我应该在故障单表中添加一个标志,说明拥有它的类型。

6 个答案:

答案 0 :(得分:132)

您有几个选项,所有选项都在“正确性”和易用性方面有所不同。一如既往,正确的设计取决于您的需求。

  • 您可以在Ticket,OwnedByUserId和OwnedByGroupId中创建两列,并且每个表都有可为空的Fks。

  • 您可以创建M:M参考表,同时启用ticket:user和ticket:组关系。也许将来您可能希望允许多个用户或组拥有单个票证?此设计不强制票证必须仅由单个实体拥有。

  • 您可以为每个用户创建一个默认组,并使票证仅由真实组或用户的默认组拥有。

  • 或(我的选择)建模一个实体,它充当用户和组的基础,并拥有该实体拥有的票证。

下面是使用您发布的架构的粗略示例:

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
)

答案 1 :(得分:27)

@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表有两列,OwnerGroupOwnerUser,两者都是可以为空的外键。 (其他两个表中的相应列相应地成为主键。)CK_Ticket_GroupUser检查约束确保两个外键列中只有一个包含引用(另一个为NULL,这就是为什么两者都必须可为空)。

(对于这个特定的实现,Ticket.ID上的主键不是必需的,但在这样的表中有一个肯定不会有害。)

答案 2 :(得分:2)

另一种方法是创建一个关联表,其中包含每种潜在资源类型的列。在您的示例中,两个现有所有者类型中的每一个都有自己的表(这意味着您要引用一些内容)。如果永远都是这种情况,那么您可以进行如下操作:

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_ID int NOT NULL,
    Subject varchar(50) NULL
)

CREATE TABLE dbo.Owner
(
    ID int NOT NULL,
    User_ID int NULL,
    Group_ID int NULL,
    {{AdditionalEntity_ID}} int NOT NULL
)

使用此解决方案,您将在向数据库中添加新实体时继续添加新列,并删除并重新创建@Nathan Skerl显示的外键约束模式。此解决方案与@Nathan Skerl非常相似,但看起来有所不同(根据喜好设置)。

如果您不打算为每种新的所有者类型创建一个新表,那么最好为每个潜在所有者包括一个owner_type而不是外键列:

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_ID int NOT NULL,
    Owner_Type string NOT NULL, -- In our example, this would be "User" or "Group"
    Subject varchar(50) NULL
)

使用上述方法,您可以根据需要添加任意数量的所有者类型。 Owner_ID没有外键约束,但将用作对其他表的引用。缺点是您必须查看该表以查看所有者键入的内容,因为根据架构并不能立即看出所有者的类型。如果您事先不知道所有者类型并且他们不会链接到其他表,我只会建议您这样做。如果您确实知道所有者类型,那么我会使用@Nathan Skerl之类的解决方案。

对不起,如果我输入的SQL错误,我将其放在一起。

答案 3 :(得分:0)

另一种选择是,在Ticket中,有一个列指定拥有实体类型(UserGroup),第二列中引用了User或{{ 1}} id而不是使用外键,而是依靠触发器来实现参照完整性。

与Nathan的excellent model(在上方)相比,我在这里看到了两个优点:

  • 更加直接的清晰度和简洁性。
  • 更简单的查询来编写。

答案 4 :(得分:0)

您还可以使用枚举来确定 it('should hide edit button if not owner', () => { let selector = '.edit-button'; component.isOwner = false; fixture.detectChanges(); expect(fixture.debugElement.query(By.css(selector))).toBeFalsy(); }); it('should show edit button for owner', () => { let selector = '.edit-button'; component.isOwner = true; fixture.detectChanges(); expect(fixture.debugElement.query(By.css(selector))).toBeTruthy(); }); 是用户还是组,如下所示:

Owner

也许它并不比任何提议的解决方案更好,它可能不会提供任何优势。事实上,我认为这可能需要改变 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 TYPE Enum_OwnerType AS ENUM ('Group', 'User'); CREATE TABLE dbo.Ticket ( ID int NOT NULL, Owner int NOT NULL, OwnerType Enum_OwnerType NOT NULL, Subject varchar(50) NULL ) 甚至 Enum_OwnerType 才能改变 ticket,我猜...我希望它无论如何都有用。

答案 5 :(得分:-2)

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

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

我认为这是表示你想要的而不是使用旗帜的最常用方法。