¿Cómo obtener la ID de la fila en conflicto en upsert?


18

Tengo una tabla tagcon 2 columnas: id(uuid) y name(texto). Ahora quiero insertar una nueva etiqueta en la tabla, pero si la etiqueta ya existe, simplemente quiero obtener la iddel registro existente.

Supuse que podría usarlo ON CONFLICT DO NOTHINGen combinación con RETURNING "id":

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

Pero esto devuelve un conjunto de resultados vacío, si la etiqueta con el nombre "foo" ya existe.

Luego cambié la consulta para usar una DO UPDATEcláusula noop :

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

Esto funciona según lo previsto, pero es algo confuso, porque solo estoy configurando el nombre al valor ya existente.

¿Es esta la forma de solucionar este problema o me falta un enfoque más simple?


intentaste returning excluded.id?
a_horse_with_no_name

@a_horse_with_no_name Eso solo me da ERROR: missing FROM-clause entry for table "excluded"cuando lo uso DO NOTHING.
Der Hochstapler

Respuestas:


8

Esto funcionará (por lo que probé) en los 3 casos, si los valores a insertar son todos nuevos o todos ya están en la tabla o una mezcla:

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 ;

Probablemente hay otras formas de hacer esto, tal vez sin usar la nueva ON CONFLICTsintaxis.


4

No tengo idea de cómo funcionará esto, pero solo como otra opción para intentar, aquí está haciendo lo mismo de la vieja escuela (sin 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)
;

Es decir, inserte solo los nombres [únicos] que no se encuentran en la tagtabla y devuelva los ID; combine eso con las ID de los nombres que existen en tag, para el resultado final. También puede incluirname en la salida, como lo sugiere ypercubeᵀᴹ , para que sepa qué ID coincide con qué nombre.


1
Si. El último SELECT de mi código también se puede escribir comoSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.