La mejor manera de insertar la última identidad en una tabla

35

¿Cuál es la mejor opción para obtener el valor de identidad que acabo de generar mediante una inserción? ¿Cuál es el impacto de estas declaraciones en términos de desempeño?

  1. SCOPE_IDENTITY()
  2. Función agregada MAX()
  3. SELECT TOP 1IdentityColumn FROM TableNameORDER BY IdentityColumn DESC
AA.SC
fuente
1
Use postgreSQL y lo tendrá en el estante postgresql.org/docs/9.1/static/sql-insert.html
Yevgeniy Afanasyev
Opción de campo izquierdo: si tiene una columna Guid en la tabla y puede generar un nuevo Guid e insertarlo en la nueva columna durante la inserción, puede seleccionar la fila con ese Guid para extraer la identidad int generada.
niico

Respuestas:

56

ÚseloSCOPE_IDENTITY() si está insertando una sola fila y desea recuperar la ID que se generó.

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

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

SELECT SCOPE_IDENTITY();

Resultado:

----
1

Use la OUTPUTcláusula si está insertando varias filas y necesita recuperar el conjunto de ID que se generaron.

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

Resultado:

----
2
3

¿Y por qué esta es la mejor opción más rápida?

Dejando de lado el rendimiento, estos son los únicos que se garantiza que son correctos en el nivel de aislamiento predeterminado y / o con múltiples usuarios. Incluso si ignora el aspecto de corrección, SQL Server mantiene el valor insertado SCOPE_IDENTITY()en la memoria, por lo que, naturalmente, será más rápido que ejecutar y ejecutar su propia consulta aislada en la tabla o en las tablas del sistema.

Ignorar el aspecto de corrección es como decirle al cartero que hizo un buen trabajo entregando el correo de hoy: terminó su ruta 10 minutos más rápido que su tiempo promedio, el problema es que ninguno de los correos se entregó a la casa correcta.

No use ninguno de los siguientes:

  • @@IDENTITY - ya que esto no se puede usar en todos los escenarios, por ejemplo, cuando una tabla con una columna de identidad tiene un activador que también se inserta en otra tabla con su propia columna de identidad, obtendrá el valor incorrecto.
  • IDENT_CURRENT()- de entrar en detalles sobre esto aquí , y los comentarios leyendo útil también, pero en esencia, en virtud de la concurrencia, a menudo obtendrá la respuesta equivocada.
  • MAX()o TOP 1- tendría que proteger las dos declaraciones con aislamiento serializable para asegurarse de que lo MAX()que obtiene no sea de otra persona. Esto es mucho más costoso que solo usarlo SCOPE_IDENTITY().

Estas funciones también fallan cada vez que inserta dos o más filas y necesita todos los valores de identidad generados; su única opción es la OUTPUTcláusula.

Aaron Bertrand
fuente
Una pregunta más se activó en mi memoria. ¿Cuándo necesitamos obtener la última identidad generada en una tabla específica por cualquier sesión o usuario, la única forma correcta y mejor es MAX () de esa columna?
AA.SC
¿qué tal cuando inserto varias filas en una tabla SCOPE_IDENTITY () siempre devolverá la última identidad generada? ¿Qué sucede si la columna es la clave principal pero no la columna de identidad?
AA.SC
@ AA.SC sí, devolverá el último. Si no es una columna de identidad, no, ninguna de estas funciones funcionará. ¿De dónde viene el valor PK en ese caso?
Aaron Bertrand
He visto esto para una columna en nuestra aplicación donde la columna es de tipo INT, y los desarrolladores usan MAX (nombre de columna) +1 cuando necesitan insertar un nuevo registro
AA.SC
Entonces ya saben qué valor acaban de insertar. SQL Server no tiene ninguna forma de decirle eso (no puede confiar en extraer MAX nuevamente después del hecho, a menos que haya aislado por completo toda su transacción, lo que no será bueno para el rendimiento o la concurrencia).
Aaron Bertrand
7

Además del rendimiento, todos tienen significados bastante diferentes.

SCOPE_IDENTITY()le dará el último valor de identidad insertado en cualquier tabla directamente dentro del alcance actual (alcance = lote, procedimiento almacenado, etc. pero no dentro, por ejemplo, de un disparador que fue disparado por el alcance actual).

IDENT_CURRENT()le dará el último valor de identidad insertado en una tabla específica desde cualquier ámbito, por cualquier usuario.

@@IDENTITYle proporciona el último valor de identidad generado por la instrucción INSERT más reciente para la conexión actual, independientemente de la tabla o el alcance. (Nota al margen: Access usa esta función y, por lo tanto, tiene algunos problemas con los desencadenantes que insertan valores en tablas con columnas de identidad).

Usar MAX()o TOP 1puede darle resultados completamente incorrectos si la tabla tiene un paso de identidad negativo, o si ha insertado filas SET IDENTITY_INSERTen juego. Aquí hay un script que demuestra todo esto:

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

Resumen: palo con SCOPE_IDENTITY(), IDENT_CURRENT()o @@IDENTITY, y asegurarse de que está usando el que vuelve lo que realmente necesita.

db2
fuente
1
¿Por qué fomenta el uso de IDENT_CURRENT()y @@IDENTITYcuando su propio script demuestra que generan resultados incorrectos?
Aaron Bertrand
1
@AaronBertrand No estoy seguro de seguir. El último valor de identidad generado fue 8998 (observe que el paso es -1), y eso es lo que IDENT_CURRENT()devuelve. MAX () nunca devuelve el valor correcto más allá de la primera fila, ya que id está contando hacia atrás, y con IDENTITY_INSERTon, 9005 no es un valor de identidad generado , por lo tanto, no se refleja en IDENT_CURRENT(). Pero puede devolver resultados "incorrectos" si realmente busca lo que SCOPE_IDENTITY()devuelve. Elija la herramienta adecuada para el trabajo.
db2
El OP parece estar después del valor de identidad que insertaron, en este caso 8998 es incorrecto. Los casos extremos que mencionas (incremento hacia atrás e IDENTITY_INSERT en) argumentan aún más en contra de usar IDENT_CURRENT en mi opinión, y @@ IDENTITY nunca debería usarse realmente debido al peligro de los desencadenantes (ahora o agregado más adelante). Todavía me cuesta entender por qué IDENT_CURRENT sería el que el OP querría usar (especialmente bajo concurrencia) o por qué @@ IDENTITY alguna vez sería utilizado por alguien cuando existen métodos mucho más confiables.
Aaron Bertrand
@AaronBertrand No está 100% claro a partir de la pregunta que el resultado deseado es el último inserto del alcance actual (la opción 1 difiere de 2 y 3 en ese sentido), así que pensé que sería una buena idea describir ambos y cómo diferir de. Pero sí estoy de acuerdo en que @@IDENTITYcasi nunca es la forma ideal de obtener valores de identidad generados. El punto principal es eso MAX()o TOP 1son como una versión menos confiable de IDENT_CURRENT(), que es una función perfectamente buena para usar si entiendes lo que hace. Podría ser útil para trabajos de mantenimiento o algo así.
db2