2013-03-18 54 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