MySQL EN LA LLAVE DUPLICADA: ¿última identificación de inserción?

132

Tengo la siguiente consulta:

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

Quiero la identificación de la inserción o la actualización. Por lo general, ejecuto una segunda consulta para obtener esto, ya que creo que insert_id () solo devuelve la ID 'insertada' y no la ID actualizada.

¿Hay alguna manera de INSERTAR / ACTUALIZAR y recuperar la ID de la fila sin ejecutar dos consultas?

thekevinscott
fuente
3
En lugar de suponer, ¿por qué no lo pruebas tú mismo? El SQL en la edición anterior funciona, y a través de mis pruebas es más rápido que detectar un error de inserción, usar INSERT IGNORE o seleccionar para ver si hay un duplicado primero.
Michael Fenwick
44
ADVERTENCIA: La solución propuesta funciona, pero el valor de auto_increment continúa incrementándose, incluso si no hay inserción. Si la clave duplicada ocurre con frecuencia, es posible que desee ejecutar alter table tablename AUTO_INCREMENT = 0;después de la consulta anterior, para evitar grandes brechas en sus valores de identificación.
Frank Forte

Respuestas:

175

Consulte esta página: https://web.archive.org/web/20150329004325/https://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
Al final de la página explican cómo puede hacer que LAST_INSERT_ID sea significativo para las actualizaciones pasando una expresión a esa función MySQL.

Del ejemplo de documentación de MySQL:

Si una tabla contiene una columna AUTO_INCREMENT e INSERT ... UPDATE inserta una fila, la función LAST_INSERT_ID () devuelve el valor AUTO_INCREMENT. Si la declaración actualiza una fila, LAST_INSERT_ID () no tiene sentido. Sin embargo, puede solucionar esto utilizando LAST_INSERT_ID (expr). Supongamos que id es la columna AUTO_INCREMENT. Para que LAST_INSERT_ID () sea significativo para las actualizaciones, inserte filas de la siguiente manera:

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;
fredrik
fuente
2
De alguna manera me perdí eso al mirar esa página. Entonces, la parte de actualización aparece como: UPDATE id = LAST_INSERT_ID (id) Y eso funciona muy bien. ¡Gracias!
thekevinscott
77
Se dice que la función php mysql_insert_id () devuelve el valor correcto en ambos casos: php.net/manual/en/function.mysql-insert-id.php#59718 .
jayarjo
2
@PetrPeller: bueno, sin mirar las partes internas de MySQL, probablemente significa que producirá un valor, pero ese valor no está relacionado con la consulta que acaba de ejecutar. En otras palabras, un problema que es difícil de depurar.
Jason
13
Después de 5.1.12 esto supuestamente ya no es necesario, sin embargo, encontré una excepción a eso hoy. Si tiene un paquete de autoincremento, y una clave única para decir una dirección de correo electrónico, y los activadores 'en actualización duplicada' se basan en la dirección de correo electrónico, tenga en cuenta que last_insert_id 'NO será el valor de autoincremento de la fila actualizada. Parece ser el valor de autoincremento insertado más recientemente. Esto hace una diferencia enorme. La solución es la misma que se muestra aquí, es decir, usar id = LAST_INSERT_ID (id) en la consulta de actualización.
sckd
1
En 5.5 el comentario de @ sckd todavía es cierto.
e18r
37

Para ser exactos, si esta es la consulta original:

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

e 'id' es la clave primaria de incremento automático de lo que sería la solución de trabajo:

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

Está todo aquí: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

Si una tabla contiene una columna AUTO_INCREMENT e INSERT ... UPDATE inserta una fila, la función LAST_INSERT_ID () devuelve el valor AUTO_INCREMENT. Si la declaración actualiza una fila, LAST_INSERT_ID () no tiene sentido. Sin embargo, puede solucionar esto utilizando LAST_INSERT_ID (expr). Supongamos que id es la columna AUTO_INCREMENT.

Aleksandar Popovic
fuente
77
Sí, vea la respuesta aceptada por lo mismo que dijo. No es necesario revivir publicaciones de 3 años. Gracias por tu esfuerzo de todos modos.
fancyPants
1
@tombom la única razón por la que publiqué esta respuesta es porque la respuesta aceptada no es correcta: no funcionará si no hay nada que actualizar.
Aleksandar Popovic
2

Puede mirar REPLACE, que es esencialmente una eliminación / inserción si existe el registro. Pero esto cambiaría el campo de incremento automático si está presente, lo que podría romper las relaciones con otros datos.

Brent Baisley
fuente
1
Ah sí, estoy buscando algo que no elimine las identificaciones anteriores
Thekevinscott
Esto también puede ser peligroso porque también puede causar la eliminación de otros datos relacionados (por restricciones).
Serge
1

Me he encontrado con un problema, cuando ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID (id) incrementa la clave primaria en 1. Por lo tanto, la id de la siguiente entrada dentro de la sesión se incrementará en 2

Oleksii Zymovets
fuente
0

Vale la pena señalar, y esto puede ser obvio (pero lo diré de todos modos para mayor claridad aquí), que REPLACE eliminará la fila coincidente existente antes de insertar sus nuevos datos. ON DUPLICATE KEY UPDATE solo actualizará las columnas que especifique y conservará la fila.

Del manual :

REPLACE funciona exactamente como INSERT, excepto que si una fila anterior de la tabla tiene el mismo valor que una fila nueva para una CLAVE PRIMARIA o un índice ÚNICO, la fila anterior se elimina antes de insertar la nueva fila.

Greg K
fuente
0

Las soluciones existentes funcionan si utiliza autoincrement. Tengo una situación en la que el usuario puede definir un prefijo y debería reiniciar la secuencia en 3000. Debido a este prefijo variado, no puedo usar autoincrement, lo que hace que last_insert_id esté vacío para las inserciones. Lo resolví con lo siguiente:

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

Si el prefijo existe, lo incrementará y completará last_insert_id. Si el prefijo no existe, insertará el prefijo con el valor 3000 y completará last_insert_id con 3000.

Aaron
fuente