在T-SQL中,是否可以使用由1列定义表类型组成的复合键和从表类型列中引用的表中定义行的Id的另一列组成的关系表?
对于共享电子邮件地址示例:
三个不同的用户表(UserA,UserB,UserC)
一个用户类型表(UserType)
一个电子邮件表(EmailAddress)
一个电子邮件 - 用户关系表(EmailRelationship)
EmailRelationship表包含三列,EmailId,UserTypeId和UserId
我可以从每个User表到EmailRelationship表(或其他方式?)建立关系以保持参照完整性吗?
我已经尝试将EmailRelationship表中的所有三列都设为主键,我尝试只将UserTypeId和UserId设为主键。
CREATE TABLE [dbo].[UserType](
[Id] [int] IDENTITY(1,1) NOT NULL ,
[Type] [varchar](50) NOT NULL)
insert into [dbo].[UserType]
([Type])
values
('A'),('B'),('C')
CREATE TABLE [dbo].[UserA](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserA]
(UserTypeId,Name)
values
(1,'UserA')
CREATE TABLE [dbo].[UserB](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserB]
(UserTypeId,Name)
values
(2,'UserB')
CREATE TABLE [dbo].[UserC](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserC]
(UserTypeId,Name)
values
(3,'UserC')
CREATE TABLE [dbo].[Email](
[Id] [int] IDENTITY(1,1) NOT NULL,
[EmailAddress] [varchar](50) NOT NULL)
insert into [dbo].[email]
(EmailAddress)
values
('SharedEmail@SharedEmail.com')
CREATE TABLE [dbo].[EmailRelationship](
[EmailId] [int] NOT NULL,
[UserTypeId] [int] NOT NULL,
[UserId] [int] NOT NULL)
insert into [dbo].[EmailRelationship]
(EmailId, UserTypeId, UserId)
values
(1,1,1),(1,2,1),(1,3,1)
答案 0 :(得分:1)
不,没有,外键可以引用一个表,只有一个表,我可以想到三种方法。
第一个是有3列,每个用户表一个列,每个列都有一个外键,还有一个检查约束来检查一个,只有一个值不为空
CREATE TABLE dbo.EmailRelationship
(
EmailId INT NOT NULL,
UserTypeId INT NOT NULL,
UserAId INT NULL,
UserBId INT NULL,
UserCId INT NULL,
CONSTRAINT FK_EmailRelationship__UserAID FOREIGN KEY (UserAId)
REFERENCES dbo.UserA (Id),
CONSTRAINT FK_EmailRelationship__UserBID FOREIGN KEY (UserBId)
REFERENCES dbo.UserB (Id),
CONSTRAINT FK_EmailRelationship__UserCID FOREIGN KEY (UserCId)
REFERENCES dbo.UserC (Id),
CONSTRAINT CK_EmailRelationship__ValidUserId CHECK
(CASE WHEN UserTypeID = 1 AND UserAId IS NOT NULL AND ISNULL(UserBId, UserCId) IS NULL THEN 1
WHEN UserTypeID = 2 AND UserBId IS NOT NULL AND ISNULL(UserAId, UserCId) IS NULL THEN 1
WHEN UserTypeID = 3 AND UserCId IS NOT NULL AND ISNULL(UserAId, UserBId) IS NULL THEN 1
ELSE 0
END = 1)
);
然后,作为一个快速示例,尝试插入用户类型ID为2的UserAId会给您一个错误:
INSERT EmailRelationship (EmailID, UserTypeID, UserAId)
VALUES (1, 1, 1);
INSERT语句与CHECK约束冲突" CK_EmailRelationship__ValidUserId"。
第二种方法是只有一个用户表,并存储用户类型以及任何其他常见属性
CREATE TABLE dbo.[User]
(
Id INT IDENTITY(1, 1) NOT NULL,
UserTypeID INT NOT NULL,
Name VARCHAR(50) NOT NULL,
CONSTRAINT PK_User__UserID PRIMARY KEY (Id),
CONSTRAINT FK_User__UserTypeID FOREIGN KEY (UserTypeID) REFERENCES dbo.UserType (UserTypeID),
CONSTRAINT UQ_User__Id_UserTypeID UNIQUE (Id, UserTypeID)
);
-- NOTE THE UNIQUE CONSTRAINT, THIS WILL BE USED LATER
然后,您可以在电子邮件关系表中使用正常的外键约束:
CREATE TABLE dbo.EmailRelationship
(
EmailId INT NOT NULL,
UserId INT NOT NULL,
CONSTRAINT PK_EmailRelationship PRIMARY KEY (EmailID),
CONSTRAINT FK_EmailRelationship__EmailId
FOREIGN KEY (EmailID) REFERENCES dbo.Email (Id),
CONSTRAINT FK_EmailRelationship__UserId
FOREIGN KEY (UserId) REFERENCES dbo.[User] (Id)
);
然后,不再需要针对电子邮件关系存储UserTypeId
,因为您可以加入User
以获取此信息。
然后,如果出于某种原因,您确实需要针对不同用户类型的特定表(这不是闻所未闻),您可以创建这些表,并对用户表强制引用完整性:
CREATE TABLE dbo.UserA
(
UserID INT NOT NULL,
UserTypeID AS 1 PERSISTED,
SomeOtherCol VARCHAR(50),
CONSTRAINT PK_UserA__UserID PRIMARY KEY (UserID),
CONSTRAINT FK_UserA__UserID_UserTypeID FOREIGN KEY (UserID, UserTypeID)
REFERENCES dbo.[User] (Id, UserTypeID)
);
来自UserID
的外键和计算列UserTypeID
返回User
表,确保您只能在此表中输入UserTypeID
为1的用户
第三个选项是为每个User表建立一个单独的联结表:
CREATE TABLE dbo.UserAEmailRelationship
(
EmailId INT NOT NULL,
UserAId INT NOT NULL,
CONSTRAINT PK_UserAEmailRelationship PRIMARY KEY (EmailId, UserAId),
CONSTRAINT FK_UserAEmailRelationship__EmailId FOREIGN KEY (EmailId)
REFERENCES dbo.Email (Id),
CONSTRAINT FK_UserAEmailRelationship__UserAId FOREIGN KEY (UserAId)
REFERENCES dbo.UserA (Id)
);
CREATE TABLE dbo.UserBEmailRelationship
(
EmailId INT NOT NULL,
UserBId INT NOT NULL,
CONSTRAINT PK_UserBEmailRelationship PRIMARY KEY (EmailId, UserBId),
CONSTRAINT FK_UserBEmailRelationship__EmailId FOREIGN KEY (EmailId)
REFERENCES dbo.Email (Id),
CONSTRAINT FK_UserBEmailRelationship__UserBId FOREIGN KEY (UserBId)
REFERENCES dbo.UserB (Id)
);
每种方法都有其优点和缺点,因此您需要评估最适合您情景的方法。
答案 1 :(得分:0)
不,它不起作用。您不能将列值用作对不同表的动态引用。
一般来说,数据设计存在缺陷。
答案 2 :(得分:-1)
感谢@GarethD我创建了一个CHECK约束,该约束调用了一个强制参照完整性的标量函数(仅在插入时,参考下面的警告):
使用我上面的例子:
alter FUNCTION [dbo].[UserTableConstraint](@Id int, @UserTypeId int)
RETURNS int
AS
BEGIN
IF EXISTS (SELECT Id From [dbo].[UserA] WHERE Id = @Id and UserTypeId = @UserTypeId)
return 1
ELSE IF EXISTS (SELECT Id From [dbo].[UserB] WHERE Id = @Id and UserTypeId = @UserTypeId)
return 1
ELSE IF EXISTS (SELECT Id From [dbo].[UserC] WHERE Id = @Id and UserTypeId = @UserTypeId)
return 1
return 0
end;
alter table [dbo].[emailrelationship]
--drop constraint CK_UserType
with CHECK add constraint CK_UserType
CHECK([dbo].[UserTableConstraint](UserId,UserTypeId) = 1)
我确信在CONSTRAINT中对标量函数调用有一个不可忽视的开销。如果上述内容变得令人望而却步,我将在这里报告,尽管有问题的表格不需要处理大量的INSERT。
如果有其他原因不能做到,请听听。谢谢!
我用100k行测试了INSERT和UPDATE(SQL Server 2014,2.1ghz quadcore w / 8gb ram):
启用IO和TIME STATISTICS会导致INSERT测试运行:
我将STATISTICS留给UPDATE 100k行测试:
我引用的表(UserA,UserB,UserC,来自我的例子)每个只包含大约10k行,所以任何想要实现上述内容的人都可能想要运行一些额外的测试,特别是如果你引用的表包含数百万行。 / p>
上述解决方案可能不适合大多数用途,因为唯一一次检查参照完整性是在INSERT时的CHECK CONSTRAINT期间。对数据的任何其他操作或修改都需要考虑到这一点。例如,使用上述内容,如果删除电子邮件,则任何相关的EmailRelationship条目都将指向无效数据。