2016-06-28 55 views
0

在T-SQL中,是否有可能具有由组合键组成的关系表,该列由定义表类型的1列和定义参考表中的行的标识符组成在Table Type列中?T-SQL中的复合键和参照完整性

对于共享电子邮件地址例如:
三种不同的用户表(用户A,用户B,用户C)
一用户类型表(用户类型)
一个电子邮件表(EmailAddress的)
一个电子邮件用户关系表(EmailRelationship)

EmailRelationship表包含三列,EmailId,UserTypeId和UserId

为了保持参照完整性,我可以从每个用户表到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 
('[email protected]') 

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

我明白,你几乎可以肯定地发布你的实际情况的一个小例子,但我不能真正看到你需要所有3个用户表,为什么不只是有单用户表? – GarethD

+0

让我直说吧? [EmailRelationship]应该知道哪三个表是FK?更何况为什么你需要三个相同的表? – Paparazzi

+0

这是理解_的简化示例。它不是用户所说的,而是SalesCompany,SalesCompanyRep,ParticipatingCompany,ParticipatingCompanyRep以及其他不符合“用户”类别的信息,但都可以收到信函。我的最终目标是建立一个系统,该系统会发邮件给在SalesCompany和SalesCompanyReps表中定义的所有实体,并最终扩大到发送信件到ParticipatingCompany和ParticipatingCompanyRep表(或员工或其他)中定义的所有的entites。 – RIanGillis

回答

-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。

如果还有其他原因不能做到上述,我想听听他们。谢谢!

更新:

我测试过的INSERT和UPDATE与100K行(SQL服务器2014年,2.1GHz的四核W/8GB内存):

  • INSERT需要2秒出约束
  • 和3秒钟,检查约束

开启IO和时间统计导致INSERT测试中运行:

  • 1.7秒用了约束
  • 和10秒的CHECK约束

我留在了UPDATE 100K行测试统计:

  • 刚过用了1秒CONSTRAINT
  • and 1.5sec with the CHECK CONSTRAINT

我引用的表格(UserA,UserB,UserC,来自我的例子)每个只包含大约10k行,所以其他人希望实现上述操作可能需要运行一些额外的测试,特别是如果您引用的表包含数百万行。

警告:

上述溶液可能不适合于大多数的用途,如参照完整性检查的唯一时间是在INSERT检查约束中。数据的任何其他操作或修改都需要考虑到这一点。例如,使用上述内容,如果电子邮件被删除,则任何相关的EmailRelationship条目都将指向无效数据。

+1

您不妨读一读[裹在CHECK约束标量UDF是非常缓慢的,并可能会失败的多行更新](http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/06/25/scalar-udfs-wrapped-在检查约束-是-非常慢 - 和 - 可能失效换多行-updates.aspx),并且还[另一个隐藏并行杀手:标量UDF在检查约束](HTTPS://www.brentozar。 COM /存档/ 2016/04 /另一个隐藏的平行度杀手 - 标量UDF的检查约束/) – GarethD

+2

理由,被下投票,将不胜感激。 – RIanGillis

0

不,它不会那样工作。您不能将列值用作对不同表格的动态引用。

一般而言,数据设计是有缺陷的。

1

不,没有外键可以指一张表,只有一个表,我可以想出三种方法可以处理这个问题。

首先是有3列,每一个用户表,有一个外键的每一列和检查约束检查的一个,只有一个值不为null

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) 
); 

然后作为一个简单的例子试图插入一个UserAId为2的用户类型ID给你一个错误:

INSERT EmailRelationship (EmailID, UserTypeID, UserAId) 
VALUES (1, 1, 1); 

The INSERT statement conflicted with the CHECK constraint "CK_EmailRelationship__ValidUserId".

第二种方法是只具有一个用户表,并存储用户类型与它,al翁与任何其他共同属性

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的用户。

第三种选择就是有一个单独的结表中的每个用户表:

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) 
); 

每一种方法都有它的优点和缺点,所以你需要评估什么是最适合您的方案。

+0

谢谢,CHECK()的第一个例子帮助我指出了正确的方向。 – RIanGillis