MySQL ON DUPLICATE KEY - последний идентификатор вставки?

132

У меня есть следующий запрос:

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

Мне нужен идентификатор вставки или обновления. Обычно я запускаю второй запрос, чтобы получить это, поскольку считаю, что insert_id () возвращает только «вставленный» идентификатор, а не обновленный идентификатор.

Есть ли способ INSERT / UPDATE и получить идентификатор строки без выполнения двух запросов?

thekevinscott
источник
3
Вместо того чтобы предполагать, почему бы вам не проверить это самостоятельно? SQL в приведенном выше редактировании действительно работает, и мое тестирование выполняется быстрее, чем обнаружение ошибки вставки, использование INSERT IGNORE или выбор, чтобы сначала увидеть, есть ли дубликат.
Майкл Фенвик
4
ПРЕДУПРЕЖДЕНИЕ. Предлагаемое решение работает, но значение auto_increment продолжает увеличиваться, даже если вставка отсутствует. Если повторяющийся ключ встречается часто, вы можете запустить его alter table tablename AUTO_INCREMENT = 0;после вышеуказанного запроса, чтобы избежать больших пробелов в значениях идентификаторов.
Фрэнк Форте,

Ответы:

175

Проверьте эту страницу: https://web.archive.org/web/20150329004325/https://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
Внизу страницы они объясняют, как можно сделать LAST_INSERT_ID значимым для обновлений, передав выражение этой функции MySQL.

Из примера документации MySQL:

Если таблица содержит столбец AUTO_INCREMENT и INSERT ... UPDATE вставляет строку, функция LAST_INSERT_ID () возвращает значение AUTO_INCREMENT. Если вместо этого оператор обновляет строку, LAST_INSERT_ID () не имеет смысла. Однако вы можете обойти это, используя LAST_INSERT_ID (expr). Предположим, что id - это столбец AUTO_INCREMENT. Чтобы сделать LAST_INSERT_ID () значимым для обновлений, вставьте строки следующим образом:

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;
Фредрик
источник
2
Как-то я пропустил это, когда посмотрел на ту страницу. Таким образом, часть обновления выглядит так: UPDATE id = LAST_INSERT_ID (id) И это отлично работает. Спасибо!
thekevinscott
7
Говорят, что функция php mysql_insert_id () возвращает правильное значение в обоих случаях: php.net/manual/en/function.mysql-insert-id.php#59718 .
jayarjo
2
@PetrPeller - ну, если не смотреть на внутренности MySQL, это, вероятно, означает, что он выдаст значение, но это значение не связано с только что выполненным запросом. Другими словами, проблема, которую трудно отладить.
Джейсон
13
После 5.1.12 в этом якобы больше нет необходимости, однако сегодня я нашел исключение. Если у вас есть автоинкремент pk и уникальный ключ, скажем, на адресе электронной почты, и триггеры «при дублировании обновления» на основе адреса электронной почты, обратите внимание, что last_insert_id НЕ будет значением автоинкремента для обновленной строки. Похоже, это последнее добавленное значение автоинкремента. Это имеет огромное значение. Обход такой же, как показано здесь, а именно использование id = LAST_INSERT_ID (id) в запросе обновления.
sckd
1
На 5.5 комментарий @ sckd по-прежнему актуален.
e18r 08
37

Если быть точным, если это исходный запрос:

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

а id - это первичный ключ с автоинкрементом, чем это было бы рабочее решение:

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

Все здесь: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

Если таблица содержит столбец AUTO_INCREMENT и INSERT ... UPDATE вставляет строку, функция LAST_INSERT_ID () возвращает значение AUTO_INCREMENT. Если вместо этого оператор обновляет строку, LAST_INSERT_ID () не имеет смысла. Однако вы можете обойти это, используя LAST_INSERT_ID (expr). Предположим, что id - это столбец AUTO_INCREMENT.

Александар Попович
источник
7
Да, см. Принятый ответ на то же, что вы сказали. Не нужно реанимировать посты трехлетней давности. В любом случае спасибо за ваши усилия.
fancyPants 04
1
@tombom единственная причина, по которой я опубликовал этот ответ, заключается в том, что принятый ответ неверен - он не будет работать, если нечего обновлять.
Aleksandar Popovic
2

Вы можете посмотреть REPLACE, который по сути является удалением / вставкой, если запись существует. Но это изменит поле автоматического увеличения, если оно есть, что может нарушить отношения с другими данными.

Брент Бэйсли
источник
1
Ах да - я ищу что-то, что не избавит от предыдущих удостоверений личности
thekevinscott
Это может быть опасно еще и потому, что это может также вызвать удаление других связанных данных (по ограничениям).
Serge
1

Я столкнулся с проблемой, когда ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID (id) увеличивает первичный ключ на 1. Таким образом, идентификатор следующего ввода в сеансе будет увеличен на 2

Алексей Зымовец
источник
0

Стоит отметить, и это может быть очевидно (но я все равно скажу это для ясности здесь), что REPLACE удалит существующую совпадающую строку перед вставкой ваших новых данных. ON DUPLICATE KEY UPDATE обновит только указанные вами столбцы и сохранит строку.

Из руководства :

REPLACE работает точно так же, как INSERT, за исключением того, что если старая строка в таблице имеет то же значение, что и новая строка для PRIMARY KEY или индекса UNIQUE, старая строка удаляется перед вставкой новой строки.

Грег К
источник
0

Существующие решения работают, если вы используете автоинкремент. У меня есть ситуация, когда пользователь может определить префикс, и он должен перезапустить последовательность с 3000. Из-за этого разнообразного префикса я не могу использовать автоинкремент, который делает last_insert_id пустым для вставок. Я решил это следующим образом:

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

Если префикс существует, он будет увеличивать его и заполнять last_insert_id. Если префикс не существует, он вставит префикс со значением 3000 и заполнит last_insert_id значением 3000.

Аарон
источник