问题
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
, andDELETE
statements setFOUND
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