Ce face INSERT ... ON CONFLICT (upsert)
Un „upsert” este un INSERT care știe ce să facă atunci când rândul există deja. Clauza ON CONFLICT se activează când inserarea ar încălca o constrângere PRIMARY KEY sau UNIQUE. În loc de eroare, SQLite face ce scrii după DO: DO UPDATE SET ... modifică rândul existent, iar DO NOTHING ignoră rândul nou.
În DO UPDATE, numele de coloane simple se referă la rândul care există deja în tabel, iar excluded.coloana este valoarea pe care ai încercat să o inserezi. Așa poți aduna cantități, păstra cea mai nouă dată sau număra vizite. Un WHERE după SET face actualizarea doar dacă o condiție este adevărată.
Sintaxa este aceeași ca în PostgreSQL. Din SQLite 3.35.0 poți scrie mai multe clauze ON CONFLICT în aceeași comandă (pentru constrângeri diferite), iar la ultima clauză coloana țintă poate lipsi. Spre deosebire de INSERT OR REPLACE, upsert-ul nu șterge rândul vechi: coloanele pe care nu le pomenești în SET rămân neschimbate.
Parametri
| Parametru | Descriere |
|---|---|
(coloana_unica) |
Coloana (sau coloanele) cu PRIMARY KEY sau UNIQUE care poate produce conflictul. Obligatorie pentru DO UPDATE, cu excepția ultimei clauze din 3.35.0. |
DO UPDATE SET ... |
Modifică rândul existent. excluded.x este valoarea din rândul care nu a putut fi inserat. |
DO NOTHING |
Rândul nou este ignorat, fără eroare. |
WHERE (după SET) |
Opțional: actualizarea se face doar pentru rândurile care îndeplinesc condiția. |
Exemple
Exemplele de pe această pagină pornesc de la datele de mai jos. Rulează mai întâi acest cod, apoi exemplul.
CREATE TABLE stoc (
produs TEXT PRIMARY KEY,
cantitate INTEGER NOT NULL,
actualizat TEXT
);
INSERT INTO stoc VALUES
('mere', 10, '2026-10-20'),
('pere', 4, '2026-10-20');
Exemplul 1: Adună cantitatea dacă produsul există deja
„mere” exista, deci cantitatea devine 10 + 5 = 15 și data se schimbă. „prune” nu exista, deci a fost inserat normal. cantitate este valoarea din tabel, excluded.cantitate este valoarea nouă.
INSERT INTO stoc VALUES ('mere', 5, '2026-10-25'), ('prune', 7, '2026-10-25')
ON CONFLICT (produs) DO UPDATE SET
cantitate = cantitate + excluded.cantitate,
actualizat = excluded.actualizat;
SELECT * FROM stoc ORDER BY produs;
Rezultat
╭────────┬───────────┬────────────╮
│ produs │ cantitate │ actualizat │
╞════════╪═══════════╪════════════╡
│ mere │ 15 │ 2026-10-25 │
│ pere │ 4 │ 2026-10-20 │
│ prune │ 7 │ 2026-10-25 │
╰────────┴───────────┴────────────╯
Exemplul 2: DO NOTHING: păstrează rândul existent
Rândul pentru „pere” există, așa că inserarea este ignorată fără eroare. changes() arată câte rânduri a modificat ultima comandă: 0.
INSERT INTO stoc VALUES ('pere', 99, '2026-10-25')
ON CONFLICT DO NOTHING;
SELECT changes() AS modificate;
SELECT * FROM stoc ORDER BY produs;
Rezultat
╭────────────╮
│ modificate │
╞════════════╡
│ 0 │
╰────────────╯
╭────────┬───────────┬────────────╮
│ produs │ cantitate │ actualizat │
╞════════╪═══════════╪════════════╡
│ mere │ 10 │ 2026-10-20 │
│ pere │ 4 │ 2026-10-20 │
╰────────┴───────────┴────────────╯
Exemplul 3: Contor de vizite și actualizare condiționată
Primul tabel numără vizitele pe pagină: prima inserare creează rândul, următoarele cresc numărul. În al doilea caz, WHERE face ca data să fie înlocuită doar dacă cea nouă este mai recentă, deci rândul pentru „mere” rămâne cu data din 20 octombrie.
CREATE TABLE vizite (pagina TEXT PRIMARY KEY, numar INTEGER NOT NULL);
INSERT INTO vizite VALUES ('/acasa', 1) ON CONFLICT (pagina) DO UPDATE SET numar = numar + 1;
INSERT INTO vizite VALUES ('/acasa', 1) ON CONFLICT (pagina) DO UPDATE SET numar = numar + 1;
INSERT INTO vizite VALUES ('/contact', 1) ON CONFLICT (pagina) DO UPDATE SET numar = numar + 1;
INSERT INTO vizite VALUES ('/acasa', 1) ON CONFLICT (pagina) DO UPDATE SET numar = numar + 1;
SELECT * FROM vizite ORDER BY pagina;
INSERT INTO stoc VALUES ('mere', 3, '2026-10-01')
ON CONFLICT (produs) DO UPDATE SET
cantitate = excluded.cantitate,
actualizat = excluded.actualizat
WHERE excluded.actualizat > stoc.actualizat;
SELECT * FROM stoc WHERE produs = 'mere';
Rezultat
╭──────────┬───────╮
│ pagina │ numar │
╞══════════╪═══════╡
│ /acasa │ 3 │
│ /contact │ 1 │
╰──────────┴───────╯
╭────────┬───────────┬────────────╮
│ produs │ cantitate │ actualizat │
╞════════╪═══════════╪════════════╡
│ mere │ 10 │ 2026-10-20 │
╰────────┴───────────┴────────────╯
Exemplul 4: Coloana din ON CONFLICT trebuie să fie unică
Aici b nu are UNIQUE și nici PRIMARY KEY, așa că SQLite refuză comanda încă de la citire.
CREATE TABLE t (a INTEGER, b INTEGER);
INSERT INTO t VALUES (1, 2) ON CONFLICT (b) DO NOTHING;
Rezultat (eroare)
Parse error near line 2: ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint
Greșeli frecvente
- Folosești
INSERT OR REPLACEcând vrei de fapt un upsert.REPLACEșterge rândul vechi și inserează unul nou: coloanele pe care nu le dai devinNULLsau valoarea implicită, iar declanșatorii de ștergere pot rula. - Scrii
INSERT INTO t SELECT ... FROM x ON CONFLICT ...fărăWHEREînSELECT. Parserul poate confundaONcu condiția unuiJOIN; documentația cere să adaugi măcarWHERE trueînSELECT. - Uiți că un nume simplu de coloană din
SETînseamnă valoarea veche.SET cantitate = cantitatenu schimbă nimic; valoarea nouă esteexcluded.cantitate.
Comenzi înrudite
INSERT OR IGNORE / OR REPLACE: Alege ce face SQLite când un INSERT sau UPDATE încalcă o constrângere: oprește comanda, sare peste rând sau înlocuiește rândul vechi.RETURNING: Face ca INSERT, UPDATE sau DELETE să returneze rândurile modificate, de exemplu id-ul generat, fără un SELECT separat.CREATE TRIGGER: Rulează automat instrucțiuni SQL când un rând este inserat, modificat sau șters: jurnal de modificări, validări, câmpuri calculate.CREATE TABLE: Creează un tabel nou. În SQLite tipul coloanei este opțional și doar sugerează cum se salvează valorile.