PostgreSQL 多列唯一约束和 NULL 值

Man*_*duc 137 postgresql null constraint unique-constraint

我有一张如下表:

create table my_table (
    id   int8 not null,
    id_A int8 not null,
    id_B int8 not null,
    id_C int8 null,
    constraint pk_my_table primary key (id),
    constraint u_constrainte unique (id_A, id_B, id_C)
);
Run Code Online (Sandbox Code Playgroud)

我想(id_A, id_B, id_C)在任何情况下都与众不同。所以下面的两个插入肯定会导致错误:

INSERT INTO my_table VALUES (1, 1, 2, NULL);
INSERT INTO my_table VALUES (2, 1, 2, NULL);
Run Code Online (Sandbox Code Playgroud)

但它没有按预期运行,因为根据文档,两个NULL值不会相互比较,因此两个插入都没有错误地通过。

我怎么能保证我的唯一约束,即使id_C可以NULL在这种情况下?实际上,真正的问题是:我可以在“纯 sql”中保证这种唯一性,还是必须在更高级别(在我的情况下为 java)来实现它?

Erw*_*ter 135

您可以在纯 SQL 中做到这一点。除了您拥有的索引之外 ,还创建一个部分唯一索引

CREATE UNIQUE INDEX ab_c_null_idx ON my_table (id_A, id_B) WHERE id_C IS NULL;
Run Code Online (Sandbox Code Playgroud)

这样你就可以(id_A, id_B, id_C)在你的表中输入:

(1, 2, 1)
(1, 2, 2)
(1, 2, NULL)
Run Code Online (Sandbox Code Playgroud)

但这些都不是第二次。

或者使用两个部分UNIQUE索引而没有完整索引(或约束)。最佳解决方案取决于您的详细要求。相比:

虽然这对于UNIQUE索引中的单个可空列来说既优雅又高效,但对于不止一个列,它很快就会失控。讨论这个 - 以及如何将 UPSERT 与部分索引一起使用:

旁白

在 PostgreSQL 中不使用没有双引号的混合大小写标识符

可以serial作为主键或Postgres 10 或更高版本中的IDENTITY。有关的:

所以:

CREATE TABLE my_table (
   my_table_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY  -- for pg 10+
-- my_table_id bigserial PRIMARY KEY  -- for pg 9.6 or older
 , id_a int8 NOT NULL
 , id_b int8 NOT NULL
 , id_c int8
 , CONSTRAINT u_constraint UNIQUE (id_a, id_b, id_c)
);
Run Code Online (Sandbox Code Playgroud)

如果您不希望在表的生命周期内有超过 20 亿行(> 2147483647)(包括浪费和删除的行),请考虑integer(4 字节)而不是bigint(8 字节)。


Luc*_*c M 17

我遇到了同样的问题,我找到了另一种方法来将唯一的 NULL 放入表中。

CREATE UNIQUE INDEX index_name ON table_name( COALESCE( foreign_key_field, -1) )
Run Code Online (Sandbox Code Playgroud)

就我而言,该字段foreign_key_field是一个正整数,永远不会是 -1。

因此,要回答 Manual Leduc,另一个解决方案可能是

CREATE UNIQUE INDEX  u_constrainte (COALESCE(id_a, -1), COALESCE(id_b,-1),COALESCE(id_c, -1) )
Run Code Online (Sandbox Code Playgroud)

我假设 ids 不会是 -1。

创建部分索引有什么好处?
如果您没有 NOT NULL 子句,id_a,id_bid_c只能同时为 NULL 一次。
对于部分索引,这 3 个字段可能不止一次为 NULL。

  • > 创建部分索引有什么好处?您使用`COALESCE` 完成它的方式可以有效地限制重复,但索引在查询中不会很有用,因为它的表达式索引可能与查询表达式不匹配。也就是说,除非你`SELECT COALESCE(col, -1) ...` 你不会命中索引。 (4认同)

ype*_*eᵀᴹ 8

Null 可能意味着该行的值目前未知,但将来会在已知时添加(例如FinishDaterunning Project),或者不能为该行应用任何值(例如EscapeVelocityblack hole Star)。

在我看来,通过消除所有空值来规范化表格通常会更好。

在您的情况下,您希望NULLs在您的列中允许,但您只想允许一个NULL。为什么?这两张表是什么关系?

也许您可以简单地将列更改为NOT NULL并存储,而不是存储NULL一个-1已知永远不会出现的特殊值(如)。这将解决唯一性约束问题(但可能有其他可能不需要的副作用。例如,使用-1表示“未知/不适用”将歪曲列上的任何总和或平均值计算。或者所有此类计算都必须采用考虑特殊值并忽略它。)

  • 我只想向@Manuel 指出,这个答案中对空值的看法并非普遍存在,并且引起了很多争论。许多人,像我一样,认为 null 可以用于您希望的任何目的(但应该只表示每个字段的 * 一个 * 事情并记录在案,可能在字段名称或列注释中) (3认同)
  • 在我的情况下,NULL 确实是 NULL(例如,id_C 是 table_c 的外键,因此它不能具有 -1 值),这意味着它们在“my_table”和“table_c”之间没有关系。所以它具有功能意义。顺便说一下 [(1, 1,1,null), (2, 1,2,null), (3,2,4,null)] 是插入数据的有效列表。 (2认同)