merge.sql

application/sql

Filename: merge.sql
Type: application/sql
Part: 0
Message: Re: [HACKERS] MERGE SQL Statement for PG11
/*
 * "MERGE provides a single SQL statement that can conditionally INSERT, UPDATE
 *  or DELETE rows, a task that would otherwise require multiple procedural
 *  language statements."
 *
 *  - SQL MERGE docs from v14 of the patch
 *
 * This test case illustrates just how true this is.
 */

\set VERBOSITY default
\timing off
SET client_min_messages=notice;
DROP TABLE IF EXISTS sq_target;
DROP TABLE IF EXISTS sq_source;
CREATE TABLE sq_target (tid integer NOT NULL, balance integer);
CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0);

-- Create initial state
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);

-------------------
-- Use SQL MERGE --
-------------------

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;

-- Show results:
SELECT * FROM sq_target;

------------------------
-- Equivalent plpgsql --
------------------------

-- Create same initial state as before
TRUNCATE sq_target;
TRUNCATE sq_source;
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);

-- My equivalent procedural code assumes that it doesn't have to guard against
-- a cardinality violation.
--
-- Note that Simon's patch guards against cardinality violations imperfectly,
-- since it only stops a row from being affected a second time.  That's not the
-- same thing as being sure that we won't even think about affecting a row a
-- second time.
--
-- I'm not arguing that Simon needs to do better; the point is that SQL MERGE
-- requires that we care about cardinality violations in that other sense so
-- that there is no ambiguity about any of the UPDATEs or DELETEs affecting
-- more than one row in each individual execution (as well as for the MERGE
-- operation as a whole -- a slightly different thing).
CREATE OR REPLACE FUNCTION simulate_merge() RETURNS void AS
$BODY$
DECLARE
r record;
BEGIN
  -- LOOP acquires snapshot:
  FOR r IN
    SELECT
    delta,
    sid,
    s.balance s_balance,
    tid,
    t.balance t_balance
    FROM sq_source s
    LEFT JOIN sq_target t ON tid = sid
    LOOP
      -- (r.tid IS NOT NULL) = WHEN MATCHED:
      IF (r.tid IS NOT NULL) and r.tid > 2 THEN
        -- This acquires new MVCC snapshot, which could easily differ from
        -- LEFT JOIN's:
        UPDATE sq_target SET balance = r.t_balance + r.delta WHERE tid = r.tid;
        IF FOUND THEN
          CONTINUE;
        END IF;
      END IF;
      -- (r.tid IS NULL) = WHEN NOT MATCHED:
      IF (r.tid IS NULL) THEN
        INSERT INTO sq_target(balance, tid) SELECT r.s_balance + r.delta, r.sid;
        -- (r.tid IS NOT NULL) = WHEN MATCHED:
      END IF;
      IF (r.tid IS NOT NULL) and r.tid < 2 THEN
        -- This acquires new MVCC snapshot, which could easily differ from
        -- either would-be UPDATE's, or LEFT JOIN's:
        DELETE FROM sq_target WHERE tid = r.tid;
      END IF;
    END LOOP;
  END
  $BODY$
  LANGUAGE plpgsql;

-- Execute:
SELECT * FROM simulate_merge();

-- Show results, which should match:
SELECT * FROM sq_target;