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 COMMITTED
modalità 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 UPDATE
invece un errore . I UPDATE
blocchi in attesa del INSERT
rollback o del commit. Quando esegue il rollback, la UPDATE
condizione di ricontrollo corrisponde a zero righe, quindi anche se i UPDATE
commit 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 UPDATE
allo stesso tempo, abbinando zero righe e continuando. Quindi entrambi fanno il EXISTS
test, 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:
CREATE
un TEMPORARY
tavolo
COPY
o inserire in blocco i nuovi dati nella tabella temporanea
LOCK
la tabella di destinazione IN EXCLUSIVE MODE
. Ciò consente ad altre transazioni di SELECT
, ma non apportare modifiche alla tabella.
Esegui uno UPDATE ... FROM
dei record esistenti utilizzando i valori nella tabella temporanea;
Esegui una INSERT
delle 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 INSERT
per 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 MERGE
realtà 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 MERGE
per gli upsert, ma in realtà è sbagliato.
Altri DB: