Saya memiliki dua kolom dalam tabel col1
, col2
keduanya diindeks unik (col1 unik dan begitu juga col2).
Saya perlu memasukkan ke dalam tabel ini, menggunakan ON CONFLICT
sintaks dan memperbarui kolom lain, tetapi saya tidak dapat menggunakan kedua kolom dalam conflict_target
klausa.
Berhasil:
INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here
Tetapi bagaimana melakukan ini untuk beberapa kolom, seperti ini:
...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....
postgresql
upsert
postgresql-9.5
Oto Shavadze
sumber
sumber
Jawaban:
Tabel dan data sampel
CREATE TABLE dupes(col1 int primary key, col2 int, col3 text, CONSTRAINT col2_unique UNIQUE (col2) ); INSERT INTO dupes values(1,1,'a'),(2,2,'b');
Mereproduksi masalah
INSERT INTO dupes values(3,2,'c') ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2
Sebut saja Q1 ini. Hasilnya adalah
Apa yang dokumentasi mengatakan
Ini memberi kesan bahwa kueri berikut harus berfungsi, tetapi tidak karena sebenarnya akan memerlukan indeks unik bersama pada col1 dan col2. Namun indeks seperti itu tidak akan menjamin bahwa col1 dan col2 akan menjadi unik secara individual yang merupakan salah satu persyaratan OP.
INSERT INTO dupes values(3,2,'c') ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2
Sebut saja kueri ini Q2 (ini gagal dengan kesalahan sintaks)
Mengapa?
Postgresql berperilaku seperti ini karena apa yang seharusnya terjadi ketika konflik terjadi pada kolom kedua tidak didefinisikan dengan baik. Ada beberapa kemungkinan. Misalnya dalam kueri Q1 di atas, haruskah postgresql memperbarui
col1
ketika ada konflikcol2
? Tapi bagaimana jika itu mengarah pada konflik laincol1
? bagaimana postgresql diharapkan menangani itu?Sebuah solusi
Solusinya adalah menggabungkan ON CONFLICT dengan UPSERT model lama .
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS $$ BEGIN LOOP -- first try to update the key UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2; IF found THEN RETURN; END IF; -- not there, so try to insert the key -- if someone else inserts the same key concurrently, or key2 -- already exists in col2, -- we could get a unique-key failure BEGIN INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data; RETURN; EXCEPTION WHEN unique_violation THEN BEGIN INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data; RETURN; EXCEPTION WHEN unique_violation THEN -- Do nothing, and loop to try the UPDATE again. END; END; END LOOP; END; $$ LANGUAGE plpgsql;
Anda perlu mengubah logika dari fungsi yang disimpan ini sehingga ia memperbarui kolom persis seperti yang Anda inginkan. Panggil seperti itu
SELECT merge_db(3,2,'c'); SELECT merge_db(1,2,'d');
sumber
ON CONFLICT
membutuhkan indeks * unik untuk melakukan deteksi konflik. Jadi, Anda hanya perlu membuat indeks unik di kedua kolom:t=# create table t (id integer, a text, b text); CREATE TABLE t=# create unique index idx_t_id_a on t (id, a); CREATE INDEX t=# insert into t values (1, 'a', 'foo'); INSERT 0 1 t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar'; INSERT 0 1 t=# select * from t; id | a | b ----+---+----- 1 | a | bar
* Selain indeks unik, Anda juga dapat menggunakan batasan pengecualian . Ini sedikit lebih umum daripada batasan unik. Misalkan tabel Anda memiliki kolom untuk
id
danvalid_time
(danvalid_time
atsrange
), dan Anda ingin mengizinkan duplikatid
, tetapi tidak untuk periode waktu yang tumpang tindih. Batasan unik tidak akan membantu Anda, tetapi dengan batasan pengecualian Anda dapat mengatakan "kecualikan record baru jika merekaid
sama dengan yang lamaid
dan juga merekavalid_time
tumpang tindihvalid_time
."sumber
ON CONFLICT
?on conflict
perintah. Kesalahannya hanya "kolom my_index_name tidak ada".Saat ini (sepertinya) tidak mungkin. Baik versi
ON CONFLICT
sintaks terakhir tidak mengizinkan untuk mengulangi klausa, maupun dengan CTE tidak dimungkinkan: tidak mungkin untuk membobol INSERT dari ON CONFLICT untuk menambahkan lebih banyak konflik-target.sumber
Jika Anda menggunakan postgres 9.5, Anda dapat menggunakan ruang TERKECUALI.
Contoh diambil dari Apa yang baru di PostgreSQL 9.5 :
INSERT INTO user_logins (username, logins) VALUES ('Naomi',1),('James',1) ON CONFLICT (username) DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
sumber
ATAU DAN
sumber
Vlad mendapat ide yang tepat.
Pertama, Anda harus membuat batasan unik tabel pada kolom
col1, col2
Kemudian setelah Anda melakukannya, Anda dapat melakukan hal berikut:INSERT INTO dupes values(3,2,'c') ON CONFLICT ON CONSTRAINT dupes_pkey DO UPDATE SET col3 = 'c', col2 = 2
sumber
Agak hacky tetapi saya menyelesaikan ini dengan menggabungkan dua nilai dari col1 dan col2 ke dalam kolom baru, col3 (semacam indeks dari keduanya) dan membandingkannya. Ini hanya berfungsi jika Anda membutuhkannya untuk mencocokkan KEDUA col1 dan col2.
INSERT INTO table ... ON CONFLICT ( col3 ) DO UPDATE SET -- update needed columns here
Dimana col3 = rangkaian nilai dari col1 dan col2.
sumber
on conflict
.Anda biasanya (menurut saya) dapat menghasilkan pernyataan dengan hanya satu
on conflict
yang menentukan satu-satunya kendala yang relevan, untuk hal yang Anda masukkan.Karena biasanya, hanya satu kendala yang "relevan", pada satu waktu. (Jika banyak, maka saya bertanya-tanya apakah ada yang aneh / dirancang dengan aneh, hmm.)
Contoh:
(Lisensi: Bukan CC0, hanya CC-By)
// there're these unique constraints: // unique (site_id, people_id, page_id) // unique (site_id, people_id, pages_in_whole_site) // unique (site_id, people_id, pages_in_category_id) // and only *one* of page-id, category-id, whole-site-true/false // can be specified. So only one constraint is "active", at a time. val thingColumnName = thingColumnName(notfificationPreference) val insertStatement = s""" insert into page_notf_prefs ( site_id, people_id, notf_level, page_id, pages_in_whole_site, pages_in_category_id) values (?, ?, ?, ?, ?, ?) -- There can be only one on-conflict clause. on conflict (site_id, people_id, $thingColumnName) <—— look do update set notf_level = excluded.notf_level """ val values = List( siteId.asAnyRef, notfPref.peopleId.asAnyRef, notfPref.notfLevel.toInt.asAnyRef, // Only one of these is non-null: notfPref.pageId.orNullVarchar, if (notfPref.wholeSite) true.asAnyRef else NullBoolean, notfPref.pagesInCategoryId.orNullInt) runUpdateSingleRow(insertStatement, values)
Dan:
The
on conflict
klausa yang dihasilkan secara dinamis, tergantung pada apa yang saya coba lakukan. Jika saya memasukkan preferensi pemberitahuan, untuk halaman - maka mungkin ada konflik unik, padasite_id, people_id, page_id
batasan tersebut. Dan jika saya mengonfigurasi preferensi notifikasi, untuk kategori - maka saya tahu bahwa batasan yang bisa dilanggar adalahsite_id, people_id, category_id
.Jadi saya bisa, dan kemungkinan besar Anda juga, dalam kasus Anda ?, menghasilkan yang benar
on conflict (... columns )
, karena saya tahu apa yang ingin saya lakukan, dan kemudian saya tahu satu dari sekian banyak kendala unik, yang dapat dilanggar.sumber
ON CONFLICT adalah solusi yang sangat ceroboh, jalankan
UPDATE dupes SET key1=$1, key2=$2 where key3=$3 if rowcount > 0 INSERT dupes (key1, key2, key3) values ($1,$2,$3);
bekerja di Oracle, Postgres dan semua database lainnya
sumber