【问题标题】:Run SQL statements in PL/pgSQL only if a row doesn't exist仅当行不存在时才在 PL/pgSQL 中运行 SQL 语句
【发布时间】:2017-05-16 19:07:00
【问题描述】:

我想在 Postgres 9.6 的 PL/pgSQL 函数中做这样的事情:

INSERT INTO table1 (id, value) VALUES (1, 'a') ON CONFLICT DO NOTHING;
--- If the above statement didn't insert a new row
---   because id of 1 already existed, 
---   then run the following statements

INSERT INTO table2 (table1_id, value) VALUES (1, 'a');
UPDATE table3 set (table1_id, time) = (1, now());

但是,我不知道如何确定第一个 INSERT 是否实际插入了新行,或者 ON CONFLICT DO NOTHING 是否已完成。

在运行所有 SQL 语句之前,我可以在函数开头执行 SELECT 以查看 id 为 1 的记录是否存在于 table1 中,但我认为这会导致竞争条件。

【问题讨论】:

  • 当你写 "Postgres function" 你的意思是LANGUAGE plpgsql?还是LANGUAGE sql?请清楚,并始终添加您的 Postgres 版本。
  • 感谢您的建议,以后会做。在这种情况下,它是 plpgsql 和 9.6。

标签: sql postgresql stored-procedures plpgsql sql-insert


【解决方案1】:

对于 plpgsql 函数,使用special variable FOUND:

CREATE FUNCTION foo(int, text)
 RETURNS void AS
$$
BEGIN
   INSERT INTO table1 (id, value) VALUES ($1, $2) ON CONFLICT DO NOTHING;

   IF NOT FOUND THEN
      INSERT INTO table2 (table1_id, value) VALUES ($1, $2);
      UPDATE table3 set (table1_id, time) = ($1, now())
      WHERE  ????;  -- you surely don't want to update all rows in table3
   END IF;
END
$$

呼叫:

SELECT foo(1, 'a');
如果INSERT 实际上没有插入任何行,则

FOUND 设置为false。

The manual about the ON CONFLICT Clause:

ON CONFLICT DO NOTHING 只是避免插入一行作为它的 替代行动。

The manual about Obtaining the Result Status

UPDATE、INSERT 和 DELETE 语句集 FOUND 如果至少有一个则为真 行受到影响,如果没有行受到影响,则为 false。

需要明确的是,如果table1 中的行确实 已经存在,则运行后面的语句,因此不会插入新行。 (正如您所要求的,但与您的问题标题相反。)

如果你只想检查一行是否存在:

比赛条件?

如果同一事务中的后续命令依赖 table1 中的现有行(例如使用 FK),您需要锁定它以防止并发事务删除或更新它同时。一种方法是:代替DO NOTHING 使用DO UPDATE,但不 实际上更新该行。该行仍被锁定:

INSERT INTO table1 AS t (id, value)
VALUES ($1, $2)
ON     CONFLICT (id) DO UPDATE  -- specify unique column(s) or constraint / index
SET    id = t.id WHERE FALSE;   -- never executed, but locks the row

显然,如果您可以排除可能以冲突方式删除或更新同一行的并发事务,那么问题就不存在了。

详细解释:

【讨论】:

  • 您能否举例说明在这种情况下可能出现竞争条件的位置?为了避免竞争条件,我需要在@GordonLinoff 的回答(CTE)中做类似的事情吗?
  • @user779159:示例:事务 T1 可能会发现 id = 1 已经存在并跳过插入。与此同时,T2 可能会删除同一行(并提交)——在 T1 运行后续的插入和更新操作之前……可写 CTE 使时间窗口变小,但它们没有防御这种竞争原则上的条件。我的答案中的最后一个链接包含更多详细信息和问题的解决方案。
  • @user779159:我在上面添加了更多解释、代码和另一个链接。
  • @user779159:T2 的 DELETE 一直等到行锁被释放,这发生在 T1 提交或回滚时。然后 T2 重新检查条件(行可能已更改)并再次尝试删除。例如,如果 T1 同时添加了对该行的 FK 引用,则 DELETE 受到限制,并引发无法删除该行的异常。除非您捕获错误,否则 T2 将回滚。但是,因为您已明确说明您只想在实际插入一行时继续,所以这种情况不适用。在这种情况下毕竟没有明显的竞争条件。
  • @user779159:您最后两个 cmets 中的所有内容看起来都完全正确。只要您使用 FK 约束强制执行参照完整性,Postgres 在任何情况下都不允许出现不一致的状态。
【解决方案2】:

Postgres 有 returning 子句和 CTE 来做你想做的事:

WITH t1 as (
      INSERT INTO table1 (id, value)
          VALUES (1, 'a')
          ON CONFLICT DO NOTHING
          RETURNING *
     ),
     t2 as (
      INSERT INTO table2 (table1_id, value) 
          SELECT id, value 
          FROM (SELECT 1 as id, 'a' as value) t
          WHERE NOT EXISTS (SELECT 1 FROM t1) 
   )
UPDATE table3
    set (table1_id, time) = (1, now())
    WHERE NOT EXISTS (SELECT 1 FROM t1);

update 看起来很奇怪,因为它更新了table3 中的所有行。

【讨论】:

  • 在第一个INSERT 上,我可以执行RETURNING INTO 之类的操作来将插入的值放入函数中的变量中吗?然后在IF 语句中检查该值是否存在,如果存在则运行下一条语句?
  • 是的,就是这样。但是,该语句使用WHERE NOT EXISTS,而不是if。
  • 谢谢。看起来还有另一个答案(@MtwStark)使用IF 选项。由于我是 CTE 的新手并且对它们了解不多,因此按照他建议的方式完成它有什么缺点吗?还是这种情况需要 CTE 来避免某种问题,例如竞态条件?
  • @user779159 。 . .在任何一种情况下,您都可以使用显式事务包装语句。当您使用来自returning 的结果时(而不是检查它们是否为空),这种方法很好,因为您不必考虑事务和锁定。
  • @GordonLinoff:实际上,仍然存在竞争条件,您仍然需要考虑事务和锁定。 CTE 无法抵御竞争条件。
【解决方案3】:

也许你的意思是这样的?

INSERT INTO table1 (id, value) VALUES (1, 'a') ON CONFLICT DO NOTHING;
--- If the above statement didn't insert a new row
---   because id of 1 already existed, 
---   then run the following statements

affected_rows := SQL%ROWCOUNT;

IF affected_rows = 0 THEN
    INSERT INTO table2 (table1_id, value) VALUES (1, 'a');
    UPDATE table3 set (table1_id, time) = (1, now());
END IF

【讨论】:

    【解决方案4】:

    最简单可靠的方法是使用特殊变量 FOUND,这样:

    INSERT INTO table1 (id, value) values (1, ‘a’) on conflict do nothing;
    

    如果找到那么

    --成功

    否则

    --失败

    如果结束;

    这是诊断语句的文档 https://www.postgresql.org/docs/9.6/static/plpgsql-statements.html

    【讨论】:

      猜你喜欢
      • 2018-12-05
      • 1970-01-01
      • 2022-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-16
      相关资源
      最近更新 更多