Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PostgreSQL - CTE upsert returning modified rows

I've written an 'upsert' query using CTEs that looks something like this:

WITH
  new_data (id, value) AS (
    VALUES (1, 2), (3, 4), ...
  ),
  updated AS (
    UPDATE table t set
      value = t.value + new_data.value
    FROM new_data
    WHERE t.id = new_data.id
    RETURNING t.*
  )
INSERT INTO table (id, value)
  SELECT id, value
  FROM new_data
  WHERE NOT EXISTS (
    SELECT 1 FROM updated WHERE updated.id = new_data.id
  )

However I then need to work with the new values in my application, but this query will not return anything. Adding returning * to the end of the insert will return all the rows that were inserted, but none of the rows that were updated.

So, the question is (how) can I extend this to return the rows that were updated AND the rows that were inserted?

EDIT: of course I could run this followed by a SELECT in a transaction, however I'm curious to see if there's a single-query way.

like image 523
connec Avatar asked Jul 10 '13 16:07

connec


People also ask

Does PostgreSQL support Upsert?

PostgreSQL doesn't have any built-in UPSERT (or MERGE ) facility, and doing it efficiently in the face of concurrent use is very difficult.

How does Upsert work in PostgreSQL?

In relational databases, the term upsert is referred to as merge. The idea is that when you insert a new row into the table, PostgreSQL will update the row if it already exists, otherwise, it will insert the new row. That is why we call the action is upsert (the combination of update or insert).

Does CTE work in Postgres?

In PostgreSQL, the CTE(Common Table Expression) is used as a temporary result set that the user can reference within another SQL statement like SELECT, INSERT, UPDATE or DELETE. CTEs are temporary in the sense that they only exist during the execution of the query.

What is Upsert mode?

Use the ODBC Enterprise stage in upsert mode to insert, update, or delete records from an external data source table. You can also use the stage to insert and then update records or update and then insert records. You can match records based on field names.


1 Answers

Try something like:

WITH
  new_data (id, value) AS (
    VALUES (1, 2), (3, 4), ...
  ),
  updated AS (
    UPDATE table t set
      value = t.value + new_data.value
    FROM new_data
    WHERE t.id = new_data.id
    RETURNING t.*
  ),
  inserted as (
  INSERT INTO table (id, value)
  SELECT id, value
  FROM new_data
  WHERE NOT EXISTS (
    SELECT 1 FROM updated WHERE updated.id = new_data.id
  )
  RETURNING id, value)
SELECT id, value
FROM inserted 
UNION ALL
SELECT id, value
FROM updated 

BTW this query is not a classical Postgres upsert. It will fail, if someone concurrently insert rows while UPDATE table t is going.

like image 77
Ihor Romanchenko Avatar answered Nov 10 '22 04:11

Ihor Romanchenko