Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return the old value of a column in a postgres INSERT ON CONFLICT DO UPDATE?

Tags:

sql

postgresql

I am attempting to run an "upsert" in postgres like:

INSERT INTO my_table (
    id, -- unique key
    name,
    hash
) VALUES (
    '4b544dea-b355-463c-8fba-40c36ac7cb0c',
    'example',
    '0x0123456789'
) ON CONFLICT (
    id
) DO UPDATE SET
    name = 'example',
    hash = '0x0123456789'
RETURNING
    OLD.hash;

I would like to return the previous value of the hash column (the example above is not a valid query due to OLD not being a valid table alias). Importantly, I am trying to find a method that does this in a way that won't cause conflicts under load. Is this even possible? Or is the only solution to do a read-before-write in a transaction?

like image 347
Burg Avatar asked Sep 05 '18 18:09

Burg


People also ask

What is needed for an insert on conflict update to work?

You must have INSERT privilege on a table in order to insert into it. If ON CONFLICT DO UPDATE is present, UPDATE privilege on the table is also required. If a column list is specified, you only need INSERT privilege on the listed columns.

What does Postgres return on insert?

PostgreSQL used the OID internally as a primary key for its system tables. Typically, the INSERT statement returns OID with value 0. The count is the number of rows that the INSERT statement inserted successfully.

How do I change the value of a column in PostgreSQL?

First, specify the name of the table that you want to update data after the UPDATE keyword. Second, specify columns and their new values after SET keyword. The columns that do not appear in the SET clause retain their original values. Third, determine which rows to update in the condition of the WHERE clause.

What is insert on conflict?

The INSERT ON CONFLICT statement allows you to update an existing row that contains a primary key when you execute the INSERT statement to insert a new row that contains the same primary key. This feature is also known as UPSERT or INSERT OVERWRITE. It is similar to the REPLACE INTO statement of MySQL.


2 Answers

I did end up finding a workaround, though it doesn't strictly guarantee atomicity, but in general could be a good strategy depending on your use case.

INSERT INTO my_table (
    id, -- unique key
    name,
    hash
) VALUES (
    '4b544dea-b355-463c-8fba-40c36ac7cb0c',
    'example',
    '0x0123456789'
) ON CONFLICT (
    id
) DO UPDATE SET
    name = 'example',
    hash = '0x0123456789'
RETURNING
    name,
    hash, -- will be the new hash
    (SELECT hash FROM my_table WHERE my_table.id = '4b544dea-b355-463c-8fba-40c36ac7cb0c') -- will be the old hash
    ;
like image 135
Burg Avatar answered Nov 14 '22 22:11

Burg


Another possible answer, if you are willing to update the schema, would be to store the previous value in another column:

ALTER table my_table add column prev_hash text;

INSERT INTO my_table (
    id, -- unique key
    hash
) VALUES (
    1,
    'a'
) ON CONFLICT (
    id
) DO UPDATE SET
    hash = excluded.hash,
    prev_hash = my_table.hash
RETURNING
    id,
    hash,      -- will be the new hash
    prev_hash  -- will be the old hash
like image 22
Scott B Avatar answered Nov 14 '22 22:11

Scott B