Search code examples
postgresqlupsertpostgresql-9.5

How to find out if an upsert was an update with PostgreSQL 9.5+ UPSERT?


Writable CTEs were considered a solution to UPSERT prior to 9.5 as described in Insert, on duplicate update in PostgreSQL?

It is possible to perform an UPSERT with the information whether it ended up as an UPDATE or an INSERT with the following Writable CTEs idiom:

WITH
    update_cte AS (
        UPDATE t SET v = $1 WHERE id = $2 RETURNING 'updated'::text status
    ),
    insert_cte AS (
        INSERT INTO t(id, v) SELECT $2, $1 WHERE NOT EXISTS
            (SELECT 1 FROM update_cte) RETURNING 'inserted'::text status
    )
 (SELECT status FROM update_cte) UNION (SELECT status FROM insert_cte)

This query will return either "updated" or "inserted", or may (rarely) fail with a constraint violation in as described in https://dba.stackexchange.com/questions/78510/why-is-cte-open-to-lost-updates

Can something similar be achieved using PostgreSQL 9.5+ new "UPSERT" syntax, benefiting from its optimization and avoiding the possible constraint violation?


Solution

  • I believe xmax::text::int > 0 would be the easiest trick:

    so=# DROP TABLE IF EXISTS tab;
    NOTICE:  table "tab" does not exist, skipping
    DROP TABLE
    so=# CREATE TABLE tab(id INT PRIMARY KEY, col text);
    CREATE TABLE
    so=# INSERT INTO tab(id, col) VALUES (1,'a');
    INSERT 0 2
    so=# INSERT INTO tab(id, col)
    VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
    ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
    returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid;
     id | col  |   case   | ctid
    ----+------+----------+-------
      3 | c    | inserted | (0,3)
      4 | d    | inserted | (0,4)
      1 | aaaa | updated  | (0,5)
    (3 rows)
    
    INSERT 0 3
    so=# INSERT INTO tab(id, col)
    VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
    ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
    returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid;
     id | col  |  case   | ctid
    ----+------+---------+-------
      3 | c    | updated | (0,6)
      4 | d    | updated | (0,7)
      1 | aaaa | updated | (0,8)
    (3 rows)
    
    INSERT 0 3