2013-03-18 59 views
1

我試圖有條件地向表中插入一些數據,其中列值的每個組合只能在表中最多出現一次。該模式看起來是這樣的:具有NULL值的唯一列值組合

CREATE TABLE foobar (
    id SERIAL PRIMARY KEY, 
    a_id INTEGER, 
    b_id INTEGER, 
    c_id INTEGER, 
    ident VARCHAR(32), 
    date_a timestamp, 
    date_b timestamp, 
    FOREIGN KEY a_id REFERENCES a (id) ON DELETE CASCADE, 
    FOREIGN KEY b_id REFERENCES b (id) ON DELETE CASCADE, 
    FOREIGN KEY c_id REFERENCES c (id) ON DELETE CASCADE)); 

的組合(A_ID,B_ID,C_ID,IDENT)是獨一無二的,但只爲date_a和date_b都是空行。

我希望只有在a_id,b_id,c_id,任務組合不在db中時才能插入新行。如果是這樣,它不需要做任何事情。

起初我試圖在這些列上創建一個唯一約束,但問題是允許a_id,b_id和c_id爲NULL,只要其中至少有一個不爲null即可。這破壞了獨特的約束。因爲a,b和c_id字段是外鍵,所以我不能將它們設置爲其他存根值(如-1)。

我試着玩鎖定(反對我的更好的判斷),導致在幾分鐘的測試中出現死鎖。

有沒有這個問題的標準解決方案?

+0

你要這個邏輯作爲一種約束/觸發器(如果違反會拋出一個錯誤) ?或者你會添加到您的INSERT語句(如果違反,什麼都不做)? – PinnyM 2013-03-18 16:20:35

+0

如果a_id,b_id和c_id可以爲null,那麼爲什麼要讓它們成爲外鍵?實際上,postgresql甚至允許這樣做嗎? – 2013-03-18 16:23:31

+0

@DanBracuk,[絕對](http://stackoverflow.com/questions/1363887/does-a-foreign-key-referencing-pk-need-the-not-null-constraint)。 – PinnyM 2013-03-18 16:24:32

回答

1

而不是使用唯一的約束,你應該使用一個唯一的索引在你想檢查的確切條件。然後,您可以將空值合併爲一個虛擬值,例如-1。喜歡的東西:

CREATE UNIQUE INDEX 
foobar_test ON foobar 
(COALESCE(a_id, -1), COALESCE(b_id, -1), COALESCE(c_id, -1), ident) -- Nulls become -1 
WHERE date_a is null and date_b is null; -- Only check when date_a and date_b is null 

這將確保a_idb_idc_id,並ident是唯一的(包括其空值的組合)的所有行,既date_adate_bnull

+0

當我到辦公室去試試看,謝謝! – Blubber 2013-03-19 05:50:24

+0

問題解決了,再次感謝! – Blubber 2013-03-19 07:18:49

0

在我的多個生產數據庫的類似情況下,我只需要將「默認行」添加到引用表。我使用id = 0爲這些,並開始真正的代理鍵在10或100。或者,如果0保留,你可以使用-1(或任何適合你)。

這樣我可以將所有FK列NOT NULL DEFAULT 0和(部分)UNIQUE約束工作開箱:

CREATE UNIQUE INDEX tbl_uni_idx ON tbl (a_id, b_id, c_id, ident) 
WHERE date_a IS NULL AND date_b IS NULL; 
+0

它想到做到這一點,但它增加了另一層可能出錯的地方,我並不特別喜歡它。 – Blubber 2013-03-19 05:49:54