Como obter o ID da linha em conflito no upsert?

18

Eu tenho uma tabela tagcom 2 colunas: id(uuid) e name(texto). Agora, quero inserir uma nova tag na tabela, mas se a tag já existir, quero simplesmente obter o idregistro existente.

Eu assumi que eu poderia apenas usar ON CONFLICT DO NOTHINGem combinação com RETURNING "id":

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

Mas isso retorna um conjunto de resultados vazio, se a tag com o nome "foo" já existir.

Alterei a consulta para usar uma DO UPDATEcláusula noop :

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

Isso funciona como pretendido, mas é um pouco confuso, porque estou apenas definindo o nome para o valor já existente.

É este o caminho a seguir sobre esse problema ou há uma abordagem mais simples que estou perdendo?

Der Hochstapler
fonte
você tentou returning excluded.id?
A_horse_with_no_name 16/02
@a_horse_with_no_name Isso só me dá ERROR: missing FROM-clause entry for table "excluded"ao usar DO NOTHING.
Der Hochstapler
11
Veja também stackoverflow.com/a/42217872/1411457
harmic

Respostas:

8

Isso funcionará (até onde eu testei) nos 3 casos, se os valores a serem inseridos forem todos novos ou já estiverem na tabela ou em uma mistura:

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 ;

Provavelmente, existem outras maneiras de fazer isso, talvez sem usar a nova ON CONFLICTsintaxe.

ypercubeᵀᴹ
fonte
4

Não faço ideia se como isso vai funcionar, mas apenas como outra opção para tentar, aqui está fazendo o mesmo da maneira antiga (sem 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)
;

Ou seja, insira apenas os nomes [únicos] não encontrados na tagtabela e retorne os IDs; combine isso com os IDs dos nomes que existem em tag, para a saída final. Você também pode nameenviar para a saída, conforme sugerido pelo ypercubeᵀᴹ , para saber qual ID corresponde a qual nome.

Andriy M
fonte
11
Sim. O último SELECT do meu código também pode ser escrito comoSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ