web-dev-qa-db-fra.com

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

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

J'ai supposé que je pouvais simplement utiliser ON CONFLICT DO NOTHING en 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 portant le nom "foo" existe déjà.

J'ai ensuite modifié la requête pour utiliser un noop DO UPDATE clause:

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?

18
Der Hochstapler

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 existe probablement d'autres façons de procéder, peut-être sans utiliser le nouveau ON CONFLICT syntaxe.

9
ypercubeᵀᴹ

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 la table tag et renvoyez les ID; combinez cela avec les ID des noms qui existent dans tag, pour la sortie finale. Vous pouvez également lancer name dans la sortie, comme suggéré par ypercubeᵀᴹ , afin de savoir quel ID correspond à quel nom.

4
Andriy M