Bagaimana cara 'menyisipkan jika tidak ada' di MySQL?

838

Saya mulai dengan googling, dan menemukan artikel ini yang membahas tentang tabel mutex.

Saya punya meja dengan ~ 14 juta catatan. Jika saya ingin menambahkan lebih banyak data dalam format yang sama, apakah ada cara untuk memastikan catatan yang ingin saya masukkan tidak ada tanpa menggunakan sepasang kueri (yaitu, satu permintaan untuk memeriksa dan satu untuk memasukkan adalah hasil yang ditetapkan adalah kosong)?

Apakah uniquebatasan pada bidang menjamin insertkehendak gagal jika sudah ada?

Tampaknya dengan hanya kendala, ketika saya mengeluarkan insert via php, skripnya serak.

warren
sumber
Lihat stackoverflow.com/questions/44550788/... untuk diskusi tentang tidak membakar nilai auto_inc.
Rick James
@ RickJames - itu adalah q yang menarik .. tetapi tidak yakin itu terkait langsung dengan q ini :)
warren
1
Itu disebutkan dalam komentar, dan bahwa Pertanyaan lain mengklaim Pertanyaan ini adalah "duplikat persis". Jadi, saya merasa ide yang bagus untuk menghubungkan pertanyaan bersama untuk kepentingan orang lain.
Rick James
1
Oh, aku tidak pernah berpikir untuk melihat bar samping.
Rick James

Jawaban:

807

menggunakan INSERT IGNORE INTO table

lihat http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html

ada juga INSERT … ON DUPLICATE KEY UPDATEsintaks, Anda dapat menemukan penjelasan di dev.mysql.com


Posting dari bogdan.org.ua sesuai dengan cache web Google :

18 Oktober 2007

Untuk memulai: pada MySQL terbaru, sintaks yang disajikan dalam judul tidak mungkin. Tetapi ada beberapa cara yang sangat mudah untuk mencapai apa yang diharapkan menggunakan fungsi yang ada.

Ada 3 solusi yang mungkin: menggunakan INSERT IGNORE, REPLACE, atau INSERT ... ON DUPLICATE UPDATE KEY.

Bayangkan kita punya meja:

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Sekarang bayangkan bahwa kita memiliki pipa otomatis yang mengimpor meta-data transkrip dari Ensembl, dan bahwa karena berbagai alasan pipa itu mungkin rusak pada setiap langkah eksekusi. Jadi, kita perlu memastikan dua hal:

  1. eksekusi berulang-ulang dari pipeline tidak akan menghancurkan database kami

  2. eksekusi berulang tidak akan mati karena kesalahan 'duplikat kunci utama'.

Metode 1: menggunakan REPLACE

Ini sangat sederhana:

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Jika catatan ada, itu akan ditimpa; jika belum ada, itu akan dibuat. Namun, menggunakan metode ini tidak efisien untuk kasus kami: kami tidak perlu menimpa catatan yang ada, tidak apa-apa hanya untuk melewatkannya.

Metode 2: menggunakan INSERT IGNORE Juga sangat sederhana:

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Di sini, jika 'ensembl_transcript_id' sudah ada dalam database, ia akan dilewati secara diam-diam (diabaikan). (Lebih tepatnya, inilah kutipan dari manual referensi MySQL: "Jika Anda menggunakan kata kunci IGNORE, kesalahan yang terjadi saat mengeksekusi pernyataan INSERT diperlakukan sebagai peringatan. Sebagai contoh, tanpa IGNORE, baris yang menggandakan indeks UNIQUE yang ada atau nilai KUNCI UTAMA dalam tabel menyebabkan kesalahan duplikat kunci dan pernyataan dibatalkan. ".) Jika catatan belum ada, itu akan dibuat.

Metode kedua ini memiliki beberapa kelemahan potensial, termasuk non-aborsi permintaan jika terjadi masalah lain (lihat manual). Dengan demikian itu harus digunakan jika sebelumnya diuji tanpa kata kunci IGNORE.

Metode 3: menggunakan INSERT ... ON DUPLICATE UPDATE KUNCI:

Opsi ketiga adalah menggunakan INSERT … ON DUPLICATE KEY UPDATE sintaks, dan di bagian UPDATE hanya melakukan apa-apa melakukan beberapa operasi (kosong) tidak berarti, seperti menghitung 0 + 0 (Geoffray menyarankan melakukan tugas id = id untuk mesin optimasi MySQL untuk mengabaikan operasi ini). Keuntungan dari metode ini adalah bahwa ia hanya mengabaikan peristiwa kunci duplikat, dan masih membatalkan kesalahan lainnya.

Sebagai pemberitahuan terakhir: posting ini terinspirasi oleh Xaprb. Saya juga menyarankan untuk berkonsultasi posnya yang lain tentang penulisan query SQL yang fleksibel.

rajutan
sumber
3
dan dapatkah saya menggabungkannya dengan "tertunda" untuk mempercepat skrip?
warren
3
ya, memasukkan yang tertunda dapat mempercepat hal-hal untuk Anda. coba saja
knittl
32
Ya, dan perlu diingat bahwa REPLACE INTO melakukan DELETE then INSERT, not UPDATE
bobobobo
10
INSERT … ON DUPLICATE KEY UPDATElebih baik karena tidak menghapus baris, menjaga auto_incrementkolom dan data lainnya.
Diredakan
15
Hanya untuk memberi tahu semua orang. Menggunakan INSERT … ON DUPLICATE KEY UPDATEmetode tidak menambah kolom AUTO_INCREMENT apa pun dengan gagal memasukkan. Mungkin karena itu tidak benar-benar gagal, tetapi DIPERBARUI.
not2qubit
216

Larutan:

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 

Penjelasan:

Permintaan terdalam

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1

digunakan sebagai WHERE NOT EXISTS-condition mendeteksi jika sudah ada baris dengan data yang akan dimasukkan. Setelah satu baris dari jenis ini ditemukan, kueri mungkin berhenti, karenanya LIMIT 1(optimasi mikro, dapat dihilangkan).

Kueri perantara

SELECT 'stuff for value1', 'stuff for value2' FROM DUAL

mewakili nilai yang akan dimasukkan. DUALmerujuk ke satu baris khusus, satu tabel kolom hadir secara default di semua database Oracle (lihat https://en.wikipedia.org/wiki/DUAL_table ). Pada MySQL-Server versi 5.7.26 saya mendapat permintaan yang valid ketika menghilangkan FROM DUAL, tetapi versi yang lebih lama (seperti 5.5.60) tampaknya memerlukan FROMinformasi. Dengan menggunakan WHERE NOT EXISTSkueri perantara, mengembalikan hasil kosong yang disetel jika kueri paling dalam menemukan data yang cocok.

Kueri luar

INSERT INTO `table` (`value1`, `value2`) 

menyisipkan data, jika ada yang dikembalikan oleh kueri perantara.

Server
sumber
4
dapatkah Anda memberikan lebih banyak info tentang cara menggunakan ini?
Alex V
36
Varian ini cocok jika tidak ada kunci unik di atas meja ( INSERT IGNOREdan INSERT ON DUPLICATE KEYmemerlukan batasan kunci unik)
rabudde
2
Jika Anda menggunakan "from dual" pada baris 2 bukannya "from table", maka Anda tidak memerlukan klausa "limit 1".
Kaya
6
Bagaimana jika stuff for value1dan stuff for value2identik? Ini akan melemparDuplicate column name
Robin
1
Saya juga lebih suka SELECT 1daripada SELECT *di subqueries. Jauh lebih mungkin bahwa ini dapat dipenuhi oleh indeks.
Arth
58

pada pembaruan kunci rangkap , atau masukkan abaikan dapat menjadi solusi yang layak dengan MySQL.


Contoh tentang pembaruan pembaruan kunci duplikat berdasarkan mysql.com

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

Contoh memasukkan abaikan berdasarkan mysql.com

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

Atau:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

Atau:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Zed
sumber
24

Kendala sederhana apa pun harus dilakukan, jika pengecualian dapat diterima. Contoh:

  • kunci primer jika bukan pengganti
  • kendala unik pada kolom
  • kendala unik multi-kolom

Maaf apakah ini tampak sederhana. Saya tahu itu terlihat buruk dihadapkan pada tautan yang Anda bagikan dengan kami. ;-(

Tapi saya tetap memberikan jawaban ini, karena sepertinya memenuhi kebutuhan Anda. (Jika tidak, ini dapat memicu Anda memperbarui persyaratan Anda, yang juga akan menjadi "Hal yang Baik" (TM)).

Diedit : Jika sebuah insert akan merusak batasan unik basis data, sebuah pengecualian dilemparkan ke tingkat basis data, diteruskan oleh driver. Itu pasti akan menghentikan skrip Anda, dengan kegagalan. Harus dimungkinkan dalam PHP untuk mengatasi kasus itu ...

KLE
sumber
1
saya menambahkan klarifikasi ke pertanyaan - apakah jawaban Anda masih berlaku?
warren
2
Saya percaya begitu. Batasan unik akan menyebabkan kegagalan sisipan yang salah. Catatan: Anda harus berurusan dengan kegagalan ini dalam kode Anda, tetapi ini cukup standar.
KLE
1
untuk saat ini aku akan tetap dengan solusi saya diterima - tapi akan lebih melihat ke dalam menangani kegagalan INSERT dll sebagai aplikasi tumbuh
warren
3
INSERT IGNOREpada dasarnya mengubah semua kesalahan menjadi peringatan sehingga skrip Anda tidak terganggu. Anda kemudian dapat melihat peringatan apa pun dengan perintah SHOW WARNINGS. Dan catatan penting lainnya : kendala UNIK tidak bekerja dengan nilai NULL, yaitu. row1 (1, NULL) dan row2 (1, NULL) keduanya akan dimasukkan (kecuali kendala lain seperti kunci utama rusak). Disayangkan
Simon East
18

Berikut adalah fungsi PHP yang akan menyisipkan baris hanya jika semua nilai kolom yang ditentukan belum ada dalam tabel.

  • Jika salah satu kolom berbeda, baris akan ditambahkan.

  • Jika tabel kosong, baris akan ditambahkan.

  • Jika ada baris di mana semua kolom yang ditentukan memiliki nilai yang ditentukan, baris tersebut tidak akan ditambahkan.

    function insert_unique($table, $vars)
    {
      if (count($vars)) {
        $table = mysql_real_escape_string($table);
        $vars = array_map('mysql_real_escape_string', $vars);
    
        $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
        $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
        $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
    
        foreach ($vars AS $col => $val)
          $req .= "`$col`='$val' AND ";
    
        $req = substr($req, 0, -5) . ") LIMIT 1";
    
        $res = mysql_query($req) OR die();
        return mysql_insert_id();
      }
    
      return False;
    }

Contoh penggunaan:

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>
Jrm
sumber
5
Cukup mahal jika Anda memiliki banyak sekali insersi.
Эџad Дьdulяңмaи
benar, tetapi efisien jika Anda perlu menambahkan pemeriksaan khusus
Charles Forest
1
Peringatan: mysql_* ekstensi sudah ditinggalkan pada PHP 5.5.0, dan telah dihapus pada PHP 7.0.0. Sebaliknya, ekstensi mysqli atau PDO_MySQL harus digunakan. Lihat juga Ikhtisar API MySQL untuk bantuan lebih lanjut saat memilih API MySQL.
Dharman
17
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Jika catatan ada, itu akan ditimpa; jika belum ada, itu akan dibuat.

Rocio
sumber
10
REPLACEdapat menghapus baris dan kemudian memasukkan alih-alih pembaruan. Efek sampingnya adalah bahwa kendala dapat menghapus objek lain dan menghapus pemicu dipecat.
xmedeko
1
Dari manual MySQL: "REPLACE hanya masuk akal jika sebuah tabel memiliki indeks KUNCI UTAMA atau UNIK. Jika tidak, itu menjadi setara dengan INSERT, karena tidak ada indeks yang akan digunakan untuk menentukan apakah baris baru duplikat yang lain."
BurninLeo
16

Coba yang berikut ini:

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END
Jeb
sumber
5
Coba Jawaban ini bernilai rendah di StackOverflow karena mereka sangat sedikit mendidik OP dan ribuan peneliti masa depan. Harap edit jawaban ini untuk memasukkan cara kerja solusi dan mengapa itu adalah ide yang bagus.
mickmackusa
1
Solusi sempurna seandainya bidang yang cocok bukan kunci ..!
Leo
6

Ada beberapa jawaban yang mencakup cara mengatasi ini jika Anda memiliki UNIQUEindeks yang dapat Anda periksa dengan ON DUPLICATE KEYatau INSERT IGNORE. Itu tidak selalu terjadi, dan karena UNIQUEmemiliki batasan panjang (1000 byte) Anda mungkin tidak dapat mengubahnya. Misalnya, saya harus bekerja dengan metadata di WordPress ( wp_postmeta).

Saya akhirnya menyelesaikannya dengan dua pertanyaan:

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);

Kueri 1 adalah UPDATEkueri reguler tanpa efek ketika dataset yang dimaksud tidak ada. Kueri 2 adalah INSERTyang tergantung pada a NOT EXISTS, yaitu INSERThanya dieksekusi ketika dataset tidak ada.

berjalan cepat
sumber
2

Sesuatu yang perlu dicatat adalah bahwa INSERT IGNORE masih akan menambah kunci utama apakah pernyataan itu sukses atau tidak seperti INSERT biasa.

Ini akan menyebabkan celah pada kunci utama Anda yang mungkin membuat seorang programmer tidak stabil secara mental. Atau jika aplikasi Anda dirancang dengan buruk dan tergantung pada kunci primer tambahan yang sempurna, itu mungkin menjadi sakit kepala.

Lihat ke dalam innodb_autoinc_lock_mode = 0(pengaturan server, dan datang dengan sedikit performa), atau gunakan SELECT terlebih dahulu untuk memastikan permintaan Anda tidak akan gagal (yang juga datang dengan kinerja dan kode tambahan).

Gilly
sumber
Mengapa "celah pada kunci utama Anda" - bahkan berpotensi - "membuat seorang programmer tidak stabil secara mental"? Kesenjangan terjadi sepanjang waktu di kunci utama - setiap kali Anda menghapus catatan, misalnya.
warren
Dimulai dengan SELECTkekalahan seluruh tujuan hanya menyerahkan batch besar INSERTdan tidak ingin khawatir tentang duplikat.
warren
2

Perbarui atau masukkan tanpa kunci utama yang diketahui

Jika Anda sudah memiliki kunci utama atau unik, jawaban yang lain dengan salah satu INSERT INTO ... ON DUPLICATE KEY UPDATE ...atau REPLACE INTO ...harus berfungsi dengan baik (perhatikan bahwa ganti menjadi dihapus jika ada dan kemudian sisipkan - dengan demikian tidak memperbarui sebagian nilai yang ada).

Tetapi jika Anda memiliki nilai untuk some_column_iddan some_type, kombinasi yang dikenal unik. Dan Anda ingin memperbarui some_valuejika ada, atau masukkan jika tidak ada. Dan Anda ingin melakukannya hanya dalam satu permintaan (untuk menghindari menggunakan transaksi). Ini mungkin solusi:

INSERT INTO my_table (id, some_column_id, some_type, some_value)
SELECT t.id, t.some_column_id, t.some_type, t.some_value
FROM (
    SELECT id, some_column_id, some_type, some_value
    FROM my_table
    WHERE some_column_id = ? AND some_type = ?
    UNION ALL
    SELECT s.id, s.some_column_id, s.some_type, s.some_value
    FROM (SELECT NULL AS id, ? AS some_column_id, ? AS some_type, ? AS some_value) AS s
) AS t
LIMIT 1
ON DUPLICATE KEY UPDATE
some_value = ?

Pada dasarnya, kueri mengeksekusi dengan cara ini (tidak sesulit kelihatannya):

  • Pilih baris yang ada melalui WHEREpencocokan klausa.
  • Menyatukan yang menghasilkan dengan baris baru yang potensial (tabel s), di mana nilai kolom secara eksplisit diberikan (s.id adalah NULL, sehingga akan menghasilkan pengidentifikasi kenaikan otomatis baru).
  • Jika baris yang ada ditemukan, maka baris baru yang potensial dari tabel sakan dibuang (karena LIMIT 1 pada tabel t), dan itu akan selalu memicu ON DUPLICATE KEYyang akan UPDATEmenjadi some_valuekolom.
  • Jika baris yang ada tidak ditemukan, maka baris baru yang potensial dimasukkan (seperti yang diberikan oleh tabel s).

Catatan: Setiap tabel dalam database relasional harus memiliki setidaknya idkolom kenaikan otomatis utama . Jika Anda tidak memiliki ini, tambahkan, bahkan ketika Anda tidak membutuhkannya pada pandangan pertama. Pasti diperlukan untuk "trik" ini.

Yeti
sumber
Beberapa penjawab lain telah mengajukan INSERT INTO ... SELECT FROMformat. Kenapa kamu juga?
warren
2
@warren Entah Anda tidak membaca jawaban saya, Anda tidak memahaminya, atau saya tidak menjelaskannya dengan benar. Bagaimanapun, izinkan saya menekankan hal berikut: ini bukan hanya INSERT INTO... SELECT FROM...solusi biasa . Silakan merujuk kepada saya tautan ke jawaban yang sama, jika Anda dapat menemukannya saya akan menghapus jawaban ini, jika tidak, Anda membatalkan jawaban saya (kesepakatan?). Pastikan untuk memverifikasi bahwa jawaban yang akan Anda tautkan hanya menggunakan 1 kueri (untuk pembaruan + sisipan), tidak ada transaksi, dan mampu menargetkan kombinasi kolom yang dikenal unik (jadi, secara terpisah kolom tidak harus unik).
Yeti