2016-06-28 56 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