PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come eseguire l'UPSERT (MERGE, INSERT ... SU AGGIORNAMENTO DUPLICATO) in PostgreSQL?

9.5 e versioni successive:

Supporto per PostgreSQL 9.5 e versioni successive INSERT ... ON CONFLICT (key) DO UPDATE (e ON CONFLICT (key) DO NOTHING ), cioè upsert.

Confronto con ON DUPLICATE KEY UPDATE .

Spiegazione rapida.

Per l'utilizzo, vedere il manuale, in particolare conflict_action clausola nel diagramma sintattico e nel testo esplicativo.

A differenza delle soluzioni per 9.4 e precedenti fornite di seguito, questa funzionalità funziona con più righe in conflitto e non richiede il blocco esclusivo o un ciclo di tentativi.

Il commit che aggiunge la funzionalità è qui e la discussione sul suo sviluppo è qui.

Se utilizzi la 9.5 e non è necessario essere compatibile con le versioni precedenti, puoi interrompere la lettura ora .

9.4 e precedenti:

PostgreSQL non ha alcun UPSERT integrato (o MERGE ) e farlo in modo efficiente di fronte all'uso simultaneo è molto difficile.

Questo articolo discute il problema in modo dettagliato.

In generale devi scegliere tra due opzioni:

  • Operazioni di inserimento/aggiornamento individuali in un ciclo di tentativi; o
  • Blocco della tabella e unione batch

Ciclo di tentativi per riga singola

L'utilizzo di upsert di singole righe in un ciclo di tentativi è l'opzione ragionevole se si desidera che più connessioni tenti contemporaneamente di eseguire gli inserimenti.

La documentazione di PostgreSQL contiene un'utile procedura che ti consentirà di farlo in un ciclo all'interno del database. Protegge dagli aggiornamenti persi e dalle gare di inserimento, 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 trigger o chiavi univoche secondarie causano violazioni univoche.

Questa strategia è molto inefficiente. Quando possibile, dovresti fare la coda al lavoro ed eseguire invece un upsert collettivo come descritto di seguito.

Molti tentativi di soluzione 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 INSERT con successo S; l'altro riceve un errore di chiave duplicata ed esegue un UPDATE invece. Il UPDATE blocchi in attesa di INSERT per eseguire il rollback o eseguire il commit. Quando torna indietro, il UPDATE il ricontrollo delle condizioni corrisponde a zero righe, quindi anche se UPDATE commits non ha effettivamente fatto l'upsert che ti aspettavi. Devi controllare il conteggio delle righe dei risultati e riprovare se necessario.

Anche alcune soluzioni tentate non prendono in considerazione le gare SELECT. Se provi l'ovvio e il 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 degli aggiornamenti. Un altro è dove entrambi UPDATE allo stesso tempo, facendo corrispondere zero righe e continuando. Quindi entrambi eseguono EXISTS test, che avviene prima il INSERT . Entrambi ottengono zero righe, quindi entrambi eseguono INSERT . Uno non riesce con un errore di chiave duplicata.

Questo è il motivo per cui hai bisogno di un nuovo ciclo di tentativi. Potresti pensare di poter prevenire errori di chiave duplicati o aggiornamenti persi con un SQL intelligente, ma non puoi. Devi controllare il conteggio delle righe o gestire gli errori di chiave duplicata (a seconda dell'approccio scelto) e riprovare.

Per favore, non lanciare la tua soluzione per questo. Come con l'accodamento dei messaggi, probabilmente è sbagliato.

Ribaltamento in blocco con lucchetto

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 è molto più efficiente dei singoli upsert di riga e dovrebbe essere preferito ogni volta che è possibile.

In questo caso, in genere segui la seguente procedura:

  • CREATE un TEMPORARY tabella

  • COPY o inserisci 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 un UPDATE ... FROM di record esistenti utilizzando i valori nella tabella temporanea;

  • Fai un INSERT di righe che non esistono già nella tabella di destinazione;

  • COMMIT , rilasciando il blocco.

Ad esempio, per l'esempio fornito nella domanda, utilizzando INSERT multivalore 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

  • Pagina wiki UPSERT
  • UPSERTIsmi in Postgres
  • Inserisci, su aggiornamento duplicato in PostgreSQL?
  • http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
  • Annulla con una transazione
  • SELEZIONA o INSERT in una funzione soggetta a condizioni di gara?
  • SQL MERGE sul wiki di PostgreSQL
  • Il modo più idiomatico per implementare UPSERT in Postgresql al giorno d'oggi

Che ne dici di MERGE ?

MERGE standard SQL in realtà ha una semantica di concorrenza poco definita e non è adatto per l'upserting senza prima bloccare una tabella.

È un'istruzione OLAP davvero utile per la fusione dei dati, ma in realtà non è una soluzione utile per l'upsert sicuro per la concorrenza. Ci sono molti consigli per le persone che usano altri DBMS per usare MERGE per upserts, ma in realtà è sbagliato.

Altri DB:

  • INSERT ... ON DUPLICATE KEY UPDATE in MySQL
  • MERGE da MS SQL Server (ma vedi sopra su MERGE problemi)
  • MERGE da Oracle (ma vedi sopra su MERGE problemi)