簡體   English   中英

對一對多關系的約束

[英]Constraint for one-to-many relationship

我們有兩個表與一對多的關系。 我們希望強制執行約束,即給定父記錄至少存在一個子記錄。

這可能嗎?

如果沒有,您是否會更改模式以支持此類約束? 如果是這樣你會怎么做?

編輯:我正在使用SQL Server 2005

從架構的角度來看,這樣的約束是不可能的,因為你遇到了“雞或雞蛋”類型的場景。 在這種情況下,當我插入父表時,我必須在子表中有一行,但是在父表中有一行之前,我不能在子表中有一行。

這是更好的強制客戶端。

如果您的后端支持可延遲約束,PostgreSQL也是如此。

這實際上並不是“在客戶端更好地執行”,而是在某些數據庫實現中強制實施的東西。 實際上,作業屬於數據庫,並且下面的解決方法中至少有一個應該有效。

最終你想要的是將父母約束為孩子。 這保證了孩子的存在。 不幸的是,這會導致雞蛋問題,因為孩子必須指向導致約束沖突的同一父母。

在系統的其余部分中解決問題而沒有可見的副作用需要兩種能力中的一種 - 在SQL Server中都找不到這兩種能力。

1)延遲約束驗證 - 這會導致在事務結束時驗證約束。 通常它們發生在聲明的最后。 這是雞蛋問題的根源,因為它會阻止你插入第一個孩子或父行而缺少另一個,這就解決了它。

2)您可以使用CTE插入CTE掛起插入父節點的語句的第一個子節點(反之亦然)。 這會在同一語句中插入兩行,從而產生類似於延遲約束驗證的效果。

3)沒有任何一個,你別無選擇,只能在其中一個引用中允許空值,這樣你就可以在沒有依賴性檢查的情況下插入該行。 然后,您必須返回並使用對第二行的引用更新null。 如果您使用此技術,則需要注意使系統的其余部分通過一個視圖來引用父表,該視圖在子引用列中隱藏所有具有null的行。

在任何情況下,您刪除的子項都同樣復雜,因為您無法刪除證明至少存在一個子項的子項,除非您首先更新父項以指向不會被刪除的子項。

當您要刪除最后一個子項時,您必須同時拋出錯誤或刪除父項。 如果您沒有先將父指針設置為null(或延遲驗證),則會自動發生錯誤。 如果您確實推遲(或將子指針設置為null),則可以刪除子項,然后也可以刪除父項。

我多年來一直在研究這個問題,我看每個版本的SQL Server都可以解決這個問題,因為它很常見。

盡快有人提供實用的解決方案,請發布!

PS您需要在引用來自父項的子證明行時使用復合鍵,或者觸發以確保提供證據的孩子實際上認為該行是其父項。

PPS雖然如果你在同一個事務中同時執行插入和更新,那么對於系統的其余部分來說,null永遠不應該是可見的,這依賴於可能失敗的行為。 約束的關鍵是確保邏輯故障不會使數據庫處於無效狀態。 通過使用隱藏空值的視圖保護表,任何非法行都將不可見。 很明顯,你的插入邏輯必須考慮到這樣一行可能存在的可能性,但無論如何它都需要內部知識,而其他任何事情都不需要知道。

我遇到了這個問題,並在Oracle rel.11.2.4中實現了一個解決方案。

  1. 為了確保每個孩子都有父母,我將一個典型的外鍵約束從孩子的FK應用到父母的PK。 - 這里沒有巫術
  2. 為了確保每個父母至少有一個孩子,我做了如下:

創建一個接受父PK的函數,並為該PK返回COUNT個子項。 - 我確保NO_DATA_FOUND異常返回0。

在父表上創建一個虛擬列CHILD_COUNT ,並將其計算為函數結果。

CHILD_COUNT虛擬列上創建可延遲的CHECK約束,條件為CHILD_COUNT > 0

它的工作原理如下:

  • 如果插入父行且尚未存在子項。 然后,如果該行已提交,則CHILD_COUNT > 0 CHECK約束將失敗,並且事務將回滾。
  • 如果插入父行並在同一事務中插入相應的子行; 然后在發出COMMIT時滿足所有完整性約束。
  • 如果插入對應於現有父行的子行,則在COMMIT上重新計算CHILD_COUNT虛擬列,並且不會發生完整性沖突。
  • 父行的任何刪除都必須級聯到子級,否則在提交刪除事務時,孤立的子行將違反FOREIGN KEY約束。 (照常)
  • 子行的任何刪除都必須為每個父項留下至少一個子項,否則在事務提交時將違反CHILD_COUNT檢查約束。

注意:如果Oracle在rel.11.2.4中允許基於用戶功能的CHECK constraints ,那么我就不需要虛擬列。

一個簡單的非可空列怎么樣?

Create Table ParentTable
(
ParentID
ChildID not null,
Primary Key (ParentID), 
Foreign Key (ChildID ) references Childtable (ChildID));
)

如果您的業務邏輯允許並且您具有默認值,則可以從數據庫中查詢每個新父記錄,然后可以在父表上使用before insert trigger來填充非可空子列。

CREATE or REPLACE TRIGGER trigger_name
BEFORE INSERT
    ON ParentTable
    FOR EACH ROW 
BEGIN

    -- ( insert new row into ChildTable )
    -- update childID column in ParentTable 

END;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM