2022-03-28 16:45:58 +02:00
|
|
|
--
|
|
|
|
-- MERGE
|
|
|
|
--
|
2022-03-28 17:10:36 +02:00
|
|
|
CREATE USER regress_merge_privs;
|
|
|
|
CREATE USER regress_merge_no_privs;
|
2024-02-21 17:18:52 +01:00
|
|
|
CREATE USER regress_merge_none;
|
2022-03-28 16:45:58 +02:00
|
|
|
DROP TABLE IF EXISTS target;
|
|
|
|
NOTICE: table "target" does not exist, skipping
|
|
|
|
DROP TABLE IF EXISTS source;
|
|
|
|
NOTICE: table "source" does not exist, skipping
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE target (tid integer, balance integer)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE source (sid integer, delta integer) -- no index
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
INSERT INTO target VALUES (1, 10);
|
|
|
|
INSERT INTO target VALUES (2, 20);
|
|
|
|
INSERT INTO target VALUES (3, 30);
|
|
|
|
SELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;
|
|
|
|
matched | tid | balance | sid | delta
|
|
|
|
---------+-----+---------+-----+-------
|
|
|
|
t | 1 | 10 | |
|
|
|
|
t | 2 | 20 | |
|
|
|
|
t | 3 | 30 | |
|
|
|
|
(3 rows)
|
|
|
|
|
2022-03-28 17:10:36 +02:00
|
|
|
ALTER TABLE target OWNER TO regress_merge_privs;
|
|
|
|
ALTER TABLE source OWNER TO regress_merge_privs;
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE target2 (tid integer, balance integer)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE source2 (sid integer, delta integer)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 17:10:36 +02:00
|
|
|
ALTER TABLE target2 OWNER TO regress_merge_no_privs;
|
|
|
|
ALTER TABLE source2 OWNER TO regress_merge_no_privs;
|
|
|
|
GRANT INSERT ON target TO regress_merge_no_privs;
|
|
|
|
SET SESSION AUTHORIZATION regress_merge_privs;
|
2022-03-28 16:45:58 +02:00
|
|
|
EXPLAIN (COSTS OFF)
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------
|
|
|
|
Merge on target t
|
|
|
|
-> Merge Join
|
|
|
|
Merge Cond: (t.tid = s.sid)
|
|
|
|
-> Sort
|
|
|
|
Sort Key: t.tid
|
|
|
|
-> Seq Scan on target t
|
|
|
|
-> Sort
|
|
|
|
Sort Key: s.sid
|
|
|
|
-> Seq Scan on source s
|
|
|
|
(9 rows)
|
|
|
|
|
|
|
|
--
|
|
|
|
-- Errors
|
|
|
|
--
|
|
|
|
MERGE INTO target t RANDOMWORD
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
ERROR: syntax error at or near "RANDOMWORD"
|
|
|
|
LINE 1: MERGE INTO target t RANDOMWORD
|
|
|
|
^
|
|
|
|
-- MATCHED/INSERT error
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
ERROR: syntax error at or near "INSERT"
|
2024-03-30 11:00:26 +01:00
|
|
|
LINE 5: INSERT DEFAULT VALUES;
|
|
|
|
^
|
|
|
|
-- NOT MATCHED BY SOURCE/INSERT error
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
ERROR: syntax error at or near "INSERT"
|
2022-03-28 16:45:58 +02:00
|
|
|
LINE 5: INSERT DEFAULT VALUES;
|
|
|
|
^
|
|
|
|
-- incorrectly specifying INTO target
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT INTO target DEFAULT VALUES;
|
|
|
|
ERROR: syntax error at or near "INTO"
|
|
|
|
LINE 5: INSERT INTO target DEFAULT VALUES;
|
|
|
|
^
|
|
|
|
-- Multiple VALUES clause
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (1,1), (2,2);
|
|
|
|
ERROR: syntax error at or near ","
|
|
|
|
LINE 5: INSERT VALUES (1,1), (2,2);
|
|
|
|
^
|
|
|
|
-- SELECT query for INSERT
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT SELECT (1, 1);
|
|
|
|
ERROR: syntax error at or near "SELECT"
|
|
|
|
LINE 5: INSERT SELECT (1, 1);
|
|
|
|
^
|
|
|
|
-- NOT MATCHED/UPDATE
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
ERROR: syntax error at or near "UPDATE"
|
2024-03-30 11:00:26 +01:00
|
|
|
LINE 5: UPDATE SET balance = 0;
|
|
|
|
^
|
|
|
|
-- NOT MATCHED BY TARGET/UPDATE
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED BY TARGET THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
ERROR: syntax error at or near "UPDATE"
|
2022-03-28 16:45:58 +02:00
|
|
|
LINE 5: UPDATE SET balance = 0;
|
|
|
|
^
|
|
|
|
-- UPDATE tablename
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE target SET balance = 0;
|
|
|
|
ERROR: syntax error at or near "target"
|
|
|
|
LINE 5: UPDATE target SET balance = 0;
|
|
|
|
^
|
|
|
|
-- source and target names the same
|
|
|
|
MERGE INTO target
|
|
|
|
USING target
|
|
|
|
ON tid = tid
|
|
|
|
WHEN MATCHED THEN DO NOTHING;
|
|
|
|
ERROR: name "target" specified more than once
|
|
|
|
DETAIL: The name is used both as MERGE target table and data source.
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
-- used in a CTE without RETURNING
|
2022-08-12 12:05:50 +02:00
|
|
|
WITH foo AS (
|
|
|
|
MERGE INTO target USING source ON (true)
|
|
|
|
WHEN MATCHED THEN DELETE
|
|
|
|
) SELECT * FROM foo;
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
ERROR: WITH query "foo" does not have a RETURNING clause
|
|
|
|
LINE 4: ) SELECT * FROM foo;
|
|
|
|
^
|
|
|
|
-- used in COPY without RETURNING
|
2022-08-12 12:05:50 +02:00
|
|
|
COPY (
|
|
|
|
MERGE INTO target USING source ON (true)
|
|
|
|
WHEN MATCHED THEN DELETE
|
|
|
|
) TO stdout;
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
ERROR: COPY query must have a RETURNING clause
|
2022-03-28 16:45:58 +02:00
|
|
|
-- unsupported relation types
|
|
|
|
-- materialized view
|
|
|
|
CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
|
|
|
|
MERGE INTO mv t
|
|
|
|
USING source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
ERROR: cannot execute MERGE on relation "mv"
|
|
|
|
DETAIL: This operation is not supported for materialized views.
|
|
|
|
DROP MATERIALIZED VIEW mv;
|
|
|
|
-- permissions
|
2024-02-21 17:18:52 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_merge_none;
|
|
|
|
MERGE INTO target
|
|
|
|
USING (SELECT 1)
|
|
|
|
ON true
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DO NOTHING;
|
|
|
|
ERROR: permission denied for table target
|
|
|
|
SET SESSION AUTHORIZATION regress_merge_privs;
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO target
|
|
|
|
USING source2
|
|
|
|
ON target.tid = source2.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
ERROR: permission denied for table source2
|
2022-03-28 17:10:36 +02:00
|
|
|
GRANT INSERT ON target TO regress_merge_no_privs;
|
|
|
|
SET SESSION AUTHORIZATION regress_merge_no_privs;
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO target
|
|
|
|
USING source2
|
|
|
|
ON target.tid = source2.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
ERROR: permission denied for table target
|
2022-03-28 17:10:36 +02:00
|
|
|
GRANT UPDATE ON target2 TO regress_merge_privs;
|
|
|
|
SET SESSION AUTHORIZATION regress_merge_privs;
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO target2
|
|
|
|
USING source
|
|
|
|
ON target2.tid = source.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
ERROR: permission denied for table target2
|
|
|
|
MERGE INTO target2
|
|
|
|
USING source
|
|
|
|
ON target2.tid = source.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
ERROR: permission denied for table target2
|
|
|
|
-- check if the target can be accessed from source relation subquery; we should
|
|
|
|
-- not be able to do so
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT * FROM source WHERE t.tid > sid) s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
ERROR: invalid reference to FROM-clause entry for table "t"
|
|
|
|
LINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s
|
|
|
|
^
|
2022-11-23 00:46:31 +01:00
|
|
|
DETAIL: There is an entry for table "t", but it cannot be referenced from this part of the query.
|
2022-03-28 16:45:58 +02:00
|
|
|
--
|
|
|
|
-- initial tests
|
|
|
|
--
|
|
|
|
-- zero rows in source has no effect
|
|
|
|
MERGE INTO target
|
|
|
|
USING source
|
|
|
|
ON target.tid = source.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
ROLLBACK;
|
|
|
|
-- insert some non-matching source rows to work from
|
|
|
|
INSERT INTO source VALUES (4, 40);
|
|
|
|
SELECT * FROM source ORDER BY sid;
|
|
|
|
sid | delta
|
|
|
|
-----+-------
|
|
|
|
4 | 40
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
DO NOTHING;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT DEFAULT VALUES;
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
|
|
|
|
|
(4 rows)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
ROLLBACK;
|
|
|
|
-- DELETE/INSERT not matched by source/target
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
DELETE
|
|
|
|
WHEN NOT MATCHED BY TARGET THEN
|
|
|
|
INSERT VALUES (s.sid, s.delta)
|
|
|
|
RETURNING merge_action(), t.*;
|
|
|
|
merge_action | tid | balance
|
|
|
|
--------------+-----+---------
|
|
|
|
DELETE | 1 | 10
|
|
|
|
DELETE | 2 | 20
|
|
|
|
DELETE | 3 | 30
|
|
|
|
INSERT | 4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
4 | 40
|
|
|
|
(1 row)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
ROLLBACK;
|
|
|
|
-- index plans
|
|
|
|
INSERT INTO target SELECT generate_series(1000,2500), 0;
|
|
|
|
ALTER TABLE target ADD PRIMARY KEY (tid);
|
|
|
|
ANALYZE target;
|
|
|
|
EXPLAIN (COSTS OFF)
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------
|
|
|
|
Merge on target t
|
|
|
|
-> Hash Join
|
|
|
|
Hash Cond: (s.sid = t.tid)
|
|
|
|
-> Seq Scan on source s
|
|
|
|
-> Hash
|
|
|
|
-> Seq Scan on target t
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
EXPLAIN (COSTS OFF)
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------
|
|
|
|
Merge on target t
|
|
|
|
-> Hash Join
|
|
|
|
Hash Cond: (s.sid = t.tid)
|
|
|
|
-> Seq Scan on source s
|
|
|
|
-> Hash
|
|
|
|
-> Seq Scan on target t
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
EXPLAIN (COSTS OFF)
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (4, NULL);
|
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------
|
|
|
|
Merge on target t
|
|
|
|
-> Hash Left Join
|
|
|
|
Hash Cond: (s.sid = t.tid)
|
|
|
|
-> Seq Scan on source s
|
|
|
|
-> Hash
|
|
|
|
-> Seq Scan on target t
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
DELETE FROM target WHERE tid > 100;
|
|
|
|
ANALYZE target;
|
|
|
|
-- insert some matching source rows to work from
|
|
|
|
INSERT INTO source VALUES (2, 5);
|
|
|
|
INSERT INTO source VALUES (3, 20);
|
|
|
|
SELECT * FROM source ORDER BY sid;
|
|
|
|
sid | delta
|
|
|
|
-----+-------
|
|
|
|
2 | 5
|
|
|
|
3 | 20
|
|
|
|
4 | 40
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
-- equivalent of an UPDATE join
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 0
|
|
|
|
3 | 0
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- equivalent of a DELETE join
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DO NOTHING;
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (4, NULL);
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 |
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- duplicate source row causes multiple target row update ERROR
|
|
|
|
INSERT INTO source VALUES (2, 5);
|
|
|
|
SELECT * FROM source ORDER BY sid;
|
|
|
|
sid | delta
|
|
|
|
-----+-------
|
|
|
|
2 | 5
|
|
|
|
2 | 5
|
|
|
|
3 | 20
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
ERROR: MERGE command cannot affect row a second time
|
|
|
|
HINT: Ensure that not more than one source row matches any one target row.
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
ERROR: MERGE command cannot affect row a second time
|
|
|
|
HINT: Ensure that not more than one source row matches any one target row.
|
|
|
|
ROLLBACK;
|
|
|
|
-- remove duplicate MATCHED data from source data
|
|
|
|
DELETE FROM source WHERE sid = 2;
|
|
|
|
INSERT INTO source VALUES (2, 5);
|
|
|
|
SELECT * FROM source ORDER BY sid;
|
|
|
|
sid | delta
|
|
|
|
-----+-------
|
|
|
|
2 | 5
|
|
|
|
3 | 20
|
|
|
|
4 | 40
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
-- duplicate source row on INSERT should fail because of target_pkey
|
|
|
|
INSERT INTO source VALUES (4, 40);
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (4, NULL);
|
|
|
|
ERROR: duplicate key value violates unique constraint "target_pkey"
|
|
|
|
DETAIL: Key (tid)=(4) already exists.
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
ERROR: current transaction is aborted, commands ignored until end of transaction block
|
|
|
|
ROLLBACK;
|
|
|
|
-- remove duplicate NOT MATCHED data from source data
|
|
|
|
DELETE FROM source WHERE sid = 4;
|
|
|
|
INSERT INTO source VALUES (4, 40);
|
|
|
|
SELECT * FROM source ORDER BY sid;
|
|
|
|
sid | delta
|
|
|
|
-----+-------
|
|
|
|
2 | 5
|
|
|
|
3 | 20
|
|
|
|
4 | 40
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
-- remove constraints
|
|
|
|
alter table target drop CONSTRAINT target_pkey;
|
|
|
|
alter table target alter column tid drop not null;
|
|
|
|
-- multiple actions
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (4, 4)
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0;
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 0
|
|
|
|
3 | 0
|
|
|
|
4 | 4
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- should be equivalent
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (4, 4);
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 0
|
|
|
|
3 | 0
|
|
|
|
4 | 4
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- column references
|
|
|
|
-- do a simple equivalent of an UPDATE join
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = t.balance + s.delta;
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 25
|
|
|
|
3 | 50
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- do a simple equivalent of an INSERT SELECT
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (s.sid, s.delta);
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- and again with duplicate source rows
|
|
|
|
INSERT INTO source VALUES (5, 50);
|
|
|
|
INSERT INTO source VALUES (5, 50);
|
|
|
|
-- do a simple equivalent of an INSERT SELECT
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (s.sid, s.delta);
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 | 40
|
|
|
|
5 | 50
|
|
|
|
5 | 50
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- removing duplicate source rows
|
|
|
|
DELETE FROM source WHERE sid = 5;
|
|
|
|
-- and again with explicitly identified column list
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (s.sid, s.delta);
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- and again with a subtle error: referring to non-existent target row for NOT MATCHED
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (t.tid, s.delta);
|
|
|
|
ERROR: invalid reference to FROM-clause entry for table "t"
|
|
|
|
LINE 5: INSERT (tid, balance) VALUES (t.tid, s.delta);
|
|
|
|
^
|
2022-11-23 00:46:31 +01:00
|
|
|
DETAIL: There is an entry for table "t", but it cannot be referenced from this part of the query.
|
2022-03-28 16:45:58 +02:00
|
|
|
-- and again with a constant ON clause
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON (SELECT true)
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (t.tid, s.delta);
|
|
|
|
ERROR: invalid reference to FROM-clause entry for table "t"
|
|
|
|
LINE 5: INSERT (tid, balance) VALUES (t.tid, s.delta);
|
|
|
|
^
|
2022-11-23 00:46:31 +01:00
|
|
|
DETAIL: There is an entry for table "t", but it cannot be referenced from this part of the query.
|
2022-03-28 16:45:58 +02:00
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
ERROR: current transaction is aborted, commands ignored until end of transaction block
|
|
|
|
ROLLBACK;
|
|
|
|
-- now the classic UPSERT
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = t.balance + s.delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (s.sid, s.delta);
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 25
|
|
|
|
3 | 50
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- unreachable WHEN clause should ERROR
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN /* Terminal WHEN clause for MATCHED */
|
|
|
|
DELETE
|
2023-01-10 15:17:47 +01:00
|
|
|
WHEN MATCHED THEN
|
2022-03-28 16:45:58 +02:00
|
|
|
UPDATE SET balance = t.balance - s.delta;
|
|
|
|
ERROR: unreachable WHEN clause specified after unconditional WHEN clause
|
|
|
|
ROLLBACK;
|
|
|
|
-- conditional WHEN clause
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE wq_source (balance integer, sid integer)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
INSERT INTO wq_source (sid, balance) VALUES (1, 100);
|
|
|
|
BEGIN;
|
|
|
|
-- try a simple INSERT with default values first
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid) VALUES (s.sid);
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | -1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- this time with a FALSE condition
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED AND FALSE THEN
|
|
|
|
INSERT (tid) VALUES (s.sid);
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
-- this time with an actual condition which returns false
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED AND s.balance <> 100 THEN
|
|
|
|
INSERT (tid) VALUES (s.sid);
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
-- and now with a condition which returns true
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED AND s.balance = 100 THEN
|
|
|
|
INSERT (tid) VALUES (s.sid);
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | -1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- conditions in the NOT MATCHED clause can only refer to source columns
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED AND t.balance = 100 THEN
|
|
|
|
INSERT (tid) VALUES (s.sid);
|
|
|
|
ERROR: invalid reference to FROM-clause entry for table "t"
|
|
|
|
LINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN
|
|
|
|
^
|
2022-11-23 00:46:31 +01:00
|
|
|
DETAIL: There is an entry for table "t", but it cannot be referenced from this part of the query.
|
2022-03-28 16:45:58 +02:00
|
|
|
SELECT * FROM wq_target;
|
|
|
|
ERROR: current transaction is aborted, commands ignored until end of transaction block
|
|
|
|
ROLLBACK;
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED AND s.balance = 100 THEN
|
|
|
|
INSERT (tid) VALUES (s.sid);
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | -1
|
|
|
|
(1 row)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
-- conditions in NOT MATCHED BY SOURCE clause can only refer to target columns
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED BY SOURCE AND s.balance = 100 THEN
|
|
|
|
DELETE;
|
|
|
|
ERROR: invalid reference to FROM-clause entry for table "s"
|
|
|
|
LINE 3: WHEN NOT MATCHED BY SOURCE AND s.balance = 100 THEN
|
|
|
|
^
|
|
|
|
DETAIL: There is an entry for table "s", but it cannot be referenced from this part of the query.
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED BY SOURCE AND t.balance = 100 THEN
|
|
|
|
DELETE;
|
2022-03-28 16:45:58 +02:00
|
|
|
-- conditions in MATCHED clause can refer to both source and target
|
|
|
|
SELECT * FROM wq_source;
|
|
|
|
balance | sid
|
|
|
|
---------+-----
|
|
|
|
100 | 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND s.balance = 100 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 99
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance = 100 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 99
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- check if AND works
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 99
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 199
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- check if OR works
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 199
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 299
|
|
|
|
(1 row)
|
|
|
|
|
2022-04-12 09:29:39 +02:00
|
|
|
-- check source-side whole-row references
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON (t.tid = s.sid)
|
|
|
|
WHEN matched and t = s or t.tid = s.sid THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 399
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
2022-03-28 16:45:58 +02:00
|
|
|
-- check if subqueries work in the conditions?
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
-- check if we can access system columns in the conditions
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.xmin = t.xmax THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
ERROR: cannot use system column "xmin" in MERGE WHEN condition
|
|
|
|
LINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN
|
|
|
|
^
|
|
|
|
MERGE INTO wq_target t
|
|
|
|
USING wq_source s ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.tableoid >= 0 THEN
|
|
|
|
UPDATE SET balance = t.balance + s.balance;
|
|
|
|
SELECT * FROM wq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 499
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
DROP TABLE wq_target, wq_source;
|
|
|
|
-- test triggers
|
|
|
|
create or replace function merge_trigfunc () returns trigger
|
|
|
|
language plpgsql as
|
|
|
|
$$
|
|
|
|
DECLARE
|
|
|
|
line text;
|
|
|
|
BEGIN
|
|
|
|
SELECT INTO line format('%s %s %s trigger%s',
|
|
|
|
TG_WHEN, TG_OP, TG_LEVEL, CASE
|
|
|
|
WHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'
|
|
|
|
THEN format(' row: %s', NEW)
|
|
|
|
WHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'
|
|
|
|
THEN format(' row: %s -> %s', OLD, NEW)
|
|
|
|
WHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'
|
|
|
|
THEN format(' row: %s', OLD)
|
|
|
|
END);
|
|
|
|
|
|
|
|
RAISE NOTICE '%', line;
|
|
|
|
IF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN
|
|
|
|
IF (TG_OP = 'DELETE') THEN
|
|
|
|
RETURN OLD;
|
|
|
|
ELSE
|
|
|
|
RETURN NEW;
|
|
|
|
END IF;
|
|
|
|
ELSE
|
|
|
|
RETURN NULL;
|
|
|
|
END IF;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
CREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
CREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
|
|
|
|
-- now the classic UPSERT, with a DELETE
|
|
|
|
BEGIN;
|
|
|
|
UPDATE target SET balance = 0 WHERE tid = 3;
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (3,30) -> (3,0)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (3,30) -> (3,0)
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
--EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance > s.delta THEN
|
|
|
|
UPDATE SET balance = t.balance - s.delta
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (s.sid, s.delta);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE DELETE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE DELETE ROW trigger row: (3,0)
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (2,20) -> (2,15)
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER DELETE ROW trigger row: (3,0)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (2,20) -> (2,15)
|
|
|
|
NOTICE: AFTER INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER DELETE STATEMENT trigger
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 15
|
|
|
|
4 | 40
|
|
|
|
(3 rows)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
ROLLBACK;
|
|
|
|
-- UPSERT with UPDATE/DELETE when not matched by source
|
|
|
|
BEGIN;
|
|
|
|
DELETE FROM SOURCE WHERE sid = 2;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance > s.delta THEN
|
|
|
|
UPDATE SET balance = t.balance - s.delta
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 0
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (s.sid, s.delta)
|
|
|
|
WHEN NOT MATCHED BY SOURCE AND tid = 1 THEN
|
|
|
|
UPDATE SET balance = 0
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
DELETE
|
|
|
|
RETURNING merge_action(), t.*;
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE DELETE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (3,30) -> (3,10)
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: BEFORE DELETE ROW trigger row: (2,20)
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (1,10) -> (1,0)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (3,30) -> (3,10)
|
|
|
|
NOTICE: AFTER INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER DELETE ROW trigger row: (2,20)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (1,10) -> (1,0)
|
|
|
|
NOTICE: AFTER DELETE STATEMENT trigger
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
merge_action | tid | balance
|
|
|
|
--------------+-----+---------
|
|
|
|
UPDATE | 3 | 10
|
|
|
|
INSERT | 4 | 40
|
|
|
|
DELETE | 2 | 20
|
|
|
|
UPDATE | 1 | 0
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 0
|
|
|
|
3 | 10
|
|
|
|
4 | 40
|
|
|
|
(3 rows)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
ROLLBACK;
|
|
|
|
-- Test behavior of triggers that turn UPDATE/DELETE into no-ops
|
|
|
|
create or replace function skip_merge_op() returns trigger
|
|
|
|
language plpgsql as
|
|
|
|
$$
|
|
|
|
BEGIN
|
|
|
|
RETURN NULL;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
SELECT * FROM target full outer join source on (sid = tid);
|
|
|
|
tid | balance | sid | delta
|
|
|
|
-----+---------+-----+-------
|
|
|
|
3 | 30 | 3 | 20
|
|
|
|
2 | 20 | 2 | 5
|
|
|
|
| | 4 | 40
|
|
|
|
1 | 10 | |
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
create trigger merge_skip BEFORE INSERT OR UPDATE or DELETE
|
|
|
|
ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();
|
2023-03-13 12:09:39 +01:00
|
|
|
DO $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta
|
|
|
|
WHEN MATCHED THEN DELETE
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (sid, delta);
|
2023-03-13 12:09:39 +01:00
|
|
|
IF FOUND THEN
|
|
|
|
RAISE NOTICE 'Found';
|
|
|
|
ELSE
|
|
|
|
RAISE NOTICE 'Not found';
|
|
|
|
END IF;
|
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
RAISE NOTICE 'ROW_COUNT = %', result;
|
|
|
|
END;
|
|
|
|
$$;
|
2022-03-28 16:45:58 +02:00
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE DELETE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (3,30) -> (3,50)
|
|
|
|
NOTICE: BEFORE DELETE ROW trigger row: (2,20)
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER DELETE STATEMENT trigger
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
2023-03-13 12:09:39 +01:00
|
|
|
NOTICE: Not found
|
|
|
|
NOTICE: ROW_COUNT = 0
|
2022-03-28 16:45:58 +02:00
|
|
|
SELECT * FROM target FULL OUTER JOIN source ON (sid = tid);
|
|
|
|
tid | balance | sid | delta
|
|
|
|
-----+---------+-----+-------
|
|
|
|
3 | 30 | 3 | 20
|
|
|
|
2 | 20 | 2 | 5
|
|
|
|
| | 4 | 40
|
|
|
|
1 | 10 | |
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
DROP TRIGGER merge_skip ON target;
|
|
|
|
DROP FUNCTION skip_merge_op();
|
|
|
|
-- test from PL/pgSQL
|
|
|
|
-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO
|
|
|
|
BEGIN;
|
|
|
|
DO LANGUAGE plpgsql $$
|
|
|
|
BEGIN
|
|
|
|
MERGE INTO target t
|
|
|
|
USING source AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED AND t.balance > s.delta THEN
|
|
|
|
UPDATE SET balance = t.balance - s.delta;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (3,30) -> (3,10)
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (2,20) -> (2,15)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (3,30) -> (3,10)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (2,20) -> (2,15)
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
ROLLBACK;
|
|
|
|
--source constants
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT 9 AS sid, 57 AS delta) AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (s.sid, s.delta);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (9,57)
|
|
|
|
NOTICE: AFTER INSERT ROW trigger row: (9,57)
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
9 | 57
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
--source query
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT sid, delta FROM source WHERE delta > 0) AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (s.sid, s.delta);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (s.sid, s.newname);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
--self-merge
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t1
|
|
|
|
USING target t2
|
|
|
|
ON t1.tid = t2.tid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = t1.balance + t2.balance
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (t2.tid, t2.balance);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (1,10) -> (1,20)
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (2,20) -> (2,40)
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (3,30) -> (3,60)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (1,10) -> (1,20)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (2,20) -> (2,40)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (3,30) -> (3,60)
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 20
|
|
|
|
2 | 40
|
|
|
|
3 | 60
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (s.sid, s.delta);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO target t
|
|
|
|
USING
|
|
|
|
(SELECT sid, max(delta) AS delta
|
|
|
|
FROM source
|
|
|
|
GROUP BY sid
|
|
|
|
HAVING count(*) = 1
|
|
|
|
ORDER BY sid ASC) AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance) VALUES (s.sid, s.delta);
|
|
|
|
NOTICE: BEFORE INSERT STATEMENT trigger
|
|
|
|
NOTICE: BEFORE INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER INSERT ROW trigger row: (4,40)
|
|
|
|
NOTICE: AFTER INSERT STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
4 | 40
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- plpgsql parameters and results
|
|
|
|
BEGIN;
|
|
|
|
CREATE FUNCTION merge_func (p_id integer, p_bal integer)
|
|
|
|
RETURNS INTEGER
|
|
|
|
LANGUAGE plpgsql
|
|
|
|
AS $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT p_id AS sid) AS s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = t.balance - p_bal;
|
|
|
|
IF FOUND THEN
|
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
END IF;
|
|
|
|
RETURN result;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
SELECT merge_func(3, 4);
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (3,30) -> (3,26)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (3,30) -> (3,26)
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
merge_func
|
|
|
|
------------
|
|
|
|
1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 10
|
|
|
|
2 | 20
|
|
|
|
3 | 26
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- PREPARE
|
|
|
|
BEGIN;
|
|
|
|
prepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;
|
|
|
|
execute foom;
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (1,10) -> (1,1)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (1,10) -> (1,1)
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 1
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
PREPARE foom2 (integer, integer) AS
|
|
|
|
MERGE INTO target t
|
|
|
|
USING (SELECT 1) s
|
|
|
|
ON t.tid = $1
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = $2;
|
|
|
|
--EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
|
|
|
|
execute foom2 (1, 1);
|
|
|
|
NOTICE: BEFORE UPDATE STATEMENT trigger
|
|
|
|
NOTICE: BEFORE UPDATE ROW trigger row: (1,10) -> (1,1)
|
|
|
|
NOTICE: AFTER UPDATE ROW trigger row: (1,10) -> (1,1)
|
|
|
|
NOTICE: AFTER UPDATE STATEMENT trigger
|
|
|
|
SELECT * FROM target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 1
|
|
|
|
2 | 20
|
|
|
|
3 | 30
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- subqueries in source relation
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE sq_target (tid integer NOT NULL, balance integer)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
INSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);
|
|
|
|
INSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING (SELECT * FROM sq_source) s
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED AND t.balance > delta THEN
|
|
|
|
UPDATE SET balance = t.balance + delta;
|
|
|
|
SELECT * FROM sq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
3 | 300
|
|
|
|
1 | 110
|
|
|
|
2 | 220
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- try a view
|
|
|
|
CREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO sq_target
|
|
|
|
USING v
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = v.balance + delta;
|
|
|
|
SELECT * FROM sq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
2 | 200
|
|
|
|
3 | 300
|
|
|
|
1 | 10
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- ambiguous reference to a column
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO sq_target
|
|
|
|
USING v
|
|
|
|
ON tid = sid
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
2022-03-28 16:45:58 +02:00
|
|
|
UPDATE SET balance = balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE;
|
|
|
|
ERROR: column reference "balance" is ambiguous
|
|
|
|
LINE 5: UPDATE SET balance = balance + delta
|
|
|
|
^
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING v
|
|
|
|
ON tid = sid
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
2022-03-28 16:45:58 +02:00
|
|
|
UPDATE SET balance = t.balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE;
|
|
|
|
SELECT * FROM sq_target;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
2 | 200
|
|
|
|
3 | 300
|
|
|
|
-1 | -11
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- CTEs
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
|
|
|
|
WITH targq AS (
|
|
|
|
SELECT * FROM v
|
|
|
|
)
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING v
|
|
|
|
ON tid = sid
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
2022-03-28 16:45:58 +02:00
|
|
|
UPDATE SET balance = t.balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE;
|
|
|
|
ROLLBACK;
|
|
|
|
-- RETURNING
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
SELECT * FROM sq_source ORDER BY sid;
|
|
|
|
delta | sid | balance
|
|
|
|
-------+-----+---------
|
|
|
|
10 | 1 | 0
|
|
|
|
20 | 2 | 0
|
|
|
|
40 | 4 | 0
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM sq_target ORDER BY tid;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 100
|
|
|
|
2 | 200
|
|
|
|
3 | 300
|
|
|
|
(3 rows)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
BEGIN;
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
CREATE TABLE merge_actions(action text, abbrev text);
|
|
|
|
INSERT INTO merge_actions VALUES ('INSERT', 'ins'), ('UPDATE', 'upd'), ('DELETE', 'del');
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO sq_target t
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
USING sq_source s
|
2022-03-28 16:45:58 +02:00
|
|
|
ON tid = sid
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
2022-03-28 16:45:58 +02:00
|
|
|
UPDATE SET balance = t.balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
2022-03-28 16:45:58 +02:00
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
DELETE
|
|
|
|
RETURNING (SELECT abbrev FROM merge_actions
|
|
|
|
WHERE action = merge_action()) AS action,
|
|
|
|
t.*,
|
|
|
|
CASE merge_action()
|
|
|
|
WHEN 'INSERT' THEN 'Inserted '||t
|
|
|
|
WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
|
|
|
|
WHEN 'DELETE' THEN 'Removed '||t
|
|
|
|
END AS description;
|
|
|
|
action | tid | balance | description
|
|
|
|
--------+-----+---------+---------------------
|
|
|
|
del | 1 | 100 | Removed (1,100)
|
|
|
|
upd | 2 | 220 | Added 20 to balance
|
|
|
|
ins | 4 | 40 | Inserted (4,40)
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- error when using merge_action() outside MERGE
|
|
|
|
SELECT merge_action() FROM sq_target;
|
|
|
|
ERROR: MERGE_ACTION() can only be used in the RETURNING list of a MERGE command
|
|
|
|
LINE 1: SELECT merge_action() FROM sq_target;
|
|
|
|
^
|
|
|
|
UPDATE sq_target SET balance = balance + 1 RETURNING merge_action();
|
|
|
|
ERROR: MERGE_ACTION() can only be used in the RETURNING list of a MERGE command
|
|
|
|
LINE 1: ...ATE sq_target SET balance = balance + 1 RETURNING merge_acti...
|
|
|
|
^
|
|
|
|
-- RETURNING in CTEs
|
|
|
|
CREATE TABLE sq_target_merge_log (tid integer NOT NULL, last_change text);
|
|
|
|
INSERT INTO sq_target_merge_log VALUES (1, 'Original value');
|
|
|
|
BEGIN;
|
|
|
|
WITH m AS (
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING sq_source s
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
|
|
|
UPDATE SET balance = t.balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE
|
|
|
|
RETURNING merge_action() AS action, t.*,
|
|
|
|
CASE merge_action()
|
|
|
|
WHEN 'INSERT' THEN 'Inserted '||t
|
|
|
|
WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
|
|
|
|
WHEN 'DELETE' THEN 'Removed '||t
|
|
|
|
END AS description
|
|
|
|
), m2 AS (
|
|
|
|
MERGE INTO sq_target_merge_log l
|
|
|
|
USING m
|
|
|
|
ON l.tid = m.tid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET last_change = description
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (m.tid, description)
|
|
|
|
RETURNING action, merge_action() AS log_action, l.*
|
|
|
|
)
|
|
|
|
SELECT * FROM m2;
|
|
|
|
action | log_action | tid | last_change
|
|
|
|
--------+------------+-----+---------------------
|
|
|
|
DELETE | UPDATE | 1 | Removed (1,100)
|
|
|
|
UPDATE | INSERT | 2 | Added 20 to balance
|
|
|
|
INSERT | INSERT | 4 | Inserted (4,40)
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM sq_target_merge_log ORDER BY tid;
|
|
|
|
tid | last_change
|
|
|
|
-----+---------------------
|
|
|
|
1 | Removed (1,100)
|
|
|
|
2 | Added 20 to balance
|
|
|
|
4 | Inserted (4,40)
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- COPY (MERGE ... RETURNING) TO ...
|
|
|
|
BEGIN;
|
|
|
|
COPY (
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING sq_source s
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
|
|
|
UPDATE SET balance = t.balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE
|
|
|
|
RETURNING merge_action(), t.*
|
|
|
|
) TO stdout;
|
|
|
|
DELETE 1 100
|
|
|
|
UPDATE 2 220
|
|
|
|
INSERT 4 40
|
|
|
|
ROLLBACK;
|
|
|
|
-- SQL function with MERGE ... RETURNING
|
|
|
|
BEGIN;
|
|
|
|
CREATE FUNCTION merge_into_sq_target(sid int, balance int, delta int,
|
|
|
|
OUT action text, OUT tid int, OUT new_balance int)
|
|
|
|
LANGUAGE sql AS
|
|
|
|
$$
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta)
|
|
|
|
ON tid = v.sid
|
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
|
|
|
UPDATE SET balance = t.balance + v.delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE
|
|
|
|
RETURNING merge_action(), t.*;
|
|
|
|
$$;
|
|
|
|
SELECT m.*
|
|
|
|
FROM (VALUES (1, 0, 0), (3, 0, 20), (4, 100, 10)) AS v(sid, balance, delta),
|
|
|
|
LATERAL (SELECT action, tid, new_balance FROM merge_into_sq_target(sid, balance, delta)) m;
|
|
|
|
action | tid | new_balance
|
|
|
|
--------+-----+-------------
|
|
|
|
DELETE | 1 | 100
|
|
|
|
UPDATE | 3 | 320
|
|
|
|
INSERT | 4 | 110
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- SQL SRF with MERGE ... RETURNING
|
|
|
|
BEGIN;
|
|
|
|
CREATE FUNCTION merge_sq_source_into_sq_target()
|
|
|
|
RETURNS TABLE (action text, tid int, balance int)
|
|
|
|
LANGUAGE sql AS
|
|
|
|
$$
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING sq_source s
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
|
|
|
UPDATE SET balance = t.balance + delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (balance + delta, sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE
|
|
|
|
RETURNING merge_action(), t.*;
|
|
|
|
$$;
|
|
|
|
SELECT * FROM merge_sq_source_into_sq_target();
|
|
|
|
action | tid | balance
|
|
|
|
--------+-----+---------
|
|
|
|
DELETE | 1 | 100
|
|
|
|
UPDATE | 2 | 220
|
|
|
|
INSERT | 4 | 40
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- PL/pgSQL function with MERGE ... RETURNING ... INTO
|
|
|
|
BEGIN;
|
|
|
|
CREATE FUNCTION merge_into_sq_target(sid int, balance int, delta int,
|
|
|
|
OUT r_action text, OUT r_tid int, OUT r_balance int)
|
|
|
|
LANGUAGE plpgsql AS
|
|
|
|
$$
|
|
|
|
BEGIN
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta)
|
|
|
|
ON tid = v.sid
|
|
|
|
WHEN MATCHED AND tid >= 2 THEN
|
|
|
|
UPDATE SET balance = t.balance + v.delta
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid)
|
|
|
|
WHEN MATCHED AND tid < 2 THEN
|
|
|
|
DELETE
|
|
|
|
RETURNING merge_action(), t.* INTO r_action, r_tid, r_balance;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
SELECT m.*
|
|
|
|
FROM (VALUES (1, 0, 0), (3, 0, 20), (4, 100, 10)) AS v(sid, balance, delta),
|
|
|
|
LATERAL (SELECT r_action, r_tid, r_balance FROM merge_into_sq_target(sid, balance, delta)) m;
|
|
|
|
r_action | r_tid | r_balance
|
|
|
|
----------+-------+-----------
|
|
|
|
DELETE | 1 | 100
|
|
|
|
UPDATE | 3 | 320
|
|
|
|
INSERT | 4 | 110
|
|
|
|
(3 rows)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
ROLLBACK;
|
|
|
|
-- EXPLAIN
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE ex_mtarget (a int, b int)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE ex_msource (a int, b int)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
INSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;
|
|
|
|
INSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;
|
|
|
|
CREATE FUNCTION explain_merge(query text) RETURNS SETOF text
|
|
|
|
LANGUAGE plpgsql AS
|
|
|
|
$$
|
|
|
|
DECLARE ln text;
|
|
|
|
BEGIN
|
|
|
|
FOR ln IN
|
|
|
|
EXECUTE 'explain (analyze, timing off, summary off, costs off) ' ||
|
|
|
|
query
|
|
|
|
LOOP
|
2022-05-18 21:20:49 +02:00
|
|
|
ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \S*', '\1: xxx', 'g');
|
2022-03-28 16:45:58 +02:00
|
|
|
RETURN NEXT ln;
|
|
|
|
END LOOP;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
-- only updates
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET b = t.b + 1');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
Tuples: updated=50
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Merge Join (actual rows=50 loops=1)
|
|
|
|
Merge Cond: (t.a = s.a)
|
|
|
|
-> Sort (actual rows=50 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=50 loops=1)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
(12 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
-- only updates to selected tuples
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN MATCHED AND t.a < 10 THEN
|
|
|
|
UPDATE SET b = t.b + 1');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
Tuples: updated=5 skipped=45
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Merge Join (actual rows=50 loops=1)
|
|
|
|
Merge Cond: (t.a = s.a)
|
|
|
|
-> Sort (actual rows=50 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=50 loops=1)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
(12 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
-- updates + deletes
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN MATCHED AND t.a < 10 THEN
|
|
|
|
UPDATE SET b = t.b + 1
|
|
|
|
WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN
|
|
|
|
DELETE');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
Tuples: updated=5 deleted=5 skipped=40
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Merge Join (actual rows=50 loops=1)
|
|
|
|
Merge Cond: (t.a = s.a)
|
|
|
|
-> Sort (actual rows=50 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=50 loops=1)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
(12 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
-- only inserts
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN NOT MATCHED AND s.a < 10 THEN
|
|
|
|
INSERT VALUES (a, b)');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
Tuples: inserted=4 skipped=96
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Merge Left Join (actual rows=100 loops=1)
|
|
|
|
Merge Cond: (s.a = t.a)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
|
|
|
-> Sort (actual rows=45 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=45 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
(12 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
-- all three
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN MATCHED AND t.a < 10 THEN
|
|
|
|
UPDATE SET b = t.b + 1
|
|
|
|
WHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN
|
|
|
|
DELETE
|
|
|
|
WHEN NOT MATCHED AND s.a < 20 THEN
|
|
|
|
INSERT VALUES (a, b)');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
Tuples: inserted=10 updated=9 deleted=5 skipped=76
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Merge Left Join (actual rows=100 loops=1)
|
|
|
|
Merge Cond: (s.a = t.a)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
|
|
|
-> Sort (actual rows=49 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=49 loops=1)
|
2022-05-18 18:33:04 +02:00
|
|
|
(12 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
-- not matched by source
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN NOT MATCHED BY SOURCE and t.a < 10 THEN
|
|
|
|
DELETE');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
|
|
|
Tuples: skipped=54
|
|
|
|
-> Merge Left Join (actual rows=54 loops=1)
|
|
|
|
Merge Cond: (t.a = s.a)
|
|
|
|
-> Sort (actual rows=54 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=54 loops=1)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
|
|
|
(12 rows)
|
|
|
|
|
|
|
|
-- not matched by source and target
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
|
|
|
|
WHEN NOT MATCHED BY SOURCE AND t.a < 10 THEN
|
|
|
|
DELETE
|
|
|
|
WHEN NOT MATCHED BY TARGET AND s.a < 20 THEN
|
|
|
|
INSERT VALUES (a, b)');
|
|
|
|
explain_merge
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
|
|
|
Tuples: skipped=100
|
|
|
|
-> Merge Full Join (actual rows=100 loops=1)
|
|
|
|
Merge Cond: (t.a = s.a)
|
|
|
|
-> Sort (actual rows=54 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=54 loops=1)
|
|
|
|
-> Sort (actual rows=100 loops=1)
|
|
|
|
Sort Key: s.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
|
|
|
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
|
|
|
|
(12 rows)
|
|
|
|
|
2022-05-18 21:20:49 +02:00
|
|
|
-- nothing
|
|
|
|
SELECT explain_merge('
|
|
|
|
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000
|
|
|
|
WHEN MATCHED AND t.a < 10 THEN
|
|
|
|
DO NOTHING');
|
|
|
|
explain_merge
|
|
|
|
--------------------------------------------------------------------
|
|
|
|
Merge on ex_mtarget t (actual rows=0 loops=1)
|
Revert "Optimize order of GROUP BY keys".
This reverts commit db0d67db2401eb6238ccc04c6407a4fd4f985832 and
several follow-on fixes. The idea of making a cost-based choice
of the order of the sorting columns is not fundamentally unsound,
but it requires cost information and data statistics that we don't
really have. For example, relying on procost to distinguish the
relative costs of different sort comparators is pretty pointless
so long as most such comparator functions are labeled with cost 1.0.
Moreover, estimating the number of comparisons done by Quicksort
requires more than just an estimate of the number of distinct values
in the input: you also need some idea of the sizes of the larger
groups, if you want an estimate that's good to better than a factor of
three or so. That's data that's often unknown or not very reliable.
Worse, to arrive at estimates of the number of calls made to the
lower-order-column comparison functions, the code needs to make
estimates of the numbers of distinct values of multiple columns,
which are necessarily even less trustworthy than per-column stats.
Even if all the inputs are perfectly reliable, the cost algorithm
as-implemented cannot offer useful information about how to order
sorting columns beyond the point at which the average group size
is estimated to drop to 1.
Close inspection of the code added by db0d67db2 shows that there
are also multiple small bugs. These could have been fixed, but
there's not much point if we don't trust the estimates to be
accurate in-principle.
Finally, the changes in cost_sort's behavior made for very large
changes (often a factor of 2 or so) in the cost estimates for all
sorting operations, not only those for multi-column GROUP BY.
That naturally changes plan choices in many situations, and there's
precious little evidence to show that the changes are for the better.
Given the above doubts about whether the new estimates are really
trustworthy, it's hard to summon much confidence that these changes
are better on the average.
Since we're hard up against the release deadline for v15, let's
revert these changes for now. We can always try again later.
Note: in v15, I left T_PathKeyInfo in place in nodes.h even though
it's unreferenced. Removing it would be an ABI break, and it seems
a bit late in the release cycle for that.
Discussion: https://postgr.es/m/TYAPR01MB586665EB5FB2C3807E893941F5579@TYAPR01MB5866.jpnprd01.prod.outlook.com
2022-10-03 16:56:16 +02:00
|
|
|
-> Merge Join (actual rows=0 loops=1)
|
|
|
|
Merge Cond: (t.a = s.a)
|
|
|
|
-> Sort (actual rows=0 loops=1)
|
|
|
|
Sort Key: t.a
|
|
|
|
Sort Method: quicksort Memory: xxx
|
2022-05-18 21:20:49 +02:00
|
|
|
-> Seq Scan on ex_mtarget t (actual rows=0 loops=1)
|
|
|
|
Filter: (a < '-1000'::integer)
|
|
|
|
Rows Removed by Filter: 54
|
Revert "Optimize order of GROUP BY keys".
This reverts commit db0d67db2401eb6238ccc04c6407a4fd4f985832 and
several follow-on fixes. The idea of making a cost-based choice
of the order of the sorting columns is not fundamentally unsound,
but it requires cost information and data statistics that we don't
really have. For example, relying on procost to distinguish the
relative costs of different sort comparators is pretty pointless
so long as most such comparator functions are labeled with cost 1.0.
Moreover, estimating the number of comparisons done by Quicksort
requires more than just an estimate of the number of distinct values
in the input: you also need some idea of the sizes of the larger
groups, if you want an estimate that's good to better than a factor of
three or so. That's data that's often unknown or not very reliable.
Worse, to arrive at estimates of the number of calls made to the
lower-order-column comparison functions, the code needs to make
estimates of the numbers of distinct values of multiple columns,
which are necessarily even less trustworthy than per-column stats.
Even if all the inputs are perfectly reliable, the cost algorithm
as-implemented cannot offer useful information about how to order
sorting columns beyond the point at which the average group size
is estimated to drop to 1.
Close inspection of the code added by db0d67db2 shows that there
are also multiple small bugs. These could have been fixed, but
there's not much point if we don't trust the estimates to be
accurate in-principle.
Finally, the changes in cost_sort's behavior made for very large
changes (often a factor of 2 or so) in the cost estimates for all
sorting operations, not only those for multi-column GROUP BY.
That naturally changes plan choices in many situations, and there's
precious little evidence to show that the changes are for the better.
Given the above doubts about whether the new estimates are really
trustworthy, it's hard to summon much confidence that these changes
are better on the average.
Since we're hard up against the release deadline for v15, let's
revert these changes for now. We can always try again later.
Note: in v15, I left T_PathKeyInfo in place in nodes.h even though
it's unreferenced. Removing it would be an ABI break, and it seems
a bit late in the release cycle for that.
Discussion: https://postgr.es/m/TYAPR01MB586665EB5FB2C3807E893941F5579@TYAPR01MB5866.jpnprd01.prod.outlook.com
2022-10-03 16:56:16 +02:00
|
|
|
-> Sort (never executed)
|
|
|
|
Sort Key: s.a
|
|
|
|
-> Seq Scan on ex_msource s (never executed)
|
|
|
|
(12 rows)
|
2022-05-18 21:20:49 +02:00
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
DROP TABLE ex_msource, ex_mtarget;
|
|
|
|
DROP FUNCTION explain_merge(text);
|
2024-03-17 11:17:11 +01:00
|
|
|
-- EXPLAIN SubPlans and InitPlans
|
|
|
|
CREATE TABLE src (a int, b int, c int, d int);
|
|
|
|
CREATE TABLE tgt (a int, b int, c int, d int);
|
|
|
|
CREATE TABLE ref (ab int, cd int);
|
|
|
|
EXPLAIN (verbose, costs off)
|
|
|
|
MERGE INTO tgt t
|
|
|
|
USING (SELECT *, (SELECT count(*) FROM ref r
|
|
|
|
WHERE r.ab = s.a + s.b
|
|
|
|
AND r.cd = s.c - s.d) cnt
|
|
|
|
FROM src s) s
|
|
|
|
ON t.a = s.a AND t.b < s.cnt
|
|
|
|
WHEN MATCHED AND t.c > s.cnt THEN
|
|
|
|
UPDATE SET (b, c) = (SELECT s.b, s.cnt);
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------------------
|
|
|
|
Merge on public.tgt t
|
|
|
|
-> Hash Join
|
|
|
|
Output: t.ctid, s.a, s.b, s.c, s.d, s.ctid
|
|
|
|
Hash Cond: (t.a = s.a)
|
|
|
|
Join Filter: (t.b < (SubPlan 1))
|
|
|
|
-> Seq Scan on public.tgt t
|
|
|
|
Output: t.ctid, t.a, t.b
|
|
|
|
-> Hash
|
|
|
|
Output: s.a, s.b, s.c, s.d, s.ctid
|
|
|
|
-> Seq Scan on public.src s
|
|
|
|
Output: s.a, s.b, s.c, s.d, s.ctid
|
|
|
|
SubPlan 1
|
|
|
|
-> Aggregate
|
|
|
|
Output: count(*)
|
|
|
|
-> Seq Scan on public.ref r
|
|
|
|
Output: r.ab, r.cd
|
|
|
|
Filter: ((r.ab = (s.a + s.b)) AND (r.cd = (s.c - s.d)))
|
|
|
|
SubPlan 4
|
|
|
|
-> Aggregate
|
|
|
|
Output: count(*)
|
|
|
|
-> Seq Scan on public.ref r_2
|
|
|
|
Output: r_2.ab, r_2.cd
|
|
|
|
Filter: ((r_2.ab = (s.a + s.b)) AND (r_2.cd = (s.c - s.d)))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
SubPlan 3
|
2024-03-17 11:17:11 +01:00
|
|
|
-> Result
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Output: s.b, (InitPlan 2).col1
|
|
|
|
InitPlan 2
|
2024-03-17 11:17:11 +01:00
|
|
|
-> Aggregate
|
|
|
|
Output: count(*)
|
|
|
|
-> Seq Scan on public.ref r_1
|
|
|
|
Output: r_1.ab, r_1.cd
|
|
|
|
Filter: ((r_1.ab = (s.a + s.b)) AND (r_1.cd = (s.c - s.d)))
|
|
|
|
(32 rows)
|
|
|
|
|
|
|
|
DROP TABLE src, tgt, ref;
|
2022-03-28 16:45:58 +02:00
|
|
|
-- Subqueries
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING v
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = (SELECT count(*) FROM sq_target);
|
|
|
|
SELECT * FROM sq_target WHERE tid = 1;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 3
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING v
|
|
|
|
ON tid = sid
|
|
|
|
WHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN
|
|
|
|
UPDATE SET balance = 42;
|
|
|
|
SELECT * FROM sq_target WHERE tid = 1;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 42
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO sq_target t
|
|
|
|
USING v
|
|
|
|
ON tid = sid AND (SELECT count(*) > 0 FROM sq_target)
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = 42;
|
|
|
|
SELECT * FROM sq_target WHERE tid = 1;
|
|
|
|
tid | balance
|
|
|
|
-----+---------
|
|
|
|
1 | 42
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
ROLLBACK;
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
DROP TABLE sq_target, sq_target_merge_log, sq_source CASCADE;
|
2022-03-28 16:45:58 +02:00
|
|
|
NOTICE: drop cascades to view v
|
|
|
|
CREATE TABLE pa_target (tid integer, balance float, val text)
|
|
|
|
PARTITION BY LIST (tid);
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE part4 PARTITION OF pa_target DEFAULT
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE pa_source (sid integer, delta float);
|
|
|
|
-- insert many rows to the source table
|
|
|
|
INSERT INTO pa_source SELECT id, id * 10 FROM generate_series(1,14) AS id;
|
|
|
|
-- insert a few rows in the target table (odd numbered tid)
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,15,2) AS id;
|
2022-03-28 16:45:58 +02:00
|
|
|
-- try simple MERGE
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET val = val || ' not matched by source';
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
2022-03-28 16:45:58 +02:00
|
|
|
1 | 110 | initial updated by merge
|
|
|
|
2 | 20 | inserted by merge
|
|
|
|
3 | 330 | initial updated by merge
|
|
|
|
4 | 40 | inserted by merge
|
|
|
|
5 | 550 | initial updated by merge
|
|
|
|
6 | 60 | inserted by merge
|
|
|
|
7 | 770 | initial updated by merge
|
|
|
|
8 | 80 | inserted by merge
|
|
|
|
9 | 990 | initial updated by merge
|
|
|
|
10 | 100 | inserted by merge
|
|
|
|
11 | 1210 | initial updated by merge
|
|
|
|
12 | 120 | inserted by merge
|
|
|
|
13 | 1430 | initial updated by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial not matched by source
|
|
|
|
(15 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- same with a constant qual
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid AND tid = 1
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET val = val || ' not matched by source';
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
2022-03-28 16:45:58 +02:00
|
|
|
1 | 110 | initial updated by merge
|
|
|
|
2 | 20 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
3 | 300 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
3 | 30 | inserted by merge
|
|
|
|
4 | 40 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
5 | 500 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
5 | 50 | inserted by merge
|
|
|
|
6 | 60 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
7 | 700 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
7 | 70 | inserted by merge
|
|
|
|
8 | 80 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
9 | 900 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
9 | 90 | inserted by merge
|
|
|
|
10 | 100 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
11 | 1100 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
11 | 110 | inserted by merge
|
|
|
|
12 | 120 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
13 | 1300 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
13 | 130 | inserted by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial not matched by source
|
|
|
|
(21 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- try updating the partition key column
|
|
|
|
BEGIN;
|
2023-02-22 10:39:09 +01:00
|
|
|
CREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET tid = 1, val = val || ' not matched by source';
|
2023-02-22 10:39:09 +01:00
|
|
|
IF FOUND THEN
|
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
END IF;
|
|
|
|
RETURN result;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
SELECT merge_func();
|
|
|
|
merge_func
|
|
|
|
------------
|
2024-03-30 11:00:26 +01:00
|
|
|
15
|
2023-02-22 10:39:09 +01:00
|
|
|
(1 row)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
|
|
|
1 | 1500 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
2 | 110 | initial updated by merge
|
|
|
|
2 | 20 | inserted by merge
|
|
|
|
4 | 330 | initial updated by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
4 | 40 | inserted by merge
|
2022-03-28 16:45:58 +02:00
|
|
|
6 | 550 | initial updated by merge
|
|
|
|
6 | 60 | inserted by merge
|
|
|
|
8 | 770 | initial updated by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
8 | 80 | inserted by merge
|
2022-03-28 16:45:58 +02:00
|
|
|
10 | 990 | initial updated by merge
|
|
|
|
10 | 100 | inserted by merge
|
|
|
|
12 | 1210 | initial updated by merge
|
|
|
|
12 | 120 | inserted by merge
|
|
|
|
14 | 1430 | initial updated by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
(15 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
ROLLBACK;
|
|
|
|
-- update partition key to partition not initially scanned
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid AND t.tid = 1
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
RETURNING merge_action(), t.*;
|
|
|
|
merge_action | tid | balance | val
|
|
|
|
--------------+-----+---------+--------------------------
|
|
|
|
UPDATE | 2 | 110 | initial updated by merge
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT * FROM pa_target ORDER BY tid;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+--------------------------
|
|
|
|
2 | 110 | initial updated by merge
|
|
|
|
3 | 300 | initial
|
|
|
|
5 | 500 | initial
|
|
|
|
7 | 700 | initial
|
|
|
|
9 | 900 | initial
|
|
|
|
11 | 1100 | initial
|
|
|
|
13 | 1300 | initial
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial
|
|
|
|
(8 rows)
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
ROLLBACK;
|
|
|
|
DROP TABLE pa_target CASCADE;
|
|
|
|
-- The target table is partitioned in the same way, but this time by attaching
|
|
|
|
-- partitions which have columns in different order, dropped columns etc.
|
|
|
|
CREATE TABLE pa_target (tid integer, balance float, val text)
|
|
|
|
PARTITION BY LIST (tid);
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE part1 (tid integer, balance float, val text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE part2 (balance float, tid integer, val text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE part3 (tid integer, balance float, val text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE part4 (extraid text, tid integer, balance float, val text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
ALTER TABLE part4 DROP COLUMN extraid;
|
|
|
|
ALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);
|
|
|
|
ALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);
|
|
|
|
ALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);
|
|
|
|
ALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;
|
|
|
|
-- insert a few rows in the target table (odd numbered tid)
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,15,2) AS id;
|
2022-03-28 16:45:58 +02:00
|
|
|
-- try simple MERGE
|
|
|
|
BEGIN;
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
DO $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET val = val || ' not matched by source';
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
RAISE NOTICE 'ROW_COUNT = %', result;
|
|
|
|
END;
|
|
|
|
$$;
|
2024-03-30 11:00:26 +01:00
|
|
|
NOTICE: ROW_COUNT = 15
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
2022-03-28 16:45:58 +02:00
|
|
|
1 | 110 | initial updated by merge
|
|
|
|
2 | 20 | inserted by merge
|
|
|
|
3 | 330 | initial updated by merge
|
|
|
|
4 | 40 | inserted by merge
|
|
|
|
5 | 550 | initial updated by merge
|
|
|
|
6 | 60 | inserted by merge
|
|
|
|
7 | 770 | initial updated by merge
|
|
|
|
8 | 80 | inserted by merge
|
|
|
|
9 | 990 | initial updated by merge
|
|
|
|
10 | 100 | inserted by merge
|
|
|
|
11 | 1210 | initial updated by merge
|
|
|
|
12 | 120 | inserted by merge
|
|
|
|
13 | 1430 | initial updated by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial not matched by source
|
|
|
|
(15 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- same with a constant qual
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid AND tid IN (1, 5)
|
|
|
|
WHEN MATCHED AND tid % 5 = 0 THEN DELETE
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET val = val || ' not matched by source';
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
2022-03-28 16:45:58 +02:00
|
|
|
1 | 110 | initial updated by merge
|
|
|
|
2 | 20 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
3 | 300 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
3 | 30 | inserted by merge
|
|
|
|
4 | 40 | inserted by merge
|
|
|
|
6 | 60 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
7 | 700 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
7 | 70 | inserted by merge
|
|
|
|
8 | 80 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
9 | 900 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
9 | 90 | inserted by merge
|
|
|
|
10 | 100 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
11 | 1100 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
11 | 110 | inserted by merge
|
|
|
|
12 | 120 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
13 | 1300 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
13 | 130 | inserted by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial not matched by source
|
|
|
|
(19 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- try updating the partition key column
|
|
|
|
BEGIN;
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
DO $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET tid = 1, val = val || ' not matched by source';
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
RAISE NOTICE 'ROW_COUNT = %', result;
|
|
|
|
END;
|
|
|
|
$$;
|
2024-03-30 11:00:26 +01:00
|
|
|
NOTICE: ROW_COUNT = 15
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
|
|
|
1 | 1500 | initial not matched by source
|
2022-03-28 16:45:58 +02:00
|
|
|
2 | 110 | initial updated by merge
|
|
|
|
2 | 20 | inserted by merge
|
|
|
|
4 | 330 | initial updated by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
4 | 40 | inserted by merge
|
2022-03-28 16:45:58 +02:00
|
|
|
6 | 550 | initial updated by merge
|
|
|
|
6 | 60 | inserted by merge
|
|
|
|
8 | 770 | initial updated by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
8 | 80 | inserted by merge
|
2022-03-28 16:45:58 +02:00
|
|
|
10 | 990 | initial updated by merge
|
|
|
|
10 | 100 | inserted by merge
|
|
|
|
12 | 1210 | initial updated by merge
|
|
|
|
12 | 120 | inserted by merge
|
|
|
|
14 | 1430 | initial updated by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
(15 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
ROLLBACK;
|
|
|
|
-- as above, but blocked by BEFORE DELETE ROW trigger
|
|
|
|
BEGIN;
|
|
|
|
CREATE FUNCTION trig_fn() RETURNS trigger LANGUAGE plpgsql AS
|
|
|
|
$$ BEGIN RETURN NULL; END; $$;
|
|
|
|
CREATE TRIGGER del_trig BEFORE DELETE ON pa_target
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trig_fn();
|
|
|
|
DO $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET val = val || ' not matched by source';
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
RAISE NOTICE 'ROW_COUNT = %', result;
|
|
|
|
END;
|
|
|
|
$$;
|
2024-03-30 11:00:26 +01:00
|
|
|
NOTICE: ROW_COUNT = 11
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
1 | 100 | initial
|
|
|
|
2 | 20 | inserted by merge
|
|
|
|
3 | 300 | initial
|
|
|
|
4 | 40 | inserted by merge
|
|
|
|
6 | 550 | initial updated by merge
|
|
|
|
6 | 60 | inserted by merge
|
|
|
|
7 | 700 | initial
|
|
|
|
8 | 80 | inserted by merge
|
|
|
|
9 | 900 | initial
|
|
|
|
10 | 100 | inserted by merge
|
|
|
|
12 | 1210 | initial updated by merge
|
|
|
|
12 | 120 | inserted by merge
|
|
|
|
14 | 1430 | initial updated by merge
|
|
|
|
14 | 140 | inserted by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial not matched by source
|
|
|
|
(15 rows)
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
|
|
|
|
ROLLBACK;
|
|
|
|
-- as above, but blocked by BEFORE INSERT ROW trigger
|
|
|
|
BEGIN;
|
|
|
|
CREATE FUNCTION trig_fn() RETURNS trigger LANGUAGE plpgsql AS
|
|
|
|
$$ BEGIN RETURN NULL; END; $$;
|
|
|
|
CREATE TRIGGER ins_trig BEFORE INSERT ON pa_target
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trig_fn();
|
|
|
|
DO $$
|
|
|
|
DECLARE
|
|
|
|
result integer;
|
|
|
|
BEGIN
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
2024-03-30 11:00:26 +01:00
|
|
|
INSERT VALUES (sid, delta, 'inserted by merge')
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN
|
|
|
|
UPDATE SET val = val || ' not matched by source';
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
GET DIAGNOSTICS result := ROW_COUNT;
|
|
|
|
RAISE NOTICE 'ROW_COUNT = %', result;
|
|
|
|
END;
|
|
|
|
$$;
|
2024-03-30 11:00:26 +01:00
|
|
|
NOTICE: ROW_COUNT = 4
|
|
|
|
SELECT * FROM pa_target ORDER BY tid, val;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+-------------------------------
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
6 | 550 | initial updated by merge
|
|
|
|
12 | 1210 | initial updated by merge
|
|
|
|
14 | 1430 | initial updated by merge
|
2024-03-30 11:00:26 +01:00
|
|
|
15 | 1500 | initial not matched by source
|
|
|
|
(4 rows)
|
Fix BEFORE ROW trigger handling in cross-partition MERGE update.
Fix a bug during MERGE if a cross-partition update is attempted on a
partitioned table with a BEFORE DELETE ROW trigger that returns NULL,
to prevent the update. This would cause an error to be thrown, or an
assert failure in an assert-enabled build.
This was an oversight in 9321c79c86, which failed to properly
distinguish a DELETE prevented by a trigger from one prevented by a
concurrent update. Fix by having ExecDelete() return the TM_Result
status to ExecCrossPartitionUpdate(), so that it can distinguish the
two cases, and make ExecCrossPartitionUpdate() return the TM_Result
status to ExecUpdateAct(), so that it can return the correct status
from a concurrent update.
In addition, ensure that the command tag is correctly updated by
having ExecMergeMatched() pass canSetTag to ExecUpdateAct(), rather
than passing false, so that it updates the command tag if it does a
cross-partition update, making this code path in ExecMergeMatched()
consistent with ExecUpdate().
Per bug #18238 from Alexander Lakhin. Back-patch to v15, where MERGE
was introduced.
Dean Rasheed, reviewed by Richard Guo and Jian He.
Discussion: https://postgr.es/m/18238-2f2bdc7f720180b9%40postgresql.org
2023-12-21 13:55:22 +01:00
|
|
|
|
2023-02-22 11:51:34 +01:00
|
|
|
ROLLBACK;
|
|
|
|
-- test RLS enforcement
|
|
|
|
BEGIN;
|
|
|
|
ALTER TABLE pa_target ENABLE ROW LEVEL SECURITY;
|
|
|
|
ALTER TABLE pa_target FORCE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY pa_target_pol ON pa_target USING (tid != 0);
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING pa_source s
|
|
|
|
ON t.tid = s.sid AND t.tid IN (1,2,3,4)
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET tid = tid - 1;
|
|
|
|
ERROR: new row violates row-level security policy for table "pa_target"
|
2022-03-28 16:45:58 +02:00
|
|
|
ROLLBACK;
|
|
|
|
DROP TABLE pa_source;
|
|
|
|
DROP TABLE pa_target CASCADE;
|
|
|
|
-- Sub-partitioning
|
|
|
|
CREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)
|
|
|
|
PARTITION BY RANGE (logts);
|
|
|
|
CREATE TABLE part_m01 PARTITION OF pa_target
|
|
|
|
FOR VALUES FROM ('2017-01-01') TO ('2017-02-01')
|
|
|
|
PARTITION BY LIST (tid);
|
|
|
|
CREATE TABLE part_m01_odd PARTITION OF part_m01
|
2022-09-20 12:38:48 +02:00
|
|
|
FOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE part_m01_even PARTITION OF part_m01
|
2022-09-20 12:38:48 +02:00
|
|
|
FOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE part_m02 PARTITION OF pa_target
|
|
|
|
FOR VALUES FROM ('2017-02-01') TO ('2017-03-01')
|
|
|
|
PARTITION BY LIST (tid);
|
|
|
|
CREATE TABLE part_m02_odd PARTITION OF part_m02
|
2022-09-20 12:38:48 +02:00
|
|
|
FOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE part_m02_even PARTITION OF part_m02
|
2022-09-20 12:38:48 +02:00
|
|
|
FOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE pa_source (sid integer, delta float)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
-- insert many rows to the source table
|
|
|
|
INSERT INTO pa_source SELECT id, id * 10 FROM generate_series(1,14) AS id;
|
|
|
|
-- insert a few rows in the target table (odd numbered tid)
|
|
|
|
INSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;
|
|
|
|
INSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;
|
|
|
|
-- try simple MERGE
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO pa_target t
|
|
|
|
USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s
|
|
|
|
ON t.tid = s.sid
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
|
|
|
|
WHEN NOT MATCHED THEN
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge')
|
|
|
|
RETURNING merge_action(), t.*;
|
|
|
|
merge_action | logts | tid | balance | val
|
|
|
|
--------------+--------------------------+-----+---------+--------------------------
|
|
|
|
UPDATE | Tue Jan 31 00:00:00 2017 | 1 | 110 | initial updated by merge
|
|
|
|
UPDATE | Tue Feb 28 00:00:00 2017 | 2 | 220 | initial updated by merge
|
|
|
|
INSERT | Sun Jan 15 00:00:00 2017 | 3 | 30 | inserted by merge
|
|
|
|
UPDATE | Tue Jan 31 00:00:00 2017 | 4 | 440 | initial updated by merge
|
|
|
|
UPDATE | Tue Feb 28 00:00:00 2017 | 5 | 550 | initial updated by merge
|
|
|
|
INSERT | Sun Jan 15 00:00:00 2017 | 6 | 60 | inserted by merge
|
|
|
|
UPDATE | Tue Jan 31 00:00:00 2017 | 7 | 770 | initial updated by merge
|
|
|
|
UPDATE | Tue Feb 28 00:00:00 2017 | 8 | 880 | initial updated by merge
|
|
|
|
INSERT | Sun Jan 15 00:00:00 2017 | 9 | 90 | inserted by merge
|
|
|
|
(9 rows)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
SELECT * FROM pa_target ORDER BY tid;
|
|
|
|
logts | tid | balance | val
|
|
|
|
--------------------------+-----+---------+--------------------------
|
|
|
|
Tue Jan 31 00:00:00 2017 | 1 | 110 | initial updated by merge
|
|
|
|
Tue Feb 28 00:00:00 2017 | 2 | 220 | initial updated by merge
|
|
|
|
Sun Jan 15 00:00:00 2017 | 3 | 30 | inserted by merge
|
|
|
|
Tue Jan 31 00:00:00 2017 | 4 | 440 | initial updated by merge
|
|
|
|
Tue Feb 28 00:00:00 2017 | 5 | 550 | initial updated by merge
|
|
|
|
Sun Jan 15 00:00:00 2017 | 6 | 60 | inserted by merge
|
|
|
|
Tue Jan 31 00:00:00 2017 | 7 | 770 | initial updated by merge
|
|
|
|
Tue Feb 28 00:00:00 2017 | 8 | 880 | initial updated by merge
|
|
|
|
Sun Jan 15 00:00:00 2017 | 9 | 90 | inserted by merge
|
|
|
|
(9 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
2023-02-20 21:18:22 +01:00
|
|
|
DROP TABLE pa_source;
|
|
|
|
DROP TABLE pa_target CASCADE;
|
|
|
|
-- Partitioned table with primary key
|
|
|
|
CREATE TABLE pa_target (tid integer PRIMARY KEY) PARTITION BY LIST (tid);
|
|
|
|
CREATE TABLE pa_targetp PARTITION OF pa_target DEFAULT;
|
|
|
|
CREATE TABLE pa_source (sid integer);
|
|
|
|
INSERT INTO pa_source VALUES (1), (2);
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------
|
|
|
|
Merge on public.pa_target t
|
|
|
|
Merge on public.pa_targetp t_1
|
|
|
|
-> Hash Left Join
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: s.sid, s.ctid, t_1.tableoid, t_1.ctid
|
2023-02-20 21:18:22 +01:00
|
|
|
Inner Unique: true
|
|
|
|
Hash Cond: (s.sid = t_1.tid)
|
|
|
|
-> Seq Scan on public.pa_source s
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: s.sid, s.ctid
|
2023-02-20 21:18:22 +01:00
|
|
|
-> Hash
|
|
|
|
Output: t_1.tid, t_1.tableoid, t_1.ctid
|
|
|
|
-> Seq Scan on public.pa_targetp t_1
|
|
|
|
Output: t_1.tid, t_1.tableoid, t_1.ctid
|
|
|
|
(12 rows)
|
|
|
|
|
|
|
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
|
|
|
TABLE pa_target;
|
|
|
|
tid
|
|
|
|
-----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
(2 rows)
|
|
|
|
|
2023-03-28 17:36:50 +02:00
|
|
|
-- Partition-less partitioned table
|
|
|
|
-- (the bug we are checking for appeared only if table had partitions before)
|
|
|
|
DROP TABLE pa_targetp;
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
|
|
|
QUERY PLAN
|
|
|
|
--------------------------------------------
|
|
|
|
Merge on public.pa_target t
|
|
|
|
-> Hash Left Join
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: s.sid, s.ctid, t.ctid
|
2023-03-28 17:36:50 +02:00
|
|
|
Inner Unique: true
|
|
|
|
Hash Cond: (s.sid = t.tid)
|
|
|
|
-> Seq Scan on public.pa_source s
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: s.sid, s.ctid
|
2023-03-28 17:36:50 +02:00
|
|
|
-> Hash
|
|
|
|
Output: t.tid, t.ctid
|
|
|
|
-> Result
|
|
|
|
Output: t.tid, t.ctid
|
|
|
|
One-Time Filter: false
|
|
|
|
(12 rows)
|
|
|
|
|
|
|
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
|
|
|
ERROR: no partition of relation "pa_target" found for row
|
|
|
|
DETAIL: Partition key of the failing row contains (tid) = (1).
|
2022-03-28 16:45:58 +02:00
|
|
|
DROP TABLE pa_source;
|
|
|
|
DROP TABLE pa_target CASCADE;
|
|
|
|
-- some complex joins on the source side
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE cj_target (tid integer, balance float, val text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)
|
|
|
|
WITH (autovacuum_enabled=off);
|
|
|
|
CREATE TABLE cj_source2 (sid2 integer, sval text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
INSERT INTO cj_source1 VALUES (1, 10, 100);
|
|
|
|
INSERT INTO cj_source1 VALUES (1, 20, 200);
|
|
|
|
INSERT INTO cj_source1 VALUES (2, 20, 300);
|
|
|
|
INSERT INTO cj_source1 VALUES (3, 10, 400);
|
|
|
|
INSERT INTO cj_source2 VALUES (1, 'initial source2');
|
|
|
|
INSERT INTO cj_source2 VALUES (2, 'initial source2');
|
|
|
|
INSERT INTO cj_source2 VALUES (3, 'initial source2');
|
|
|
|
-- source relation is an unaliased join
|
|
|
|
MERGE INTO cj_target t
|
|
|
|
USING cj_source1 s1
|
|
|
|
INNER JOIN cj_source2 s2 ON sid1 = sid2
|
|
|
|
ON t.tid = sid1
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (sid1, delta, sval);
|
|
|
|
-- try accessing columns from either side of the source join
|
|
|
|
MERGE INTO cj_target t
|
|
|
|
USING cj_source2 s2
|
|
|
|
INNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20
|
|
|
|
ON t.tid = sid1
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (sid2, delta, sval)
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
DELETE;
|
|
|
|
-- some simple expressions in INSERT targetlist
|
|
|
|
MERGE INTO cj_target t
|
|
|
|
USING cj_source2 s2
|
|
|
|
INNER JOIN cj_source1 s1 ON sid1 = sid2
|
|
|
|
ON t.tid = sid1
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (sid2, delta + scat, sval)
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET val = val || ' updated by merge';
|
|
|
|
MERGE INTO cj_target t
|
|
|
|
USING cj_source2 s2
|
|
|
|
INNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20
|
|
|
|
ON t.tid = sid1
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET val = val || ' ' || delta::text;
|
|
|
|
SELECT * FROM cj_target;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+----------------------------------
|
|
|
|
3 | 400 | initial source2 updated by merge
|
|
|
|
1 | 220 | initial source2 200
|
|
|
|
1 | 110 | initial source2 200
|
|
|
|
2 | 320 | initial source2 300
|
|
|
|
(4 rows)
|
|
|
|
|
2023-03-15 16:59:18 +01:00
|
|
|
-- try it with an outer join and PlaceHolderVar
|
|
|
|
MERGE INTO cj_target t
|
|
|
|
USING (SELECT *, 'join input'::text AS phv FROM cj_source1) fj
|
|
|
|
FULL JOIN cj_source2 fj2 ON fj.scat = fj2.sid2 * 10
|
|
|
|
ON t.tid = fj.scat
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT (tid, balance, val) VALUES (fj.scat, fj.delta, fj.phv);
|
|
|
|
SELECT * FROM cj_target;
|
|
|
|
tid | balance | val
|
|
|
|
-----+---------+----------------------------------
|
|
|
|
3 | 400 | initial source2 updated by merge
|
|
|
|
1 | 220 | initial source2 200
|
|
|
|
1 | 110 | initial source2 200
|
|
|
|
2 | 320 | initial source2 300
|
|
|
|
10 | 100 | join input
|
|
|
|
10 | 400 | join input
|
|
|
|
20 | 200 | join input
|
|
|
|
20 | 300 | join input
|
|
|
|
| |
|
|
|
|
(9 rows)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
ALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;
|
|
|
|
ALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;
|
|
|
|
TRUNCATE cj_target;
|
|
|
|
MERGE INTO cj_target t
|
|
|
|
USING cj_source1 s1
|
|
|
|
INNER JOIN cj_source2 s2 ON s1.sid = s2.sid
|
|
|
|
ON t.tid = s1.sid
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (s2.sid, delta, sval);
|
|
|
|
DROP TABLE cj_source2, cj_source1, cj_target;
|
|
|
|
-- Function scans
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE fs_target (a int, b int, c text)
|
|
|
|
WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE INTO fs_target t
|
|
|
|
USING generate_series(1,100,1) AS id
|
|
|
|
ON t.a = id
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET b = b + id
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (id, -1);
|
|
|
|
MERGE INTO fs_target t
|
|
|
|
USING generate_series(1,100,2) AS id
|
|
|
|
ON t.a = id
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
UPDATE SET b = b + id, c = 'updated '|| id.*::text
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
INSERT VALUES (id, -1, 'inserted ' || id.*::text);
|
|
|
|
SELECT count(*) FROM fs_target;
|
|
|
|
count
|
|
|
|
-------
|
|
|
|
100
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
DROP TABLE fs_target;
|
|
|
|
-- SERIALIZABLE test
|
|
|
|
-- handled in isolation tests
|
|
|
|
-- Inheritance-based partitioning
|
|
|
|
CREATE TABLE measurement (
|
|
|
|
city_id int not null,
|
|
|
|
logdate date not null,
|
|
|
|
peaktemp int,
|
|
|
|
unitsales int
|
2022-09-20 12:38:48 +02:00
|
|
|
) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE measurement_y2006m02 (
|
|
|
|
CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
|
2022-09-20 12:38:48 +02:00
|
|
|
) INHERITS (measurement) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE measurement_y2006m03 (
|
|
|
|
CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
|
2022-09-20 12:38:48 +02:00
|
|
|
) INHERITS (measurement) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE TABLE measurement_y2007m01 (
|
|
|
|
filler text,
|
|
|
|
peaktemp int,
|
|
|
|
logdate date not null,
|
|
|
|
city_id int not null,
|
|
|
|
unitsales int
|
|
|
|
CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')
|
2022-09-20 12:38:48 +02:00
|
|
|
) WITH (autovacuum_enabled=off);
|
2022-03-28 16:45:58 +02:00
|
|
|
ALTER TABLE measurement_y2007m01 DROP COLUMN filler;
|
|
|
|
ALTER TABLE measurement_y2007m01 INHERIT measurement;
|
2022-12-09 11:00:01 +01:00
|
|
|
INSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);
|
2022-03-28 16:45:58 +02:00
|
|
|
CREATE OR REPLACE FUNCTION measurement_insert_trigger()
|
|
|
|
RETURNS TRIGGER AS $$
|
|
|
|
BEGIN
|
|
|
|
IF ( NEW.logdate >= DATE '2006-02-01' AND
|
|
|
|
NEW.logdate < DATE '2006-03-01' ) THEN
|
|
|
|
INSERT INTO measurement_y2006m02 VALUES (NEW.*);
|
|
|
|
ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
|
|
|
|
NEW.logdate < DATE '2006-04-01' ) THEN
|
|
|
|
INSERT INTO measurement_y2006m03 VALUES (NEW.*);
|
|
|
|
ELSIF ( NEW.logdate >= DATE '2007-01-01' AND
|
|
|
|
NEW.logdate < DATE '2007-02-01' ) THEN
|
|
|
|
INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)
|
|
|
|
VALUES (NEW.*);
|
|
|
|
ELSE
|
|
|
|
RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!';
|
|
|
|
END IF;
|
|
|
|
RETURN NULL;
|
|
|
|
END;
|
|
|
|
$$ LANGUAGE plpgsql ;
|
|
|
|
CREATE TRIGGER insert_measurement_trigger
|
|
|
|
BEFORE INSERT ON measurement
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
|
|
|
|
INSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);
|
|
|
|
INSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);
|
|
|
|
INSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);
|
|
|
|
INSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);
|
|
|
|
INSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);
|
|
|
|
INSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);
|
|
|
|
SELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;
|
|
|
|
tableoid | city_id | logdate | peaktemp | unitsales
|
|
|
|
----------------------+---------+------------+----------+-----------
|
2022-12-09 11:00:01 +01:00
|
|
|
measurement | 0 | 07-21-2005 | 5 | 15
|
2022-03-28 16:45:58 +02:00
|
|
|
measurement_y2006m02 | 1 | 02-10-2006 | 35 | 10
|
|
|
|
measurement_y2006m02 | 1 | 02-16-2006 | 45 | 20
|
|
|
|
measurement_y2006m03 | 1 | 03-17-2006 | 25 | 10
|
|
|
|
measurement_y2006m03 | 1 | 03-27-2006 | 15 | 40
|
|
|
|
measurement_y2007m01 | 1 | 01-15-2007 | 10 | 10
|
|
|
|
measurement_y2007m01 | 1 | 01-17-2007 | 10 | 10
|
2022-12-09 11:00:01 +01:00
|
|
|
(7 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
2022-09-20 12:38:48 +02:00
|
|
|
CREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);
|
2022-12-09 11:00:01 +01:00
|
|
|
INSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);
|
2022-03-28 16:45:58 +02:00
|
|
|
INSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);
|
|
|
|
INSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);
|
|
|
|
INSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);
|
|
|
|
INSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);
|
|
|
|
INSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);
|
|
|
|
INSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);
|
|
|
|
INSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);
|
2022-12-09 11:00:01 +01:00
|
|
|
BEGIN;
|
|
|
|
MERGE INTO ONLY measurement m
|
|
|
|
USING new_measurement nm ON
|
|
|
|
(m.city_id = nm.city_id and m.logdate=nm.logdate)
|
|
|
|
WHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE
|
|
|
|
SET peaktemp = greatest(m.peaktemp, nm.peaktemp),
|
|
|
|
unitsales = m.unitsales + coalesce(nm.unitsales, 0)
|
|
|
|
WHEN NOT MATCHED THEN INSERT
|
|
|
|
(city_id, logdate, peaktemp, unitsales)
|
|
|
|
VALUES (city_id, logdate, peaktemp, unitsales);
|
|
|
|
SELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;
|
|
|
|
tableoid | city_id | logdate | peaktemp | unitsales
|
|
|
|
----------------------+---------+------------+----------+-----------
|
|
|
|
measurement | 0 | 07-21-2005 | 25 | 35
|
|
|
|
measurement_y2006m02 | 1 | 02-10-2006 | 35 | 10
|
|
|
|
measurement_y2006m02 | 1 | 02-16-2006 | 45 | 20
|
|
|
|
measurement_y2006m02 | 1 | 02-16-2006 | 50 | 10
|
|
|
|
measurement_y2006m03 | 1 | 03-01-2006 | 20 | 10
|
|
|
|
measurement_y2006m03 | 1 | 03-17-2006 | 25 | 10
|
|
|
|
measurement_y2006m03 | 1 | 03-27-2006 | 15 | 40
|
|
|
|
measurement_y2006m03 | 1 | 03-27-2006 | |
|
|
|
|
measurement_y2007m01 | 1 | 01-15-2007 | 5 |
|
|
|
|
measurement_y2007m01 | 1 | 01-15-2007 | 10 | 10
|
|
|
|
measurement_y2007m01 | 1 | 01-16-2007 | 10 | 10
|
|
|
|
measurement_y2007m01 | 1 | 01-17-2007 | 10 | 10
|
|
|
|
measurement_y2007m01 | 1 | 01-17-2007 | |
|
|
|
|
measurement_y2006m02 | 2 | 02-10-2006 | 20 | 20
|
|
|
|
(14 rows)
|
|
|
|
|
|
|
|
ROLLBACK;
|
2022-03-28 16:45:58 +02:00
|
|
|
MERGE into measurement m
|
|
|
|
USING new_measurement nm ON
|
|
|
|
(m.city_id = nm.city_id and m.logdate=nm.logdate)
|
|
|
|
WHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE
|
|
|
|
SET peaktemp = greatest(m.peaktemp, nm.peaktemp),
|
|
|
|
unitsales = m.unitsales + coalesce(nm.unitsales, 0)
|
|
|
|
WHEN NOT MATCHED THEN INSERT
|
|
|
|
(city_id, logdate, peaktemp, unitsales)
|
|
|
|
VALUES (city_id, logdate, peaktemp, unitsales);
|
|
|
|
SELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;
|
|
|
|
tableoid | city_id | logdate | peaktemp | unitsales
|
|
|
|
----------------------+---------+------------+----------+-----------
|
2022-12-09 11:00:01 +01:00
|
|
|
measurement | 0 | 07-21-2005 | 25 | 35
|
2022-03-28 16:45:58 +02:00
|
|
|
measurement_y2006m02 | 1 | 02-10-2006 | 35 | 10
|
|
|
|
measurement_y2006m02 | 1 | 02-16-2006 | 50 | 30
|
|
|
|
measurement_y2006m03 | 1 | 03-01-2006 | 20 | 10
|
|
|
|
measurement_y2006m03 | 1 | 03-17-2006 | 25 | 10
|
|
|
|
measurement_y2007m01 | 1 | 01-15-2007 | 10 | 10
|
|
|
|
measurement_y2007m01 | 1 | 01-16-2007 | 10 | 10
|
|
|
|
measurement_y2006m02 | 2 | 02-10-2006 | 20 | 20
|
2022-12-09 11:00:01 +01:00
|
|
|
(8 rows)
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
MERGE INTO new_measurement nm
|
|
|
|
USING ONLY measurement m ON
|
|
|
|
(nm.city_id = m.city_id and nm.logdate=m.logdate)
|
|
|
|
WHEN MATCHED THEN DELETE;
|
|
|
|
SELECT * FROM new_measurement ORDER BY city_id, logdate;
|
|
|
|
city_id | logdate | peaktemp | unitsales
|
|
|
|
---------+------------+----------+-----------
|
|
|
|
1 | 02-16-2006 | 50 | 10
|
|
|
|
1 | 03-01-2006 | 20 | 10
|
|
|
|
1 | 03-27-2006 | |
|
|
|
|
1 | 01-15-2007 | 5 |
|
|
|
|
1 | 01-16-2007 | 10 | 10
|
|
|
|
1 | 01-17-2007 | |
|
|
|
|
2 | 02-10-2006 | 20 | 20
|
2022-03-28 16:45:58 +02:00
|
|
|
(7 rows)
|
|
|
|
|
2022-12-09 11:00:01 +01:00
|
|
|
ROLLBACK;
|
|
|
|
MERGE INTO new_measurement nm
|
|
|
|
USING measurement m ON
|
|
|
|
(nm.city_id = m.city_id and nm.logdate=m.logdate)
|
|
|
|
WHEN MATCHED THEN DELETE;
|
|
|
|
SELECT * FROM new_measurement ORDER BY city_id, logdate;
|
|
|
|
city_id | logdate | peaktemp | unitsales
|
|
|
|
---------+------------+----------+-----------
|
|
|
|
1 | 03-27-2006 | |
|
|
|
|
1 | 01-17-2007 | |
|
|
|
|
(2 rows)
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
DROP TABLE measurement, new_measurement CASCADE;
|
|
|
|
NOTICE: drop cascades to 3 other objects
|
|
|
|
DETAIL: drop cascades to table measurement_y2006m02
|
|
|
|
drop cascades to table measurement_y2006m03
|
|
|
|
drop cascades to table measurement_y2007m01
|
|
|
|
DROP FUNCTION measurement_insert_trigger();
|
|
|
|
-- prepare
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
DROP TABLE target, target2;
|
|
|
|
DROP TABLE source, source2;
|
|
|
|
DROP FUNCTION merge_trigfunc();
|
2022-03-28 17:10:36 +02:00
|
|
|
DROP USER regress_merge_privs;
|
|
|
|
DROP USER regress_merge_no_privs;
|
2024-02-21 17:18:52 +01:00
|
|
|
DROP USER regress_merge_none;
|