MySQL ON DUPLICATE KEY - última inserção id?


132

Eu tenho a seguinte consulta:

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

Quero o ID da inserção ou da atualização. Normalmente, eu executo uma segunda consulta para obter isso, pois acredito que insert_id () retorna apenas o ID 'inserido' e não o ID atualizado.

Existe uma maneira de INSERT / UPDATE e recuperar o ID da linha sem executar duas consultas?


3
Em vez de supor, por que você não prova? O SQL na edição acima funciona e, através dos meus testes, é mais rápido do que detectar uma falha de inserção, usando INSERT IGNORE ou selecionando para ver se há uma duplicata primeiro.
Michael Fenwick

4
AVISO: A solução proposta funciona, mas o valor de incremento automático continua a aumentar, mesmo se não houver inserção. Se a chave duplicada acontecer com frequência, convém executar alter table tablename AUTO_INCREMENT = 0;a consulta acima para evitar grandes lacunas nos valores de seu ID.
Frank Forte

Respostas:


175

Verifique esta página: https://web.archive.org/web/20150329004325/https://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
Na parte inferior da página eles explicam como você pode tornar LAST_INSERT_ID significativo para atualizações, passando uma expressão para essa função do MySQL.

No exemplo de documentação do MySQL:

Se uma tabela contiver uma coluna AUTO_INCREMENT e INSERT ... UPDATE inserir uma linha, a função LAST_INSERT_ID () retornará o valor AUTO_INCREMENT. Se a instrução atualizar uma linha, LAST_INSERT_ID () não fará sentido. No entanto, você pode contornar isso usando LAST_INSERT_ID (expr). Suponha que id seja a coluna AUTO_INCREMENT. Para tornar LAST_INSERT_ID () significativo para atualizações, insira linhas da seguinte maneira:

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

2
De alguma forma, eu perdi isso ao olhar para essa página. Portanto, a parte da atualização aparece como: UPDATE id = LAST_INSERT_ID (id) E isso funciona muito bem. Obrigado!
thekevinscott

7
Dizem que a função php mysql_insert_id () retorna o valor correto nos dois casos: php.net/manual/en/function.mysql-insert-id.php#59718 .
Jayarjo 10/06

2
@PetrPeller - bem, sem examinar os internos do MySQL, provavelmente significa que produzirá um valor, mas esse valor não está relacionado à consulta que você acabou de executar. Em outras palavras, um problema difícil de depurar.
23413 Jason

13
Após o 5.1.12, isso supostamente não é mais necessário, no entanto, encontrei uma exceção a isso hoje. Se você tiver um código de incremento automático e uma chave exclusiva, por exemplo, um endereço de email, e o 'na atualização duplicada' disparar com base no endereço de email, observe que last_insert_id 'NÃO será o valor de incremento automático da linha atualizada. Parece ser o valor de incremento automático inserido mais recentemente. Isso faz uma grande diferença. A solução alternativa é a mesma mostrada aqui, ou seja, usar id = LAST_INSERT_ID (id) na consulta de atualização.
sckd 19/09/14

1
Em 5.5, o comentário de @ sckd ainda é verdadeiro.
e18r

37

Para ser exato, se esta é a consulta original:

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

e 'id' é a chave primária de incremento automático do que esta seria a solução de trabalho:

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

Está tudo aqui: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

Se uma tabela contiver uma coluna AUTO_INCREMENT e INSERT ... UPDATE inserir uma linha, a função LAST_INSERT_ID () retornará o valor AUTO_INCREMENT. Se a instrução atualizar uma linha, LAST_INSERT_ID () não fará sentido. No entanto, você pode contornar isso usando LAST_INSERT_ID (expr). Suponha que id seja a coluna AUTO_INCREMENT.


7
Sim, veja a resposta aceita pelo mesmo que você disse. Não há necessidade de reviver posts de 3 anos. Obrigado pelo seu esforço de qualquer maneira.
precisa saber é o seguinte

1
@ Tombom A única razão pela qual publiquei esta resposta é porque a resposta aceita não está correta - não funcionará se não houver nada para atualizar.
Aleksandar Popovic

2

Você pode olhar para REPLACE, que é essencialmente uma exclusão / inserção, se o registro existir. Mas isso alteraria o campo de incremento automático, se presente, o que poderia interromper o relacionamento com outros dados.


1
Ah, sim - Eu estou procurando algo que não vai se livrar do ID anterior
thekevinscott

Isso também pode ser perigoso, pois também pode causar a exclusão de outros dados relacionados (por restrições).
Serge


1

Eu encontrei um problema, quando ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID (id) incrementa a chave primária em 1. Portanto, o id da próxima entrada da sessão será incrementado em 2


0

Vale a pena notar, e isso pode ser óbvio (mas eu vou dizer assim mesmo para maior clareza aqui), que REPLACE irá explodir a linha correspondente existente antes de inserir seus novos dados. ON DUPLICATE KEY UPDATE atualizará apenas as colunas especificadas e preservará a linha.

Do manual :

REPLACE funciona exatamente como INSERT, exceto que, se uma linha antiga da tabela tiver o mesmo valor que uma nova linha para um índice PRIMARY KEY ou UNIQUE, a linha antiga será excluída antes da inserção da nova linha.


0

As soluções existentes funcionam se você usar o incremento automático. Eu tenho uma situação em que o usuário pode definir um prefixo e deve reiniciar a sequência em 3000. Devido a esse prefixo variado, não posso usar o incremento automático, o que torna last_insert_id vazio para inserções. Eu o resolvi com o seguinte:

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 o prefixo existir, ele será incrementado e preenchido last_insert_id. Se o prefixo não existir, ele inserirá o prefixo com o valor 3000 e preencherá last_insert_id com 3000.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.