Sql server 多个表的外键

Sql server 多个表的外键,sql-server,relational-database,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, Su

我的数据库中有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字段归组或用户所有

描述票证与用户或组(可选)之间关系的最正确方式是什么

我在想,我应该在票证表中添加一个标志,表明什么类型拥有它

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

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

我认为这是表示您所需内容的最通用方法,而不是使用标志。

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

  • 您可以简单地在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
)
列表中的第一个选项是在我曾经参与的一个项目中实现的,在这个项目中,三个表之间建立了类似的关系。(其中一个引用了另外两个,一次一个。)

因此,引用表有两个外键列,并且它还有一个约束,以确保一行引用了一个表(不是两个,也不是两个)

以下是应用于表时的外观:

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\u Ticket\u GroupUser
check约束确保两个外键列中只有一个包含引用(另一个为NULL,这就是为什么两者都必须为NULL)


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

还有一个选项,就是在
Ticket
中有一列指定所属实体类型(
用户
),第二列引用了
用户
id,不使用外键,而是依赖触发器来强制引用完整性

我在这里看到了Nathan(上图)的两个优势:

  • 更直接的清晰和简单
  • 编写更简单的查询

另一种方法是创建一个关联表,其中包含每个潜在资源类型的列。在您的示例中,两个现有所有者类型中的每一个都有自己的表(这意味着您要引用某些内容)。如果总是这样,你可以有这样的东西:

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非常相似,但外观不同(取决于偏好)

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

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错误了,我只是把它放在了一起。

您还可以使用枚举来标识
所有者是用户还是组,如下所示:

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
)

创建表dbo.Group
(
ID int不为空,
名称varchar(50)不为空
)  
创建表dbo.User
(
ID int不为空,
名称varchar(50)不为空
)
创建类型Enum_OwnerType作为Enum('Group','User');
创建表dbo.Ticket
(
ID int不为空,
所有者int不为空,
OwnerType枚举\u OwnerType不为空,
Subject varchar(50)NULL
)

也许它并不比任何建议的解决方案好,它可能不会提供任何优势。事实上,我认为这可能需要修改
Enum_OwnerType
,甚至
ticket
,才能修改
OwnerType
,我想。。。无论如何,我希望这是有用的。

在我看来,每张票都属于一个团体。这只是一个用户是一组用户。从@nathan skerl模型中选择4。如果您使用guid作为键,那么整个过程也可以很好地运行,查询用户/组票证会是什么样子?谢谢。在组和用户表中持久化计算列有什么好处?Party表中的主键已经确保了组ID和用户ID之间没有重叠,因此外键只需要位于PartyId上。任何编写的查询仍然需要从PartyTypeName中了解表。@保留列阻止我们创建User类型的参与方并将其与dbo.Group中的记录关联。@paulkon我知道这是一个老问题,但查询类似于
选择t.Subject作为ticketObject,u.Name不为空时的大小写