Comment obtenir l'ID de la ligne en conflit dans upsert?


18

J'ai un tableau tagavec 2 colonnes: id(uuid) et name(text). Je veux maintenant insérer une nouvelle balise dans la table, mais si la balise existe déjà, je veux simplement obtenir le idde l'enregistrement existant.

J'ai supposé que je pouvais simplement l'utiliser ON CONFLICT DO NOTHINGen combinaison avec RETURNING "id":

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

Mais cela renvoie un jeu de résultats vide, si la balise avec le nom "foo" existe déjà.

J'ai ensuite modifié la requête pour utiliser une DO UPDATEclause noop :

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

Cela fonctionne comme prévu, mais c'est un peu déroutant, car je ne fais que définir le nom sur la valeur déjà existante.

Est-ce la façon de régler ce problème ou y a-t-il une approche plus simple qui me manque?


as-tu essayé returning excluded.id?
a_horse_with_no_name

@a_horse_with_no_name Cela me donne juste ERROR: missing FROM-clause entry for table "excluded"lors de l'utilisation DO NOTHING.
Der Hochstapler

Réponses:


8

Cela fonctionnera (pour autant que j'ai testé) dans les 3 cas, si les valeurs à insérer sont toutes nouvelles ou toutes déjà dans le tableau ou un mélange:

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 ;

Il y a probablement d'autres façons de le faire, peut-être sans utiliser la nouvelle ON CONFLICTsyntaxe.


4

Aucune idée de la façon dont cela fonctionnera, mais juste comme une autre option à essayer, voici la même chose à l'ancienne (sans 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)
;

Autrement dit, insérez uniquement les noms [uniques] introuvables dans le tagtableau et renvoyez les ID; combinez cela avec les ID des noms qui existent dans tagla sortie finale. Vous pouvez également lancer namedans la sortie, comme suggéré par ypercubeᵀᴹ , afin de savoir quel ID correspond à quel nom.


1
Oui. Le dernier SELECT de mon code peut également s'écrireSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.