9.5 e successivi:
PostgreSQL 9.5 e supporto più recente INSERT ... ON CONFLICT UPDATE(e ON CONFLICT DO NOTHING), ovvero upsert.
Confronto conON DUPLICATE KEY UPDATE .
Spiegazione veloce .
Per l'utilizzo, consultare il manuale , in particolare la clausola conflitti_azione nel diagramma della sintassi e il testo esplicativo .
A differenza delle soluzioni per 9.4 e precedenti fornite di seguito, questa funzione funziona con più righe in conflitto e non richiede il blocco esclusivo o un ciclo di tentativi.
Il commit che aggiunge la funzione è qui e la discussione sul suo sviluppo è qui .
Se hai 9.5 e non hai bisogno di essere retrocompatibile, puoi smettere di leggere ora .
9.4 e precedenti:
PostgreSQL non ha alcuna funzione UPSERT(o MERGE) integrata e farlo in modo efficiente di fronte a un uso simultaneo è molto difficile.
Questo articolo discute il problema con dettagli utili .
In generale è necessario scegliere tra due opzioni:
- Operazioni individuali di inserimento / aggiornamento in un ciclo di tentativi; o
- Blocco della tabella e unione in batch
Ciclo di tentativi per riga singola
L'uso di upsert di riga individuali in un ciclo di tentativi è l'opzione ragionevole se si desidera che molte connessioni provino contemporaneamente a eseguire inserimenti.
La documentazione di PostgreSQL contiene una procedura utile che ti permetterà di farlo in un ciclo all'interno del database . Protegge da aggiornamenti persi e inserisce gare, a differenza della maggior parte delle soluzioni ingenue. Funzionerà solo in READ COMMITTEDmodalità ed è sicuro solo se è l'unica cosa che fai nella transazione, però. La funzione non funzionerà correttamente se i trigger o i tasti univoci secondari causano violazioni univoche.
Questa strategia è molto inefficiente. Ogniqualvolta pratico, dovresti fare la fila per il lavoro e fare un upsert in blocco come descritto di seguito.
Molte tentate soluzioni a questo problema non prendono in considerazione i rollback, quindi si traducono in aggiornamenti incompleti. Due transazioni corrono l'una con l'altra; uno di loro è riuscito INSERT; l'altro riceve un errore chiave duplicato e fa UPDATEinvece un errore . I UPDATEblocchi in attesa del INSERTrollback o del commit. Quando esegue il rollback, la UPDATEcondizione di ricontrollo corrisponde a zero righe, quindi anche se i UPDATEcommit non ha effettivamente effettuato l'upert previsto. Devi controllare il conteggio delle righe dei risultati e riprovare dove necessario.
Alcune soluzioni tentate non riescono anche a considerare le gare SELECT. Se provi l'ovvio e semplice:
-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.
BEGIN;
UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;
-- Remember, this is WRONG. Do NOT COPY IT.
INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);
COMMIT;
quindi quando due vengono eseguiti contemporaneamente ci sono diverse modalità di errore. Uno è il problema già discusso con un nuovo controllo di aggiornamento. Un altro è dove entrambi UPDATEallo stesso tempo, abbinando zero righe e continuando. Quindi entrambi fanno il EXISTStest, che accade prima del INSERT. Entrambi ottengono zero righe, quindi entrambi fanno INSERT. Uno fallisce con un errore chiave duplicato.
Questo è il motivo per cui è necessario un ciclo di riprovare. Potresti pensare che puoi prevenire errori di chiave duplicati o perdere gli aggiornamenti con SQL intelligente, ma non puoi. È necessario controllare il conteggio delle righe o gestire errori di chiave duplicati (a seconda dell'approccio scelto) e riprovare.
Per favore non creare la tua soluzione per questo. Come con l'accodamento dei messaggi, probabilmente è sbagliato.
Bulk upert con blocco
A volte si desidera eseguire un upsert in blocco, in cui si dispone di un nuovo set di dati che si desidera unire in un set di dati esistente precedente. Questo è di gran lunga più efficiente rispetto ai singoli aumenti di riga e dovrebbe essere preferito ogni volta che è pratico.
In questo caso, in genere si segue la seguente procedura:
CREATEun TEMPORARYtavolo
COPY o inserire in blocco i nuovi dati nella tabella temporanea
LOCKla tabella di destinazione IN EXCLUSIVE MODE. Ciò consente ad altre transazioni di SELECT, ma non apportare modifiche alla tabella.
Esegui uno UPDATE ... FROMdei record esistenti utilizzando i valori nella tabella temporanea;
Esegui una INSERTdelle righe che non esistono già nella tabella di destinazione;
COMMIT, rilasciando la serratura.
Ad esempio, per l'esempio fornito nella domanda, utilizzare il multi-valore INSERTper popolare la tabella temporanea:
BEGIN;
CREATE TEMPORARY TABLE newvals(id integer, somedata text);
INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');
LOCK TABLE testtable IN EXCLUSIVE MODE;
UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;
INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;
COMMIT;
Lettura correlata
Che dire MERGE?
Lo standard SQL in MERGErealtà ha una semantica della concorrenza scarsamente definita e non è adatto per l'uperting senza prima bloccare una tabella.
È un'istruzione OLAP davvero utile per l'unione dei dati, ma in realtà non è una soluzione utile per upsert sicuri per la concorrenza. Ci sono molti consigli per le persone che usano altri DBMS da utilizzare MERGEper gli upsert, ma in realtà è sbagliato.
Altri DB: