Melhor maneira de obter a identidade da linha inserida?


1119

Qual é a melhor maneira de obter IDENTITYda linha inserida?

Eu sei sobre @@IDENTITYe IDENT_CURRENTe SCOPE_IDENTITYmas não entendo os prós e contras ligados a cada um.

Alguém pode explicar as diferenças e quando devo usá-las?


5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...), ou método mais antigo: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();você pode obtê-lo em c # usando ExecuteScalar ().
S.Serpooshan

4
Como isso é melhor do que as outras respostas? (também - por que você não está postando isso como uma resposta em vez de um comentário). Escreva uma resposta completa (e explique por que essa é uma opção melhor do que as postadas - se for específica da versão, diga isso).
Oded

é como um breve resumo. ; D A resposta aceita não menciona a sintaxe da cláusula OUTPUT e não possui uma amostra. Também amostras em outros lugares não são tão limpo ...
S.Serpooshan

2
@saeedserpooshan - depois edite isso. Você pode fazer isso, sabe? Veja quando essa resposta foi publicada? Isso antecede a OUTPUTcláusula no SQL Server.
Oded

Respostas:


1435
  • @@IDENTITYretorna o último valor de identidade gerado para qualquer tabela na sessão atual, em todos os escopos. Você precisa ter cuidado aqui , já que é através de escopos. Você pode obter um valor de um gatilho, em vez de sua declaração atual.

  • SCOPE_IDENTITY()retorna o último valor de identidade gerado para qualquer tabela na sessão atual e o escopo atual. Geralmente o que você deseja usar .

  • IDENT_CURRENT('tableName')retorna o último valor de identidade gerado para uma tabela específica em qualquer sessão e qualquer escopo. Isso permite especificar de qual tabela você deseja obter o valor, caso as duas acima não sejam exatamente o que você precisa ( muito raro ). Além disso, como @ Guy Starbuck mencionou, "Você pode usar isso se quiser obter o valor atual de IDENTITY para uma tabela na qual você não inseriu um registro".

  • A OUTPUTcláusula da INSERTinstrução permitirá acessar todas as linhas que foram inseridas por meio dessa instrução. Como o escopo da declaração específica é mais direto do que as outras funções acima. No entanto, é um pouco mais detalhado (você precisará inserir em uma variável de tabela / tabela temporária e depois consultá-la) e fornece resultados mesmo em um cenário de erro em que a instrução é revertida. Dito isto, se sua consulta usar um plano de execução paralelo, este é o único método garantido para obter a identidade (exceto para desativar o paralelismo). No entanto, é executado antes dos acionadores e não pode ser usado para retornar valores gerados pelo acionador.


48
bug conhecido com SCOPE_IDENTITY () retornando os valores incorretos: blog.sqlauthority.com/2009/03/24/… a solução é não executar o INSERT em um plano paralelo de multiprocessador ou usar a cláusula OUTPUT
KM.

3
Quase toda vez que eu sempre quis 'identidade', eu queria saber a (s) chave (s) do (s) registro (s) que acabei de inserir. Se essa for sua situação, você deseja usar a cláusula OUTPUT. Se você quiser algo mais, faça um esforço para ler e entender a resposta do bdukes.
Jerry

3
Com outputvocê, você não precisa criar uma tabela temporária para armazenar e consultar os resultados. Apenas deixe de fora a intoparte da cláusula de saída e ela será exibida em um conjunto de resultados.
spb

96
Para salvar os outros de panicing, O erro mencionado acima foi corrigido na atualização cumulativa 5 para o SQL Server 2008 R2 Service Pack 1.
GaTechThomas

1
@niico, acho que a recomendação é a mesma que tem sido, que OUTPUTé a "melhor", desde que você não esteja usando gatilhos e esteja lidando com erros, mas SCOPE_IDENTITYseja o mais simples e muito raramente tenha problemas
bdukes

180

Acredito que o método mais seguro e preciso de recuperar o ID inserido seria usar a cláusula de saída.

por exemplo (extraído do seguinte artigo do MSDN )

USE AdventureWorks2008R2;
GO
DECLARE @MyTableVar table( NewScrapReasonID smallint,
                           Name varchar(50),
                           ModifiedDate datetime);
INSERT Production.ScrapReason
    OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
        INTO @MyTableVar
VALUES (N'Operator error', GETDATE());

--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM @MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate 
FROM Production.ScrapReason;
GO

3
Sim, este é o método correto daqui para frente, use apenas um dos outros se você não está no SQL Server 2008 (não tomávamos 2005 então não tenho certeza se a saída estava disponível na época)
HLGEM

1
@HLGEM Há uma página MSDN para OUTPUTno SQL Server 2005 , de modo que parece que é apenas SQL Server 2000 e anteriores que estão sem ele
bdukes

6
woohoo! CLÁUSULA DE SAÍDA arrasa :) Isso simplificará minha tarefa atual. Não conhecia essa afirmação antes. Obrigado pessoal!
28411 SwissCoder

8
Para um exemplo realmente conciso para obter apenas o ID inserido, dê uma olhada em: stackoverflow.com/a/10999467/2003325
Luke

O uso do INTO com OUTPUT é uma boa ideia. Consulte: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (De um comentário aqui: stackoverflow.com/questions/7917695/… )
shlgug

112

Estou dizendo a mesma coisa que os outros caras, então todo mundo está correto, só estou tentando deixar mais claro.

@@IDENTITYretorna o ID da última coisa que foi inserida pela conexão do seu cliente com o banco de dados.
Na maioria das vezes, isso funciona bem, mas às vezes um gatilho insere uma nova linha que você não conhece e você obtém o ID dessa nova linha, em vez da que deseja

SCOPE_IDENTITY()resolve esse problema. Retorna o ID da última coisa que você inseriu no código SQL enviado ao banco de dados. Se os gatilhos criarem linhas extras, eles não farão com que o valor errado seja retornado. Hooray

IDENT_CURRENTretorna o último ID que foi inserido por qualquer pessoa. Se algum outro aplicativo inserir outra linha em um momento impreciso, você obterá o ID dessa linha em vez da sua.

Se você quiser jogar pelo seguro, use sempre SCOPE_IDENTITY(). Se você continuar @@IDENTITYe alguém decidir adicionar um gatilho mais tarde, todo o seu código será interrompido.


64

A melhor (leia-se: mais segura) maneira de obter a identidade de uma linha recém-inserida é usando a outputcláusula:

create table TableWithIdentity
           ( IdentityColumnName int identity(1, 1) not null primary key,
             ... )

-- type of this table's column must match the type of the
-- identity column of the table you'll be inserting into
declare @IdentityOutput table ( ID int )

insert TableWithIdentity
     ( ... )
output inserted.IdentityColumnName into @IdentityOutput
values
     ( ... )

select @IdentityValue = (select ID from @IdentityOutput)

5
O clustering do servidor SQL é um recurso de alta disponibilidade e não tem influência no paralelismo. É muito incomum que inserções de linha única (o caso mais comum scope_identity()) obtenham planos paralelos de qualquer maneira. E esse bug foi corrigido mais de um ano antes desta resposta.
Martin Smith

O que você quer dizer com paralelismo.
user1451111

@MartinSmith O cliente não estava disposto a permitir o tempo de inatividade no cluster de servidores para instalar a UC corrigindo esse problema (não brincando), portanto, a única solução era reescrever todo o SQL a ser usado em outputvez de scope_identity(). Eu removi o FUD sobre o agrupamento na resposta.
Ian Kemp

1
Obrigado, este é o único exemplo que pude encontrar que mostra como usar o valor da saída em uma variável em vez de apenas produzi-la.
21718 Sean Ray

26

Adicionar

SELECT CAST(scope_identity() AS int);

até o final de sua instrução sql de inserção,

NewId = command.ExecuteScalar()

irá recuperá-lo.


18

Quando você usa o Entity Framework, ele usa internamente a OUTPUTtécnica para retornar o valor de ID recém-inserido

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID ]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

Os resultados da saída são armazenados em uma variável de tabela temporária, unidos de volta à tabela e retornam o valor da linha da tabela.

Nota: Não tenho idéia de por que a EF uniria a tabela efêmera de volta à tabela real (em que circunstâncias as duas não coincidiriam).

Mas é isso que a EF faz.

Essa técnica ( OUTPUT) está disponível apenas no SQL Server 2008 ou mais recente.

Editar - O motivo da associação

O motivo pelo qual o Entity Framework se une novamente à tabela original, em vez de simplesmente usar os OUTPUTvalores, é porque o EF também usa essa técnica para obter o valorrowversion linha recém-inserida.

Você pode usar simultaneidade otimista em seus modelos de estrutura de entidade por usando o Timestampatributo: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

   [Timestamp]
   public byte[] RowVersion { get; set; }
}

Ao fazer isso, o Entity Framework precisará rowversionda linha recém-inserida:

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID], t.[RowVersion]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

E para recuperar isso, Timetsampvocê não pode usar umOUTPUT cláusula.

Isso porque se houver um gatilho na mesa, qualquer TimestampOUTPUT que você estiver errado estará errado:

  • Inserção inicial. Registro de data e hora: 1
  • A cláusula OUTPUT gera o registro de data e hora: 1
  • trigger modifica a linha. Registro de data e hora: 2

O registro de data e hora retornado nunca estará correto se você tiver um gatilho na mesa. Então você deve usar um separadoSELECT .

E mesmo se você estava disposto a sofrer a conversão de linha incorreta, o outro motivo para realizar uma separação separada SELECTé que você não pode OUTPUT a rowversionem uma variável de tabela:

DECLARE @generated_keys table([Id] uniqueidentifier, [Rowversion] timestamp)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID, inserted.Rowversion INTO @generated_keys
VALUES('Malleable logarithmic casing');

A terceira razão para fazê-lo é por simetria. Ao executar uma UPDATEtabela com um gatilho, você não pode usar uma OUTPUTcláusula. Tentando fazer UPDATEcom um OUTPUTnão é suportado e dará um erro:

A única maneira de fazer isso é com uma SELECTdeclaração de acompanhamento :

UPDATE TurboEncabulators
SET StatorSlots = 'Lotus-O deltoid type'
WHERE ((TurboEncabulatorID = 1) AND (RowVersion = 792))

SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1

2
Eu imagino que eles correspondam a eles para garantir a integridade (por exemplo, no modo de concorrência otimista, enquanto você seleciona na variável da tabela, alguém pode ter removido as linhas do insersor). Além disso, ama seu TurboEncabulators:)
zaitsman

16

MSDN

@@ IDENTITY, SCOPE_IDENTITY e IDENT_CURRENT são funções semelhantes, pois retornam o último valor inserido na coluna IDENTITY de uma tabela.

@@ IDENTITY e SCOPE_IDENTITY retornarão o último valor de identidade gerado em qualquer tabela na sessão atual. No entanto, SCOPE_IDENTITY retorna o valor somente dentro do escopo atual; @@ IDENTITY não está limitado a um escopo específico.

IDENT_CURRENT não é limitado por escopo e sessão; está limitado a uma tabela especificada. IDENT_CURRENT retorna o valor da identidade gerado para uma tabela específica em qualquer sessão e qualquer escopo. Para mais informações, consulte IDENT_CURRENT.

  • IDENT_CURRENT é uma função que aceita uma tabela como argumento.
  • @@ IDENTITY pode retornar um resultado confuso quando você tem um gatilho na mesa
  • SCOPE_IDENTITY é seu herói na maioria das vezes.

14

@@ IDENTITY é a última identidade inserida usando a conexão SQL atual. Esse é um bom valor para retornar de um procedimento armazenado de inserção, onde você só precisa da identidade inserida para o seu novo registro e não se importa se mais linhas foram adicionadas posteriormente.

SCOPE_IDENTITY é a última identidade inserida usando a Conexão SQL atual e, no escopo atual - ou seja, se houvesse uma segunda IDENTITY inserida com base em um gatilho após a inserção, ela não seria refletida em SCOPE_IDENTITY, apenas a inserção que você executou . Francamente, nunca tive um motivo para usar isso.

IDENT_CURRENT (nome da tabela) é a última identidade inserida, independentemente da conexão ou escopo. Você pode usar isso se desejar obter o valor atual de IDENTITY para uma tabela na qual você não inseriu um registro.


2
Você nunca deve usar @@ identity para esse fim. Se alguém adicionar um gatilho mais tarde, você perderá a integridade dos dados. @@ identiy é uma prática extremamente perigosa.
HLGEM

1
"valor para uma tabela na qual você <<não>> inseriu um registro." Mesmo?
Abdul Saboor 26/02

13

Não posso falar com outras versões do SQL Server, mas em 2012, a saída diretamente funciona muito bem. Você não precisa se preocupar com uma mesa temporária.

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES (...)

A propósito, essa técnica também funciona ao inserir várias linhas.

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES
    (...),
    (...),
    (...)

Resultado

ID
2
3
4

Se você quiser usá-lo mais tarde, porém, eu imagino que você precisa a tabela temporária
JohnOsborne

@JohnOsborne Você pode usar uma tabela temporária, se quiser, mas meu argumento é que não é uma exigência OUTPUT. Se você não precisa da tabela temporária, sua consulta acaba sendo muito mais simples.
MarcelCheese #

10

SEMPRE use scope_identity (), NUNCA há necessidade de mais nada.


13
Não é bem assim não , mas 99 vezes de 100, você vai usar SCOPE_IDENTITY ().
CJM

Para o que você já usou alguma outra coisa?
9119 erikkallen

11
se você inserir várias linhas com um INSERT-SELECT, seria necessário capturar os vários IDs usando a cláusula OUTPUT
KM.

1
@KM: Sim, mas eu me referi a scope_identity vs @@ identity vs ident_current. OUTPUT é uma classe completamente diferente e frequentemente útil.
precisa saber é o seguinte

2
Confira a resposta de Orry ( stackoverflow.com/a/6073578/2440976 ) a esta pergunta - em paralelismo e, como uma prática recomendada, você deve seguir a configuração dele ... simplesmente brilhante!
B

2

Crie um uuide também insira-o em uma coluna. Então você pode identificar facilmente sua linha com o uuid. Essa é a única solução 100% funcional que você pode implementar. Todas as outras soluções são muito complicadas ou não estão funcionando nos mesmos casos extremos. Por exemplo:

1) Criar linha

INSERT INTO table (uuid, name, street, zip) 
        VALUES ('2f802845-447b-4caa-8783-2086a0a8d437', 'Peter', 'Mainstreet 7', '88888');

2) Criar linha

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';

Não se esqueça de criar um índice para o uuidno banco de dados. Portanto, a linha será encontrada mais rapidamente.
Frank Roth

Para node.js você pode usar este módulo para simplesmente criar um UUID: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth

Um GUID não é um valor de identidade, possui alguns retrocessos em comparação com um número inteiro simples.
Alejandro

1

Uma outra maneira de garantir a identidade das linhas inseridas é especificar os valores da identidade e usar the SET IDENTITY_INSERT ONe then OFF. Isso garante que você saiba exatamente quais são os valores de identidade! Desde que os valores não estejam em uso, você poderá inserir esses valores na coluna de identidade.

CREATE TABLE #foo 
  ( 
     fooid   INT IDENTITY NOT NULL, 
     fooname VARCHAR(20) 
  ) 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (1, 
             'one'), 
            (2, 
             'Two') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

INSERT INTO #foo 
            (fooname) 
VALUES      ('Three') 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

-- YOU CAN INSERT  
SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (10, 
             'Ten'), 
            (11, 
             'Eleven') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SELECT * 
FROM   #foo 

Essa pode ser uma técnica muito útil se você estiver carregando dados de outra fonte ou mesclando dados de dois bancos de dados etc.


0

Mesmo sendo um segmento mais antigo, existe uma maneira mais recente de fazer isso, que evita algumas das armadilhas da coluna IDENTITY em versões mais antigas do SQL Server, como falhas nos valores de identidade após a reinicialização do servidor . As seqüências estão disponíveis no SQL Server 2016 e, a seguir, a maneira mais nova é criar um objeto SEQUENCE usando TSQL. Isso permite que você crie seu próprio objeto de sequência numérica no SQL Server e controle como ele é incrementado.

Aqui está um exemplo:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Em TSQL, você faria o seguinte para obter o próximo ID de sequência:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Aqui estão os links para CREATE SEQUENCE e NEXT VALUE FOR


As seqüências têm os mesmos problemas de identidade, como as lacunas (que não são realmente problemas).
Alejandro

-1

Após a inserção da instrução, você precisará adicionar isso. Verifique se o nome da tabela em que os dados estão sendo inseridos. Você não obterá a linha atual no lugar onde a linha foi afetada agora pela sua instrução de inserção.

IDENT_CURRENT('tableName')

2
Você notou que essa mesma sugestão foi respondida várias vezes antes?
TT.

sim. mas estou tentando descrever a solução do meu jeito.
Khan Ataur Rahman

E se alguém tiver inserido uma linha entre sua instrução de inserção e sua chamada IDENT_CURRENT (), você obterá o ID do registro que outra pessoa inseriu - provavelmente não o que você deseja. Como observado na maioria das respostas acima - na maioria dos casos, você deve usar SCOPE_IDENTITY ().
Trondster 9/03
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.