bagaimana cara meniru "masukkan Abaikan" dan "pada pembaruan kunci duplikat" (gabungan sql) dengan postgresql?

140

Beberapa server SQL memiliki fitur di mana INSERTdilewati jika akan melanggar batasan kunci primer / unik. Sebagai contoh, MySQL memiliki INSERT IGNORE.

Apa cara terbaik untuk meniru INSERT IGNOREdan ON DUPLICATE KEY UPDATEdengan PostgreSQL?

gpilotino
sumber
Lihat juga: stackoverflow.com/questions/5269590/...
Dave Jarvis
6
pada 9,5, itu mungkin secara alami: stackoverflow.com/a/34639631/4418
warren
Meniru MySQL: ON DUPLICATE KEY UPDATEpada PgSQL 9.5 masih agak tidak mungkin, karena PgSQL yang ON CLAUSEsetara mengharuskan Anda untuk memberikan nama kendala, sementara MySQL dapat menangkap kendala apa pun tanpa perlu mendefinisikannya. Ini mencegah saya dari "meniru" fitur ini tanpa menulis ulang pertanyaan.
NeverEndingQueue

Jawaban:

35

Coba lakukan PEMBARUAN. Jika tidak mengubah baris apa pun yang berarti tidak ada, maka lakukan penyisipan. Jelas, Anda melakukan ini di dalam suatu transaksi.

Anda tentu saja dapat membungkus ini dalam suatu fungsi jika Anda tidak ingin meletakkan kode tambahan di sisi klien. Anda juga memerlukan perulangan untuk kondisi balapan yang sangat langka dalam pemikiran itu.

Ada contohnya dalam dokumentasi ini: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , contoh 40-2 tepat di bagian bawah.

Itu biasanya cara termudah. Anda dapat melakukan beberapa sihir dengan aturan, tetapi kemungkinannya akan jauh lebih berantakan. Saya akan merekomendasikan pendekatan wrap-in-function lebih dari itu setiap hari.

Ini berfungsi untuk satu baris, atau beberapa baris, nilai. Jika Anda berurusan dengan sejumlah besar baris, misalnya dari subquery, Anda sebaiknya membaginya menjadi dua pertanyaan, satu untuk INSERT dan satu untuk UPDATE (sebagai gabungan / subseleksi yang tepat tentunya - tidak perlu menulis utama Anda filter dua kali)

Magnus Hagander
sumber
4
"Jika Anda berurusan dengan sejumlah besar baris" itu persis seperti kasus saya. Saya ingin memperbarui / menyisipkan baris dan dengan mysql saya dapat melakukan ini hanya dengan SATU kueri tanpa pengulangan. Sekarang saya bertanya-tanya apakah ini mungkin dengan postgresql juga: untuk menggunakan hanya satu permintaan untuk pembaruan massal atau memasukkan. Anda berkata: "Anda sebaiknya membaginya menjadi dua pertanyaan, satu untuk INSERT dan satu untuk UPDATE" tetapi bagaimana saya bisa melakukan penyisipan yang tidak membuang kesalahan pada kunci duplikat? (mis. "INSERT IGNORE")
gpilotino
4
Magnus berarti Anda menggunakan kueri seperti ini: "mulai transaksi; buat tabel sementara temporary_table sebagai pilih * dari pengujian mana yang salah; salin temporary_table dari 'data_file.csv'; uji tabel kunci; perbarui uji set data = temporary_table.data dari temporary_table di mana test.id = temporary_table.id; masukkan ke dalam test select * dari temporary_table di mana id tidak berada dalam (select id from test) sebagai "
Tometzky
25
Perbarui: dengan PostgreSQL 9.5 ini sekarang sesederhana INSERT ... ON CONFLICT DO NOTHING;. Lihat juga jawab stackoverflow.com/a/34639631/2091700 .
Alphaaa
Penting, SQL-standar MERGEadalah bukan suatu yang aman upsert concurrency, kecuali jika Anda mengambil LOCK TABLEpertama. Orang menggunakannya seperti itu, tetapi itu salah.
Craig Ringer
1
Dengan v9.5 sekarang fitur 'asli', jadi silakan periksa komentar @Alphaaa (hanya mengiklankan komentar yang mengiklankan jawabannya)
Camilo Delvasto
178

Dengan PostgreSQL 9.5, ini sekarang fungsionalitas asli (seperti yang telah dimiliki MySQL selama beberapa tahun):

INSERT ... ON CONFLICT DO NOTHING / UPDATE ("UPSERT")

9.5 membawa dukungan untuk operasi "UPSERT". INSERT diperpanjang untuk menerima klausa ON CONFLICT DO UPDATE / IGNORE. Klausa ini menentukan tindakan alternatif yang harus diambil jika terjadi pelanggaran duplikat calon.

...

Contoh lebih lanjut dari sintaks baru:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
warren
sumber
100

Sunting: jika Anda melewatkan jawaban warren , PG9.5 sekarang memiliki ini secara asli; saatnya untuk meningkatkan!


Membangun berdasarkan jawaban Bill Karwin, untuk menjabarkan seperti apa pendekatan berbasis aturan (transfer dari skema lain dalam DB yang sama, dan dengan kunci primer multi-kolom):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Catatan: Aturan ini berlaku untuk semua INSERToperasi sampai aturan dibatalkan, jadi tidak cukup ad hoc.

Eoghan
sumber
@sema yang Anda maksud jika another_schema.my_tableberisi duplikat sesuai dengan kendala my_table?
EoghanM
2
@ EoghanM Saya menguji aturan di postgresql 9.3 dan masih bisa menyisipkan duplikat dengan pernyataan penyisipan beberapa baris seperti misalnya INSERT INTO "my_table" (a, b), (a, b); (Dengan asumsi baris itu (a, b) belum ada di "my_table".)
sema
@sema, gotcha - itu berarti aturan dijalankan pada awal semua data yang akan dimasukkan, dan tidak dijalankan kembali setelah setiap baris dimasukkan. Salah satu pendekatan akan memasukkan data Anda ke tabel sementara lain pertama yang tidak memiliki kendala, dan kemudian lakukanINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM
@ EoghanM Pendekatan lain adalah untuk mengendurkan kendala duplikat sementara dan menerima duplikat saat dimasukkan, tetapi menghapus duplikat sesudahnya denganDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema
Saya mengalami masalah yang dijelaskan oleh @sema. Jika saya melakukan penyisipan (a, b), (a, b), ia melempar kesalahan. Apakah ada cara untuk menekan kesalahan, juga dalam hal ini?
Diogo Melo
35

Bagi Anda yang memiliki Postgres 9.5 atau lebih tinggi, sintaks ON ON CONFLICT DO NOTHING seharusnya berfungsi:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Bagi kita yang memiliki versi sebelumnya, hak bergabung ini akan berfungsi sebagai gantinya:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;
Hanmari
sumber
Pendekatan kedua tidak bekerja ketika membuat insert besar di lingkungan bersamaan. Anda mendapatkan Unique violation: 7 ERROR: duplicate key value violates unique constraintketika target_tablebaris lain dimasukkan ke dalamnya ketika query ini dieksekusi, jika kunci mereka, memang, saling menduplikasi. Saya percaya bahwa mengunci target_tableakan membantu, tetapi konkurensi jelas akan menderita.
G. Kashtanov
1
ON CONFLICT (field_one) DO NOTHINGadalah bagian terbaik dari jawabannya.
Abel Callejo
24

Untuk mendapatkan logika abaikan sisipan Anda dapat melakukan sesuatu seperti di bawah ini. Saya menemukan hanya menyisipkan dari pernyataan pilih dari nilai literal yang paling berhasil, maka Anda dapat menutupi kunci duplikat dengan klausa TIDAK ADA. Untuk mendapatkan pembaruan pada logika duplikat saya curiga loop pl / pgsql akan diperlukan.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)
Keyo
sumber
Bagaimana jika tmp berisi baris duplikat, yang dapat terjadi?
Henley Chiu
Anda selalu dapat memilih dengan kata kunci yang berbeda.
Keyo
5
Sama seperti FYI, trik "WHERE NOT EXISTS" tidak berfungsi di beberapa transaksi karena transaksi yang berbeda tidak dapat melihat data yang baru ditambahkan dari transaksi lainnya.
Dave Johansen
21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
pengguna2342158
sumber
Apa dampak dari beberapa transaksi yang semuanya berusaha melakukan hal yang sama? Mungkinkah antara di mana tidak ada mengeksekusi dan memasukkan mengeksekusi beberapa transaksi lain tidak memasukkan baris? Dan jika Postgres dapat mencegah hal itu, bukankah postgres memperkenalkan titik sinkronisasi di semua transaksi ketika mereka mencapai ini?
Καrτhικ
Ini tidak berfungsi dengan beberapa transaksi, karena data yang baru ditambahkan tidak terlihat oleh transaksi lainnya.
Dave Johansen
12

Sepertinya PostgreSQL mendukung objek skema yang disebut aturan .

http://www.postgresql.org/docs/current/static/rules-update.html

Anda bisa membuat aturan ON INSERTuntuk tabel tertentu, membuatnya NOTHINGjika baris ada dengan nilai kunci utama yang diberikan, atau membuatnya melakukan UPDATEalih - alih INSERTjika baris ada dengan nilai kunci utama yang diberikan.

Saya belum mencoba ini sendiri, jadi saya tidak dapat berbicara dari pengalaman atau memberikan contoh.

Bill Karwin
sumber
1
jika saya mengerti dengan baik aturan-aturan ini adalah pemicu yang dieksekusi setiap kali sebuah pernyataan dipanggil. bagaimana jika saya ingin menerapkan aturan hanya untuk satu permintaan? saya harus membuat aturan maka segera hancurkan? (bagaimana dengan kondisi balapan?)
gpilotino
3
Ya, saya punya pertanyaan yang sama juga. Mekanisme aturan adalah hal terdekat yang dapat saya temukan di PostgreSQL dengan MySQL INSERT IGNORE atau ON DUPLICATE KEY UPDATE. Jika kita mencari "postgresql pada pembaruan kunci duplikat" Anda menemukan orang lain merekomendasikan mekanisme Aturan, meskipun Aturan akan berlaku untuk INSERT apa pun, tidak hanya berdasarkan ad hoc.
Bill Karwin
4
PostgreSQL mendukung DDL transaksional, yang berarti bahwa jika Anda membuat aturan dan menjatuhkannya dalam satu transaksi, aturan tersebut tidak akan pernah terlihat di luar (dan karenanya tidak akan pernah memiliki efek di luar) transaksi itu.
cdhowie
6

Seperti @hanmari disebutkan dalam komentarnya. ketika memasukkan ke dalam tabel postgres, konflik on (..) do nothing adalah kode terbaik untuk digunakan untuk tidak memasukkan data duplikat .:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

Baris kode ON CONFLICT akan memungkinkan pernyataan insert tetap memasukkan baris data. Kode kueri dan nilai adalah contoh tanggal yang dimasukkan dari Excel ke tabel postgres db. Saya memiliki kendala yang ditambahkan ke tabel postgres yang saya gunakan untuk memastikan bidang ID unik. Alih-alih menjalankan penghapusan pada baris data yang sama, saya menambahkan baris kode sql yang menomori ulang kolom ID mulai dari 1. Contoh:

q = 'ALTER id_column serial RESTART WITH 1'

Jika data saya memiliki bidang ID, saya tidak menggunakan ini sebagai ID utama / ID seri, saya membuat kolom ID dan saya mengaturnya ke serial. Saya harap informasi ini bermanfaat bagi semua orang. * Saya tidak memiliki gelar sarjana dalam pengembangan / pengkodean perangkat lunak. Semua yang saya tahu dalam coding, saya belajar sendiri.

Yankeeownz
sumber
ini tidak berfungsi pada indeks unik gabungan!
Nulik
4

Solusi ini menghindari penggunaan aturan:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

tetapi memiliki kekurangan kinerja (lihat PostgreSQL.org ):

Blok yang berisi klausa PENGECUALIAN secara signifikan lebih mahal untuk masuk dan keluar daripada blok tanpa klausa. Karena itu, jangan gunakan PENGECUALIAN tanpa perlu.

Nomor empat
sumber
1

Secara massal, Anda selalu dapat menghapus baris sebelum menyisipkan. Penghapusan baris yang tidak ada tidak menyebabkan kesalahan, jadi tidak dilewati dengan aman.

David Noriega
sumber
2
Pendekatan ini akan sangat rentan terhadap kondisi balapan yang aneh, saya tidak akan merekomendasikan hal itu ...
Steven Schlansker
1
+1 Ini mudah dan generik. Jika digunakan dengan hati-hati, ini sebenarnya bisa menjadi solusi sederhana.
Wouter van Nifterick
1
Ini juga tidak akan berfungsi ketika data yang ada telah diubah pasca-masukkan (tetapi tidak pada kunci duplikat) dan kami ingin menyimpan pembaruan. Ini adalah skenario ketika ada skrip SQL yang ditulis untuk sejumlah sistem yang sedikit berbeda, seperti pembaruan db yang berjalan pada sistem produksi, QA, dev dan pengujian.
Hanno Fietz
1
Kunci asing bisa menjadi masalah jika Anda membuatnya dengan DEFERRABLE INITIALLY DEFERREDbendera.
temoto
-1

Untuk skrip impor data, untuk mengganti "JIKA TIDAK ADA", dengan cara tertentu, ada formulasi yang agak canggung yang tetap bekerja:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
analytik_work
sumber