我正在尝试有条件地将一些数据插入到表中,其中列值的每个组合最多只能在表中出现一次。架构看起来像这样:
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都为NULL的行。
我希望只有在数据库中没有a_id,b_id,c_id,任务组合时才能插入新行。如果它不需要做任何事情。
首先,我尝试在这些列上创建一个唯一约束,但问题是a_id,b_id和c_id允许为NULL,只要其中至少有一个不为null。这破坏了独特的约束。因为a,b和c_id字段是外键,所以我不能将它们设置为其他某个存根值(如-1)。
我尝试使用锁定(违背我的判断),这导致在几分钟的测试中出现死锁。
这个问题有没有标准解决方案?
答案 0 :(得分: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_id
,b_id
,c_id
和ident
对于{{1}的所有行都是唯一的(包括它们的空值组合) }和date_a
是date_b
。
答案 1 :(得分:0)
在我的多个高效数据库中的类似情况中,我只是向引用的表添加“默认行”。我使用id = 0
作为这些并在10或100处开始真正的代理键。或者,如果保留-1
,您可以使用0
(或任何适合您的人)。
这样我就可以将所有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;