MySQL ON DUPLICATE KEY - ID ultimo inserimento?


132

Ho la seguente domanda:

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

Voglio l'ID dell'inserto o dell'aggiornamento. Di solito eseguo una seconda query per ottenerlo poiché credo che insert_id () restituisca solo l'ID "inserito" e non l'ID aggiornato.

C'è un modo per INSERIRE / AGGIORNARE e recuperare l'ID della riga senza eseguire due query?


3
Invece di supporre, perché non lo provi tu stesso? L'SQL nella modifica sopra funziona e, attraverso i miei test, è più veloce della rilevazione di un errore di inserimento, usando INSERT IGNORE o selezionando per vedere se c'è prima un duplicato.
Michael Fenwick,

4
ATTENZIONE: la soluzione proposta funziona, ma il valore auto_increment continua ad aumentare, anche se non è presente alcun inserimento. Se la chiave duplicata si verifica spesso, è possibile che si desideri eseguire alter table tablename AUTO_INCREMENT = 0;la query precedente per evitare grandi lacune nei valori ID.
Frank Forte,

Risposte:


175

Dai un'occhiata a questa pagina: https://web.archive.org/web/20150329004325/https://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
Nella parte inferiore della pagina spiegano come rendere LAST_INSERT_ID significativo per gli aggiornamenti passando un'espressione a quella funzione MySQL.

Dall'esempio di documentazione di MySQL:

Se una tabella contiene una colonna AUTO_INCREMENT e INSERT ... UPDATE inserisce una riga, la funzione LAST_INSERT_ID () restituisce il valore AUTO_INCREMENT. Se invece l'istruzione aggiorna una riga, LAST_INSERT_ID () non è significativo. Tuttavia, è possibile aggirare il problema utilizzando LAST_INSERT_ID (expr). Supponiamo che id sia la colonna AUTO_INCREMENT. Per rendere significativo LAST_INSERT_ID () per gli aggiornamenti, inserire le righe come segue:

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

2
In qualche modo me lo sono perso guardando quella pagina. Quindi la parte di aggiornamento appare come: UPDATE id = LAST_INSERT_ID (id) e funziona alla grande. Grazie!
thekevinscott,

7
Si dice che la funzione php mysql_insert_id () restituisca il valore corretto in entrambi i casi: php.net/manual/en/function.mysql-insert-id.php#59718 .
jayarjo,

2
@PetrPeller - beh, senza guardare gli interni di MySQL, probabilmente significa che produrrà un valore, ma quel valore non è correlato alla query che hai appena eseguito. In altre parole, un problema che rende difficile il debug.
Jason,

13
Dopo il 5.1.12 questo non è più necessario, tuttavia oggi ho trovato un'eccezione. Se si dispone di un pk di incremento automatico e di una chiave univoca su un indirizzo e-mail e il "su aggiornamento duplicato" si innesca in base all'indirizzo e-mail, tenere presente che last_insert_id "NON sarà il valore di incremento automatico della riga aggiornata. Sembra essere l'ultimo valore di autoincremento inserito. Questo fa un'enorme differenza. La soluzione è la stessa mostrata qui, vale a dire usare id = LAST_INSERT_ID (id) nella query di aggiornamento.
sckd,

1
Su 5.5 il commento di sckd è ancora vero.
e18r

37

Per l'esattezza, se questa è la query originale:

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

e 'id' è la chiave primaria di auto-incremento di questa sarebbe la soluzione funzionante:

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

È tutto qui: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

Se una tabella contiene una colonna AUTO_INCREMENT e INSERT ... UPDATE inserisce una riga, la funzione LAST_INSERT_ID () restituisce il valore AUTO_INCREMENT. Se invece l'istruzione aggiorna una riga, LAST_INSERT_ID () non è significativo. Tuttavia, è possibile aggirare il problema utilizzando LAST_INSERT_ID (expr). Supponiamo che id sia la colonna AUTO_INCREMENT.


7
Sì, vedi la risposta accettata per lo stesso che hai detto. Non è necessario ripristinare i post di 3 anni. Grazie comunque per il tuo impegno.
fancyPants

1
@tombom l'unico motivo per cui ho pubblicato questa risposta è perché la risposta accettata non è corretta - non funzionerà se non c'è nulla da aggiornare.
Aleksandar Popovic,

2

Potresti guardare REPLACE, che è essenzialmente un elimina / inserisci se il record esiste. Ciò cambierebbe il campo di incremento automatico, se presente, che potrebbe interrompere le relazioni con altri dati.


1
Ah sì - sto cercando qualcosa che non si sbarazzerà degli ID precedenti
thekevinscott

Ciò può essere pericoloso anche perché può anche causare la cancellazione di altri dati correlati (per vincoli).
Serge,


1

Ho riscontrato un problema, quando ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID (id) incrementa la chiave primaria di 1. Quindi l'ID dell'input successivo all'interno della sessione verrà incrementato di 2


0

Vale la pena notare, e questo potrebbe essere ovvio (ma lo dirò comunque per chiarezza qui), che REPLACE eliminerà la riga corrispondente esistente prima di inserire i tuoi nuovi dati. ON DUPLICATE KEY UPDATE aggiorna solo le colonne specificate e conserva la riga.

Dal manuale :

REPLACE funziona esattamente come INSERT, tranne per il fatto che se una vecchia riga nella tabella ha lo stesso valore di una nuova riga per un PRIMARY KEY o un indice UNIQUE, la vecchia riga viene eliminata prima di inserire la nuova riga.


0

Le soluzioni esistenti funzionano se si utilizza l'incremento automatico. Ho una situazione in cui l'utente può definire un prefisso e dovrebbe riavviare la sequenza a 3000. A causa di questo variegato prefisso, non posso usare l'autoincrement, il che rende last_insert_id vuoto per gli inserti. L'ho risolto con il seguente:

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();

Se il prefisso esiste, lo incrementerà e popolerà last_insert_id. Se il prefisso non esiste, inserirà il prefisso con il valore 3000 e popolerà last_insert_id con 3000.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.