Bagaimana cara mendapatkan ID dari baris yang bertentangan di upsert?

18

Saya punya tabel tagdengan 2 kolom: id(uuid) dan name(teks). Saya sekarang ingin memasukkan tag baru ke dalam tabel, tetapi jika tag sudah ada, saya hanya ingin mendapatkan idcatatan yang ada.

Saya berasumsi saya hanya bisa menggunakan ON CONFLICT DO NOTHINGdalam kombinasi dengan RETURNING "id":

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

Tapi ini mengembalikan set hasil kosong, jika tag dengan nama "foo" sudah ada.

Saya kemudian mengubah kueri untuk menggunakan DO UPDATEklausa noop :

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

Ini berfungsi sebagaimana dimaksud, tetapi agak membingungkan, karena saya hanya mengatur nama ke nilai yang sudah ada.

Apakah ini cara untuk mengatasi masalah ini atau apakah ada pendekatan yang lebih sederhana yang saya lewatkan?

Der Hochstapler
sumber
Apakah kamu sudah mencoba returning excluded.id?
a_horse_with_no_name
@a_horse_with_no_name Itu hanya memberi saya ERROR: missing FROM-clause entry for table "excluded"saat menggunakan DO NOTHING.
Der Hochstapler
1
Lihat juga stackoverflow.com/a/42217872/1411457
berbahaya

Jawaban:

8

Ini akan berfungsi (sejauh yang saya uji) dalam ketiga kasus, jika nilai yang akan dimasukkan semuanya baru atau semua sudah ada dalam tabel atau campuran:

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 ;

Mungkin ada beberapa cara lain untuk melakukan ini, mungkin tanpa menggunakan ON CONFLICTsintaks baru .

ypercubeᵀᴹ
sumber
4

Tidak tahu apakah ini akan melakukan tetapi hanya sebagai pilihan lain untuk mencoba, di sini adalah melakukan cara sekolah yang lama (tanpa 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)
;

Artinya, masukkan hanya nama [unik] yang tidak ditemukan dalam tagtabel dan kembalikan ID; menggabungkannya dengan ID dari nama-nama yang ada di sana tag, untuk hasil akhir. Anda juga dapat melempar nameke output, seperti yang disarankan oleh ypercubeᵀᴹ , sehingga Anda tahu ID mana yang cocok dengan nama yang mana.

Andriy M
sumber
1
Iya. PILIH terakhir dari kode saya juga dapat ditulis sebagaiSELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ