MySQL ON DUPLICATE KEY - id sisipan terakhir?

132

Saya memiliki pertanyaan berikut:

INSERT INTO table (a) VALUES (0)
  ON DUPLICATE KEY UPDATE a=1

Saya ingin ID dari insert atau pembaruan. Biasanya saya menjalankan kueri kedua untuk mendapatkan ini karena saya percaya insert_id () hanya mengembalikan ID yang 'dimasukkan' dan bukan ID yang diperbarui.

Apakah ada cara untuk INSERT / UPDATE dan mengambil ID dari baris tanpa menjalankan dua query?

thekevinscott
sumber
3
Daripada seandainya, mengapa Anda tidak mengujinya sendiri? SQL dalam edit di atas tidak berfungsi, dan melalui pengujian saya lebih cepat daripada menangkap gagal penyisipan, menggunakan INSERT IGNORE, atau memilih untuk melihat apakah ada duplikat terlebih dahulu.
Michael Fenwick
4
PERINGATAN: Solusi yang diusulkan berfungsi, tetapi nilai auto_increment terus bertambah, bahkan jika tidak ada sisipan. Jika kunci duplikat sering terjadi, Anda mungkin ingin menjalankan alter table tablename AUTO_INCREMENT = 0;setelah permintaan di atas, untuk menghindari kesenjangan besar dalam nilai id Anda.
Frank Forte

Jawaban:

175

Lihat halaman ini: https://web.archive.org/web/20150329004325/https://dev.mysql.com/doc/refman/5.0/id/insert-on-duplicate.html
Di bagian bawah halaman mereka menjelaskan bagaimana Anda dapat membuat LAST_INSERT_ID bermakna untuk pembaruan dengan memberikan ekspresi ke fungsi MySQL itu.

Dari contoh dokumentasi MySQL:

Jika sebuah tabel berisi kolom AUTO_INCREMENT dan INSERT ... UPDATE menyisipkan baris, fungsi LAST_INSERT_ID () mengembalikan nilai AUTO_INCREMENT. Jika pernyataan memperbarui satu baris sebagai gantinya, LAST_INSERT_ID () tidak bermakna. Namun, Anda dapat mengatasinya dengan menggunakan LAST_INSERT_ID (expr). Misalkan id adalah kolom AUTO_INCREMENT. Untuk membuat LAST_INSERT_ID () bermakna untuk pembaruan, masukkan baris sebagai berikut:

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;
fredrik
sumber
2
Entah bagaimana saya melewatkannya ketika melihat halaman itu. Jadi bagian pembaruan muncul sebagai: UPDATE id = LAST_INSERT_ID (id) Dan itu berfungsi dengan baik. Terima kasih!
thekevinscott
7
Dikatakan bahwa fungsi php mysql_insert_id () mengembalikan nilai yang benar dalam kedua kasus: php.net/manual/en/function.mysql-insert-id.php#59718 .
jayarjo
2
@PetrPeller - yah, tanpa melihat internal MySQL, itu mungkin berarti akan menghasilkan nilai, tetapi nilai itu tidak terkait dengan permintaan yang baru saja Anda jalankan. Dengan kata lain, masalah yang merepotkan adalah debug.
Jason
13
Setelah 5.1.12 ini seharusnya tidak lagi diperlukan, namun saya menemukan pengecualian untuk hari ini. Jika Anda memiliki pk peningkatan otomatis, dan kunci unik saat mengatakan alamat email, dan pemicu 'saat pembaruan duplikat' berdasarkan alamat email, perhatikan bahwa last_insert_id 'TIDAK akan menjadi nilai peningkatan otomatis dari baris yang diperbarui. Tampaknya merupakan nilai autoincrement yang baru saja disisipkan. Ini membuat perbedaan besar. Cara mengatasinya sama seperti yang ditunjukkan di sini, yaitu menggunakan id = LAST_INSERT_ID (id) dalam permintaan pembaruan.
sckd
1
Pada 5.5 @ komentar sckd masih berlaku.
e18r
37

Tepatnya, jika ini adalah permintaan asli:

INSERT INTO table (a) VALUES (0)
 ON DUPLICATE KEY UPDATE a=1

dan 'id' adalah kunci utama kenaikan-otomatis daripada ini yang akan menjadi solusi yang berfungsi:

INSERT INTO table (a) VALUES (0)
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), a=1

Semuanya ada di sini: http://dev.mysql.com/doc/refman/5.0/id/insert-on-duplicate.html

Jika sebuah tabel berisi kolom AUTO_INCREMENT dan INSERT ... UPDATE menyisipkan baris, fungsi LAST_INSERT_ID () mengembalikan nilai AUTO_INCREMENT. Jika pernyataan memperbarui satu baris sebagai gantinya, LAST_INSERT_ID () tidak bermakna. Namun, Anda dapat mengatasinya dengan menggunakan LAST_INSERT_ID (expr). Misalkan id adalah kolom AUTO_INCREMENT.

Aleksandar Popovic
sumber
7
Ya, lihat jawaban yang diterima untuk apa yang Anda katakan. Tidak perlu menghidupkan kembali posting lama 3 tahun. Terima kasih atas usaha Anda.
FancyPants
1
@ Tomombom satu-satunya alasan mengapa saya memposting jawaban ini adalah karena jawaban yang diterima tidak benar - itu tidak akan berfungsi jika tidak ada yang diperbarui.
Aleksandar Popovic
2

Anda mungkin melihat REPLACE, yang pada dasarnya adalah menghapus / menyisipkan jika catatan ada. Tetapi ini akan mengubah bidang kenaikan otomatis jika ada, yang dapat memutuskan hubungan dengan data lain.

Brent Baisley
sumber
1
Ah yeah - Saya sedang mencari sesuatu yang tidak akan menghilangkan ID sebelumnya
thekevinscott
Ini mungkin berbahaya juga karena ini juga dapat menyebabkan penghapusan data terkait lainnya (oleh kendala).
Serge
1

Saya telah menemukan masalah, ketika ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID (id) menambah kunci primer dengan 1. Jadi id dari input selanjutnya dalam sesi akan bertambah 2

Oleksii Zymovets
sumber
0

Perlu dicatat, dan ini mungkin jelas (tapi saya akan mengatakannya untuk kejelasan di sini), bahwa REPLACE akan menghilangkan baris pencocokan yang ada sebelum memasukkan data baru Anda. PADA UPDATE KUNCI DUPLICATE hanya akan memperbarui kolom yang Anda tentukan dan mempertahankan baris.

Dari manual :

REPLACE bekerja persis seperti INSERT, kecuali bahwa jika baris lama dalam tabel memiliki nilai yang sama dengan baris baru untuk KUNCI UTAMA atau indeks UNIK, baris lama dihapus sebelum baris baru dimasukkan.

Greg K.
sumber
0

Solusi yang ada berfungsi jika Anda menggunakan peningkatan otomatis. Saya memiliki situasi di mana pengguna dapat menetapkan awalan dan harus memulai ulang urutan pada 3000. Karena awalan yang bervariasi ini, saya tidak dapat menggunakan autoincrement, yang membuat last_insert_id kosong untuk dimasukkan. Saya menyelesaikannya dengan yang berikut:

INSERT INTO seq_table (prefix, id) VALUES ('$user_prefix', LAST_INSERT_ID(3000)) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id + 1);
SELECT LAST_INSERT_ID();

Jika awalan ada, itu akan menambahnya dan mengisi last_insert_id. Jika awalan tidak ada, itu akan menyisipkan awalan dengan nilai 3000 dan mengisi last_insert_id dengan 3000.

Harun
sumber