Wie erhalte ich die ID der in Konflikt stehenden Zeile in Upsert?


18

Ich habe eine Tabelle tagmit 2 Spalten: id(UUID) und name(Text). Ich möchte jetzt ein neues Tag in die Tabelle einfügen, aber wenn das Tag bereits vorhanden ist, möchte ich einfach idden vorhandenen Datensatz abrufen.

Ich nahm an, ich könnte nur ON CONFLICT DO NOTHINGin Kombination mit RETURNING "id":

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

Dies gibt jedoch eine leere Ergebnismenge zurück, wenn das Tag mit dem Namen "foo" bereits vorhanden ist.

Ich habe dann die Abfrage geändert, um eine noop- DO UPDATEKlausel zu verwenden:

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

Dies funktioniert wie beabsichtigt, ist jedoch etwas verwirrend, da ich nur den Namen auf den bereits vorhandenen Wert setze.

Ist dies der Weg, um dieses Problem zu lösen, oder gibt es einen einfacheren Ansatz, den ich vermisse?


haben Sie versucht returning excluded.id?
a_horse_with_no_name

@a_horse_with_no_name Das gibt mir nur ERROR: missing FROM-clause entry for table "excluded"bei der Verwendung DO NOTHING.
Der Hochstapler

Antworten:


8

Dies funktioniert (soweit ich es getestet habe) in allen 3 Fällen, wenn die einzufügenden Werte alle neu oder alle bereits in der Tabelle oder einer Mischung sind:

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 ;

Es gibt wahrscheinlich einige andere Möglichkeiten, dies zu tun, möglicherweise ohne die neue ON CONFLICTSyntax zu verwenden.


4

Keine Ahnung, wie sich das verhält, aber als weitere Möglichkeit, es zu versuchen, wird hier das Gleiche auf eine altmodische Art und Weise (ohne ON CONFLICT) getan :

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)
;

Das heißt, fügen Sie nur die [eindeutigen] Namen ein, die nicht in der tagTabelle gefunden wurden, und geben Sie die IDs zurück. Kombinieren Sie dies mit den IDs der Namen, die in existieren tag, für die endgültige Ausgabe. Sie können auch name, wie von ypercube® vorgeschlagen , in die Ausgabe werfen , damit Sie wissen, welche ID mit welchem ​​Namen übereinstimmt.


1
Ja. Das letzte SELECT meines Codes kann auch alsSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ 18.02.16
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.