come emulare "insert ignore" e "on duplicate key update" (sql merge) con postgresql?


142

Alcuni server SQL hanno una funzione in cui INSERTviene saltato se violerebbe un vincolo di chiave primario / univoco. Ad esempio, MySQL ha INSERT IGNORE.

Qual è il modo migliore per emulare INSERT IGNOREe ON DUPLICATE KEY UPDATEcon PostgreSQL?




6
a partire dal 9.5, è possibile nativamente: stackoverflow.com/a/34639631/4418
warren

Emulazione di MySQL: ON DUPLICATE KEY UPDATEsu PgSQL 9.5 è ancora in qualche modo impossibile, perché l' ON CLAUSEequivalente di PgSQL richiede di fornire il nome del vincolo, mentre MySQL potrebbe catturare qualsiasi vincolo senza la necessità di definirlo. Questo mi impedisce di "emulare" questa funzione senza riscrivere le query.
NeverEndingQueue,

Risposte:


35

Prova a fare un AGGIORNAMENTO. Se non modifica alcuna riga significa che non esiste, quindi esegui un inserimento. Ovviamente, lo fai all'interno di una transazione.

Ovviamente puoi racchiuderlo in una funzione se non vuoi mettere il codice extra sul lato client. Hai anche bisogno di un ciclo per la rarissima condizione di razza in quel pensiero.

C'è un esempio di questo nella documentazione: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , esempio 40-2 proprio in fondo.

Di solito è il modo più semplice. Puoi fare un po 'di magia con le regole, ma probabilmente sarà molto più disordinato. Consiglierei l'approccio avvolgente in questo senso ogni giorno.

Funziona con valori a riga singola o a poche righe. Se hai a che fare con grandi quantità di righe, ad esempio da una sottoquery, è meglio suddividerla in due query, una per INSERT e una per UPDATE (ovviamente come join / subselect appropriato - non è necessario scrivere il tuo principale filtro due volte)


4
"Se hai a che fare con grandi quantità di righe" è esattamente il mio caso. Voglio aggiornare / inserire file di massa e con mysql posso farlo con UNA sola query senza alcun ciclo. Ora mi chiedo se ciò sia possibile anche con postgresql: utilizzare una sola query per aggiornare in blocco O inserire. Dici: "è meglio dividerlo in due query, una per INSERT e una per UPDATE", ma come posso fare un inserimento che non genera errori su chiavi duplicate? (es. "INSERISCI IGNORA")
gpilotino,

4
Magnus intendeva utilizzare una query come questa: "avvia transazione; crea una tabella temporanea tabella_ temporanea come selezionare * dal test dove falso; copia tabella_modifica da 'data_file.csv'; blocco tabella test; aggiorna i dati del set di test = tabella_data.data da tabella_modifica dove test.id = temporary_table.id; inserisci in test select * da temporary_table dove id non è presente (seleziona id da test) come "
Tometzky,

25
Aggiornamento: con PostgreSQL 9.5 questo è ora semplice come INSERT ... ON CONFLICT DO NOTHING;. Vedi anche risposta stackoverflow.com/a/34639631/2091700 .
Alphaaa,

Importante, lo standard SQL nonMERGE è un upsert sicuro di concorrenza, a meno che non si prenda per primo. Le persone lo usano in questo modo, ma è sbagliato. LOCK TABLE
Craig Ringer,

1
Con la v9.5 ora è una funzionalità "nativa", quindi controlla il commento di @Alphaaa (pubblicizza solo il commento che pubblicizza la risposta)
Camilo Delvasto

179

Con PostgreSQL 9.5, questa è ora funzionalità nativa (come MySQL ha avuto per diversi anni):

INSERISCI ... A CONFLITTO NON FARE NULLA / AGGIORNAMENTO ("UPSERT")

9.5 offre supporto per le operazioni "UPSERT". INSERT è esteso per accettare una clausola ON CONFLICT DO UPDATE / IGNORE. Questa clausola specifica un'azione alternativa da intraprendere in caso di una potenziale violazione duplicata.

...

Ulteriore esempio di nuova sintassi:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

100

Modifica: nel caso in cui tu abbia perso la risposta di Warren , PG9.5 ora ha questo nativamente; tempo di aggiornare!


Basandosi sulla risposta di Bill Karwin, per chiarire come sarebbe un approccio basato su regole (trasferimento da un altro schema nello stesso DB e con una chiave primaria multi-colonna):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Nota: la regola si applica a tutte le INSERToperazioni fino a quando la regola non viene eliminata, quindi non del tutto ad hoc.


@sema intendi se another_schema.my_tablecontiene duplicati in base ai vincoli di my_table?
EoghanM,

2
@EoghanM Ho testato la regola in postgresql 9.3 e potrei ancora inserire duplicati con istruzioni di inserimento di più righe come ad esempio INSERT INTO "my_table" (a, b), (a, b); (Supponendo che la riga (a, b) non esistesse ancora in "my_table".)
sema

@sema, gotcha: ciò significa che la regola viene eseguita all'inizio su tutti i dati da inserire e non rieseguita dopo l'inserimento di ogni riga. Un approccio sarebbe quello di inserire prima i tuoi dati in un'altra tabella temporanea che non ha alcun vincolo e poi fareINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM,

@EoghanM Un altro approccio è quello di allentare temporaneamente i vincoli dei duplicati e di accettare i duplicati durante l'inserimento, ma successivamente rimuovere i duplicati conDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema,

Sto riscontrando il problema descritto da @sema. Se eseguo un inserimento (a, b), (a, b), viene generato un errore. C'è un modo per sopprimere gli errori, anche in questo caso?
Diogo Melo,

36

Per quelli di voi che hanno Postgres 9.5 o versioni successive, la nuova sintassi ON CONFLICT DO NOTHING dovrebbe funzionare:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Per quelli di noi che hanno una versione precedente, questo join corretto funzionerà invece:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

Il secondo approccio non funziona quando si effettua un inserimento di grandi dimensioni in un ambiente concorrente. Si ottiene Unique violation: 7 ERROR: duplicate key value violates unique constraintquando è target_tablestata inserita un'altra riga durante l'esecuzione di questa query, se le loro chiavi, in effetti, si duplicano. Credo che il blocco target_tableaiuterà, ma ovviamente la concorrenza ne risentirà.
G. Kashtanov,

1
ON CONFLICT (field_one) DO NOTHINGè la parte migliore della risposta.
Abel Callejo,

24

Per far sì che l' inserto ignori la logica, puoi fare qualcosa come sotto. Ho trovato semplicemente l'inserimento da un'istruzione selezionata di valori letterali ha funzionato meglio, quindi è possibile mascherare le chiavi duplicate con una clausola NOT EXISTS. Per ottenere l'aggiornamento su una logica duplicata sospetto che sarebbe necessario un ciclo pl / pgsql.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

Cosa succede se tmp contiene una riga duplicata, cosa che può accadere?
Henley Chiu,

Puoi sempre selezionare con la parola chiave distinta.
Keyo

5
Proprio come una FYI, il trucco "DOVE NON ESISTE" non funziona su più transazioni perché le diverse transazioni non possono vedere i dati appena aggiunti dalle altre transazioni.
Dave Johansen,

21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

Qual è l'impatto di più transazioni che provano tutte a fare la stessa cosa? È possibile che tra dove non esiste l'esecuzione e l'inserimento che esegue un'altra transazione inserisca una riga? E se Postgres può impedirlo, Postgres non sta introducendo un punto di sincronizzazione tra tutte quelle transazioni quando colpiscono questo?
Καrτhικ,

Questo non funziona con più transazioni, perché i dati appena aggiunti non sono visibili alle altre transazioni.
Dave Johansen,

12

Sembra che PostgreSQL supporti un oggetto schema chiamato regola .

http://www.postgresql.org/docs/current/static/rules-update.html

È possibile creare una regola ON INSERTper una determinata tabella, facendola fare NOTHINGse esiste una riga con il valore della chiave primaria dato, oppure facendola fare UPDATEinvece che INSERTse esiste una riga con il valore della chiave primaria dato.

Non ho provato questo da solo, quindi non posso parlare per esperienza o offrire un esempio.


1
se ho capito bene queste regole sono trigger che vengono eseguiti ogni volta che viene chiamata una dichiarazione. cosa succede se voglio applicare la regola per una sola query? devo creare la regola e poi distruggerla immediatamente? (che dire delle condizioni di gara?)
gpilotino,

3
Sì, avrei anche le stesse domande. Il meccanismo delle regole è la cosa più simile a PostgreSQL che ho potuto trovare in INSERT IGNORE di MySQL o in DUPLICATE KEY UPDATE. Se cerchiamo "postgresql su aggiornamento chiave duplicato", troviamo altre persone che raccomandano il meccanismo della regola, anche se una regola si applicherebbe a qualsiasi INSERT, non solo su base ad hoc.
Bill Karwin,

4
PostgreSQL supporta DDL transazionale, il che significa che se si crea una regola e la si rilascia all'interno di una singola transazione, la regola non sarà mai visibile al di fuori (e quindi non avrà mai avuto alcun effetto al di fuori di) tale transazione.
cdhowie,

6

Come menzionato da @hanmari nel suo commento. quando si inserisce in una tabella postgres, il conflitto on (..) non fa nulla è il codice migliore da usare per non inserire dati duplicati .:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

La riga di codice ON CONFLICT consentirà all'istruzione insert di inserire ancora righe di dati. Il codice di query e valori è un esempio di data inserita da un Excel in una tabella db postgres. Ho aggiunto dei vincoli a una tabella di Postgres che utilizzo per assicurarmi che il campo ID sia univoco. Invece di eseguire un'eliminazione su file di dati uguali, aggiungo una riga di codice sql che rinumera la colonna ID a partire da 1. Esempio:

q = 'ALTER id_column serial RESTART WITH 1'

Se i miei dati hanno un campo ID, non lo uso come ID primario / ID seriale, creo una colonna ID e la imposto su seriale. Spero che queste informazioni siano utili a tutti. * Non ho una laurea in sviluppo software / codifica. Tutto ciò che so in programmazione, studio da solo.


questo non funziona su indici univoci compositi!
Nulik,

4

Questa soluzione evita l'utilizzo di regole:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

ma ha uno svantaggio prestazionale (vedi PostgreSQL.org ):

Un blocco contenente una clausola EXCEPTION è significativamente più costoso per entrare e uscire di un blocco senza uno. Pertanto, non utilizzare EXCEPTION senza necessità.


1

Alla rinfusa, è sempre possibile eliminare la riga prima dell'inserimento. Una cancellazione di una riga che non esiste non causa un errore, quindi viene saltata in modo sicuro.


2
Questo approccio sarà piuttosto soggetto a strane condizioni di gara, non lo consiglierei ...
Steven Schlansker,

1
+1 Questo è facile e generico. Se usato con cura, può effettivamente essere una soluzione semplice.
Wouter van Nifterick,

1
Inoltre, non funzionerà quando i dati esistenti sono stati modificati dopo l'inserimento (ma non sulla chiave duplicata) e vogliamo mantenere gli aggiornamenti. Questo è lo scenario in cui sono presenti script SQL scritti per un numero di sistemi leggermente diversi, come gli aggiornamenti db eseguiti su sistemi di produzione, QA, sviluppo e test.
Hanno Fietz,

1
La chiave esterna può essere un problema se si creano con DEFERRABLE INITIALLY DEFERREDflag.
temoto

-1

Per gli script di importazione dei dati, per sostituire "SE NON ESISTE", in un certo senso, esiste una formulazione leggermente imbarazzante che tuttavia funziona:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.