Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to upsert in Postgres on conflict on exactly one of 2 columns?

Tags:

sql

postgresql

I have a table that has two columns that are unique, and would like to upsert if the first column (or both columns) has a conflict, but do nothing if only the second column has a conflict. Is this possible?

CREATE TABLE test (
    username    VARCHAR(255) NOT NULL UNIQUE,
    email       VARCHAR(255) NOT NULL UNIQUE,
    status      VARCHAR(127)
);

The following works to check for conflicts on email:

INSERT INTO test(username, email)
    VALUES ('test', '[email protected]')
    ON CONFLICT(email) DO UPDATE SET status='upserted';

But I'd like to do something like this (invalid syntax below):

(INSERT INTO test(username, email)
    VALUES ('test', '[email protected]')
    ON CONFLICT(email) DO UPDATE SET status='upserted')
    ON CONFLICT DO NOTHING;
like image 284
jhhayashi Avatar asked Nov 13 '16 08:11

jhhayashi


People also ask

Does PostgreSQL support upsert?

PostgreSQL lets you either add or modify a record within a table depending on whether the record already exists. This is commonly known as an "upsert" operation (a portmanteau of "insert" and "update").

How does Postgres upsert work?

In PostgreSQL, the UPSERT operation means either UPDATE or INSERT operation. The UPSERT operation allows us to either insert a row or skip the insert operation if a row already exists and update that row instead. Suppose you want to insert bulk data from one table to another table that already has some data.

What does on conflict do PostgreSQL?

ON CONFLICT DO UPDATE updates the existing row that conflicts with the row proposed for insertion as its alternative action.

How do I make unique columns in PostgreSQL?

The syntax for creating a unique constraint using an ALTER TABLE statement in PostgreSQL is: ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (column1, column2, ... column_n); table_name.


1 Answers

Yes, you can do this, but it requires some conditional trickery.

First of all, you can have only one ON CONFLICT clause, but this clause can specify multiple columns with constraints defined on them. In your case that would be ON CONFLICT (username, email). When either or both of the columns trigger a conflict, the conflict_action kicks in.

Secondly, the conflict_action clause should compare values from the candidate row for insertion (referenced by EXCLUDED) against current values and take appropriate action. DO NOTHING will in practice not be possible, but you can assign the old value to the new row so the effect is the same (but the update will happen). Not pretty, but it will look somewhat like this:

INSERT INTO test(username, email)
    VALUES ('test', '[email protected]')
    ON CONFLICT(username, email) DO UPDATE 
        SET status = CASE WHEN username != EXCLUDED.username -- only email offending
                          THEN status                        -- so "do nothing"
                          ELSE 'upserted'                    -- some other action
                     END;
like image 185
Patrick Avatar answered Sep 25 '22 08:09

Patrick