CREATE USER regress_merge_privs; CREATE USER regress_merge_no_privs; CREATE USER regress_merge_none;
DROPTABLEIFEXISTS target; DROPTABLEIFEXISTS source; CREATETABLE target (tid integer, balance integer) WITH (autovacuum_enabled=off); CREATETABLE source (sid integer, delta integer) -- no index WITH (autovacuum_enabled=off); INSERTINTO target VALUES (1, 10); INSERTINTO target VALUES (2, 20); INSERTINTO target VALUES (3, 30); SELECT t.ctid isnotnullas matched, t.*, s.* FROM source s FULL OUTERJOIN target t ON s.sid = t.tid ORDERBY t.tid, s.sid;
ALTERTABLE target OWNER TO regress_merge_privs; ALTERTABLE source OWNER TO regress_merge_privs;
CREATETABLE target2 (tid integer, balance integer) WITH (autovacuum_enabled=off); CREATETABLE source2 (sid integer, delta integer) WITH (autovacuum_enabled=off);
ALTERTABLE target2 OWNER TO regress_merge_no_privs; ALTERTABLE source2 OWNER TO regress_merge_no_privs;
GRANTINSERTON target TO regress_merge_no_privs;
SET SESSION AUTHORIZATION regress_merge_privs;
EXPLAIN (COSTS OFF)
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN DELETE;
-- -- Errors --
MERGE INTO target t RANDOMWORD USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = 0; -- MATCHED/INSERT error
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN INSERTDEFAULTVALUES; -- NOT MATCHED BY SOURCE/INSERT error
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED BY SOURCE THEN INSERTDEFAULTVALUES; -- incorrectly specifying INTO target
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTINTO target DEFAULTVALUES; -- Multiple VALUES clause
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (1,1), (2,2); -- SELECT query for INSERT
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTSELECT (1, 1); -- NOT MATCHED/UPDATE
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN UPDATESET balance = 0; -- NOT MATCHED BY TARGET/UPDATE
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED BY TARGET THEN UPDATESET 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; -- source and target names the same
MERGE INTO target USING target ON tid = tid WHEN MATCHED THEN DO NOTHING; -- used in a CTE without RETURNING WITH foo AS (
MERGE INTO target USING source ON (true) WHEN MATCHED THENDELETE
) SELECT * FROM foo; -- used in COPY without RETURNING
COPY (
MERGE INTO target USING source ON (true) WHEN MATCHED THENDELETE
) TO stdout;
-- unsupported relation types -- materialized view CREATE MATERIALIZED VIEW mv ASSELECT * FROM target;
MERGE INTO mv t USING source s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTDEFAULTVALUES; DROP MATERIALIZED VIEW mv;
-- permissions
SET SESSION AUTHORIZATION regress_merge_none;
MERGE INTO target USING (SELECT1) ONtrue WHEN MATCHED THEN
DO NOTHING;
SET SESSION AUTHORIZATION regress_merge_privs;
MERGE INTO target USING source2 ON target.tid = source2.sid WHEN MATCHED THEN UPDATESET balance = 0;
GRANTINSERTON target TO regress_merge_no_privs; SET SESSION AUTHORIZATION regress_merge_no_privs;
MERGE INTO target USING source2 ON target.tid = source2.sid WHEN MATCHED THEN UPDATESET balance = 0;
GRANTUPDATEON target2 TO regress_merge_privs; SET SESSION AUTHORIZATION regress_merge_privs;
MERGE INTO target2 USING source ON target2.tid = source.sid WHEN MATCHED THEN DELETE;
MERGE INTO target2 USING source ON target2.tid = source.sid WHENNOT MATCHED THEN INSERTDEFAULTVALUES;
-- 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 WHENNOT MATCHED THEN INSERTDEFAULTVALUES;
-- -- initial tests -- -- zero rows in source has no effect
MERGE INTO target USING source ON target.tid = source.sid WHEN MATCHED THEN UPDATESET balance = 0;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET 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 WHENNOT MATCHED THEN INSERTDEFAULTVALUES;
ROLLBACK;
-- insert some non-matching source rows to work from INSERTINTO source VALUES (4, 40); SELECT * FROM source ORDERBY sid; SELECT * FROM target ORDERBY tid;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN
DO NOTHING;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET 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 WHENNOT MATCHED THEN INSERTDEFAULTVALUES; SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- DELETE/INSERT not matched by source/target
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED BY SOURCE THEN DELETE WHENNOT MATCHED BY TARGET THEN INSERTVALUES (s.sid, s.delta)
RETURNING merge_action(), old, new, t.*; SELECT * FROM target ORDERBY tid;
ROLLBACK;
EXPLAIN (COSTS OFF)
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = 0; EXPLAIN (COSTS OFF)
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN DELETE; EXPLAIN (COSTS OFF)
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (4, NULL); DELETEFROM target WHERE tid > 100; ANALYZE target;
-- insert some matching source rows to work from INSERTINTO source VALUES (2, 5); INSERTINTO source VALUES (3, 20); SELECT * FROM source ORDERBY sid; SELECT * FROM target ORDERBY tid;
-- equivalent of an UPDATE join
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = 0; SELECT * FROM target ORDERBY tid;
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 ORDERBY tid;
ROLLBACK;
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN
DO NOTHING; SELECT * FROM target ORDERBY tid;
ROLLBACK;
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (4, NULL); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- duplicate source row causes multiple target row update ERROR INSERTINTO source VALUES (2, 5); SELECT * FROM source ORDERBY sid; SELECT * FROM target ORDERBY tid;
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = 0;
ROLLBACK;
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN DELETE;
ROLLBACK;
-- remove duplicate MATCHED data from source data DELETEFROM source WHERE sid = 2; INSERTINTO source VALUES (2, 5); SELECT * FROM source ORDERBY sid; SELECT * FROM target ORDERBY tid;
-- duplicate source row on INSERT should fail because of target_pkey INSERTINTO source VALUES (4, 40);
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (4, NULL); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- remove duplicate NOT MATCHED data from source data DELETEFROM source WHERE sid = 4; INSERTINTO source VALUES (4, 40); SELECT * FROM source ORDERBY sid; SELECT * FROM target ORDERBY tid;
-- multiple actions
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (4, 4) WHEN MATCHED THEN UPDATESET balance = 0; SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- should be equivalent
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = 0 WHENNOT MATCHED THEN INSERTVALUES (4, 4); SELECT * FROM target ORDERBY tid;
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 UPDATESET balance = t.balance + s.delta; SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- do a simple equivalent of an INSERT SELECT
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- and again with duplicate source rows INSERTINTO source VALUES (5, 50); INSERTINTO 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 WHENNOT MATCHED THEN INSERTVALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- and again with explicitly identified column list
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
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 WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (t.tid, s.delta);
-- and again with a constant ON clause
BEGIN;
MERGE INTO target t USING source AS s ON (SELECTtrue) WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (t.tid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- now the classic UPSERT
BEGIN;
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = t.balance + s.delta WHENNOT MATCHED THEN INSERTVALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
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 WHEN MATCHED THEN UPDATESET balance = t.balance - s.delta;
ROLLBACK;
-- conditional WHEN clause CREATETABLE wq_target (tid integernotnull, balance integerDEFAULT -1) WITH (autovacuum_enabled=off); CREATETABLE wq_source (balance integer, sid integer) WITH (autovacuum_enabled=off);
BEGIN; -- try a simple INSERT with default values first
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHENNOT MATCHED THEN INSERT (tid) VALUES (s.sid); SELECT * FROM wq_target;
ROLLBACK;
-- this time with a FALSE condition
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHENNOT MATCHED ANDFALSETHEN INSERT (tid) VALUES (s.sid); SELECT * FROM wq_target;
-- this time with an actual condition which returns false
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHENNOT MATCHED AND s.balance <> 100THEN INSERT (tid) VALUES (s.sid); SELECT * FROM wq_target;
BEGIN; -- and now with a condition which returns true
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHENNOT MATCHED AND s.balance = 100THEN INSERT (tid) VALUES (s.sid); SELECT * FROM wq_target;
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 WHENNOT MATCHED AND t.balance = 100THEN INSERT (tid) VALUES (s.sid); SELECT * FROM wq_target;
ROLLBACK;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHENNOT MATCHED AND s.balance = 100THEN INSERT (tid) VALUES (s.sid); SELECT * FROM wq_target;
-- 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 WHENNOT MATCHED BY SOURCE AND s.balance = 100THEN DELETE;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHENNOT MATCHED BY SOURCE AND t.balance = 100THEN DELETE;
-- conditions in MATCHED clause can refer to both source and target SELECT * FROM wq_source;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND s.balance = 100THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND t.balance = 100THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
-- check if AND works
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND t.balance = 99AND s.balance > 100THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND t.balance = 99AND s.balance = 100THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
-- check if OR works
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND t.balance = 99OR s.balance > 100THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND t.balance = 199OR s.balance > 100THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
-- 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 UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
ROLLBACK;
-- 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 UPDATESET 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 UPDATESET balance = t.balance + s.balance;
MERGE INTO wq_target t USING wq_source s ON t.tid = s.sid WHEN MATCHED AND t.tableoid >= 0THEN UPDATESET balance = t.balance + s.balance; SELECT * FROM wq_target;
DROPTABLE wq_target, wq_source;
-- test triggers createorreplace function merge_trigfunc () returns trigger
language plpgsql as
$$ DECLARE
line text;
BEGIN SELECTINTO 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);
-- now the classic UPSERT, with a DELETE
BEGIN; UPDATE target SET balance = 0WHERE tid = 3; --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 UPDATESET balance = t.balance - s.delta WHEN MATCHED THEN DELETE WHENNOT MATCHED THEN INSERTVALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- UPSERT with UPDATE/DELETE when not matched by source
BEGIN; DELETEFROM 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 UPDATESET balance = t.balance - s.delta WHEN MATCHED THEN UPDATESET balance = 0 WHENNOT MATCHED THEN INSERTVALUES (s.sid, s.delta) WHENNOT MATCHED BY SOURCE AND tid = 1THEN UPDATESET balance = 0 WHENNOT MATCHED BY SOURCE THEN DELETE
RETURNING merge_action(), old, new, t.*; SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- Test behavior of triggers that turn UPDATE/DELETE into no-ops createorreplace function skip_merge_op() returns trigger
language plpgsql as
$$
BEGIN RETURNNULL;
END;
$$;
SELECT * FROM target full outerjoin source on (sid = tid); createtrigger merge_skip BEFOREINSERTORUPDATEorDELETE ON target FOREACH ROW EXECUTE FUNCTION skip_merge_op();
DO $$ DECLARE
result integer;
BEGIN
MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED AND s.sid = 3THENUPDATESET balance = t.balance + s.delta WHEN MATCHED THENDELETE WHENNOT MATCHED THENINSERTVALUES (sid, delta); IF FOUND THEN
RAISE NOTICE 'Found'; ELSE
RAISE NOTICE 'Not found';
END IF;
GET DIAGNOSTICS result := ROW_COUNT;
RAISE NOTICE 'ROW_COUNT = %', result;
END;
$$; SELECT * FROM target FULL OUTERJOIN source ON (sid = tid); DROPTRIGGER 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 UPDATESET balance = t.balance - s.delta;
END;
$$;
ROLLBACK;
--source constants
BEGIN;
MERGE INTO target t USING (SELECT9AS sid, 57AS delta) AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
--source query
BEGIN;
MERGE INTO target t USING (SELECT sid, delta FROM source WHERE delta > 0) AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
BEGIN;
MERGE INTO target t USING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (s.sid, s.newname); SELECT * FROM target ORDERBY tid;
ROLLBACK;
--self-merge
BEGIN;
MERGE INTO target t1 USING target t2 ON t1.tid = t2.tid WHEN MATCHED THEN UPDATESET balance = t1.balance + t2.balance WHENNOT MATCHED THEN INSERTVALUES (t2.tid, t2.balance); SELECT * FROM target ORDERBY tid;
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 WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
ROLLBACK;
BEGIN;
MERGE INTO target t USING
(SELECT sid, max(delta) AS delta FROM source GROUPBY sid HAVING count(*) = 1 ORDERBY sid ASC) AS s ON t.tid = s.sid WHENNOT MATCHED THEN INSERT (tid, balance) VALUES (s.sid, s.delta); SELECT * FROM target ORDERBY tid;
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 UPDATESET balance = t.balance - p_bal; IF FOUND THEN
GET DIAGNOSTICS result := ROW_COUNT;
END IF; RETURN result;
END;
$$; SELECT merge_func(3, 4); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- PREPARE
BEGIN;
prepare foom as merge into target t using (select1as sid) s on (t.tid = s.sid) when matched thenupdateset balance = 1;
execute foom; SELECT * FROM target ORDERBY tid;
ROLLBACK;
BEGIN;
PREPARE foom2 (integer, integer) AS
MERGE INTO target t USING (SELECT1) s ON t.tid = $1 WHEN MATCHED THEN UPDATESET balance = $2; --EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
execute foom2 (1, 1); SELECT * FROM target ORDERBY tid;
ROLLBACK;
-- subqueries in source relation
CREATETABLE sq_target (tid integerNOTNULL, balance integer) WITH (autovacuum_enabled=off); CREATETABLE sq_source (delta integer, sid integer, balance integerDEFAULT0) WITH (autovacuum_enabled=off);
BEGIN;
MERGE INTO sq_target t USING (SELECT * FROM sq_source) s ON tid = sid WHEN MATCHED AND t.balance > delta THEN UPDATESET balance = t.balance + delta; SELECT * FROM sq_target;
ROLLBACK;
-- try a view CREATE VIEW v ASSELECT * FROM sq_source WHERE sid < 2;
BEGIN;
MERGE INTO sq_target USING v ON tid = sid WHEN MATCHED THEN UPDATESET balance = v.balance + delta; SELECT * FROM sq_target;
ROLLBACK;
-- ambiguous reference to a column
BEGIN;
MERGE INTO sq_target USING v ON tid = sid WHEN MATCHED AND tid >= 2THEN UPDATESET balance = balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE;
ROLLBACK;
BEGIN; INSERTINTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t USING v ON tid = sid WHEN MATCHED AND tid >= 2THEN UPDATESET balance = t.balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE; SELECT * FROM sq_target;
ROLLBACK;
-- CTEs
BEGIN; INSERTINTO 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 WHEN MATCHED AND tid >= 2THEN UPDATESET balance = t.balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE;
ROLLBACK;
-- RETURNING SELECT * FROM sq_source ORDERBY sid; SELECT * FROM sq_target ORDERBY tid;
BEGIN; CREATETABLE merge_actions(action text, abbrev text); INSERTINTO merge_actions VALUES ('INSERT', 'ins'), ('UPDATE', 'upd'), ('DELETE', 'del');
MERGE INTO sq_target t USING sq_source s ON tid = sid WHEN MATCHED AND tid >= 2THEN UPDATESET balance = t.balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE
RETURNING (SELECT abbrev FROM merge_actions WHERE action = merge_action()) AS action,
old.tid AS old_tid, old.balance AS old_balance,
new.tid AS new_tid, new.balance AS new_balance,
(SELECT new.balance - old.balance AS delta_balance), t.*, CASE merge_action() WHEN'INSERT'THEN'Inserted '||t WHEN'UPDATE'THEN'Added '||delta||' to balance' WHEN'DELETE'THEN'Removed '||t
END AS description;
ROLLBACK;
-- error when using merge_action() outside MERGE SELECT merge_action() FROM sq_target; UPDATE sq_target SET balance = balance + 1 RETURNING merge_action();
-- RETURNING in CTEs CREATETABLE sq_target_merge_log (tid integerNOTNULL, last_change text); INSERTINTO 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 >= 2THEN UPDATESET balance = t.balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE
RETURNING merge_action() AS action, old AS old_data, new AS new_data, 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 UPDATESET last_change = description WHENNOT MATCHED THEN INSERTVALUES (m.tid, description)
RETURNING m.*, merge_action() AS log_action, old AS old_log, new AS new_log, l.*
) SELECT * FROM m2; SELECT * FROM sq_target_merge_log ORDERBY tid;
ROLLBACK;
-- COPY (MERGE ... RETURNING) TO ...
BEGIN;
COPY (
MERGE INTO sq_target t USING sq_source s ON tid = sid WHEN MATCHED AND tid >= 2THEN UPDATESET balance = t.balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE
RETURNING merge_action(), old.*, new.*
) TO stdout;
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 sqlAS
$$
MERGE INTO sq_target t USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta) ON tid = v.sid WHEN MATCHED AND tid >= 2THEN UPDATESET balance = t.balance + v.delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid) WHEN MATCHED AND tid < 2THEN 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;
ROLLBACK;
-- SQL SRF with MERGE ... RETURNING
BEGIN; CREATE FUNCTION merge_sq_source_into_sq_target()
RETURNS TABLE (action text, tid int, balance int)
LANGUAGE sqlAS
$$
MERGE INTO sq_target t USING sq_source s ON tid = sid WHEN MATCHED AND tid >= 2THEN UPDATESET balance = t.balance + delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (balance + delta, sid) WHEN MATCHED AND tid < 2THEN DELETE
RETURNING merge_action(), t.*;
$$; SELECT * FROM merge_sq_source_into_sq_target();
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 >= 2THEN UPDATESET balance = t.balance + v.delta WHENNOT MATCHED THEN INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid) WHEN MATCHED AND tid < 2THEN 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;
ROLLBACK;
-- EXPLAIN CREATETABLE ex_mtarget (a int, b int) WITH (autovacuum_enabled=off); CREATETABLE ex_msource (a int, b int) WITH (autovacuum_enabled=off); INSERTINTO ex_mtarget SELECT i, i*10FROM generate_series(1,100,2) i; INSERTINTO ex_msource SELECT i, i*10FROM 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, buffers off) ' ||
query LOOP
ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \S*', '\1: xxx', 'g'); 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 UPDATESET b = t.b + 1');
-- 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 < 10THEN UPDATESET b = t.b + 1');
-- updates + deletes SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHEN MATCHED AND t.a < 10THEN UPDATESET b = t.b + 1 WHEN MATCHED AND t.a >= 10AND t.a <= 20THEN DELETE');
-- only inserts SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHENNOT MATCHED AND s.a < 10THEN INSERTVALUES (a, b)');
-- all three SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHEN MATCHED AND t.a < 10THEN UPDATESET b = t.b + 1 WHEN MATCHED AND t.a >= 30AND t.a <= 40THEN DELETE WHENNOT MATCHED AND s.a < 20THEN INSERTVALUES (a, b)');
-- not matched by source SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHENNOT MATCHED BY SOURCE and t.a < 10THEN DELETE');
-- not matched by source and target SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHENNOT MATCHED BY SOURCE AND t.a < 10THEN DELETE WHENNOT MATCHED BY TARGET AND s.a < 20THEN INSERTVALUES (a, b)');
-- 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 < 10THEN
DO NOTHING');
DROPTABLE ex_msource, ex_mtarget; DROP FUNCTION explain_merge(text);
-- EXPLAIN SubPlans and InitPlans CREATETABLE src (a int, b int, c int, d int); CREATETABLE tgt (a int, b int, c int, d int); CREATETABLE 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 UPDATESET (b, c) = (SELECT s.b, s.cnt);
DROPTABLE src, tgt, ref;
-- Subqueries
BEGIN;
MERGE INTO sq_target t USING v ON tid = sid WHEN MATCHED THEN UPDATESET balance = (SELECT count(*) FROM sq_target); SELECT * FROM sq_target WHERE tid = 1;
ROLLBACK;
BEGIN;
MERGE INTO sq_target t USING v ON tid = sid WHEN MATCHED AND (SELECT count(*) > 0FROM sq_target) THEN UPDATESET balance = 42; SELECT * FROM sq_target WHERE tid = 1;
ROLLBACK;
BEGIN;
MERGE INTO sq_target t USING v ON tid = sid AND (SELECT count(*) > 0FROM sq_target) WHEN MATCHED THEN UPDATESET balance = 42; SELECT * FROM sq_target WHERE tid = 1;
ROLLBACK;
CREATETABLE pa_target (tid integer, balance float, val text)
PARTITION BY LIST (tid);
CREATETABLE part1 PARTITION OF pa_target FORVALUESIN (1,4) WITH (autovacuum_enabled=off); CREATETABLE part2 PARTITION OF pa_target FORVALUESIN (2,5,6) WITH (autovacuum_enabled=off); CREATETABLE part3 PARTITION OF pa_target FORVALUESIN (3,8,9) WITH (autovacuum_enabled=off); CREATETABLE part4 PARTITION OF pa_target DEFAULT WITH (autovacuum_enabled=off);
CREATETABLE pa_source (sid integer, delta float); -- insert many rows to the source table INSERTINTO pa_source SELECT id, id * 10FROM generate_series(1,14) AS id; -- insert a few rows in the target table (odd numbered tid) INSERTINTO pa_target SELECT id, id * 100, 'initial'FROM generate_series(1,15,2) AS id;
-- try simple MERGE
BEGIN;
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET val = val || ' not matched by source'; SELECT * FROM pa_target ORDERBY tid, val;
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 UPDATESET balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET val = val || ' not matched by source'; SELECT * FROM pa_target ORDERBY tid, val;
ROLLBACK;
-- try updating the partition key column
BEGIN; CREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$ DECLARE
result integer;
BEGIN
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET tid = 1, val = val || ' not matched by source'; IF FOUND THEN
GET DIAGNOSTICS result := ROW_COUNT;
END IF; RETURN result;
END;
$$; SELECT merge_func(); SELECT * FROM pa_target ORDERBY tid, val;
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 UPDATESET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
RETURNING merge_action(), old, new, t.*; SELECT * FROM pa_target ORDERBY tid;
ROLLBACK;
-- bug #18871: ExecInitPartitionInfo()'s handling of DO NOTHING actions
BEGIN;
TRUNCATE pa_target;
MERGE INTO pa_target t USING (VALUES (10, 100)) AS s(sid, delta) ON t.tid = s.sid WHENNOT MATCHED THEN INSERTVALUES (1, 10, 'inserted by merge') WHEN MATCHED THEN
DO NOTHING; SELECT * FROM pa_target ORDERBY tid, val;
ROLLBACK;
DROPTABLE 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. CREATETABLE pa_target (tid integer, balance float, val text)
PARTITION BY LIST (tid);
CREATETABLE part1 (tid integer, balance float, val text) WITH (autovacuum_enabled=off); CREATETABLE part2 (balance float, tid integer, val text) WITH (autovacuum_enabled=off); CREATETABLE part3 (tid integer, balance float, val text) WITH (autovacuum_enabled=off); CREATETABLE part4 (extraid text, tid integer, balance float, val text) WITH (autovacuum_enabled=off); ALTERTABLE part4 DROPCOLUMN extraid;
-- insert a few rows in the target table (odd numbered tid) INSERTINTO pa_target SELECT id, id * 100, 'initial'FROM generate_series(1,15,2) AS id;
-- try simple MERGE
BEGIN;
DO $$ DECLARE
result integer;
BEGIN
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET val = val || ' not matched by source';
GET DIAGNOSTICS result := ROW_COUNT;
RAISE NOTICE 'ROW_COUNT = %', result;
END;
$$; SELECT * FROM pa_target ORDERBY tid, val;
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 = 0THENDELETE WHEN MATCHED THEN UPDATESET balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET val = val || ' not matched by source'; SELECT * FROM pa_target ORDERBY tid, val;
ROLLBACK;
-- try updating the partition key column
BEGIN;
DO $$ DECLARE
result integer;
BEGIN
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHEN MATCHED THEN UPDATESET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET tid = 1, val = val || ' not matched by source';
GET DIAGNOSTICS result := ROW_COUNT;
RAISE NOTICE 'ROW_COUNT = %', result;
END;
$$; SELECT * FROM pa_target ORDERBY tid, val;
ROLLBACK;
-- as above, but blocked by BEFORE DELETE ROW trigger
BEGIN; CREATE FUNCTION trig_fn() RETURNS trigger LANGUAGE plpgsql AS
$$ BEGIN RETURNNULL; END; $$; CREATETRIGGER del_trig BEFOREDELETEON pa_target FOREACH 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 UPDATESET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET val = val || ' not matched by source';
GET DIAGNOSTICS result := ROW_COUNT;
RAISE NOTICE 'ROW_COUNT = %', result;
END;
$$; SELECT * FROM pa_target ORDERBY tid, val;
ROLLBACK;
-- as above, but blocked by BEFORE INSERT ROW trigger
BEGIN; CREATE FUNCTION trig_fn() RETURNS trigger LANGUAGE plpgsql AS
$$ BEGIN RETURNNULL; END; $$; CREATETRIGGER ins_trig BEFOREINSERTON pa_target FOREACH 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 UPDATESET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (sid, delta, 'inserted by merge') WHENNOT MATCHED BY SOURCE THEN UPDATESET val = val || ' not matched by source';
GET DIAGNOSTICS result := ROW_COUNT;
RAISE NOTICE 'ROW_COUNT = %', result;
END;
$$; SELECT * FROM pa_target ORDERBY tid, val;
ROLLBACK;
-- test RLS enforcement
BEGIN; ALTERTABLE pa_target ENABLE ROW LEVEL SECURITY; ALTERTABLE 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 UPDATESET tid = tid - 1;
ROLLBACK;
DROPTABLE pa_source; DROPTABLE pa_target CASCADE;
-- Sub-partitioning CREATETABLE pa_target (logts timestamp, tid integer, balance float, val text)
PARTITION BY RANGE (logts);
CREATETABLE part_m01 PARTITION OF pa_target FORVALUESFROM ('2017-01-01') TO ('2017-02-01')
PARTITION BY LIST (tid); CREATETABLE part_m01_odd PARTITION OF part_m01 FORVALUESIN (1,3,5,7,9) WITH (autovacuum_enabled=off); CREATETABLE part_m01_even PARTITION OF part_m01 FORVALUESIN (2,4,6,8) WITH (autovacuum_enabled=off); CREATETABLE part_m02 PARTITION OF pa_target FORVALUESFROM ('2017-02-01') TO ('2017-03-01')
PARTITION BY LIST (tid); CREATETABLE part_m02_odd PARTITION OF part_m02 FORVALUESIN (1,3,5,7,9) WITH (autovacuum_enabled=off); CREATETABLE part_m02_even PARTITION OF part_m02 FORVALUESIN (2,4,6,8) WITH (autovacuum_enabled=off);
CREATETABLE pa_source (sid integer, delta float) WITH (autovacuum_enabled=off); -- insert many rows to the source table INSERTINTO pa_source SELECT id, id * 10FROM generate_series(1,14) AS id; -- insert a few rows in the target table (odd numbered tid) INSERTINTO pa_target SELECT'2017-01-31', id, id * 100, 'initial'FROM generate_series(1,9,3) AS id; INSERTINTO 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 UPDATESET balance = balance + delta, val = val || ' updated by merge' WHENNOT MATCHED THEN INSERTVALUES (slogts::timestamp, sid, delta, 'inserted by merge')
RETURNING merge_action(), old, new, t.*; SELECT * FROM pa_target ORDERBY tid;
ROLLBACK;
DROPTABLE pa_source; DROPTABLE pa_target CASCADE;
-- Partitioned table with primary key
CREATETABLE pa_target (tid integerPRIMARYKEY) PARTITION BY LIST (tid); CREATETABLE pa_targetp PARTITION OF pa_target DEFAULT; CREATETABLE pa_source (sid integer);
INSERTINTO pa_source VALUES (1), (2);
EXPLAIN (VERBOSE, COSTS OFF)
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHENNOT MATCHED THENINSERTVALUES (s.sid);
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHENNOT MATCHED THENINSERTVALUES (s.sid);
TABLE pa_target;
-- Partition-less partitioned table -- (the bug we are checking for appeared only if table had partitions before)
DROPTABLE pa_targetp;
EXPLAIN (VERBOSE, COSTS OFF)
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHENNOT MATCHED THENINSERTVALUES (s.sid);
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid WHENNOT MATCHED THENINSERTVALUES (s.sid);
-- source relation is an unaliased join
MERGE INTO cj_target t USING cj_source1 s1 INNERJOIN cj_source2 s2 ON sid1 = sid2 ON t.tid = sid1 WHENNOT MATCHED THEN INSERTVALUES (sid1, delta, sval);
-- try accessing columns from either side of the source join
MERGE INTO cj_target t USING cj_source2 s2 INNERJOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20 ON t.tid = sid1 WHENNOT MATCHED THEN INSERTVALUES (sid2, delta, sval) WHEN MATCHED THEN DELETE;
-- some simple expressions in INSERT targetlist
MERGE INTO cj_target t USING cj_source2 s2 INNERJOIN cj_source1 s1 ON sid1 = sid2 ON t.tid = sid1 WHENNOT MATCHED THEN INSERTVALUES (sid2, delta + scat, sval) WHEN MATCHED THEN UPDATESET val = val || ' updated by merge';
MERGE INTO cj_target t USING cj_source2 s2 INNERJOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20 ON t.tid = sid1 WHEN MATCHED THEN UPDATESET val = val || ' ' || delta::text;
SELECT * FROM cj_target;
-- 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 WHENNOT MATCHED THEN INSERT (tid, balance, val) VALUES (fj.scat, fj.delta, fj.phv);
SELECT * FROM cj_target;
ALTERTABLE cj_source1 RENAMECOLUMN sid1 TO sid; ALTERTABLE cj_source2 RENAMECOLUMN sid2 TO sid;
TRUNCATE cj_target;
MERGE INTO cj_target t USING cj_source1 s1 INNERJOIN cj_source2 s2 ON s1.sid = s2.sid ON t.tid = s1.sid WHENNOT MATCHED THEN INSERTVALUES (s2.sid, delta, sval);
DROPTABLE cj_source2, cj_source1, cj_target;
-- Function scans CREATETABLE fs_target (a int, b int, c text) WITH (autovacuum_enabled=off);
MERGE INTO fs_target t USING generate_series(1,100,1) AS id ON t.a = id WHEN MATCHED THEN UPDATESET b = b + id WHENNOT MATCHED THEN INSERTVALUES (id, -1);
MERGE INTO fs_target t USING generate_series(1,100,2) AS id ON t.a = id WHEN MATCHED THEN UPDATESET b = b + id, c = 'updated '|| id.*::text WHENNOT MATCHED THEN INSERTVALUES (id, -1, 'inserted ' || id.*::text);
SELECT count(*) FROM fs_target; DROPTABLE fs_target;
-- SERIALIZABLE test -- handled in isolation tests
-- Inheritance-based partitioning CREATETABLE measurement (
city_id intnotnull,
logdate date notnull,
peaktemp int,
unitsales int
) WITH (autovacuum_enabled=off); CREATETABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01'AND logdate < DATE '2006-03-01' )
) INHERITS (measurement) WITH (autovacuum_enabled=off); CREATETABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01'AND logdate < DATE '2006-04-01' )
) INHERITS (measurement) WITH (autovacuum_enabled=off); CREATETABLE measurement_y2007m01 (
filler text,
peaktemp int,
logdate date notnull,
city_id intnotnull,
unitsales int CHECK ( logdate >= DATE '2007-01-01'AND logdate < DATE '2007-02-01')
) WITH (autovacuum_enabled=off); ALTERTABLE measurement_y2007m01 DROPCOLUMN filler; ALTERTABLE measurement_y2007m01 INHERIT measurement; INSERTINTO measurement VALUES (0, '2005-07-21', 5, 15);
CREATEORREPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGERAS $$
BEGIN IF ( NEW.logdate >= DATE '2006-02-01'AND
NEW.logdate < DATE '2006-03-01' ) THEN INSERTINTO measurement_y2006m02 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2006-03-01'AND
NEW.logdate < DATE '2006-04-01' ) THEN INSERTINTO measurement_y2006m03 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2007-01-01'AND
NEW.logdate < DATE '2007-02-01' ) THEN INSERTINTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales) VALUES (NEW.*); ELSE
RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!';
END IF; RETURNNULL;
END;
$$ LANGUAGE plpgsql ; CREATETRIGGER insert_measurement_trigger BEFOREINSERTON measurement FOREACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); INSERTINTO measurement VALUES (1, '2006-02-10', 35, 10); INSERTINTO measurement VALUES (1, '2006-02-16', 45, 20); INSERTINTO measurement VALUES (1, '2006-03-17', 25, 10); INSERTINTO measurement VALUES (1, '2006-03-27', 15, 40); INSERTINTO measurement VALUES (1, '2007-01-15', 10, 10); INSERTINTO measurement VALUES (1, '2007-01-17', 10, 10);
SELECT tableoid::regclass, * FROM measurement ORDERBY city_id, logdate;
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 ISNULLTHENDELETE WHEN MATCHED THENUPDATE SET peaktemp = greatest(m.peaktemp, nm.peaktemp),
unitsales = m.unitsales + coalesce(nm.unitsales, 0) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id, logdate, peaktemp, unitsales);
SELECT tableoid::regclass, * FROM measurement ORDERBY city_id, logdate, peaktemp;
ROLLBACK;
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 ISNULLTHENDELETE WHEN MATCHED THENUPDATE SET peaktemp = greatest(m.peaktemp, nm.peaktemp),
unitsales = m.unitsales + coalesce(nm.unitsales, 0) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id, logdate, peaktemp, unitsales);
SELECT tableoid::regclass, * FROM measurement ORDERBY city_id, logdate;
BEGIN;
MERGE INTO new_measurement nm USING ONLY measurement m ON
(nm.city_id = m.city_id and nm.logdate=m.logdate) WHEN MATCHED THENDELETE;
SELECT * FROM new_measurement ORDERBY city_id, logdate;
ROLLBACK;
MERGE INTO new_measurement nm USING measurement m ON
(nm.city_id = m.city_id and nm.logdate=m.logdate) WHEN MATCHED THENDELETE;
SELECT * FROM new_measurement ORDERBY city_id, logdate;
-- MERGE into inheritance root table DROPTRIGGER insert_measurement_trigger ON measurement; ALTERTABLE measurement ADDCONSTRAINT mcheck CHECK (city_id = 0) NO INHERIT;
EXPLAIN (COSTS OFF)
MERGE INTO measurement m USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON
(m.city_id = nm.city_id and m.logdate=nm.logdate) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id - 1, logdate, 25, 100);
BEGIN;
MERGE INTO measurement m USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON
(m.city_id = nm.city_id and m.logdate=nm.logdate) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id - 1, logdate, 25, 100); SELECT * FROM ONLY measurement ORDERBY city_id, logdate;
ROLLBACK;
ALTERTABLE measurement ENABLE ROW LEVEL SECURITY; ALTERTABLE measurement FORCE ROW LEVEL SECURITY; CREATE POLICY measurement_p ON measurement USING (peaktemp ISNOTNULL);
MERGE INTO measurement m USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON
(m.city_id = nm.city_id and m.logdate=nm.logdate) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id - 1, logdate, NULL, 100); -- should fail
MERGE INTO measurement m USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON
(m.city_id = nm.city_id and m.logdate=nm.logdate) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id - 1, logdate, 25, 100); -- ok SELECT * FROM ONLY measurement ORDERBY city_id, logdate;
MERGE INTO measurement m USING (VALUES (1, '01-18-2007'::date)) nm(city_id, logdate) ON
(m.city_id = nm.city_id and m.logdate=nm.logdate) WHENNOT MATCHED THENINSERT
(city_id, logdate, peaktemp, unitsales) VALUES (city_id - 1, logdate, 25, 200)
RETURNING merge_action(), m.*;
DROPTABLE measurement, new_measurement CASCADE; DROP FUNCTION measurement_insert_trigger();
-- -- test non-strict join clause -- CREATETABLE src (a int, b text); INSERTINTO src VALUES (1, 'src row');
CREATETABLE tgt (a int, b text); INSERTINTO tgt VALUES (NULL, 'tgt row');
MERGE INTO tgt USING src ON tgt.a ISNOTDISTINCTFROM src.a WHEN MATCHED THENUPDATESET a = src.a, b = src.b WHENNOT MATCHED BY SOURCE THENDELETE
RETURNING merge_action(), src.*, tgt.*;
SELECT * FROM tgt;
DROPTABLE src, tgt;
-- -- test for bug #18634 (wrong varnullingrels error) -- CREATETABLE bug18634t (a int, b int, c text); INSERTINTO bug18634t VALUES(1, 10, 'tgt1'), (2, 20, 'tgt2'); CREATE VIEW bug18634v AS SELECT * FROM bug18634t WHEREEXISTS (SELECT1FROM bug18634t);
CREATETABLE bug18634s (a int, b int, c text); INSERTINTO bug18634s VALUES (1, 2, 'src1');
MERGE INTO bug18634v t USING bug18634s s ON s.a = t.a WHEN MATCHED THENUPDATESET b = s.b WHENNOT MATCHED BY SOURCE THENDELETE
RETURNING merge_action(), s.c, t.*;
SELECT * FROM bug18634t;
DROPTABLE bug18634t CASCADE; DROPTABLE bug18634s;
-- prepare
RESET SESSION AUTHORIZATION;
-- try a system catalog
MERGE INTO pg_class c USING (SELECT'pg_depend'::regclass AS oid) AS j ON j.oid = c.oid WHEN MATCHED THEN UPDATESET reltuples = reltuples + 1
RETURNING j.oid;
CREATE VIEW classv ASSELECT * FROM pg_class;
MERGE INTO classv c USING pg_namespace n ON n.oid = c.relnamespace WHEN MATCHED AND c.oid = 'pg_depend'::regclass THEN UPDATESET reltuples = reltuples - 1
RETURNING c.oid;
DROPTABLE target, target2; DROPTABLE source, source2; DROP FUNCTION merge_trigfunc(); DROP USER regress_merge_privs; DROP USER regress_merge_no_privs; DROP USER regress_merge_none;
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.26 Sekunden
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.