想要在 Postgres 中使用 CTE 删除然后插入单个语句

Jos*_*osh 5 postgresql cte

我想使用 Postgres 中的 CTE 删除然后插入单个语句。我们可以使用显式事务和 2 个语句,但希望尽可能避免这种情况。我不知道是否会删除 0 行或 1+ 行,因此我认为我无法使用单个 WHERE EXISTS 或 WHERE NOT EXISTS 来确保 CTE 中的删除语句首先运行。这与我的设想类似:

WITH deletions AS (
   DELETE FROM foo WHERE a = 'abc' and b = 2 
)
INSERT INTO FOO (a, b, c) VALUES ('abc', 2, 'new value')
Run Code Online (Sandbox Code Playgroud)

但我需要一种方法来强制 CTE 首先运行。表上不存在会在同一事务中尝试两次更新同一记录时产生问题的 pk。

Erw*_*ter 5

如果没有任何UNIQUE限制,您的原始查询就可以正常工作。见下文。
我会放入一个单独的 CTE 来一次性提供所有输入值:

WITH input(a, b, c) AS (VALUES ('abc', 2, 'new value'))  -- provide values once
   , del AS (
   DELETE FROM foo AS f
   USING  input i
   WHERE  f.a = i.a
   AND    f.b = i.b
   )
INSERT INTO foo (a, b, c)
TABLE input;
Run Code Online (Sandbox Code Playgroud)

无法DELETEINSERT同一语句中删除行,因为两者都看到基础表的相同快照。这意味着,无法看到其他 CTE 中DELETE输入的行。INSERT两者实际上是同时执行的。有关的:

这也是为什么这不能与UNIQUE上的索引一起使用的原因(a.b)。始终强制执行唯一性。仍然会INSERT看到其他 CTE 中的行被删除。显而易见的替代方案是 UPSERT。但事实并非如此,因为您提到没有 PK 并且可以DELETE删除 0-n 行。

正如 a_horse 评论的那样:不过,可以使用可延迟的约束。看:

但是可延迟约束的成本要高得多,并且不能与 FK 约束一起使用,也不能作为 UPSERT 语句中的仲裁者......

请注意,独立VALUES表达式可能需要显式类型转换。看:

有关的:


也就是说,我不明白这比单个事务中的aDELETE和 a 有何优越之处- 这也适用于约束。您评论道:INSERTUNIQUE

通过单个语句和我们的数据库库使用数据库参数要容易得多。

如果经常使用该语句,请考虑使用函数

CREATE OR REPLACE FUNCTION f_foo_delins(_a text, _b int, _c text)  -- actual types
  RETURNS void LANGUAGE sql AS
$func$
   DELETE FROM foo
   WHERE  a = _a
   AND    b = _b;

   INSERT INTO foo ( a,  b,  c)
   VALUES          (_a, _b, _c);
$func$;
Run Code Online (Sandbox Code Playgroud)

那么调用就简单了:

SELECT f_foo_delins('abc', 3, 'new value');
Run Code Online (Sandbox Code Playgroud)

如果您只需要在某些会话中使用它,则可以选择临时功能。看:

或者带有上述 CTE 的准备好的语句:

PREPARE foo_delins(text, int, text) AS
WITH del AS (
   DELETE FROM foo
   WHERE  a = $1
   AND    b = $2
  )
INSERT INTO foo ( a,  b,  c)
VALUES          ($1, $2, $3);
Run Code Online (Sandbox Code Playgroud)

称呼:

EXECUTE foo_delins('abc', 4, 'new value');
Run Code Online (Sandbox Code Playgroud)

大多数语言都有自己的准备好的语句的实现,使用libpq...

准备好的语句和函数知道它们的输入类型。不需要显式类型转换(特殊情况除外)。

有关的:


小智 0

您可以返回 CTE 中删除的任何内容并将其插入表中。

WITH deletions AS (
    DELETE FROM foo WHERE a = 'abc' and b = 2 returning a,b
)
INSERT INTO FOO (a, b, c) 
    select a,b,'new value' from deletions;
Run Code Online (Sandbox Code Playgroud)