【问题标题】:CTE based insert of multiple rows into "one-per-group" table violates unique index基于 CTE 将多行插入“每组一个”表违反了唯一索引
【发布时间】:2019-11-21 16:50:11
【问题描述】:

我有一张表,其中每组只有一行可以为真。 这是由部分唯一索引(不能延迟)强制执行的。

CREATE TABLE test
(
    id        SERIAL PRIMARY KEY,
    my_group  INTEGER,
    last      BOOLEAN DEFAULT TRUE
);

CREATE UNIQUE INDEX "test.last" ON test (my_group) WHERE last;

INSERT INTO test (my_group)
VALUES (1), (2);

我正在尝试在此表中插入一个新行,该行将替换相应组的“最后一个”元素。
我也想在一个语句中完成。

通过一些 CTE 技巧,我可以做到这一点:link to Fiddle

-- the statement is structured this way to closely resemble my actual usecase
WITH
new_data AS (
    VALUES (1)
),
uncheck_old_last AS (
    UPDATE test
    SET last = FALSE
    WHERE last AND my_group in (SELECT * FROM new_data)
    RETURNING TRUE
)
INSERT INTO test (my_group)
SELECT *
FROM new_data
WHERE COALESCE((SELECT * FROM uncheck_old_last LIMIT 1), true);

到目前为止一切顺利,插入发生...没有冲突。
我不太明白为什么这是有效的,因为据我了解,所有 CTE 都应该读取相同的初始数据库状态并且不能查看其他 CTE 所做的更改

现在的问题是,当我尝试一次对多行执行相同操作时,我遇到了唯一的违规:Link to Fiddle

-- the statement is structured this way to closely resemble my actual usecase
WITH
new_data AS (
    VALUES (1), (2) -- <- difference to above query
),
uncheck_old_last AS (
    UPDATE test
    SET last = FALSE
    WHERE last AND my_group in (SELECT * FROM new_data)
    RETURNING TRUE
)
INSERT INTO test (my_group)
SELECT *
FROM new_data
WHERE COALESCE((SELECT * FROM uncheck_old_last LIMIT 1), true);

-- Schema Error: error: duplicate key value violates unique constraint "test.last"

有没有办法用一个语句插入多行/
有人可以向我解释为什么第一个查询有效而第二个查询无效吗?

【问题讨论】:

  • 我会使用 BEFORE INSERT 触发器来解决这个问题,这意味着您可以使用标准的 INSERT 语句而不必担心。
  • 触发器可能会更干净,是的。只是我们在我正在处理的项目中不做触发器(我想保持所有查询字符串自包含并使维护更容易。现在我也总是更喜欢 cte 解决方案,因为它更容易基于优化在查询计划上,使用触发器我总是必须跟踪查询实际执行的代码)。但是我会问项目负责人是否会考虑为此使用触发器,也许这在我所知道的更多地方使用并且触发器实际上是有意义的。
  • 好吧,你可以简单地将两个语句(UPDATE 然后INSERT)放入一个事务中,然后你就不需要对抗 CTE 的语义
  • 哈哈好吧,你看...我使用的包装器没有公开 pdo 事务功能。我已经创建了一个拉取请求来添加它们,但目前我们不太可能使用它,项目负责人没有时间更新服务器的 docker 映像。但感谢您的努力。
  • Postgres 允许在单个“go”中发送多个语句update test set ... where ...; insert into ...;

标签: postgresql common-table-expression unique-constraint unique-index


【解决方案1】:

这是由于 PostgreSQL 简化了我的 always true 子句:

WHERE COALESCE((SELECT * FROM uncheck_old_last LIMIT 1), true)

应该在主查询和 CTE 之间创建依赖关系,以从主查询的角度强制执行顺序。

因为limit 1 允许 PostgreSQL 忽略第二行,所以它打破了不止一个条目,因为评估只需要一个。

我通过比较 COUNT(*) &gt; -1 来修复它:

COALESCE((SELECT COUNT(*) FROM uncheck_old_last) > -1, true)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    相关资源
    最近更新 更多