Как получить идентификатор конфликтующей строки в upsert?

18

У меня есть таблица tagс 2 столбцами: id(UUID) и name(текст). Теперь я хочу вставить новый тег в таблицу, но если тег уже существует, я хочу просто получить idсуществующую запись.

Я предположил, что я мог бы просто использовать ON CONFLICT DO NOTHINGв сочетании с RETURNING "id":

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

Но это возвращает пустой набор результатов, если тег с именем «foo» уже существует.

Затем я изменил запрос, чтобы использовать предложение noop DO UPDATE:

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

Это работает как задумано, но это несколько сбивает с толку, потому что я просто устанавливаю имя на уже существующее значение.

Это способ решить эту проблему или я упускаю более простой подход?

Der Hochstapler
источник
ты пробовал returning excluded.id?
a_horse_with_no_name
@a_horse_with_no_name Это просто дает мне ERROR: missing FROM-clause entry for table "excluded"при использовании DO NOTHING.
Der Hochstapler
1
См. Также stackoverflow.com/a/42217872/1411457
вредный

Ответы:

8

Это будет работать (насколько я тестировал) во всех 3 случаях, если все подлежащие вставке значения являются новыми или все уже находятся в таблице или в смеси:

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 ;

Возможно, есть и другие способы сделать это, возможно, без использования нового ON CONFLICTсинтаксиса.

ypercubeᵀᴹ
источник
4

Понятия не имею, как это будет работать, но просто как еще один вариант, здесь все делается по-старому (без 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)
;

То есть вставьте только [уникальные] имена, не найденные в tagтаблице, и верните идентификаторы; объедините это с идентификаторами имен, которые существуют в tag, для окончательного вывода. Вы также можете добавитьname в вывод, как предлагает ypercubeᵀᴹ , чтобы вы знали, какой идентификатор соответствует какому имени.

Андрей М
источник
1
Да. Последний SELECT моего кода также может быть записан какSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ