使用一个查询递增具有唯一约束的字段中的一组值,Postgres

Posted

技术标签:

【中文标题】使用一个查询递增具有唯一约束的字段中的一组值,Postgres【英文标题】:Incrementing with one query a set of values in a field with UNIQUE constraint, Postgres 【发布时间】:2011-03-19 16:45:21 【问题描述】:

我有一个表格,其中有一个数字字段 A,它被设置为 UNIQUE。该字段用于指示必须执行某些操作的顺序。我想对所有大于 3 的值进行 UPDATE。例如, 我有

A     
1
2
3
4
5

现在,我想将 1 添加到所有大于 3A 值。所以,结果是

A     
1
2
3
5
6

问题是,是否可以只使用一个查询来完成?请记住,我在 A 列上有一个 UNIQUE 约束。

显然,我试过了

UPDATE my_table SET A = A + 1 WHERE A > 3;

但它不起作用,因为我对该字段有限制。

【问题讨论】:

以前问过这里,***.com/questions/674644/database-update-order 总而言之,你能不能把约束去掉,完成后再加回来? 这里不能选择删除约束。如果我想这样做,我可能会使用一个简单的过程并以指定的顺序更新值,这样我就可以省略重复键问题。 我已经根据部分唯一索引,用一个新想法更新了我的答案。这能解决您的问题吗? 当我能够检查它如何解决我的问题时,我会回答它。那可能是明天。 【参考方案1】:

PostgreSQL 9.0 及更高版本

PostgreSQL 9.0 添加了deferrable unique constraints,这正是您似乎需要的功能。这样,在提交时而不是更新时检查唯一性。

使用 DEFERRABLE 关键字创建 UNIQUE 约束:

ALTER TABLE foo ADD CONSTRAINT foo_uniq (foo_id) DEFERRABLE;

稍后,在运行 UPDATE 语句之前,您在同一个事务中运行:

SET CONSTRAINTS foo_uniq DEFERRED;

或者,您可以在唯一约束本身上使用INITIALLY DEFERRED 关键字创建约束——因此您不必运行SET CONSTRAINTS——但这可能会影响其他不需要的查询的性能推迟约束。

PostgreSQL 8.4 及更早版本

如果您只想使用唯一性约束来保证唯一性——而不是作为外键的目标——那么这种解决方法可能会有所帮助:

首先,在表中添加一个布尔列,例如is_temporary,用于临时区分更新和未更新的行:

CREATE TABLE foo (value int not null, is_temporary bool not null default false);

接下来创建一个部分唯一索引,它只影响 is_temporary=false 的行:

CREATE UNIQUE INDEX ON foo (value) WHERE is_temporary=false;

现在,每次进行您描述的更新时,您都分两步运行它们:

UPDATE foo SET is_temporary=true, value=value+1 WHERE value>3;
UPDATE foo SET is_temporary=false WHERE is_temporary=true;

只要这些语句发生在单个事务中,这将是完全安全的——其他会话永远不会看到临时行。缺点是你会写两次行。

请注意,这只是一个唯一索引,而不是约束,但实际上它应该无关紧要。

【讨论】:

我应该补充说我使用的是 Postgres 8.4。所以,这不适用。不过,给你一点。 在 8.4 中,您还可以通过向唯一约束添加另一列来解决此问题——它区分未更新和已更新的行。不幸的是,这也意味着它不再真正保证唯一性。如果这对您不起作用,除了放弃约束之外,9.0 是唯一的选择。 是的,我想过再增加一列,但这几乎和完全没有约束一样。现在,我正在尝试用 recursive with 组成一些解决方案。 我根据部分唯一索引更新了我的答案。 对于这么晚的回复,我深表歉意,但我有一段时间无法访问我的数据库。但是,您的解决方案完美运行。因此,我接受你的回答。再次感谢您的时间和帮助。【参考方案2】:

您可以通过一个简单的技巧在 2 个查询中完成:

首先,使用 +1 更新您的列,但添加带有 x(-1) 因子的 a:

update my_table set A=(A+1)*-1  where A > 3.

您将从 4,5,6 切换到 -5,-6,-7

二、转回操作恢复正数:

update my_table set A=(A)*-1  where A < 0.

你将拥有:5,6,7

【讨论】:

【参考方案3】:

您可以使用循环来执行此操作。我不喜欢这个解决方案,但它确实有效:

CREATE TABLE update_unique (id INT NOT NULL);
CREATE UNIQUE INDEX ux_id ON update_unique (id);
INSERT INTO update_unique(id) SELECT a FROM generate_series(1,100) AS foo(a);

DO $$
DECLARE v INT;
BEGIN
  FOR v IN SELECT id FROM update_unique WHERE id > 3 ORDER BY id DESC 
  LOOP
    UPDATE update_unique SET id = id + 1 WHERE id = v;
  END LOOP;
END;
$$ LANGUAGE 'plpgsql';

在 PostgreSQL 8.4 中,您可能必须创建一个函数来执行此操作,因为您不能使用 DO 在提示符下任意运行 PL/PGSQL(至少在我的记忆中不是这样)。

【讨论】:

这将解决更新问题,但问题是是否可以使用 one query 完成,即单独使用 SQL(无 PL/PGSQL)。

以上是关于使用一个查询递增具有唯一约束的字段中的一组值,Postgres的主要内容,如果未能解决你的问题,请参考以下文章

约束 CONSTRAINT

选择一列上的值在另一列上具有相同的一组值

Sql Server 主键 外键约束

从具有相同 id 的一组值中选择最大值

用于查找共享完全相同的一组值的对的 SQL 查询

SQL 查询 (Pro*C) 如何能够找到 db 表中不存在的一组值