Melhor maneira de inserir a última identidade em uma tabela

35

Qual é a melhor opção para obter o valor da identidade que acabei de gerar por meio de uma inserção? Qual é o impacto dessas declarações em termos de desempenho?

  1. SCOPE_IDENTITY()
  2. Função agregada MAX()
  3. SELECT TOP 1IdentityColumn FROM TableNameORDER BY IdentityColumn DESC
AA.SC
fonte
11
Use o postgreSQL e você o encontrará na prateleira postgresql.org/docs/9.1/static/sql-insert.html
Yevgeniy Afanasyev
Opção Campo Esquerdo - se você possui uma coluna Guid na tabela e pode gerar um novo Guid e inseri-lo na nova coluna durante a inserção - é possível selecionar a linha com esse Guid para extrair a identidade int gerada.
Niico 12/1118

Respostas:

56

UseSCOPE_IDENTITY() se você estiver inserindo uma única linha e quiser recuperar o ID que foi gerado.

CREATE TABLE #a(identity_column INT IDENTITY(1,1), x CHAR(1));

INSERT #a(x) VALUES('a');

SELECT SCOPE_IDENTITY();

Resultado:

----
1

Use a OUTPUTcláusula se estiver inserindo várias linhas e precisar recuperar o conjunto de IDs que foram gerados.

INSERT #a(x) 
  OUTPUT inserted.identity_column 
  VALUES('b'),('c');

Resultado:

----
2
3

e por que essa é a melhor opção mais rápida?

Além do desempenho, esses são os únicos que garantem a correção no nível de isolamento padrão e / ou com vários usuários. Mesmo se você ignorar o aspecto de correção, o SQL Server retém o valor inserido SCOPE_IDENTITY()na memória, portanto, naturalmente, isso será mais rápido do que executar e executar sua própria consulta isolada na tabela ou nas tabelas do sistema.

Ignorar o aspecto de correção é como dizer ao carteiro que ele fez um bom trabalho entregando as correspondências de hoje - ele terminou sua rota 10 minutos mais rápido que o tempo médio, o problema é que nenhuma das correspondências foi entregue na casa certa.

Não use nenhum dos seguintes:

  • @@IDENTITY - como isso não pode ser usado em todos os cenários, por exemplo, quando uma tabela com uma coluna de identidade possui um gatilho que também é inserido em outra tabela com sua própria coluna de identidade - você receberá o valor errado.
  • IDENT_CURRENT()- Entro em detalhes sobre isso aqui , e os comentários também são úteis para leitura, mas essencialmente, com a concordância, você frequentemente obterá a resposta errada.
  • MAX()ou TOP 1- você teria que proteger as duas instruções com isolamento serializável para garantir que o que MAX()você recebe não seja de outra pessoa. Isso é muito mais caro do que apenas usar SCOPE_IDENTITY().

Essas funções também falham sempre que você insere duas ou mais linhas e precisam de todos os valores de identidade gerados - sua única opção é a OUTPUTcláusula.

Aaron Bertrand
fonte
Mais uma pergunta desencadeada em minha memória. Sempre que precisamos obter a última identidade gerada em uma tabela específica por qualquer sessão ou usuário, a única maneira correta e melhor é MAX () dessa coluna?
AA.SC
que tal quando eu inserir várias linhas em uma tabela SCOPE_IDENTITY () sempre retornará a última identidade gerada? E se a coluna for Chave primária, mas não Identidade?
AA.SC
@ AA.SC sim, ele retornará o último. Se não for uma coluna de identidade, não, nenhuma dessas funções funcionará. De onde vem o valor PK nesse caso?
Aaron Bertrand
Eu já vi isso em uma coluna em nosso aplicativo, em que a coluna é do tipo INT, e os desenvolvedores usam MAX (nome da coluna) +1 sempre que precisam inserir um novo registro
AA.SC
Então eles já sabem qual valor eles acabaram de inserir. O SQL Server não tem como dizer isso (você não pode confiar em puxar o MAX novamente depois do fato, a menos que tenha isolado completamente toda a transação, o que não será bom para desempenho ou simultaneidade).
Aaron Bertrand
7

Além do desempenho, todos eles têm significados bastante diferentes.

SCOPE_IDENTITY()fornecerá o último valor de identidade inserido em qualquer tabela diretamente dentro do escopo atual (escopo = lote, procedimento armazenado, etc., mas não dentro, por exemplo, de um gatilho que foi disparado pelo escopo atual).

IDENT_CURRENT()fornecerá o último valor de identidade inserido em uma tabela específica de qualquer escopo, por qualquer usuário.

@@IDENTITYfornece o último valor de identidade gerado pela instrução INSERT mais recente para a conexão atual, independentemente da tabela ou escopo. (Observação: o Access usa essa função e, portanto, tem alguns problemas com gatilhos que inserem valores em tabelas com colunas de identidade.)

Usar MAX()ou TOP 1pode fornecer resultados totalmente errados se a tabela tiver uma etapa de identidade negativa ou se houver linhas inseridas SET IDENTITY_INSERTem execução. Aqui está um script demonstrando tudo isso:

CREATE TABLE ReverseIdent (
    id int IDENTITY(9000,-1) NOT NULL PRIMARY KEY CLUSTERED,
    data char(4)
)

INSERT INTO ReverseIdent (data)
VALUES ('a'), ('b'), ('c')

SELECT * FROM ReverseIdent

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9000

SET IDENTITY_INSERT ReverseIdent ON

INSERT INTO ReverseIdent (id, data)
VALUES (9005, 'd')

SET IDENTITY_INSERT ReverseIdent OFF

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9005

Resumo: vara com SCOPE_IDENTITY(), IDENT_CURRENT()ou @@IDENTITY, e certifique-se que você está usando o que retorna o que você realmente precisa.

db2
fonte
11
Por que você incentiva o uso IDENT_CURRENT()e @@IDENTITYquando seu próprio script demonstra que eles produzem resultados incorretos?
Aaron Bertrand
11
@AaronBertrand Não tenho certeza se sigo. O último valor de identidade gerado foi 8998 (observe que a etapa é -1), e é isso que IDENT_CURRENT()retorna. MAX () nunca retorna o valor correto além da primeira linha, pois o id está contando para trás e, com IDENTITY_INSERTativado, 9005 não é um valor de identidade gerado , portanto, não é refletido por IDENT_CURRENT(). Mas pode retornar resultados "incorretos" se você realmente estiver atrás do que SCOPE_IDENTITY()retorna. Escolha a ferramenta certa para o trabalho.
db2
O OP parece estar após o valor de identidade que eles inseriram - nesse caso, 8998 está incorreto. Os casos extremos que você menciona (incremento para trás e IDENTITY_INSERT ativado ) argumentam ainda mais contra o uso de IDENT_CURRENT na minha opinião, e @@ IDENTITY nunca deve realmente ser usado devido ao perigo de gatilhos (agora ou adicionado posteriormente). Ainda estou lutando para entender por que IDENT_CURRENT seria aquele que o OP gostaria de usar (especialmente em simultâneo) ou por que @@ IDENTITY seria usado por alguém quando existem métodos muito mais confiáveis.
Aaron Bertrand
@AaronBertrand Não está 100% claro da questão de que o resultado desejado é a última inserção do escopo atual (a opção 1 difere de 2 e 3 a esse respeito), então achei que seria uma boa ideia descrever ambos e como eles diferem. Mas concordo que @@IDENTITYquase nunca é a maneira ideal de gerar valores de identidade gerados. O ponto principal é que MAX()ou TOP 1são como uma versão menos confiável IDENT_CURRENT(), que é uma função perfeitamente adequada a ser usada se você entender o que faz. Pode ser útil para trabalhos de manutenção ou algo assim.
DB2