Inserisci, su aggiornamento duplicato in PostgreSQL?


645

Diversi mesi fa ho imparato da una risposta su Stack Overflow come eseguire più aggiornamenti contemporaneamente in MySQL usando la sintassi seguente:

INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);

Ora sono passato a PostgreSQL e apparentemente questo non è corretto. Si riferisce a tutte le tabelle corrette, quindi presumo sia una questione di parole chiave diverse utilizzate ma non sono sicuro di dove sia coperto nella documentazione di PostgreSQL.

Per chiarire, voglio inserire diverse cose e se esistono già per aggiornarle.


38
Chiunque trovi questa domanda dovrebbe leggere l'articolo di Depesz "Perché l'upert è così complicato?" . Spiega molto bene il problema e le possibili soluzioni.
Craig Ringer,

8
UPSERT sarà aggiunto in Postgres 9.5: wiki.postgresql.org/wiki/…
tommed

4
@tommed - è stato fatto: stackoverflow.com/a/34639631/4418
warren

Risposte:


515

PostgreSQL dalla versione 9.5 ha la sintassi UPSERT , con la clausola ON CONFLICT . con la seguente sintassi (simile a MySQL)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

Cercare "upert" negli archivi del gruppo di posta elettronica di postgresql porta a trovare un esempio di come si può fare ciò che si desidera fare, nel manuale :

Esempio 38-2. Eccezioni con UPDATE / INSERT

In questo esempio viene utilizzata la gestione delle eccezioni per eseguire UPDATE o INSERT, come appropriato:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

C'è forse un esempio di come eseguire questa operazione in blocco, utilizzando CTE in 9.1 e versioni successive, nella mailing list degli hacker :

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

Vedi la risposta di a_horse_with_no_name per un esempio più chiaro.


7
L'unica cosa che non mi piace di questo è che sarebbe molto più lento, perché ogni upsert sarebbe la sua "chiamata individuale nel database.
baash05

@ baash05 potrebbe esserci un modo per farlo in blocco, vedi la mia risposta aggiornata.
Stephen Denne,

2
L'unica cosa che farei diversamente è usare FOR 1..2 LOOP anziché solo LOOP in modo che se viene violato qualche altro vincolo univoco, esso non gira all'infinito.
olamork,

2
A cosa si excludedriferisce la prima soluzione qui?
ichbinallen,

2
@ichbinallen nei documenti Le clausole SET e WHERE in ON CONFLICT DO UPDATE hanno accesso alla riga esistente usando il nome della tabella (o un alias) e alle righe proposte per l'inserimento usando la speciale tabella esclusa . In questo caso, la excludedtabella speciale ti dà accesso ai valori che stavi cercando di INSERIRE in primo luogo.
TMichel,

429

Attenzione: questo non è sicuro se eseguito da più sessioni contemporaneamente (vedere avvertenze di seguito).


Un altro modo intelligente di fare un "UPSERT" in postgresql è fare due istruzioni UPDATE / INSERT sequenziali progettate ciascuna per avere successo o senza effetti.

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

L'AGGIORNAMENTO avrà esito positivo se esiste già una riga con "id = 3", altrimenti non ha alcun effetto.

L'INSERIMENTO avrà esito positivo solo se la riga con "id = 3" non esiste già.

È possibile combinare questi due in un'unica stringa ed eseguirli entrambi con un'unica istruzione SQL eseguita dall'applicazione. Si consiglia vivamente di eseguirli insieme in un'unica transazione.

Funziona molto bene se eseguito in modo isolato o su una tabella bloccata, ma è soggetto a condizioni di competizione che indicano che potrebbe comunque non riuscire con un errore di chiave duplicata se una riga viene inserita contemporaneamente o potrebbe terminare senza una riga inserita quando una riga viene eliminata contemporaneamente . Una SERIALIZABLEtransazione su PostgreSQL 9.1 o versioni successive la gestirà in modo affidabile al costo di un tasso di fallimento della serializzazione molto elevato, il che significa che dovrai riprovare molto. Scopri perché upsert è così complicato , che discute questo caso in modo più dettagliato.

Questo approccio è anche soggetto a aggiornamenti persi in read committedisolamento a meno che l'applicazione non controlli il conteggio delle righe interessate e verifichi che la riga interessata inserto quella updateinteressata .


6
Risposta breve: se il record esiste, INSERT non fa nulla. Risposta lunga: SELECT in INSERT restituirà tanti risultati quante sono le corrispondenze della clausola where. Questo è al massimo uno (se il numero uno non è il risultato della sottoselezione), altrimenti zero. L'INSERTO quindi aggiungerà una o zero righe.
Peter Becker,

3
la parte 'dove' può essere semplificata usando esiste:... where not exists (select 1 from table where id = 3);
Endy Tjahjono,

1
questa dovrebbe essere la risposta giusta .. con alcune piccole modifiche, potrebbe essere usata per fare un aggiornamento di massa .. Humm .. Mi chiedo se si possa usare una tabella temporanea ..
baash05

1
@keaplogik, che la limitazione 9.1 è con CTE scrivibile (espressioni di tabella comuni) che è descritto in un'altra delle risposte. La sintassi utilizzata in questa risposta è molto semplice ed è stata a lungo supportata.
bovino,

8
Attenzione, questo è soggetto a aggiornamenti persi in read committedisolamento a meno che l'applicazione non controlli per assicurarsi che il inserto updateabbia un conteggio delle righe diverso da zero. Vedi dba.stackexchange.com/q/78510/7788
Craig Ringer

227

Con PostgreSQL 9.1 questo può essere ottenuto usando un CTE scrivibile ( espressione di tabella comune ):

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

Vedi questi post di blog:


Questa soluzione non impedisce una violazione chiave univoca ma non è vulnerabile agli aggiornamenti persi.
Guarda il seguito di Craig Ringer su dba.stackexchange.com


1
@ FrançoisBeausoleil: la possibilità di una condizione di gara è molto più piccola rispetto all'approccio "prova /
gestisci

2
@a_horse_with_no_name Come intendi esattamente che la possibilità in condizioni di gara è molto più piccola? Quando eseguo questa query contemporaneamente con gli stessi record, visualizzo l'errore "Il valore chiave duplicato viola il vincolo univoco" il 100% delle volte fino a quando la query rileva che il record è stato inserito. È un esempio completo?
Jeroen van Dijk,

4
@a_horse_with_no_name La soluzione sembra funzionare in situazioni simultanee quando si avvolge l'istruzione upsert con il blocco seguente: BEGIN WORK; LOCK TABLE mytable IN MODALITÀ ESCLUSIVA SHOW ROW; <UPSERT QUI>; COMMITARE;
Jeroen van Dijk,

2
@JeroenvanDijk: grazie. Ciò che intendevo con "molto più piccolo" è che se diverse transazioni in questo (e impegnano la modifica!) L'intervallo di tempo tra l'aggiornamento e l'inserimento è più piccolo in quanto tutto è solo una singola istruzione. Puoi sempre generare una violazione pk da due istruzioni INSERT indipendenti. Se si blocca l'intera tabella, si serializza in modo efficace tutti gli accessi ad essa (cosa che è possibile ottenere anche con il livello di isolamento serializzabile).
a_horse_with_no_name

12
Questa soluzione è soggetta a aggiornamenti persi se la transazione di inserimento viene ripristinata; non esiste alcun controllo per imporre che le UPDATErighe interessate siano interessate.
Craig Ringer,

132

In PostgreSQL 9.5 e versioni successive è possibile utilizzare INSERT ... ON CONFLICT UPDATE.

Vedere la documentazione .

Un MySQL INSERT ... ON DUPLICATE KEY UPDATEpuò essere riformulato direttamente in a ON CONFLICT UPDATE. Né è sintassi standard SQL, sono entrambe estensioni specifiche del database. Ci sono buoni motivi per cui MERGEnon è stato usato per questo , una nuova sintassi non è stata creata solo per divertimento. (La sintassi di MySQL ha anche problemi che indicano che non è stata adottata direttamente).

ad es. impostazione data:

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

la query MySQL:

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

diventa:

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

differenze:

  • È necessario specificare il nome della colonna (o il nome univoco del vincolo) da utilizzare per il controllo di unicità. Quello è ilON CONFLICT (columnname) DO

  • La parola chiave SETdeve essere utilizzata, come se fosse UPDATEun'istruzione normale

Ha anche alcune belle funzionalità:

  • Puoi avere una WHEREclausola sul tuo UPDATE(permettendoti di trasformarti efficacemente ON CONFLICT UPDATEin ON CONFLICT IGNOREdeterminati valori)

  • I valori proposti per l'inserimento sono disponibili come variabile di riga EXCLUDED, che ha la stessa struttura della tabella di destinazione. È possibile ottenere i valori originali nella tabella utilizzando il nome della tabella. Quindi in questo caso EXCLUDED.csarà 10(perché è quello che abbiamo cercato di inserire) e "table".csarà 3perché questo è il valore corrente nella tabella. È possibile utilizzare uno o entrambi nelle SETespressioni e nella WHEREclausola.

Per informazioni su upsert vedi Come UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) in PostgreSQL?


Ho esaminato la soluzione 9.5 di PostgreSQL come descritto sopra perché stavo vivendo lacune nel campo dell'incremento automatico mentre ero sotto MySQL ON DUPLICATE KEY UPDATE. Ho scaricato Postgres 9.5 e implementato il tuo codice ma stranamente lo stesso problema si verifica in Postgres: il campo seriale della chiave primaria non è consecutivo (ci sono spazi vuoti tra gli inserti e gli aggiornamenti). Qualche idea di cosa sta succedendo qui? È normale? Qualche idea su come evitare questo comportamento? Grazie.
WM,

@WM Questo è praticamente inerente a un'operazione di inversione. È necessario valutare la funzione che genera la sequenza prima di tentare l'inserimento. Poiché tali sequenze sono progettate per funzionare contemporaneamente, sono esenti dalla normale semantica delle transazioni, ma anche se non lo fossero la generazione non viene chiamata in una sottotransazione e ripristinata, si completa normalmente e si impegna con il resto dell'operazione. Quindi questo accadrebbe anche con implementazioni di sequenza "gapless". L'unico modo in cui il DB potrebbe evitarlo sarebbe ritardare la valutazione della generazione della sequenza fino a dopo il controllo della chiave.
Craig Ringer

1
@WM che creerebbe i suoi problemi. Fondamentalmente, sei bloccato. Ma se fai affidamento sul fatto che serial / auto_increment sia gapless, hai già dei bug. È possibile avere intervalli di sequenza dovuti a rollback, inclusi errori temporanei: riavvii sotto carico, errori client durante la transazione, arresti anomali, ecc. Non è mai necessario fare affidamento su SERIAL/ SEQUENCEo AUTO_INCREMENTnon avere spazi vuoti. Se hai bisogno di sequenze gapless sono più complesse; di solito è necessario utilizzare una tabella contatore. Google ti dirà di più. Ma attenzione alle sequenze gapless impediscono a tutti gli inserimenti di concorrenza.
Craig Ringer

@WM Se sono assolutamente necessarie sequenze gapless e upsert, è possibile utilizzare l'approccio upsert basato sulle funzioni discusso nel manuale insieme a un'implementazione di sequenze gapless che utilizza una tabella contatore. Poiché l' BEGIN ... EXCEPTION ...esecuzione viene eseguita in una sottotransazione che viene ripristinata in caso di errore, l'incremento della sequenza verrebbe ripristinato in caso di errore INSERT.
Craig Ringer

Grazie mille @Craig Ringer, è stato piuttosto istruttivo. Mi sono reso conto che posso semplicemente rinunciare ad avere quella chiave primaria di incremento automatico. Ho creato un primario composito di 3 campi e per le mie particolari esigenze attuali, non c'è davvero bisogno di un campo di incremento automatico gapless. Grazie ancora, le informazioni fornite mi farebbero risparmiare tempo in futuro cercando di prevenire un comportamento DB naturale e salutare. Adesso lo capisco meglio.
WM,

17

Stavo cercando la stessa cosa quando sono venuto qui, ma la mancanza di una generica funzione "upsert" mi ha disturbato un po ', quindi ho pensato che potresti semplicemente passare l'aggiornamento e inserire sql come argomenti su quella funzione dal manuale

sarebbe simile a questo:

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

e forse per fare ciò che inizialmente volevi fare, batch "upsert", potresti usare Tcl per dividere sql_update e eseguire il loop dei singoli aggiornamenti, il colpo di preformance sarà molto piccolo vedi http://archives.postgresql.org/pgsql- prestazioni / 2006-04 / msg00557.php

il costo più alto è l'esecuzione della query dal tuo codice, dal lato del database il costo di esecuzione è molto più piccolo


3
Devi ancora eseguirlo in un ciclo di tentativi ed è soggetto a gare con un concorrente a DELETEmeno che tu non blocchi la tabella o sia in SERIALIZABLEisolamento di transazione su PostgreSQL 9.1 o versioni successive.
Craig Ringer,

13

Non esiste un comando semplice per farlo.

L'approccio più corretto è usare la funzione, come quella di documenti .

Un'altra soluzione (anche se non così sicura) è fare l'aggiornamento con la restituzione, controllare quali righe erano aggiornamenti e inserirne il resto

Qualcosa sulla falsariga di:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

assumendo id: 2 è stato restituito:

insert into table (id, column) values (1, 'aa'), (3, 'cc');

Ovviamente prima o poi verrà salvato (in un ambiente concorrente), in quanto vi sono chiare condizioni di gara qui, ma di solito funzionerà.

Ecco un articolo più lungo e completo sull'argomento .


1
Se si utilizza questa opzione, assicurarsi che l'id venga restituito anche se l'aggiornamento non fa nulla. Ho visto database ottimizzare query via come "Aggiorna tabella foo set bar = 4 dove bar = 4".
entro il

10

Personalmente, ho impostato una "regola" allegata all'istruzione insert. Supponiamo che tu abbia una tabella "dns" che registra gli hit DNS per cliente su una base temporale:

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

Volevi poter reinserire le righe con valori aggiornati o crearle se non esistevano già. Digitato su customer_id e l'ora. Qualcosa come questo:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

Aggiornamento: questo ha il potenziale per fallire se si verificano inserimenti simultanei, in quanto genererà eccezioni unique_violation. Tuttavia, la transazione non terminata continuerà e avrà esito positivo e sarà sufficiente ripetere la transazione terminata.

Tuttavia, se ci sono tonnellate di inserti che accadono continuamente, vorrai mettere un blocco della tabella attorno alle istruzioni di inserimento: il blocco SHARE ROW EXCLUSIVE impedirà qualsiasi operazione che possa inserire, eliminare o aggiornare le righe nella tabella di destinazione. Tuttavia, gli aggiornamenti che non aggiornano la chiave univoca sono sicuri, quindi se non si esegue alcuna operazione, utilizzare invece i blocchi di avviso.

Inoltre, il comando COPIA non utilizza le REGOLE, quindi se si inserisce con COPIA, è necessario utilizzare invece i trigger.


9

Uso questa funzione di unione

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql

1
È più efficiente fare semplicemente il updateprimo e quindi controllare il numero di righe aggiornate. (Vedi la risposta di Ahmad)
a_horse_with_no_name

8

Ho personalizzato la funzione "upsert" sopra, se vuoi INSERIRE E SOSTITUIRE:

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;`

E dopo l'esecuzione, fai qualcosa del genere:

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

È importante inserire la doppia virgola per evitare errori del compilatore

  • controlla la velocità ...

7

Simile alla risposta più apprezzata, ma funziona leggermente più velocemente:

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(fonte: http://www.the-art-of-web.com/sql/upsert/ )


3
Ciò non funzionerà se eseguito contemporaneamente in due sessioni, poiché nessuno dei due aggiornamenti vedrà una riga esistente, quindi entrambi gli aggiornamenti colpiranno zero righe, quindi entrambe le query genereranno un inserimento.
Craig Ringer

6

Ho lo stesso problema per la gestione delle impostazioni dell'account delle coppie valore-nome. Il criterio di progettazione prevede che client diversi possano avere impostazioni di impostazioni diverse.

La mia soluzione, simile a JWP, consiste nel cancellare e sostituire in blocco, generando il record di unione all'interno dell'applicazione.

Questo è piuttosto antiproiettile, indipendente dalla piattaforma e poiché non ci sono mai più di circa 20 impostazioni per client, si tratta solo di 3 chiamate db a carico abbastanza basso - probabilmente il metodo più veloce.

L'alternativa all'aggiornamento di singole righe - verifica delle eccezioni e poi dell'inserimento - o una combinazione di è il codice orribile, lento e spesso si interrompe perché (come menzionato sopra) la gestione delle eccezioni SQL non standard che cambia da db a db - o addirittura rilasciare per rilasciare.

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION

Benvenuti in SO. Bella introduzione! :-)
Don Domanda

1
Questo è più simile REPLACE INTOdi INSERT INTO ... ON DUPLICATE KEY UPDATE, il che può causare un problema se si utilizzano i trigger. Finirai per eliminare e inserire trigger / regole, anziché aggiornarne.
cHao,


5
CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT

5

Per unire piccoli set, usare la funzione sopra va bene. Tuttavia, se si stanno unendo grandi quantità di dati, suggerirei di consultare http://mbk.projects.postgresql.org

L'attuale best practice di cui sono a conoscenza è:

  1. COPIA i dati nuovi / aggiornati nella tabella temporanea (certo, oppure puoi INSERIRE se il costo è ok)
  2. Acquisisci blocco [opzionale] (è preferibile un avviso ai blocchi tabella, IMO)
  3. Unisci. (la parte divertente)

5

UPDATE restituirà il numero di righe modificate. Se si utilizza JDBC (Java), è possibile quindi verificare questo valore su 0 e, se non sono state interessate righe, attivare invece INSERT. Se si utilizza un altro linguaggio di programmazione, forse è ancora possibile ottenere il numero delle righe modificate, consultare la documentazione.

Potrebbe non essere così elegante, ma hai SQL molto più semplice che è più banale da usare dal codice chiamante. Diversamente, se scrivi lo script a dieci righe in PL / PSQL, probabilmente dovresti avere un test unitario di uno o un altro tipo solo per esso.


4

Modifica: non funziona come previsto. A differenza della risposta accettata, questo produce violazioni chiave uniche quando due processi chiamano ripetutamente upsert_foocontemporaneamente.

Eureka! Ho trovato un modo per farlo in una sola query: utilizzare UPDATE ... RETURNINGper verificare se sono state interessate righe:

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

La UPDATEdeve essere fatto in una procedura separata, perché, purtroppo, questo è un errore di sintassi:

... WHERE NOT EXISTS (UPDATE ...)

Ora funziona come desiderato:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');

1
È possibile combinarli in un'unica istruzione se si utilizza un CTE scrivibile. Ma come la maggior parte delle soluzioni pubblicate qui, questa è sbagliata e fallirà in presenza di aggiornamenti simultanei.
Craig Ringer
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.