具有NULL值的唯一列值组合

时间:2013-03-18 16:18:56

标签: sql postgresql

我正在尝试有条件地将一些数据插入到表中,其中列值的每个组合最多只能在表中出现一次。架构看起来像这样:

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)。

我尝试使用锁定(违背我的判断),这导致在几分钟的测试中出现死锁。

这个问题有没有标准解决方案?

2 个答案:

答案 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_idb_idc_idident对于{{1}的所有行都是唯一的(包括它们的空值组合) }和date_adate_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;