Le meilleur moyen d'obtenir l'identité de la ligne insérée?

1119

Quelle est la meilleure façon d'obtenir la IDENTITYligne insérée?

Je connais @@IDENTITYet IDENT_CURRENTet SCOPE_IDENTITYmais je ne comprends pas les avantages et les inconvénients attachés à chacun.

Quelqu'un peut-il expliquer les différences et quand dois-je les utiliser?

Oded
la source
5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...), ou une méthode plus ancienne: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();vous pouvez l'obtenir en c # en utilisant ExecuteScalar ().
S.Serpooshan
4
Comment est-ce mieux que les autres réponses? (aussi - pourquoi ne postez-vous pas cela comme une réponse au lieu d'un commentaire). Veuillez rédiger une réponse complète (et expliquer pourquoi il s'agit d'une meilleure option que celles publiées - si la version est spécifique, dites-le).
Odé
c'est comme un bref résumé. ; D La réponse acceptée ne mentionne pas la syntaxe de la clause OUTPUT et manque un échantillon. De plus, les échantillons dans d'autres articles ne sont pas si propres ...
S.Serpooshan
2
@saeedserpooshan - puis modifiez cela en. Vous pouvez le faire, vous savez? Vous voyez quand cette réponse a été publiée? Cela précède la OUTPUTclause dans SQL Server.
Odé

Réponses:

1435
  • @@IDENTITYrenvoie la dernière valeur d'identité générée pour n'importe quelle table de la session en cours, sur toutes les étendues. Vous devez être prudent ici , car il s'agit de plusieurs étendues. Vous pouvez obtenir une valeur à partir d'un déclencheur, au lieu de votre instruction actuelle.

  • SCOPE_IDENTITY()renvoie la dernière valeur d'identité générée pour n'importe quelle table de la session en cours et la portée actuelle. Généralement ce que vous souhaitez utiliser .

  • IDENT_CURRENT('tableName')renvoie la dernière valeur d'identité générée pour une table spécifique dans une session et une étendue. Cela vous permet de spécifier de quelle table vous voulez que la valeur provienne, au cas où les deux ci-dessus ne seraient pas tout à fait ce dont vous avez besoin ( très rare ). En outre, comme l'a mentionné @ Guy Starbuck , "Vous pouvez l'utiliser si vous souhaitez obtenir la valeur IDENTITY actuelle pour une table dans laquelle vous n'avez pas inséré d'enregistrement."

  • La OUTPUTclause de l' INSERTinstruction vous permettra d'accéder à chaque ligne insérée via cette instruction. Puisqu'il est limité à l'instruction spécifique, il est plus simple que les autres fonctions ci-dessus. Cependant, c'est un peu plus verbeux (vous devrez insérer dans une table variable / temp, puis interroger cela) et cela donne des résultats même dans un scénario d'erreur où l'instruction est annulée. Cela dit, si votre requête utilise un plan d'exécution parallèle, c'est la seule méthode garantie pour obtenir l'identité (à moins de désactiver le parallélisme). Cependant, il est exécuté avant les déclencheurs et ne peut pas être utilisé pour renvoyer des valeurs générées par les déclencheurs.

bdukes
la source
48
bogue connu avec SCOPE_IDENTITY () renvoyant les mauvaises valeurs: blog.sqlauthority.com/2009/03/24/… la solution consiste à ne pas exécuter INSERT dans un plan parallèle multiprocesseur ou à utiliser la clause OUTPUT
KM.
3
Presque chaque fois que j'ai toujours voulu une «identité», j'ai voulu connaître la ou les clés du ou des enregistrements que je viens d'insérer. Si tel est votre cas, vous souhaitez utiliser la clause OUTPUT. Si vous voulez autre chose, appliquez l'effort pour lire et comprendre la réponse de bdukes.
jerry
3
Avec outputvous n'avez pas besoin de créer une table temporaire pour stocker et interroger les résultats. Laissez simplement la intopartie de la clause de sortie et il les affichera dans un jeu de résultats.
spb
96
Pour éviter que d'autres ne paniquent, le bogue mentionné ci-dessus a été corrigé dans la mise à jour cumulative 5 pour SQL Server 2008 R2 Service Pack 1.
GaTechThomas
1
@niico, je pense que la recommandation est la même qu'elle a été, c'est-à- OUTPUTdire la «meilleure» tant que vous n'utilisez pas de déclencheurs et ne gérez pas d'erreurs, mais SCOPE_IDENTITYc'est la plus simple et très rarement des problèmes
bdukes
180

Je pense que la méthode la plus sûre et la plus précise pour récupérer l'identifiant inséré serait d'utiliser la clause de sortie.

par exemple (extrait de l' article MSDN suivant )

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
la source
3
Oui, c'est la bonne méthode pour l'avenir, n'utilisez l'une des autres que si vous n'êtes pas sur SQL Server 2008 (nous avons ignoré 2005, donc
je
1
@HLGEM Il y a une page MSDN pour OUTPUTdans SQL Server 2005 , il semble donc que ce soit juste SQL Server 2000 et
versions
6
woohoo! CLAUSE DE SORTIE rochers :) Cela simplifiera ma tâche actuelle. Je ne connaissais pas cette déclaration auparavant. Merci les gars!
SwissCoder
8
Pour un exemple vraiment concis pour simplement obtenir l'ID inséré, jetez un œil à: stackoverflow.com/a/10999467/2003325
Luke
Votre utilisation de INTO avec OUTPUT est une bonne idée. Voir: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (D'après un commentaire ici: stackoverflow.com/questions/7917695/… )
shlgug
112

Je dis la même chose que les autres gars, donc tout le monde a raison, j'essaie juste d'être plus clair.

@@IDENTITYrenvoie l'id de la dernière chose qui a été insérée par la connexion de votre client à la base de données.
La plupart du temps, cela fonctionne bien, mais parfois un déclencheur ira et insérera une nouvelle ligne que vous ne connaissez pas, et vous obtiendrez l'ID de cette nouvelle ligne, au lieu de celle que vous voulez

SCOPE_IDENTITY()résout ce problème. Il renvoie l'ID de la dernière chose que vous avez insérée dans le code SQL que vous avez envoyé à la base de données. Si les déclencheurs vont et créent des lignes supplémentaires, ils ne feront pas renvoyer la mauvaise valeur. Hourra

IDENT_CURRENTrenvoie le dernier ID qui a été inséré par quiconque. Si une autre application insère une autre ligne à un moment imprévu, vous obtiendrez l'ID de cette ligne au lieu de la vôtre.

Si vous voulez jouer en toute sécurité, utilisez toujours SCOPE_IDENTITY(). Si vous restez avec @@IDENTITYet que quelqu'un décide d'ajouter un déclencheur plus tard, tout votre code se cassera.

Orion Edwards
la source
64

La meilleure façon (lire: la plus sûre) d'obtenir l'identité d'une ligne nouvellement insérée est d'utiliser la outputclause:

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
la source
5
Le clustering de serveurs SQL est une fonctionnalité de haute disponibilité et n'a aucune incidence sur le parallélisme. Il est très rare que les insertions à une rangée (le cas le plus courant scope_identity()) obtiennent de toute façon des plans parallèles. Et ce bug a été corrigé plus d'un an avant cette réponse.
Martin Smith
Qu'entendez-vous par parallélisme.
user1451111
@MartinSmith Le client n'était pas disposé à autoriser un temps d'arrêt sur son cluster de serveurs pour installer la CU corrigeant ce problème (sans plaisanter), donc la seule solution était pour nous de réécrire tout le SQL à utiliser à la outputplace de scope_identity(). J'ai supprimé le FUD sur le clustering dans la réponse.
Ian Kemp
1
Merci, c'est le seul exemple que j'ai pu trouver qui montre comment utiliser la valeur de la sortie dans une variable au lieu de simplement la sortir.
Sean Ray
26

Ajouter

SELECT CAST(scope_identity() AS int);

à la fin de votre instruction SQL d'insertion, puis

NewId = command.ExecuteScalar()

va le récupérer.

Jim
la source
18

Lorsque vous utilisez Entity Framework, il utilise en interne la OUTPUTtechnique pour renvoyer la valeur d'ID nouvellement insérée

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

Les résultats de sortie sont stockés dans une variable de table temporaire, rejoints à la table et renvoient la valeur de ligne hors de la table.

Remarque: Je n'ai aucune idée de la raison pour laquelle EF fusionnerait intérieurement la table éphémère à la table réelle (dans quelles circonstances les deux ne correspondraient-elles pas).

Mais c'est ce que fait EF.

Cette technique ( OUTPUT) n'est disponible que sur SQL Server 2008 ou plus récent.

Modifier - La raison de la jointure

La raison pour laquelle Entity Framework rejoint la table d'origine, plutôt que d'utiliser simplement les OUTPUTvaleurs, c'est que EF utilise également cette technique pour obtenir la rowversionligne nouvellement insérée.

Vous pouvez utiliser la concurrence optimiste dans vos modèles de structure d'entité en utilisant l' Timestampattribut: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

Lorsque vous faites cela, Entity Framework aura besoin rowversionde la ligne nouvellement insérée:

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

Et pour récupérer cela, Timetsampvous ne pouvez pas utiliser de OUTPUTclause.

C'est parce que s'il y a un déclencheur sur la table, tout TimestampSORTIE sera faux:

  • Insert initial. Horodatage: 1
  • La clause OUTPUT génère l'horodatage: 1
  • le déclencheur modifie la ligne. Horodatage: 2

L'horodatage renvoyé ne sera jamais correct si vous avez un déclencheur sur la table. Vous devez donc utiliser un autre SELECT.

Et même si vous vouliez subir une version de ligne incorrecte, l'autre raison pour effectuer une séparation distincte SELECTest que vous ne pouvez pas SORTIR a rowversiondans une variable de table:

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 troisième raison de le faire est la symétrie. Lorsque vous effectuez une opération UPDATEsur une table avec un déclencheur, vous ne pouvez pas utiliser de OUTPUTclause. Essayer de faire UPDATEavec un OUTPUTn'est pas pris en charge et donnera une erreur:

La seule façon de le faire est avec une SELECTdéclaration de suivi :

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
la source
2
J'imagine qu'ils correspondent à eux pour garantir l'intégrité (par exemple, en mode de concurrence optimiste, pendant que vous sélectionnez dans la variable de table, quelqu'un peut avoir supprimé les lignes d'insertion). Aussi, aimez votre TurboEncabulators:)
zaitsman
16

MSDN

@@ IDENTITY, SCOPE_IDENTITY et IDENT_CURRENT sont des fonctions similaires en ce sens qu'elles renvoient la dernière valeur insérée dans la colonne IDENTITY d'une table.

@@ IDENTITY et SCOPE_IDENTITY renverront la dernière valeur d'identité générée dans n'importe quelle table de la session en cours. Cependant, SCOPE_IDENTITY renvoie la valeur uniquement dans la portée actuelle; @@ IDENTITY n'est pas limité à une portée spécifique.

IDENT_CURRENT n'est pas limité par la portée et la session; il est limité à une table spécifiée. IDENT_CURRENT renvoie la valeur d'identité générée pour une table spécifique dans n'importe quelle session et n'importe quelle étendue. Pour plus d'informations, voir IDENT_CURRENT.

  • IDENT_CURRENT est une fonction qui prend une table comme argument.
  • @@ IDENTITY peut retourner un résultat déroutant lorsque vous avez un déclencheur sur la table
  • SCOPE_IDENTITY est votre héros la plupart du temps.
Jakub Šturc
la source
14

@@ IDENTITY est la dernière identité insérée à l'aide de la connexion SQL actuelle. Il s'agit d'une bonne valeur à renvoyer à partir d'une procédure stockée d'insertion, où vous avez juste besoin de l'identité insérée pour votre nouvel enregistrement, et ne vous souciez pas si plus de lignes ont été ajoutées par la suite.

SCOPE_IDENTITY est la dernière identité insérée à l'aide de la connexion SQL actuelle, et dans la portée actuelle - c'est-à-dire, s'il y avait une deuxième IDENTITY insérée basée sur un déclencheur après votre insertion, elle ne serait pas reflétée dans SCOPE_IDENTITY, seulement l'insertion que vous avez effectuée . Franchement, je n'ai jamais eu de raison de l'utiliser.

IDENT_CURRENT (tablename) est la dernière identité insérée, quelle que soit la connexion ou la portée. Vous pouvez l'utiliser si vous souhaitez obtenir la valeur IDENTITY actuelle pour une table dans laquelle vous n'avez pas inséré d'enregistrement.

Guy Starbuck
la source
2
Vous ne devez jamais utiliser @@ identity à cette fin. Si quelqu'un ajoute un déclencheur plus tard, vous perdrez l'intégrité des données. @@ identiy est une pratique extrêmement dangereuse.
HLGEM
1
"valeur pour une table dans laquelle vous avez <<non>> inséré un enregistrement." Vraiment?
Abdul Saboor
13

Je ne peux pas parler d'autres versions de SQL Server, mais en 2012, la sortie directe fonctionne très bien. Vous n'avez pas besoin de vous soucier d'une table temporaire.

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

Soit dit en passant, cette technique fonctionne également lors de l'insertion de plusieurs lignes.

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

Production

ID
2
3
4
MarredCheese
la source
Si vous souhaitez l'utiliser plus tard, j'imagine que vous avez besoin de la table temporaire
JohnOsborne
@JohnOsborne Vous êtes invités à utiliser une table temporaire si vous le souhaitez, mais mon point était que ce n'est pas une exigence de OUTPUT. Si vous n'avez pas besoin de la table temporaire, votre requête finit par être beaucoup plus simple.
MarredCheese
10

Utilisez TOUJOURS scope_identity (), il n'y a JAMAIS besoin d'autre chose.

erikkallen
la source
13
Pas tout à fait jamais mais 99 fois sur 100, vous utiliserez Scope_Identity ().
CJM
Pour quoi avez-vous déjà utilisé autre chose?
erikkallen
11
si vous insérez plusieurs lignes avec un INSERT-SELECT, vous devrez capturer les multiples ID à l'aide de la clause OUTPUT
KM.
1
@KM: Oui, mais j'ai fait référence à scope_identity vs @@ identity vs ident_current. OUTPUT est une classe complètement différente et souvent utile.
erikkallen
2
Découvrez la réponse d' Orry ( stackoverflow.com/a/6073578/2440976 ) à cette question - en parallèle, et comme meilleure pratique, vous seriez sage de suivre sa configuration ... tout simplement génial!
Dan B
2

Créez un uuidet insérez-le également dans une colonne. Ensuite, vous pouvez facilement identifier votre ligne avec l'uuid. C'est la seule solution 100% fonctionnelle que vous pouvez implémenter. Toutes les autres solutions sont trop compliquées ou ne fonctionnent pas dans les mêmes cas de bord. Par exemple:

1) Créer une ligne

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

2) Obtenez la ligne créée

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
Frank Roth
la source
N'oubliez pas de créer un index pour le uuiddans la base de données. Ainsi, la ligne sera trouvée plus rapidement.
Frank Roth
Pour Node.js , vous pouvez utiliser ce module pour créer simplement un UUID: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth
Un GUID n'est pas une valeur d'identité, il a quelques backdraws par rapport à un simple entier.
Alejandro
1

Une autre façon de garantir l'identité des lignes que vous insérez est de spécifier les valeurs d'identité et d'utiliser le SET IDENTITY_INSERT ONpuis OFF. Cela garantit que vous savez exactement quelles sont les valeurs d'identité! Tant que les valeurs ne sont pas utilisées, vous pouvez insérer ces valeurs dans la colonne d'identité.

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 

Cela peut être une technique très utile si vous chargez des données d'une autre source ou fusionnez des données de deux bases de données, etc.

Andy Robertson
la source
0

Même s'il s'agit d'un thread plus ancien, il existe une nouvelle façon de le faire qui évite certains des pièges de la colonne IDENTITY dans les anciennes versions de SQL Server, comme les lacunes dans les valeurs d'identité après le redémarrage du serveur . Les séquences sont disponibles dans SQL Server 2016 et les versions ultérieures, la façon la plus récente consiste à créer un objet SEQUENCE à l'aide de TSQL. Cela vous permet de créer votre propre objet de séquence numérique dans SQL Server et de contrôler son incrémentation.

Voici un exemple:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Ensuite, dans TSQL, vous procéderiez comme suit pour obtenir l'ID de séquence suivant:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Voici les liens vers CREATE SEQUENCE et NEXT VALUE FOR

StevenJe
la source
Les séquences ont les mêmes problèmes d'identité, comme les lacunes (qui ne sont pas vraiment des problèmes).
Alejandro
-1

Après votre déclaration d'insertion, vous devez l'ajouter. Et assurez-vous du nom de la table où les données sont insérées. Vous obtiendrez la ligne actuelle non où la ligne est affectée à l'instant par votre instruction d'insertion.

IDENT_CURRENT('tableName')
Khan Ataur Rahman
la source
2
Avez-vous remarqué que cette même suggestion a reçu plusieurs réponses auparavant?
TT.
Oui. mais j'essaie de décrire la solution à ma façon.
Khan Ataur Rahman
Et si quelqu'un d'autre a inséré une ligne entre votre instruction d'insertion et votre appel IDENT_CURRENT (), vous obtiendrez l'ID de l'enregistrement que quelqu'un d'autre a inséré - probablement pas ce que vous voulez. Comme indiqué dans la plupart des réponses ci-dessus - dans la plupart des cas, vous devriez plutôt utiliser SCOPE_IDENTITY ().
Trondster