Le meilleur moyen d’obtenir la dernière identité insérée dans une table

35

Quelle est la meilleure option pour obtenir la valeur d’identité que je viens de générer via un insert? Quel est l'impact de ces déclarations en termes de performance?

  1. SCOPE_IDENTITY()
  2. Fonction d'agrégat MAX()
  3. SELECT TOP 1IdentityColumn FROM TableNameORDER BY IdentityColumn DESC
AA.SC
la source
1
Utilisez postgreSQL et vous l'aurez depuis l'étagère postgresql.org/docs/9.1/static/sql-insert.html
Yevgeniy Afanasyev
Option Leftfield - si vous avez une colonne Guid dans le tableau et que vous pouvez générer un nouveau Guid et l'insérer dans la nouvelle colonne lors de l'insertion, vous pouvez ensuite sélectionner la ligne avec ce Guid pour extraire l'identité int générée.
niico

Réponses:

56

Utilisez cette optionSCOPE_IDENTITY() si vous insérez une seule ligne et souhaitez récupérer l'ID généré.

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

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

SELECT SCOPE_IDENTITY();

Résultat:

----
1

Utilisez la OUTPUTclause si vous insérez plusieurs lignes et devez récupérer l' ensemble des ID générés.

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

Résultat:

----
2
3

et pourquoi c'est la meilleure option plus rapide?

Les performances mises à part, ce sont les seuls qui sont garantis d'être corrects dans le niveau d'isolation par défaut et / ou avec plusieurs utilisateurs. Même si vous ignorez l'aspect de la correction, SQL Server conserve la valeur insérée SCOPE_IDENTITY()en mémoire. Ce sera donc naturellement plus rapide que de lancer votre propre requête isolée sur la table ou sur les tables système.

Ignorer l'aspect correction est comme dire au facteur qu'il a bien fait de livrer le courrier d'aujourd'hui: il a terminé son parcours 10 minutes plus rapidement que son temps moyen, le problème est qu'aucun courrier n'a été livré à la bonne maison.

N'utilisez aucun des éléments suivants:

  • @@IDENTITY - dans la mesure où cela ne peut pas être utilisé dans tous les scénarios, par exemple, lorsqu'une table avec une colonne d'identité a un déclencheur qui est également inséré dans une autre table avec sa propre colonne d'identité - vous obtiendrez la mauvaise valeur.
  • IDENT_CURRENT()- Je vais entrer dans les détails à ce sujet ici , et les commentaires sont aussi une lecture utile, mais essentiellement, en cas de concurrence, vous obtiendrez souvent une mauvaise réponse.
  • MAX()ou TOP 1- vous devez protéger les deux déclarations avec un isolement sérialisable afin de vous assurer que ce que MAX()vous obtenez n'est pas celui de quelqu'un d'autre. C'est beaucoup plus cher que de simplement utiliser SCOPE_IDENTITY().

Ces fonctions échouent également chaque fois que vous insérez deux lignes ou plus et requièrent toutes les valeurs d'identité générées. Votre seule option est la OUTPUTclause.

Aaron Bertrand
la source
Une autre question déclenchée dans ma mémoire. Chaque fois que nous devons obtenir la dernière identité générée dans une table spécifique par une session ou un utilisateur, le seul moyen correct et optimal est MAX () de cette colonne?
AA.SC
Que diriez-vous lorsque j'insère plusieurs lignes dans une table, SCOPE_IDENTITY () retournera-t-il toujours la dernière identité générée? Que se passe-t-il si la colonne est une clé primaire mais pas une colonne d'identité?
AA.SC
@ AA.SC Oui, le dernier sera renvoyé. Si ce n'est pas une colonne d'identité, non, aucune de ces fonctions ne fonctionnera. D'où vient la valeur PK dans ce cas?
Aaron Bertrand
J'ai vu cela pour une colonne de notre application où la colonne est de type INT et les développeurs utilisent MAX (nom_colonne) +1 chaque fois qu'ils doivent insérer un nouvel enregistrement
AA.SC
Ensuite, ils savent déjà quelle valeur ils viennent d'insérer. SQL Server n'a aucun moyen de vous le dire (vous ne pouvez pas compter sur MAX à nouveau, à moins que vous n'ayez complètement isolé toute votre transaction, ce qui ne serait pas bon pour les performances ou la concurrence).
Aaron Bertrand
7

En dehors de la performance, ils ont tous des significations assez différentes.

SCOPE_IDENTITY()vous donnera la dernière valeur d'identité insérée dans une table directement dans l'étendue actuelle (scope = batch, procédure stockée, etc., mais pas à l'intérieur, par exemple, d'un déclencheur déclenché par l'étendue actuelle).

IDENT_CURRENT()vous donnera la dernière valeur d'identité insérée dans une table spécifique à partir de n'importe quelle portée, par n'importe quel utilisateur.

@@IDENTITYvous donne la dernière valeur d'identité générée par l'instruction INSERT la plus récente pour la connexion en cours, indépendamment de la table ou de la portée. (Remarque: Access utilise cette fonction et présente donc quelques problèmes avec les déclencheurs qui insèrent des valeurs dans des tables avec des colonnes d'identité.)

L'utilisation de MAX()ou TOP 1peut vous donner des résultats totalement erronés si la table comporte une étape d'identité négative ou si des lignes ont été insérées SET IDENTITY_INSERT. Voici un script démontrant toutes ces choses:

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

Résumé: restez avec SCOPE_IDENTITY(), IDENT_CURRENT()ou @@IDENTITY, et assurez-vous d’utiliser celui qui vous renvoie ce dont vous avez réellement besoin.

db2
la source
1
Pourquoi encouragez-vous l'utilisation de IDENT_CURRENT()et @@IDENTITYlorsque votre propre script démontre qu'il génère des résultats incorrects?
Aaron Bertrand
1
@AaronBertrand Je ne suis pas sûre de suivre. La dernière valeur d'identité générée était 8998 (notez que l'étape est -1), et c'est ce qui IDENT_CURRENT()revient. MAX () ne renvoie jamais la valeur correcte au-delà de la première ligne, puisque id compte en arrière et , par conséquent IDENTITY_INSERT, 9005 n'est pas une valeur d'identité générée et n'est donc pas reflétée IDENT_CURRENT(). Mais il peut retourner des résultats "incorrects" si vous voulez vraiment ce qui SCOPE_IDENTITY()revient. Choisissez le bon outil pour le travail.
db2
Le PO semble être après la valeur d'identité qu'ils ont insérée - dans ce cas, 8998 est incorrect. Les cas extrêmes que vous mentionnez (incrémentation en arrière et IDENTITY_INSERT activé) s'opposent encore davantage à l' utilisation de IDENT_CURRENT à mon avis, et @@ IDENTITY ne devrait jamais être utilisé à cause du danger des déclencheurs (ajouté ou ajouté ultérieurement). Je n'arrive toujours pas à comprendre pourquoi IDENT_CURRENT serait celui que l'OP voudrait utiliser (en particulier dans l'accès simultané) ou pourquoi @@ IDENTITY serait jamais utilisé par quiconque alors que des méthodes beaucoup plus fiables existent.
Aaron Bertrand
@AaronBertrand Il n’est pas clair à 100% de la question que le résultat souhaité est le dernier encart du champ actuel (l’option 1 diffère des options 2 et 3 à cet égard), j’ai donc pensé que ce serait une bonne idée de décrire à la fois différer. Mais je conviens que ce @@IDENTITYn’est presque jamais le moyen idéal d’obtenir des valeurs d’identité générées. Le point principal est que MAX()ou TOP 1sont comme une version moins fiable de IDENT_CURRENT(), ce qui est une fonction parfaitement utile à utiliser si vous comprenez ce qu’elle fait. Pourrait être utile pour des travaux de maintenance ou autre chose.
db2