Run SQL statements in PL/pgSQL only if a row doesn't exist

 ̄綄美尐妖づ 提交于 2019-12-29 09:22:36

问题


I want to do something like this in a PL/pgSQL function in Postgres 9.6:

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());

However, I don't know how to determine whether the first INSERT actually inserted a new row, or whether the the ON CONFLICT DO NOTHING was done.

I could do a SELECT at the beginning of the function to see whether a record with id of 1 exists in table1 before running all the SQL statements, but this would lead to race conditions I think.


回答1:


For a plpgsql function, use the 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
$$

Call:

SELECT foo(1, 'a');

FOUND is set to false if the INSERT does not actually insert any rows.

The manual about the ON CONFLICT Clause:

ON CONFLICT DO NOTHING simply avoids inserting a row as its alternative action.

The manual about Obtaining the Result Status

UPDATE, INSERT, and DELETE statements set FOUND true if at least one row is affected, false if no row is affected.

To be clear, this runs the later statements if a row in table1 does already exist, so the new row is not inserted. (Like you requested, but contrary to your question title.)

If you just want to check whether a row exists:

  • PostgreSQL IF statement

Race condition?

If subsequent commands in the same transaction depend on the existing row in table1 (with a FK for instance), you'll want to lock it to defend against concurrent transactions deleting or updating it in the meantime. One way to do this: instead of DO NOTHING use DO UPDATE, but do not actually update the row. The row is still locked:

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

Obviously, if you can rule out concurrent transactions that might delete or update the same row in a conflicting manner, then the problem does not exist.

Detailed explanation:

  • How to include excluded rows in RETURNING from INSERT ... ON CONFLICT
  • Is SELECT or INSERT in a function prone to race conditions?



回答2:


Postgres has the returning clause and CTEs to do what you want:

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);

The update looks strange because it updates all rows in table3.




回答3:


Maybe you mean something like this?

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:


The easiest and reliable way is with the especial variable FOUND, this way:

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

IF FOUND THEN

--success

ELSE

--failure

END IF;

Here is the documentation of diagnosing a statement https://www.postgresql.org/docs/9.6/static/plpgsql-statements.html



来源:https://stackoverflow.com/questions/41430826/run-sql-statements-in-pl-pgsql-only-if-a-row-doesnt-exist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!