2010-09-16 78 views
7

我有一個SQL Server下表2008分貝:如何使用引用另一個表的檢查約束?

  • tblItem,具有項目ID場;

  • tblGoodItem,它還有一個ItemID字段,並有一個指向tblItem的外鍵;

  • tblBadItem,它也有一個ItemID字段,並且還有一個指向tblItem的外鍵。

一件物品不能既是好物品又是壞物品;它必須是一個或另一個。但是,無論項目是好還是壞,它都必須是一個項目。

我的問題是這樣的:如何向tblGoodItem和tblBadItem中的ItemID字段添加一個約束,以便兩個表中都不能存在ItemID值

我讀過的堆棧溢出一些回答類似的問題,我想這個解決方案:

  • 創建一個視圖vwItem它連接tblGoodItem上tblBadItem上項目ID。

  • 編寫一個UDF fnItem它對vwItem進行查詢以查看視圖中存在多少記錄。

  • 有哪些要求fnItem並驗證返回的值是0

這是最好的辦法約束?有沒有人有更好的主意?

回答

9

添加一列tblItem.ItemType列。這個列在任何給定的行上只能有一個值(顯然)。在ItemID,ItemType上添加唯一約束。

現在的技巧:很少有人記住這一點,但外鍵可以引用唯一約束的列。

CREATE TABLE tblItem (
    ItemID INT PRIMARY KEY, 
    ItemType CHAR(1), 
    UNIQUE KEY (ItemID, ItemType) 
); 

CREATE TABLE tblGoodItem (
    ItemID INT PRIMARY KEY, 
    ItemType CHAR(1), 
    CHECK (ItemType='G') 
    FOREIGN KEY (ItemID, ItemType) REFERENCES tblItem(ItemID, ItemType) 
); 

CREATE TABLE tblBadItem (
    ItemID INT PRIMARY KEY 
    ItemType CHAR(1), 
    CHECK (ItemType='B') 
    FOREIGN KEY (ItemID, ItemType) REFERENCES tblItem(ItemID, ItemType) 
); 

如果你限制的ItemType在每個子表爲一個固定值,那麼在tblItem給定行只能由一個子表引用。

這是一個三步過程,一個項目從好變成壞的,雖然:

  1. 刪除行從tblGoodItem
  2. UPDATE行的的ItemType在tblItem
  3. 插入行中tblBadItem
+0

你打敗了我幾秒鐘。 – 2010-09-16 16:14:43

+0

我很喜歡這個想法。無需擔心從壞到好 - 事實上,我並不希望發生這樣的變化。 – 2010-09-16 16:35:12

2

擺脫tblGoodItem和tblBadItem,並使用ItemType =「G」或「B」創建一個新表,並在ItemID上放置一個唯一的索引或鍵,然後在tblItem上不需要約束。

+0

不行,恐怕。好的物品有一些不好的物品沒有的特性,反之亦然。 – 2010-09-16 16:05:24

+0

如果只有少數,10個以內的差異,那麼我仍然將它們合併,讓它們允許NULL並添加一個檢查約束來要求它們基於類型列 – 2010-09-16 17:43:43

1

您不能在CHECK約束中使用SELECT聲明 - 這不是他們的設計。

我認爲你最好的選擇是在ItemId中編寫一個UDF通道並檢查它是否存在。對於這種情況,它確實是最簡單的選擇。

我已經添加了一些測試數據和一個示例函數。

CREATE FUNCTION dbo.fn_CheckItems(@itemId INT) RETURNS BIT 

AS BEGIN 

DECLARE @i INT, 
     @rv BIT 


SET @i = 0 

IF (SELECT COUNT(*) FROM tblBadItem WHERE ItemId = @ItemId) > 0 
BEGIN 
SET @i = 1 
END 


IF (SELECT COUNT(*) FROM tblGoodItem WHERE ItemId = @ItemId) > 0 
BEGIN 
SET @i = @i + 1 
END 

IF (@i > 1) 
BEGIN 
    SET @rv = 1 
END 
ELSE 
BEGIN 
    SET @rv =0 
END 


RETURN @rv 

END 
GO 

CREATE TABLE tblItem (
    ItemID INT IDENTITY(1,1) PRIMARY KEY, 
    DateAdded DATETIME 
) 
GO 

CREATE TABLE tblGoodItem (
    ItemID INT PRIMARY KEY, 
    CHECK (dbo.fn_CheckItems(ItemId) = 0) 

) 
GO 

CREATE TABLE tblBadItem (
    ItemID INT PRIMARY KEY, 
    CHECK (dbo.fn_CheckItems(ItemId) = 0) 
) 
GO 

INSERT INTO tblItem (DateAdded) 
VALUES (GETDATE()) 

INSERT INTO tblGoodItem(ItemID) 
SELECT ItemId FROM tblItem 

--This statement will fail as the ItemId is already in GoodItems 
INSERT INTO tblBadItem(ItemID) 
SELECT ItemId FROM tblItem 


DROP TABLE tblItem 
DROP TABLE tblGoodItem 
DROP TABLE tblBadItem 
DROP FUNCTION dbo.fn_CheckItems 
+0

這樣的UDF對於多行更新無法正確工作 – 2010-09-16 16:13:52

+0

@AlexKuznetsov - 你能詳細說明爲什麼這不起作用。我創建了一個測試,它工作正常嗎? – codingbadger 2010-09-16 16:26:35

+0

我的歉意,我錯了。但是,這種UDF非常慢,如果使用快照隔離,您可能偶爾在併發下得到錯誤的結果。 – 2010-09-16 19:40:12

1

我可能在這裏不瞭解您的業務需求,但您爲什麼希望爲Good and Bad項目設置單獨的表格?這些不是同一件事物的抽象嗎?

爲什麼不使用isBadItem標誌或更具體地說itemConditionStatus列。

1

在tblItem中,添加itemType列。有一個檢查約束,以確保itemType是好還是壞。在(ItemID,itemType)上創建唯一約束

將itemType列添加到不良項目表和良品項表中。有一個檢查約束,以確保itemType在良好的表中良好,在壞的表中不好。