ON CONFLICT DO UPDATE (UPSERT) în PostgreSQL

Modificare date și tranzacții PostgreSQL 9.5+ (standard în PostgreSQL 15–17)

Rezolvă atomic conflictele de cheie unică la inserare: ignoră rândul duplicat sau îl actualizează pe loc.

Sintaxă SQL
INSERT INTO tabel (col_unica, val) VALUES (...) ON CONFLICT (col_unica) DO UPDATE SET val = EXCLUDED.val;

Ce face ON CONFLICT DO UPDATE (UPSERT)

Operația de UPSERT (Update or Insert) este implementată în PostgreSQL prin clauza ON CONFLICT atașată comenzii INSERT. Dacă rândul nou încalcă o constrângere UNIQUE sau PRIMARY KEY, PostgreSQL poate fie să ignore inserarea (DO NOTHING), fie să actualizeze rândul existent (DO UPDATE) în mod garantat atomic, fără condiții de cursă (race conditions).

În interiorul clauzei DO UPDATE SET, tabelul virtual special EXCLUDED conține valorile care ar fi fost inserate dacă nu apărea conflictul, iar numele tabelului țintă se referă la valorile deja existente în baza de date.

Clauze, operatori și argumente

Clauză / Operator Descriere
ON CONFLICT (coloane) Specifică coloana sau setul de coloane care au un index unic sau o cheie primară pe care se detectează conflictul.
ON CONFLICT ON CONSTRAINT nume Identifică explicit constrângerea unică după numele ei.
DO NOTHING Ignoră silențios rândurile care ar produce o eroare de cheie duplicată.
DO UPDATE SET col = ... Actualizează rândul existent în caz de conflict.
EXCLUDED.coloana Referință la valoarea propusă spre inserare pentru coloana respectivă.
WHERE conditie (în DO UPDATE) Efectuează actualizarea doar dacă este îndeplinită condiția (altfel lasă rândul neschimbat).

Exemple

Exemplul 1: Actualizarea stocului și a prețului cu ON CONFLICT DO UPDATE și EXCLUDED

Dacă produsul cu sku-ul dat nu există, este inserat; dacă există deja, îi cumulăm stocul și îi actualizăm prețul doar dacă s-a modificat.

INSERT INTO stoc_produse (sku, stoc, pret_unitar, actualizat_la)
VALUES ('SSD-1TB-PRO', 25, 419.00, NOW())
ON CONFLICT (sku) DO UPDATE
SET stoc = stoc_produse.stoc + EXCLUDED.stoc,
    pret_unitar = EXCLUDED.pret_unitar,
    actualizat_la = NOW()
RETURNING sku, stoc, pret_unitar;

Rezultat interogare

     sku     | stoc | pret_unitar 
-------------+------+-------------
 SSD-1TB-PRO |   40 |      419.00
(1 row)

INSERT 0 1

Exemplul 2: Inserare idempotentă cu ON CONFLICT DO NOTHING

Inserăm o listă de abonați la newsletter și ignorăm adresele de e-mail care există deja în tabel, fără ca tranzacția să eșueze.

INSERT INTO abonati_newsletter (email, sursa)
VALUES
    ('alex@exemplu.ro', 'landing_page'),
    ('diana@exemplu.ro', 'checkout')
ON CONFLICT (email) DO NOTHING
RETURNING id, email;

Rezultat interogare

 id  |       email       
-----+-------------------
 308 | diana@exemplu.ro
(1 row)

INSERT 0 1

Greșeli frecvente

  • Folosirea ON CONFLICT (coloana) pe o coloană care nu are o constrângere UNIQUE sau PRIMARY KEY: PostgreSQL va returna eroarea there is no unique or exclusion constraint matching the ON CONFLICT specification.
  • Încercarea de a actualiza același rând existent de două ori în același lot multi-row INSERT ... VALUES (...), (...) ON CONFLICT DO UPDATE, ceea ce provoacă eroarea ON CONFLICT DO UPDATE command cannot affect row a second time.

Comenzi și funcții înrudite

  • INSERT INTO ... RETURNING: Inserează unul sau mai multe rânduri și returnează imediat valorile generate (ID-uri, timestamp-uri) fără o interogare suplimentară.
  • UPDATE ... FROM: Actualizează rândurile unui tabel folosind date dintr-un alt tabel sau dintr-o listă VALUES prin clauza FROM.
  • BEGIN, COMMIT, ROLLBACK și SAVEPOINT: Controlează tranzacțiile ACID și punctele intermediare de salvare (SAVEPOINT) pentru recuperarea din erori.