2012-04-26 173 views
19

我正試圖跨多個表創建一個唯一的約束。我在這裏找到了類似的問題,但他們並沒有完全理解我想要做的事情。SQL跨多個表的唯一約束

舉個例子,我有三個表,t_Analog,t_Discrete,t_Message

CREATE TABLE t_Analog(
    [AppName] [nvarchar](20) NOT NULL, 
    [ItemName] [nvarchar](32) NOT NULL, 
    [Value] [float] NOT NULL, 
    CONSTRAINT [uc_t_Analog] UNIQUE(AppName, ItemName) 
) 

CREATE TABLE t_Discrete(
    [AppName] [nvarchar](20) NOT NULL, 
    [ItemName] [nvarchar](32) NOT NULL, 
    [Value] [bit] NOT NULL, 
    CONSTRAINT [uc_t_Discrete] UNIQUE(AppName, ItemName) 
) 

CREATE TABLE t_Message(
    [AppName] [nvarchar](20) NOT NULL, 
    [ItemName] [nvarchar](32) NOT NULL, 
    [Value] [nvarchar](256) NOT NULL, 
    CONSTRAINT [uc_t_Message] UNIQUE(AppName, ItemName) 
) 

我的目標是讓AppName的和ITEMNAME在所有3個表是唯一的。例如,應用程序X中的Y的項目名稱不能同時存在於模擬表和離散表中。

請注意,這個例子是人爲設計的,每個類型的實際數據是不同的,並且足夠大以使得組合表格和添加類型列非常難看。

如果您對此方法有任何建議,我很樂意聽到他們!

---- BEGIN編輯2012-04-26 13:28 CST ----

謝謝大家對你的答案!

看來有可能是因爲修改這個數據庫的模式,這很好。

將這些表合併成一個表並不是一個真正可行的選擇,因爲每種類型的不匹配都有30列的順序(修改這些列不幸是不可選的)。這可能會導致大部分列不在每行中使用,這似乎是一個壞主意。

添加第4張表,如John Sikora和其他人提到的,可能是一個選項,但我想先驗證一下。

修改架構是:

CREATE TABLE t_AllItems(
    [id] [bigint] IDENTITY(1,1) NOT NULL, 
    [itemType] [int] NOT NULL, 
    [AppName] [nvarchar](20) NOT NULL, 
    [ItemName] [nvarchar](32) NOT NULL, 
    CONSTRAINT [pk_t_AllItems] PRIMARY KEY CLUSTERED ([id]) 
    CONSTRAINT [uc_t_AllItems] UNIQUE([id], [AppName], [ItemName]) 
) ON [PRIMARY] 

CREATE TABLE t_Analog(
    [itemId] [bigint] NOT NULL, 
    [Value] [float] NOT NULL, 
    FOREIGN KEY (itemId) REFERENCES t_AllItems(id) 
) 

CREATE TABLE t_Discrete(
    [itemId] [bigint] NOT NULL, 
    [Value] [bit] NOT NULL, 
    FOREIGN KEY (itemId) REFERENCES t_AllItems(id) 
) 

CREATE TABLE t_Message(
    [itemId] [bigint] NOT NULL, 
    [Value] [nvarchar](256) NOT NULL, 
    FOREIGN KEY (itemId) REFERENCES t_AllItems(id) 
) 

我只是有關於這種方法的一個問題。這是否強制子表的唯一性?

例如,是否可以不存在具有'id'9的'Item'和具有'itemId'爲9且'值'爲9.3並且同時t_Message具有'itemId'9的表t_Analog的項目「富」的「價值」?

我可能不完全理解這種額外的表格方法,但我不反對它。

如果我對此有錯,請糾正我。

+2

你在正確的軌道上,但完整性約束不夠好。例如,ID號100可能出現在每個表中。爲了更緊密地使用該項目類型,請參閱[此答案](http://stackoverflow.com/a/10077883/562459)和[此答案](http://stackoverflow.com/a/5471265/562459)。 – 2012-04-27 01:24:40

回答

10

添加第4個表格,專門爲這些您想要唯一的值,然後使用一對多關係將此表格中的這些鍵鏈接到其他值。 例如,您將擁有包含ID,AppName和ItemName的唯一表格來組成其3列。然後讓這張錶鏈接到其他人。

對於如何做到這一點這裏是一個很好的例子 Create a one to many relationship using SQL Server

編輯:這是我會做什麼,但考慮到你的服務器需要可以改變所需要的:

CREATE TABLE AllItems(
    [id] [int] IDENTITY(1,1) NOT NULL, 
    [itemType] [int] NOT NULL, 
    [AppName] [nvarchar](20) NOT NULL, 
    [ItemName] [nvarchar](32) NOT NULL, 
    CONSTRAINT [pk_AllItems] PRIMARY KEY CLUSTERED ([id] ASC) 
) ON [PRIMARY] 

CREATE TABLE Analog(
    [itemId] [int] NOT NULL, 
    [Value] [float] NOT NULL 
) 

CREATE TABLE Discrete(
    [itemId] [int] NOT NULL, 
    [Value] [bit] NOT NULL 
) 

CREATE TABLE Message(
    [itemId] [bigint] NOT NULL, 
    [Value] [nvarchar](256) NOT NULL 
) 

ALTER TABLE [Analog] WITH CHECK 
    ADD CONSTRAINT [FK_Analog_AllItems] FOREIGN KEY([itemId]) 
REFERENCES [AllItems] ([id]) 
GO 
ALTER TABLE [Analog] CHECK CONSTRAINT [FK_Analog_AllItems] 
GO 

ALTER TABLE [Discrete] WITH CHECK 
    ADD CONSTRAINT [FK_Discrete_AllItems] FOREIGN KEY([itemId]) 
REFERENCES [AllItems] ([id]) 
GO 
ALTER TABLE [Discrete] CHECK CONSTRAINT [FK_Discrete_AllItems] 
GO 

ALTER TABLE [Message] WITH CHECK 
    ADD CONSTRAINT [FK_Message_AllItems] FOREIGN KEY([itemId]) 
REFERENCES [AllItems] ([id]) 
GO 
ALTER TABLE [Message] CHECK CONSTRAINT [FK_Message_AllItems] 
GO 

從我可以告訴你的語法是好的,我只是簡單地改變它,因爲我對它更熟悉,但任何一個都應該工作。

+0

John,我編輯了我的帖子,添加了我相信你正在談論的模式。你能否請確認我是否瞭解你的建議?謝謝:) – CoryC 2012-04-26 19:00:43

+0

如果你看看我的編輯,你會看到我習慣了但是你的語法對我來說很好 – 2012-04-26 19:41:15

+4

你在正確的軌道上,但這還不夠好。問題在於沒有限制防止ID號100出現在每個表中。爲了更緊密地使用該項目類型,請參閱[此答案](http://stackoverflow.com/a/10077883/562459)和[此答案](http://stackoverflow.com/a/5471265/562459)。 – 2012-04-27 01:23:45

0

這表明正常化/數據庫設計問題,尤其是你應該存儲在應用程序的名字在一個表中它自己(作爲唯一/鍵不管),那麼第2列表示的是什麼鏈接到ID,和也許是指示類型的第三列。

EG:

AppName – PrimaryKey - unique 
ID – Foreign Key of either Discrete, Analog or message 
Type – SMALLINT representing Discrete, analog or message. 
1

一個想法可能是三個表合併:

CREATE TABLE t_Generic(
[AppName] [nvarchar](20) NOT NULL, 
[ItemName] [nvarchar](32) NOT NULL, 
[Type] [nvarchar](32) NOT NULL, 
[AnalogValue] [Float] NULL, 
[DiscreteValue] [bit] NULL, 
[MessageValue] [nvarchar](256) NULL, 
CONSTRAINT [uc_t_Generic] UNIQUE(AppName, ItemName) 
) 

您的應用程序邏輯必須執行,只有一個值填充,你可以使用類型字段來跟蹤記錄的類型。

7

雖然你可能會或可能不會想要改變你的架構像其他的答案說,一個indexed view可以申請,你談論的約束:

CREATE VIEW v_Analog_Discrete_Message_UK WITH SCHEMABINDING AS 
SELECT a.AppName, a.ItemName 
FROM dbo.t_Analog a, dbo.t_Discrete b, dbo.t_Message c, dbo.Tally t 
WHERE (a.AppName = b.AppName and a.ItemName = b.ItemName) 
    OR (a.AppName = c.AppName and a.ItemName = c.ItemName) 
    OR (b.AppName = c.AppName and b.ItemName = c.ItemName) 
    AND t.N <= 2 
GO 
CREATE UNIQUE CLUSTERED INDEX IX_AppName_ItemName_UK 
    ON v_Analog_Discrete_Message_UK (AppName, ItemName) 
GO 

您將需要一個"Tally" or numbers table或者以其他方式產生Celko風格:

-- Celko-style derived numbers table to 100k 
select a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + 1 as N 
from (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) a 
     , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) b 
     , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) c 
     , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) d 
     , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) e 
order by N 
+3

謝謝,我覺得這是最優雅的解決方案 – spinalfrontier 2015-06-15 03:28:31

+0

我同意 - 這是最優雅的解決方案。但是,您不需要具有100,000行的完整理貨表,只需兩行。反正你有WHERE ... t.N <= 2。我有一個名爲chk.TwoRows的表格,只有值1和2。 – DaveBoltman 2017-04-11 09:02:17

+0

幸運的是,在這種情況下,MS SQL將NULL視爲一個值,只要唯一約束條件成立(當然這是錯誤的),所以它起作用即使對於表b和c之間重複的值,在這種情況下a.AppName,a.ItemName都是NULL。兩個錯誤在這裏做出 – DaveBoltman 2017-04-11 09:14:51

2

您也可以創建一個約多一點的邏輯並檢查所有三個表。

查看here瞭解如何使用函數執行此操作的示例。我用的,而不是插入和更新

+0

Zimdanen,coul你提供了一個這個約束如何檢查多個表的例子嗎? – CoryC 2012-04-26 18:26:18

+0

下面是一個功能選項:http://stackoverflow.com/questions/2588072/how-do-i-create-a-multiple-table-check-constraint – zimdanen 2012-04-26 18:29:56

+0

zimdanen,看起來很有希望,我可以採取這種方法。 – CoryC 2012-04-26 18:56:53

0

觸發器來解決此問題如下所示:

CREATE TRIGGER tI_Analog ON t_Analog 
INSTEAD OF INSERT 
AS 
BEGIN 
    SET NOCOUNT ON ; 

    IF EXISTS (SELECT 1 FROM inserted AS I INNER JOIN t_Analog AS T 
        ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
       UNION ALL 
       SELECT 1 FROM inserted AS I INNER JOIN t_Discrete AS T 
        ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
       UNION ALL 
       SELECT 1 FROM inserted AS I INNER JOIN t_Message AS T 
        ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
      ) 
    BEGIN 
     RAISERROR('Duplicate key', 16, 10) ; 
    END 
    ELSE 
    BEGIN 
     INSERT INTO t_Analog (AppName, ItemName, Value) 
     SELECT AppName, ItemName, Value FROM inserted ; 
    END 
END 
GO 

CREATE TRIGGER tU_Analog ON t_Analog 
INSTEAD OF UPDATE 
AS 
BEGIN 
    SET NOCOUNT ON ; 

    IF EXISTS (SELECT TOP(1) 1 
       FROM (SELECT T.AppName, T.ItemName, COUNT(*) AS numRecs 
         FROM 
          (SELECT I.AppName, I.ItemName 
           FROM inserted AS I INNER JOIN t_Analog AS T 
           ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
          UNION ALL 
          SELECT I.AppName, I.ItemName 
           FROM inserted AS I INNER JOIN t_Discrete AS T 
           ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
          UNION ALL 
          SELECT I.AppName, I.ItemName 
           FROM inserted AS I INNER JOIN t_Message AS T 
           ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
          ) AS T 
          GROUP BY T.AppName, T.ItemName 
         ) AS T 
       WHERE T.numRecs > 1 
      ) 
    BEGIN 
     RAISERROR('Duplicate key', 16, 10) ; 
    END 
    ELSE 
    BEGIN 
     UPDATE T 
      SET AppName = I.AppName 
      , ItemName = I.ItemName 
      , Value = I.Value 
      FROM inserted AS I INNER JOIN t_Message AS T 
      ON T.AppName = I.AppName AND T.ItemName = I.ItemName 
     ; 
    END 
END 
GO 

一個警告使用instead of觸發器是當有涉及標識字段。該觸發器可防止INSERT INTO命令的OUTPUT子句和@@ IDENTITY變量正常工作。