MySQL ON DUPLICATE KEY - ID de la dernière insertion?

132

J'ai la requête suivante:

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

Je veux l'ID de l'insertion ou de la mise à jour. Habituellement, j'exécute une deuxième requête pour obtenir cela car je pense que insert_id () ne renvoie que l'ID «inséré» et non l'ID mis à jour.

Existe-t-il un moyen d'insérer / mettre à jour et de récupérer l'ID de la ligne sans exécuter deux requêtes?

thekevinscott
la source
3
Plutôt que de supposer, pourquoi ne pas le tester vous-même? Le SQL dans la modification ci-dessus fonctionne et, grâce à mes tests, est plus rapide que de détecter un échec d'insertion, en utilisant INSERT IGNORE ou en sélectionnant pour voir s'il y a d'abord un doublon.
Michael Fenwick
4
AVERTISSEMENT: la solution proposée fonctionne, mais la valeur auto_increment continue de s'incrémenter, même s'il n'y a pas d'insertion. Si la clé en double se produit souvent, vous souhaiterez peut-être l'exécuter alter table tablename AUTO_INCREMENT = 0;après la requête ci-dessus, pour éviter de grandes lacunes dans vos valeurs d'identifiant.
Frank Forte

Réponses:

175

Consultez cette page: https://web.archive.org/web/20150329004325/https://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
Au bas de la page ils expliquent comment vous pouvez rendre LAST_INSERT_ID significatif pour les mises à jour en passant une expression à cette fonction MySQL.

À partir de l'exemple de documentation MySQL:

Si une table contient une colonne AUTO_INCREMENT et INSERT ... UPDATE insère une ligne, la fonction LAST_INSERT_ID () renvoie la valeur AUTO_INCREMENT. Si l'instruction met à jour une ligne à la place, LAST_INSERT_ID () n'a pas de sens. Toutefois, vous pouvez contourner ce problème en utilisant LAST_INSERT_ID (expr). Supposons que id est la colonne AUTO_INCREMENT. Pour rendre LAST_INSERT_ID () significatif pour les mises à jour, insérez des lignes comme suit:

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;
Fredrik
la source
2
D'une manière ou d'une autre, j'ai raté cela en regardant cette page. Ainsi, la partie mise à jour apparaît comme: UPDATE id = LAST_INSERT_ID (id) Et cela fonctionne très bien. Merci!
thekevinscott
7
On dit que la fonction php mysql_insert_id () renvoie la valeur correcte dans les deux cas: php.net/manual/en/function.mysql-insert-id.php#59718 .
jayarjo
2
@PetrPeller - eh bien, sans regarder les internes de MySQL, cela signifie probablement qu'il produira une valeur, mais cette valeur n'est pas liée à la requête que vous venez d'exécuter. En d'autres termes, un problème difficile à déboguer.
Jason
13
Après la version 5.1.12, ce n'est plus nécessaire, mais j'ai trouvé une exception à cela aujourd'hui. Si vous avez un pk d'auto-incrémentation, et une clé unique sur une adresse e-mail, disons, et les déclencheurs 'sur mise à jour en double' basés sur l'adresse e-mail, notez que last_insert_id 'ne sera PAS la valeur d'auto-incrémentation de la ligne mise à jour. Il semble que ce soit la valeur d'auto-incrémentation la plus récemment insérée. Cela fait une énorme différence. Le contournement est le même que celui illustré ici, à savoir utiliser id = LAST_INSERT_ID (id) dans la requête de mise à jour.
sckd
1
Sur 5.5, le commentaire de @ sckd est toujours vrai.
e18r
37

Pour être exact, s'il s'agit de la requête d'origine:

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

et 'id' est la clé primaire auto-incrémentée que ce serait la solution de travail:

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

Tout est ici: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

Si une table contient une colonne AUTO_INCREMENT et INSERT ... UPDATE insère une ligne, la fonction LAST_INSERT_ID () renvoie la valeur AUTO_INCREMENT. Si l'instruction met à jour une ligne à la place, LAST_INSERT_ID () n'a pas de sens. Toutefois, vous pouvez contourner ce problème en utilisant LAST_INSERT_ID (expr). Supposons que id est la colonne AUTO_INCREMENT.

Aleksandar Popovic
la source
7
Oui, voyez la réponse acceptée pour la même chose que vous avez dit. Pas besoin de faire revivre les posts de 3 ans. Merci pour vos efforts quand même.
fancyPants
1
@tombom la seule raison pour laquelle j'ai publié cette réponse est que la réponse acceptée n'est pas correcte - cela ne fonctionnera pas s'il n'y a rien à mettre à jour.
Aleksandar Popovic
2

Vous pouvez regarder REMPLACER, qui est essentiellement une suppression / insertion si l'enregistrement existe. Mais cela modifierait le champ d'incrémentation automatique s'il était présent, ce qui pourrait rompre les relations avec d'autres données.

Brent Baisley
la source
1
Ah ouais - Je cherche quelque chose qui ne se débarrassera pas des identifiants précédents
thekevinscott
Cela peut également être dangereux car cela peut également entraîner la suppression d'autres données associées (par contraintes).
Serge
1

J'ai rencontré un problème, lorsque ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID (id) incrémente la clé primaire de 1. Ainsi, l'identifiant de la prochaine entrée dans la session sera incrémenté de 2

Oleksii Zymovets
la source
0

Il est intéressant de noter, et cela peut être évident (mais je le dirai quand même pour plus de clarté ici), que REPLACE supprimera la ligne correspondante existante avant d'insérer vos nouvelles données. ON DUPLICATE KEY UPDATE ne met à jour que les colonnes que vous spécifiez et préserve la ligne.

À partir du manuel :

REPLACE fonctionne exactement comme INSERT, sauf que si une ancienne ligne de la table a la même valeur qu'une nouvelle ligne pour une PRIMARY KEY ou un index UNIQUE, l'ancienne ligne est supprimée avant l'insertion de la nouvelle ligne.

Greg K
la source
0

Les solutions existantes fonctionnent si vous utilisez l'auto-incrémentation. J'ai une situation où l'utilisateur peut définir un préfixe et il devrait redémarrer la séquence à 3000. En raison de ce préfixe varié, je ne peux pas utiliser l'auto-incrémentation, ce qui rend last_insert_id vide pour les insertions. Je l'ai résolu avec ce qui suit:

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 le préfixe existe, il l'incrémentera et remplira last_insert_id. Si le préfixe n'existe pas, il insérera le préfixe avec la valeur 3000 et remplira last_insert_id avec 3000.

Aaron
la source