Come ottenere l'ID della riga in conflitto in upsert?


18

Ho una tabella tagcon 2 colonne: id(uuid) e name(testo). Ora voglio inserire un nuovo tag nella tabella, ma se il tag esiste già, voglio semplicemente ottenere il idrecord esistente.

Supponevo di poter usare solo ON CONFLICT DO NOTHINGin combinazione con RETURNING "id":

INSERT INTO
    "tag" ("name")
VALUES( 'foo' )
ON CONFLICT DO NOTHING
RETURNING "id";

Ma questo restituisce un set di risultati vuoto, se il tag con il nome "pippo" esiste già.

Ho quindi modificato la query per utilizzare una DO UPDATEclausola noop :

INSERT INTO
    "tag" ("name")
VALUES( 'foo' )
ON CONFLICT ("name") DO UPDATE SET "name" = 'foo'
RETURNING "id";

Funziona come previsto, ma è un po 'confuso, perché sto solo impostando il nome sul valore già esistente.

È questo il modo di affrontare questo problema o c'è un approccio più semplice che mi manca?


hai provato returning excluded.id?
a_horse_with_no_name

@a_horse_with_no_name Questo mi dà solo ERROR: missing FROM-clause entry for table "excluded"quando uso DO NOTHING.
Der Hochstapler,

Risposte:


8

Funzionerà (per quanto ho testato) in tutti e 3 i casi, se i valori da inserire sono tutti nuovi o tutti già nella tabella o in un mix:

WITH
  val (name) AS
    ( VALUES                          -- rows to be inserted
        ('foo'),
        ('bar'),
        ('zzz')
    ),
  ins AS
    ( INSERT INTO
        tag (name)
      SELECT name FROM val
      ON CONFLICT (name) DO NOTHING
      RETURNING id, name              -- only the inserted ones
    )
SELECT COALESCE(ins.id, tag.id) AS id, 
       val.name
FROM val
  LEFT JOIN ins ON ins.name = val.name
  LEFT JOIN tag ON tag.name = val.name ;

Probabilmente ci sono altri modi per farlo, forse senza usare la nuova ON CONFLICTsintassi.


4

Non ho idea di come funzionerà, ma proprio come un'altra opzione da provare, qui sta facendo lo stesso alla vecchia maniera (senza ON CONFLICT):

WITH items (name) AS (VALUES ('foo'), ('bar'), ('zzz')),
     added        AS
      (
        INSERT INTO tag (name)

        SELECT name FROM items
        EXCEPT
        SELECT name FROM tag

        RETURNING id
      )
SELECT id FROM added

UNION ALL

SELECT id FROM tag
WHERE name IN (SELECT name FROM items)
;

Cioè, inserire solo i nomi [unici] non trovati nella tagtabella e restituire gli ID; combinalo con gli ID dei nomi esistenti in tag, per l'output finale. Puoi anche lanciare namenell'output, come suggerito da ypercubeᵀᴹ , in modo da sapere quale ID corrisponde a quale nome.


1
Sì. L'ultimo SELECT del mio codice può anche essere scritto comeSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ il
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.