¿La mejor manera de obtener la identidad de la fila insertada?

1119

¿Cuál es la mejor manera de obtener una IDENTITYfila insertada?

Sé acerca de @@IDENTITYy IDENT_CURRENTy SCOPE_IDENTITYpero no entiendo los pros y los contras asociados a cada uno.

¿Alguien puede explicar las diferencias y cuándo debería usarlas?

Oded
fuente
55
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...), o un método anterior: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();puede obtenerlo en C # usando ExecuteScalar ().
S.Serpooshan
44
¿Cómo es eso mejor que las otras respuestas? (también, ¿por qué no publica esto como una respuesta en lugar de un comentario)? Escriba una respuesta completa (y explique por qué esta es una mejor opción que las publicadas; si la versión es específica, dígalo).
Oded
es como un breve resumen. ; D La respuesta aceptada no menciona la sintaxis de la cláusula OUTPUT y carece de una muestra. También las muestras en otras publicaciones no están tan limpias ...
S.Serpooshan
2
@saeedserpooshan - luego edítalo. Puedes hacer eso, ¿sabes? ¿Ves cuándo se publicó esa respuesta? Eso es anterior a la OUTPUTcláusula en SQL Server.
Oded

Respuestas:

1435
  • @@IDENTITYdevuelve el último valor de identidad generado para cualquier tabla en la sesión actual, en todos los ámbitos. Debe tener cuidado aquí , ya que está en todos los ámbitos. Podría obtener un valor de un activador, en lugar de su declaración actual.

  • SCOPE_IDENTITY()devuelve el último valor de identidad generado para cualquier tabla en la sesión actual y el alcance actual. Generalmente lo que quieres usar .

  • IDENT_CURRENT('tableName')devuelve el último valor de identidad generado para una tabla específica en cualquier sesión y cualquier ámbito. Esto le permite especificar de qué tabla desea el valor, en caso de que los dos anteriores no sean exactamente lo que necesita ( muy raro ). Además, como mencionó @ Guy Starbuck , "podría usar esto si desea obtener el valor de IDENTIDAD actual para una tabla en la que no ha insertado un registro".

  • La OUTPUTcláusula de la INSERTdeclaración le permitirá acceder a cada fila que se insertó a través de esa declaración. Dado que está enfocado a la declaración específica, es más sencillo que las otras funciones anteriores. Sin embargo, es un poco más detallado (necesitará insertar en una tabla variable / tabla temporal y luego consultar eso) y da resultados incluso en un escenario de error donde la declaración se revierte. Dicho esto, si su consulta usa un plan de ejecución en paralelo, este es el único método garantizado para obtener la identidad (salvo desactivar el paralelismo). Sin embargo, se ejecuta antes de los disparadores y no se puede usar para devolver valores generados por disparadores.

bdukes
fuente
48
error conocido con SCOPE_IDENTITY () que devuelve los valores incorrectos: blog.sqlauthority.com/2009/03/24/… la solución es no ejecutar INSERT en un plan paralelo de multiprocesador o usar la cláusula OUTPUT
KM.
3
Casi cada vez que siempre quise 'identidad', quise saber la (s) clave (s) de los registros que acabo de insertar. Si esa es su situación, desea utilizar la cláusula OUTPUT. Si desea algo más, aplique el esfuerzo de leer y comprender la respuesta de bdukes.
jerry
3
Con outputusted no necesita crear una tabla temporal para almacenar y consultar los resultados. Simplemente omita la intoparte de la cláusula de salida y los enviará a un conjunto de resultados.
spb
96
Para salvar a otros del pánico, el error mencionado anteriormente se corrigió en la Actualización acumulativa 5 para SQL Server 2008 R2 Service Pack 1.
GaTechThomas
1
@niico, creo que la recomendación es la misma que ha sido, que OUTPUTes la "mejor" siempre y cuando no estés usando disparadores y estés manejando errores, pero SCOPE_IDENTITYes el más simple y rara vez tiene problemas
bdukes
180

Creo que el método más seguro y preciso para recuperar la identificación insertada sería usar la cláusula de salida.

por ejemplo (tomado del siguiente artículo de 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
Orry
fuente
3
Sí, este es el método correcto en el futuro, solo use uno de los otros si no está en SQL Server 2008 (omitimos 2005, así que no estoy seguro de si OUTPUT estaba disponible entonces)
HLGEM
1
@HLGEM Hay una página de MSDN OUTPUTen SQL Server 2005 , por lo que parece que solo SQL Server 2000 y
versiones
66
woohoo! CLÁUSULA DE SALIDA rocas :) Eso simplificará mi tarea actual. No sabía esa declaración antes. ¡Gracias chicos!
SwissCoder
8
Para un ejemplo realmente conciso para obtener la ID insertada, eche un vistazo a: stackoverflow.com/a/10999467/2003325
Lucas
Su uso de INTO con OUTPUT es una buena idea. Ver: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (De un comentario aquí: stackoverflow.com/questions/7917695/… )
shlgug
112

Estoy diciendo lo mismo que los otros chicos, así que todos están en lo correcto, solo estoy tratando de dejarlo más claro.

@@IDENTITYdevuelve la identificación de lo último que insertó la conexión de su cliente a la base de datos.
La mayoría de las veces esto funciona bien, pero a veces se activará un disparador e insertará una nueva fila que desconoce, y obtendrá la ID de esta nueva fila, en lugar de la que desea

SCOPE_IDENTITY()resuelve este problema Devuelve la identificación de lo último que insertó en el código SQL que envió a la base de datos. Si los disparadores van y crean filas adicionales, no harán que se devuelva el valor incorrecto. Hurra

IDENT_CURRENTdevuelve la última identificación que fue insertada por cualquiera. Si alguna otra aplicación inserta otra fila en un momento desafortunado, obtendrá la ID de esa fila en lugar de la suya.

Si quieres jugar a lo seguro, úsalo siempre SCOPE_IDENTITY(). Si te quedas @@IDENTITYy alguien decide agregar un disparador más adelante, todo tu código se romperá.

Orion Edwards
fuente
64

La mejor manera (léase: la más segura) de obtener la identidad de una fila recién insertada es mediante la 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)
Ian Kemp
fuente
55
La agrupación de servidores SQL es una característica de alta disponibilidad y no tiene relación con el paralelismo. Es muy raro que las inserciones de una sola fila (el caso más común scope_identity()) obtengan planes paralelos de todos modos. Y este error se solucionó más de un año antes de esta respuesta.
Martin Smith
¿Qué quieres decir con paralelismo?
user1451111
@MartinSmith El cliente no estaba dispuesto a permitir el tiempo de inactividad en su clúster de servidores para instalar la CU que soluciona este problema (no es broma), por lo que la única solución fue reescribir todo el SQL para usar en outputlugar de scope_identity(). He eliminado el FUD sobre la agrupación en la respuesta.
Ian Kemp
1
Gracias, este es el único ejemplo que he podido encontrar que muestra cómo usar el valor de la salida en una variable en lugar de simplemente emitirlo.
Sean Ray
26

Añadir

SELECT CAST(scope_identity() AS int);

hasta el final de su instrucción sql de inserción, luego

NewId = command.ExecuteScalar()

Lo recuperará.

Jim
fuente
18

Cuando usa Entity Framework, usa internamente la OUTPUTtécnica para devolver el valor de ID recién insertado

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

Los resultados de salida se almacenan en una variable de tabla temporal, se unen de nuevo a la tabla y devuelven el valor de fila de la tabla.

Nota: No tengo idea de por qué EF uniría internamente la tabla efímera a la tabla real (en qué circunstancias los dos no coincidirían).

Pero eso es lo que hace EF.

Esta técnica ( OUTPUT) solo está disponible en SQL Server 2008 o posterior.

Editar : el motivo de la unión

La razón por la que Entity Framework se une a la tabla original, en lugar de simplemente usar los OUTPUTvalores, es porque EF también usa esta técnica para obtener la rowversionfila recién insertada.

Puede usar la concurrencia optimista en los modelos de marco de su entidad utilizando el Timestampatributo: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

Cuando haga esto, Entity Framework necesitará rowversionla nueva fila insertada:

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

Y para recuperar esto Timetsampno puede usar una OUTPUTcláusula.

Esto se debe a que si hay un disparador en la mesa, cualquier Timestampsalida que realice estará mal:

  • Inserción inicial. Marca de tiempo: 1
  • La cláusula OUTPUT emite la marca de tiempo: 1
  • el disparador modifica la fila. Marca de tiempo: 2

La marca de tiempo devuelta nunca será correcta si tiene un activador en la tabla. Entonces debes usar un separado SELECT.

E incluso si estuviera dispuesto a sufrir la versión incorrecta de la fila, la otra razón para realizar una separación SELECTes que no puede SALIR una rowversionvariable de tabla:

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

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

La tercera razón para hacerlo es por simetría. Al realizar un UPDATEsobre una mesa con un disparador, no puede usar una OUTPUTcláusula. Intentar hacer UPDATEcon un OUTPUTno es compatible, y dará un error:

La única forma de hacerlo es con una SELECTdeclaración de seguimiento :

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

SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1
Ian Boyd
fuente
2
Me imagino que coinciden para garantizar la integridad (por ejemplo, en modo de concurrencia optimista, mientras selecciona desde la variable de tabla, alguien puede haber eliminado las filas del insertador). Además, ama a tu TurboEncabulators:)
zaitsman
16

MSDN

@@ IDENTITY, SCOPE_IDENTITY e IDENT_CURRENT son funciones similares, ya que devuelven el último valor insertado en la columna IDENTITY de una tabla.

@@ IDENTITY y SCOPE_IDENTITY devolverán el último valor de identidad generado en cualquier tabla de la sesión actual. Sin embargo, SCOPE_IDENTITY devuelve el valor solo dentro del alcance actual; @@ IDENTITY no está limitado a un alcance específico.

IDENT_CURRENT no está limitado por el alcance y la sesión; se limita a una tabla especificada. IDENT_CURRENT devuelve el valor de identidad generado para una tabla específica en cualquier sesión y cualquier ámbito. Para obtener más información, vea IDENT_CURRENT.

  • IDENT_CURRENT es una función que toma una tabla como argumento.
  • @@ IDENTITY puede devolver resultados confusos cuando tiene un activador en la tabla
  • SCOPE_IDENTITY es tu héroe la mayor parte del tiempo.
Jakub Šturc
fuente
14

@@ IDENTITY es la última identidad insertada utilizando la conexión SQL actual. Este es un buen valor para regresar de un procedimiento almacenado de inserción, donde solo necesita la identidad insertada para su nuevo registro, y no le importa si se agregaron más filas después.

SCOPE_IDENTITY es la última identidad insertada usando la conexión SQL actual, y en el alcance actual, es decir, si hubo una segunda IDENTIDAD insertada en función de un disparador después de su inserción, no se reflejaría en SCOPE_IDENTITY, solo la inserción que realizó . Francamente, nunca he tenido una razón para usar esto.

IDENT_CURRENT (nombre de tabla) es la última identidad insertada independientemente de la conexión o el alcance. Puede usar esto si desea obtener el valor de IDENTIDAD actual para una tabla en la que no ha insertado un registro.

Guy Starbuck
fuente
2
Nunca debe usar la identidad @@ para este propósito. Si alguien agrega un disparador más tarde, perderá la integridad de los datos. @@ identiy es una práctica extremadamente peligrosa.
HLGEM
1
"valor para una tabla en la que ha <<not>> insertado un registro". De Verdad?
Abdul Saboor
13

No puedo hablar con otras versiones de SQL Server, pero en 2012, la salida directamente funciona bien. No necesita molestarse con una mesa temporal.

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

Por cierto, esta técnica también funciona al insertar varias filas.

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

Salida

ID
2
3
4
Queso Marred
fuente
Sin embargo, si quieres usarlo más tarde, imagino que necesitas la tabla temporal
JohnOsborne
@JohnOsborne Puede usar una tabla temporal si lo desea, pero mi punto es que no es un requisito OUTPUT. Si no necesita la tabla temporal, su consulta termina siendo mucho más simple.
MarredCheese
10

SIEMPRE use scope_identity (), NUNCA hay necesidad de nada más.

erikkallen
fuente
13
No del todo nunca, pero 99 de cada 100 veces, usará Scope_Identity ().
CJM
¿Para qué has usado alguna otra cosa?
erikkallen
11
si inserta varias filas con un INSERT-SELECT, necesitará capturar las múltiples ID usando la cláusula OUTPUT
KM.
1
@KM: Sí, pero me referí a scope_identity vs @@ identity vs ident_current. OUTPUT es una clase completamente diferente y a menudo útil.
erikkallen
2
Echa un vistazo a la respuesta de Orry ( stackoverflow.com/a/6073578/2440976 ) a esta pregunta: en paralelo y, como práctica recomendada, sería prudente seguir su configuración ... ¡genial!
Dan B
2

Cree un uuidy también insértelo en una columna. Entonces puede identificar fácilmente su fila con el uuid. Esa es la única solución 100% funcional que puede implementar. Todas las demás soluciones son demasiado complicadas o no funcionan en los mismos casos extremos. P.ej:

1) Crear fila

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

2) Obtener fila creada

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
Frank Roth
fuente
No olvide crear un índice para uuiden la base de datos. Entonces la fila se encontrará más rápido.
Frank Roth
Para Node.js puede utilizar este módulo para simplemente crear un UUID: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth
Un GUID no es un valor de identidad, tiene algunos backdraws en comparación con un entero simple.
Alejandro
1

Otra forma de garantizar la identidad de las filas que inserte es especificar los valores de identidad y usar el SET IDENTITY_INSERT ONy luego OFF. ¡Esto garantiza que sepa exactamente cuáles son los valores de identidad! Mientras los valores no estén en uso, puede insertar estos valores en la columna de identidad.

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 

Esta puede ser una técnica muy útil si está cargando datos de otra fuente o fusionando datos de dos bases de datos, etc.

Andy Robertson
fuente
0

Aunque este es un subproceso anterior, hay una forma más nueva de hacerlo que evita algunos de los escollos de la columna IDENTIDAD en las versiones anteriores de SQL Server, como las brechas en los valores de identidad después de reiniciar el servidor . Las secuencias están disponibles en SQL Server 2016 y el reenvío, que es la forma más nueva, es crear un objeto SECUENCIA usando TSQL. Esto le permite crear su propio objeto de secuencia numérica en SQL Server y controlar cómo se incrementa.

Aquí hay un ejemplo:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Luego, en TSQL, haría lo siguiente para obtener la siguiente ID de secuencia:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Aquí están los enlaces para CREAR SECUENCIA y SIGUIENTE VALOR PARA

StevenJe
fuente
Las secuencias tienen los mismos problemas de identidad, como las brechas (que en realidad no son problemas).
Alejandro
-1

Después de su declaración de inserción, debe agregar esto. Y asegúrese del nombre de la tabla donde se insertan los datos. Obtendrá la fila actual sin la fila afectada justo ahora por su instrucción de inserción.

IDENT_CURRENT('tableName')
Khan Ataur Rahman
fuente
2
¿Notó que esta misma sugerencia ha sido respondida varias veces antes?
TT.
si. pero estoy tratando de describir la solución a mi manera.
Khan Ataur Rahman
Y si alguien más ha insertado una fila entre su declaración de inserción y su llamada IDENT_CURRENT (), obtendrá la identificación del registro que otra persona ha insertado, probablemente no lo que desea. Como se señaló en la mayoría de las respuestas anteriores, en la mayoría de los casos debería usar SCOPE_IDENTITY ().
Trondster