如何同时更新 PostgreSQL 中的唯一键?

Pau*_*per 4 postgresql update unique-constraint

CREATE TABLE widget (
  id serial PRIMARY KEY,
  name text NOT NULL,
  ordinal int NOT NULL UNIQUE
);
Run Code Online (Sandbox Code Playgroud)

我有数据

 id | name | ordinal 
----+------+---------
  1 | A    |       1
  2 | B    |       2
  3 | C    |       3
Run Code Online (Sandbox Code Playgroud)

我想将其更新为

 id | name | ordinal 
----+------+---------
  1 | A    |       3
  2 | B    |       2
  3 | C    |       1
Run Code Online (Sandbox Code Playgroud)

尽可能少地接触记录(即不要重写整个记录集,不要启动不必要的触发器),更新为ordinal目标值的通用方法是什么?

普通更新只会给我带来约束违规,即使它发生在单个语句中。

删除并重新创建唯一约束的成本很高,而且并发性很差。

这似乎是一个很常见的问题,应该有一个好的方法来做到这一点,但我只是想不出。

小智 6

将约束定义为可延迟:

CREATE TABLE widget (
  id serial PRIMARY KEY,
  name text NOT NULL,
  ordinal int NOT NULL UNIQUE DEFERRABLE
);
Run Code Online (Sandbox Code Playgroud)

然后你可以用一条语句更新它:

update widget
  set ordinal = t.new_ordinal
from (
  values (1, 3), (3,1) 
) as t(id, new_ordinal)
where t.id = widget.id   
Run Code Online (Sandbox Code Playgroud)


Erw*_*ter 5

UNIQUE默认情况下是简单约束NOT DEFERRABLE每行之后都会检查唯一的违规行为。该手册将其表述为:

每个命令后立即检查

但它确实检查了每个单独的书面行。

如果您定义了UNIQUE约束(如提供的 a_horse),则在每个语句之后DEFERRABLE都会检查唯一的违规行为。在这方面,同一查询中的多个 CTE 仍算作单个语句。

CREATE TABLE widget_deferrable (
  id serial PRIMARY KEY,
  ordinal int NOT NULL UNIQUE DEFERRABLE  -- !
);
Run Code Online (Sandbox Code Playgroud)

如果您实际上也将约束设置为DEFERRED,则对唯一违规的检查将推迟到每个事务之后

CREATE TABLE widget_deferred (
  id serial PRIMARY KEY,
  ordinal int NOT NULL UNIQUE DEFERRABLE INITIALLY DEFERRED  --!
);
Run Code Online (Sandbox Code Playgroud)

综合演示:

db<>在这里摆弄

进一步阅读: