Kesalahan yang Anda dapatkan:
Perintah ON CONFLICT DO UPDATE tidak dapat mempengaruhi baris untuk kedua kalinya
menunjukkan Anda mencoba memasukkan baris yang sama lebih dari sekali dalam satu perintah. Dengan kata lain:Anda memiliki penipuan di (name, url, email)
di VALUES
. Anda daftar. Lipat duplikat (jika itu opsi) dan itu akan berfungsi. Tetapi Anda harus memutuskan baris mana yang akan dipilih dari setiap kumpulan penipuan.
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
('blah', 'blah', 'blah', 'blah', 'blah')
-- ... more
) v(created, modified, name, url, email) -- match column list
ON CONFLICT (name, url, email) DO UPDATE
SET url = feeds_person.url
RETURNING id;
Karena kami menggunakan VALUES
yang berdiri sendiri ekspresi sekarang, Anda harus menambahkan gips tipe eksplisit untuk tipe non-default. Seperti:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
timestamptz
. Anda kolom membutuhkan pemeran tipe eksplisit, sedangkan tipe string dapat beroperasi dengan text
default . (Anda masih dapat melakukan transmisi ke varchar(n)
segera.)
Ada beberapa cara untuk menentukan baris mana yang harus dipilih dari setiap kumpulan penipuan:
- Pilih baris pertama di setiap grup GROUP BY?
Anda benar, (saat ini) tidak ada cara untuk dikecualikan baris di RETURNING
ayat. Saya mengutip Postgres Wiki:
Perhatikan bahwa
RETURNING
tidak menampilkan "EXCLUDED.*
" aliasdariUPDATE
(hanya "TARGET.*
" generik " alias terlihat di sana). Melakukan hal itu dianggap menciptakan ambiguitas yang mengganggu untuk kasus-kasus umum yang sederhana [30] dengan sedikit atau tanpa manfaat. Pada suatu saat di masa mendatang, kami mungkin mencari cara untuk mengekspos ifRETURNING
- tupel yang diproyeksikan dimasukkan dan diperbarui, tetapi ini mungkin tidak perlu membuatnya menjadi iterasi berkomitmen pertama dari fitur [31].
Namun , Anda tidak boleh memperbarui baris yang tidak seharusnya diperbarui. Pembaruan kosong hampir semahal pembaruan biasa - dan mungkin memiliki efek samping yang tidak diinginkan. Anda tidak benar-benar membutuhkan UPSERT untuk memulai, kasing Anda lebih mirip "PILIH atau MASUKKAN". Terkait:
- Apakah SELECT atau INSERT dalam fungsi rentan terhadap kondisi balapan?
Satu cara yang lebih bersih untuk menyisipkan satu set baris adalah dengan CTE yang memodifikasi data:
WITH val AS (
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
(timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
, ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
-- more (type cast only needed in 1st row)
) v(created, modified, name, url, email)
)
, ins AS (
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT created, modified, name, url, email FROM val
ON CONFLICT (name, url, email) DO NOTHING
RETURNING id, name, url, email
)
SELECT 'inserted' AS how, id FROM ins -- inserted
UNION ALL
SELECT 'selected' AS how, f.id -- not inserted
FROM val v
JOIN feeds_person f USING (name, url, email);
Kompleksitas tambahan harus membayar untuk tabel besar di mana INSERT
adalah aturan dan SELECT
pengecualian.
Awalnya, saya telah menambahkan NOT EXISTS
predikat pada SELECT
terakhir untuk mencegah duplikat dalam hasil. Tapi itu berlebihan. Semua CTE dari satu kueri melihat cuplikan tabel yang sama. Set dikembalikan dengan ON CONFLICT (name, url, email) DO NOTHING
saling eksklusif untuk set yang dikembalikan setelah INNER JOIN
pada kolom yang sama.
Sayangnya ini juga membuka jendela kecil untuk kondisi balapan . Jika ...
- transaksi bersamaan menyisipkan baris yang bertentangan
- belum berkomitmen
- tetapi akhirnya berhasil
... beberapa baris mungkin hilang.
Anda mungkin hanya INSERT .. ON CONFLICT DO NOTHING
, diikuti oleh SELECT
separate yang terpisah permintaan untuk semua baris - dalam transaksi yang sama untuk mengatasi ini. Yang pada gilirannya membuka jendela kecil lainnya untuk kondisi balapan jika transaksi bersamaan dapat melakukan penulisan ke tabel antara INSERT
dan SELECT
(secara default READ COMMITTED
tingkat isolasi). Dapat dihindari dengan REPEATABLE READ
isolasi transaksi (atau lebih ketat). Atau dengan kunci tulis (mungkin mahal atau bahkan tidak dapat diterima) di seluruh meja. Anda bisa mendapatkan perilaku apa pun yang Anda butuhkan, tetapi mungkin ada harga yang harus dibayar.
Terkait:
- Bagaimana cara menggunakan RETURNING dengan ON CONFLICT di PostgreSQL?
- Kembalikan baris dari INSERT dengan ON CONFLICT tanpa perlu memperbarui