Bester Weg, um die Identität der eingefügten Zeile zu erhalten?

1119

Was ist der beste Weg, um aus IDENTITYder eingefügten Zeile zu kommen?

Ich kenne @@IDENTITYund IDENT_CURRENTund SCOPE_IDENTITYverstehe aber nicht die Vor- und Nachteile, die mit jedem verbunden sind.

Kann jemand bitte die Unterschiede erklären und wann ich sie jeweils verwenden sollte?

Oded
quelle
5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)oder ältere Methode: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();Sie können sie mit ExecuteScalar () in c # abrufen.
Serpooshan
4
Wie ist das besser als die anderen Antworten? (auch - warum postest du dies nicht als Antwort anstelle eines Kommentars). Bitte schreiben Sie eine vollständige Antwort (und erklären Sie, warum dies eine bessere Option ist als die veröffentlichten - wenn es die Version gibt, sagen Sie es).
Oded
Es ist wie eine kurze Zusammenfassung. ; D In der akzeptierten Antwort wird die Syntax der OUTPUT-Klausel nicht erwähnt, und es fehlt ein Beispiel. Auch Proben in anderen Beiträgen sind nicht so sauber ...
S.Serpooshan
2
@saeedserpooshan - dann bearbeiten Sie das in. Sie können das tun, wissen Sie? Sehen Sie, wann diese Antwort veröffentlicht wurde? Das ist älter als die OUTPUTKlausel in SQL Server.
Oded

Antworten:

1434
  • @@IDENTITYGibt den letzten Identitätswert zurück, der für eine Tabelle in der aktuellen Sitzung in allen Bereichen generiert wurde. Sie müssen hier vorsichtig sein , da es sich um Bereiche handelt. Sie könnten anstelle Ihrer aktuellen Anweisung einen Wert von einem Trigger erhalten.

  • SCOPE_IDENTITY()Gibt den letzten Identitätswert zurück, der für eine Tabelle in der aktuellen Sitzung und den aktuellen Bereich generiert wurde. Im Allgemeinen, was Sie verwenden möchten .

  • IDENT_CURRENT('tableName')Gibt den letzten Identitätswert zurück, der für eine bestimmte Tabelle in einer Sitzung und einem Bereich generiert wurde. Auf diese Weise können Sie angeben, aus welcher Tabelle der Wert stammen soll, falls die beiden oben genannten nicht ganz Ihren Anforderungen entsprechen ( sehr selten ). Wie @ Guy Starbuck bereits sagte: "Sie können dies verwenden, wenn Sie den aktuellen IDENTITY-Wert für eine Tabelle abrufen möchten, in die Sie keinen Datensatz eingefügt haben."

  • Mit der OUTPUTKlausel der INSERTAnweisung können Sie auf jede Zeile zugreifen, die über diese Anweisung eingefügt wurde. Da es auf die spezifische Anweisung beschränkt ist, ist es einfacher als die anderen oben genannten Funktionen. Es ist jedoch etwas ausführlicher (Sie müssen es in eine Tabellenvariable / temporäre Tabelle einfügen und diese dann abfragen) und liefert Ergebnisse auch in einem Fehlerszenario, in dem die Anweisung zurückgesetzt wird. Wenn Ihre Abfrage jedoch einen Plan für die parallele Ausführung verwendet, ist dies die einzige garantierte Methode zum Abrufen der Identität (ohne die Parallelität zu deaktivieren). Es wird jedoch ausgeführt , bevor Trigger und kann nicht zurück Trigger generierten Werte verwendet werden.

bdukes
quelle
48
Bekannter Fehler, bei dem SCOPE_IDENTITY () die falschen Werte zurückgibt : blog.sqlauthority.com/2009/03/24/… Die Problemumgehung besteht darin, das INSERT nicht in einem parallelen Plan mit mehreren Prozessoren auszuführen oder die OUTPUT-Klausel
KM zu verwenden.
3
Fast jedes Mal, wenn ich jemals 'Identität' wollte, wollte ich die Schlüssel der Datensätze kennen, die ich gerade eingefügt habe. In diesem Fall möchten Sie die OUTPUT-Klausel verwenden. Wenn Sie etwas anderes wollen, bemühen Sie sich, die Reaktion von bdukes zu lesen und zu verstehen.
Jerry
3
Mit müssen outputSie keine temporäre Tabelle erstellen, um die Ergebnisse zu speichern und abzufragen. Lassen Sie einfach den intoTeil der Ausgabeklausel weg und er gibt sie an eine Ergebnismenge aus.
SPB
96
Um andere vor Panik zu bewahren, wurde der oben erwähnte Fehler in Cumulative Update 5 für SQL Server 2008 R2 Service Pack 1
behoben.
1
@niico, ich denke, die Empfehlung ist die gleiche wie bisher. Das OUTPUTist die "beste", solange Sie keine Trigger verwenden und Fehler behandeln, aber SCOPE_IDENTITYdie einfachste und sehr selten auftretende Probleme
bdukes
180

Ich glaube, die sicherste und genaueste Methode zum Abrufen der eingefügten ID wäre die Verwendung der Ausgabeklausel.

zum Beispiel (entnommen aus dem folgenden MSDN- Artikel)

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
quelle
3
Ja, dies ist die richtige Methode für die Zukunft. Verwenden Sie nur eine der anderen Methoden, wenn Sie nicht mit SQL Server 2008 arbeiten (wir haben 2005 übersprungen, sind uns also nicht sicher, ob OUTPUT damals verfügbar war)
HLGEM
1
@HLGEM Es gibt eine MSDN-Seite für OUTPUTin SQL Server 2005 , es sieht also so aus, als ob nur SQL Server 2000 und früher ohne diese Seite sind
bdukes
6
woohoo! OUTPUT CLAUSE rockt :) Das wird meine aktuelle Aufgabe vereinfachen. Ich kannte diese Aussage vorher nicht. Danke Jungs!
SwissCoder
8
Ein wirklich kurzes Beispiel, um nur die eingefügte ID zu erhalten, finden Sie unter: stackoverflow.com/a/10999467/2003325
Luke
Ihre Verwendung von INTO mit OUTPUT ist eine gute Idee. Siehe: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (Aus einem Kommentar hier: stackoverflow.com/questions/7917695/… )
shlgug
112

Ich sage das Gleiche wie die anderen, also haben alle Recht, ich versuche nur, es klarer zu machen.

@@IDENTITYGibt die ID des letzten Objekts zurück, das von der Verbindung Ihres Clients zur Datenbank eingefügt wurde.
Meistens funktioniert dies einwandfrei, aber manchmal wird ein Trigger eine neue Zeile einfügen, von der Sie nichts wissen, und Sie erhalten die ID aus dieser neuen Zeile anstelle der gewünschten

SCOPE_IDENTITY()löst dieses Problem. Es gibt die ID des letzten Objekts zurück, das Sie in den SQL-Code eingefügt haben, den Sie an die Datenbank gesendet haben . Wenn Trigger zusätzliche Zeilen erstellen, wird nicht der falsche Wert zurückgegeben. Hurra

IDENT_CURRENTGibt die letzte ID zurück, die von jemandem eingefügt wurde. Wenn eine andere App zu einem ungünstigen Zeitpunkt eine weitere Zeile einfügt, erhalten Sie die ID dieser Zeile anstelle Ihrer.

Wenn Sie auf Nummer sicher gehen möchten, verwenden Sie immer SCOPE_IDENTITY(). Wenn Sie dabei bleiben @@IDENTITYund jemand beschließt, später einen Auslöser hinzuzufügen, wird Ihr gesamter Code beschädigt.

Orion Edwards
quelle
64

Der beste (sprich: sicherste) Weg, um die Identität einer neu eingefügten Zeile zu ermitteln, ist die Verwendung der folgenden outputKlausel:

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
quelle
5
SQL Server-Clustering ist eine Hochverfügbarkeitsfunktion und hat keinen Einfluss auf die Parallelität. Es ist sehr ungewöhnlich, dass einzeilige Einfügungen (der häufigste Fall für scope_identity()) ohnehin parallele Pläne erhalten. Und dieser Fehler wurde mehr als ein Jahr vor dieser Antwort behoben.
Martin Smith
Was meinst du mit Parallelität?
user1451111
@MartinSmith Der Kunde war nicht bereit , Ausfallzeiten auf ihre Server - Cluster zu erlauben , die CU Festsetzung dieses Problem (kein Scherz) zu installieren, so dass die einzige Lösung für uns alle die SQL zu verwenden , um neu zu schreiben war outputstatt scope_identity(). Ich habe die FUD über Clustering in der Antwort entfernt.
Ian Kemp
1
Vielen Dank, dies ist das einzige Beispiel, das ich finden konnte und das zeigt, wie der Wert aus der Ausgabe in einer Variablen verwendet wird, anstatt ihn nur auszugeben.
Sean Ray
26

Hinzufügen

SELECT CAST(scope_identity() AS int);

dann bis zum Ende Ihrer SQL-Anweisung einfügen

NewId = command.ExecuteScalar()

wird es abrufen.

Jim
quelle
18

Wenn Sie Entity Framework verwenden, wird die OUTPUTTechnik intern verwendet , um den neu eingefügten ID-Wert zurückzugeben

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

Die Ausgabeergebnisse werden in einer temporären Tabellenvariablen gespeichert, wieder mit der Tabelle verknüpft und geben den Zeilenwert aus der Tabelle zurück.

Hinweis: Ich habe keine Ahnung, warum EF die kurzlebige Tabelle wieder mit der realen Tabelle verbindet (unter welchen Umständen würden die beiden nicht übereinstimmen).

Aber genau das macht EF.

Diese Technik ( OUTPUT) ist nur unter SQL Server 2008 oder neuer verfügbar.

Bearbeiten - Der Grund für den Join

Der Grund dafür, dass Entity Framework wieder mit der ursprünglichen Tabelle verknüpft wird, anstatt nur die OUTPUTWerte zu verwenden, liegt darin, dass EF diese Technik auch verwendet, um die rowversioneiner neu eingefügten Zeile abzurufen.

Sie können optimistische Parallelität in Ihren Entity-Framework-Modellen verwenden, indem Sie das folgende TimestampAttribut verwenden: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

Wenn Sie dies tun, benötigt Entity Framework die rowversionder neu eingefügten Zeile:

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

Und um diese abzurufen TimetsampSie können nicht eine verwenden OUTPUTKlausel.

Das liegt daran, dass wenn Sie einen Auslöser auf dem Tisch haben, jeder, den TimestampSie ausgeben, falsch ist:

  • Erstes Einfügen. Zeitstempel: 1
  • Die OUTPUT-Klausel gibt einen Zeitstempel aus: 1
  • Trigger ändert die Zeile. Zeitstempel: 2

Der zurückgegebene Zeitstempel ist niemals korrekt, wenn Sie einen Auslöser auf dem Tisch haben. Sie müssen also eine separate verwenden SELECT.

Und selbst wenn Sie bereit wären, die falsche Zeilenversion zu erleiden, besteht der andere Grund für die Durchführung einer separaten Version darin, SELECTdass Sie rowversioneine Variable nicht in eine Tabellenvariable ausgeben können:

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

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

Der dritte Grund dafür ist die Symmetrie. Wenn eine Durchführung UPDATEmit einem Trigger auf einem Tisch, man kann nicht eine verwenden OUTPUTKlausel. Der Versuch, UPDATEmit einem zu tun, OUTPUTwird nicht unterstützt und gibt einen Fehler aus:

Der einzige Weg, dies zu tun, ist eine Folgeerklärung SELECT:

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
quelle
2
Ich stelle mir vor, dass sie übereinstimmen, um die Integrität sicherzustellen (z. B. im optimistischen Parallelitätsmodus, während Sie aus der Tabellenvariablen auswählen, hat möglicherweise jemand die Einfügerzeilen entfernt). Auch lieben Sie Ihre TurboEncabulators:)
Zaitsman
16

MSDN

@@ IDENTITY, SCOPE_IDENTITY und IDENT_CURRENT sind ähnliche Funktionen, da sie den zuletzt in die IDENTITY-Spalte einer Tabelle eingefügten Wert zurückgeben.

@@ IDENTITY und SCOPE_IDENTITY geben den letzten Identitätswert zurück, der in einer Tabelle in der aktuellen Sitzung generiert wurde. SCOPE_IDENTITY gibt den Wert jedoch nur innerhalb des aktuellen Bereichs zurück. @@ IDENTITY ist nicht auf einen bestimmten Bereich beschränkt.

IDENT_CURRENT ist nicht durch Umfang und Sitzung beschränkt. Es ist auf eine bestimmte Tabelle beschränkt. IDENT_CURRENT gibt den Identitätswert zurück, der für eine bestimmte Tabelle in einer beliebigen Sitzung und einem beliebigen Bereich generiert wurde. Weitere Informationen finden Sie unter IDENT_CURRENT.

Jakub Šturc
quelle
14

@@ IDENTITY ist die letzte Identität, die mit der aktuellen SQL-Verbindung eingefügt wurde. Dies ist ein guter Wert, um von einer gespeicherten Prozedur zum Einfügen zurückzukehren, bei der nur die Identität für Ihren neuen Datensatz eingefügt werden muss, und es ist egal, ob danach weitere Zeilen hinzugefügt wurden.

SCOPE_IDENTITY ist die letzte Identität, die mit der aktuellen SQL-Verbindung und im aktuellen Bereich eingefügt wurde. Wenn also nach dem Einfügen eine zweite IDENTITY basierend auf einem Trigger eingefügt wurde, wird diese nicht in SCOPE_IDENTITY angezeigt, sondern nur in der von Ihnen durchgeführten Einfügung . Ehrlich gesagt hatte ich nie einen Grund, dies zu nutzen.

IDENT_CURRENT (Tabellenname) ist die zuletzt eingefügte Identität, unabhängig von Verbindung oder Bereich. Sie können dies verwenden, wenn Sie den aktuellen IDENTITY-Wert für eine Tabelle abrufen möchten, in die Sie keinen Datensatz eingefügt haben.

Guy Starbuck
quelle
2
Sie sollten niemals @@ identity für diesen Zweck verwenden. Wenn jemand später einen Auslöser hinzufügt, verlieren Sie die Datenintegrität. Die Identität ist eine äußerst gefährliche Praxis.
HLGEM
1
"Wert für eine Tabelle, in die Sie << not >> einen Datensatz eingefügt haben." "Ja wirklich?"
Abdul Saboor
13

Ich kann nicht mit anderen Versionen von SQL Server sprechen, aber 2012 funktioniert die direkte Ausgabe einwandfrei. Sie müssen sich nicht um einen temporären Tisch kümmern.

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

Diese Technik funktioniert übrigens auch beim Einfügen mehrerer Zeilen.

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

Ausgabe

ID
2
3
4
MarredCheese
quelle
Wenn Sie es später verwenden möchten, stellen Sie sich vor, Sie benötigen die temporäre Tabelle
JohnOsborne
@JohnOsborne Sie können gerne eine temporäre Tabelle verwenden, wenn Sie möchten, aber mein Punkt war, dass dies keine Anforderung von ist OUTPUT. Wenn Sie die temporäre Tabelle nicht benötigen, ist Ihre Abfrage viel einfacher.
MarredCheese
10

IMMER Verwendung scope_identity (), dann ist es nie eine Notwendigkeit für irgendetwas anderes.

erikkallen
quelle
13
Nicht ganz nie, aber 99 von 100 Mal verwenden Sie Scope_Identity ().
CJM
Wofür hast du jemals etwas anderes benutzt?
Erikkallen
11
Wenn Sie mehrere Zeilen mit INSERT-SELECT einfügen, müssen Sie die mehreren IDs mit der OUTPUT-Klausel
KM
1
@KM: Ja, aber ich habe mich auf scope_identity vs @@ identity vs ident_current bezogen. OUTPUT ist eine völlig andere Klasse und oft nützlich.
Erikkallen
2
Schauen Sie sich die Antwort von Orry ( stackoverflow.com/a/6073578/2440976 ) auf diese Frage an - parallel und als Best Practice sollten Sie seinem Setup folgen ... einfach genial!
Dan B
2

Erstellen Sie ein uuidund fügen Sie es auch in eine Spalte ein. Dann können Sie Ihre Zeile leicht mit der UUID identifizieren. Dies ist die einzige 100% funktionierende Lösung, die Sie implementieren können. Alle anderen Lösungen sind zu kompliziert oder funktionieren nicht in denselben Randfällen. Z.B:

1) Zeile erstellen

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

2) Erstellen Sie eine Zeile

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
Frank Roth
quelle
Vergessen Sie nicht, einen Index für die uuidin der Datenbank zu erstellen . So wird die Zeile schneller gefunden.
Frank Roth
Für node.js können Sie mit diesem Modul einfach eine UUID erstellen : https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth
Eine GUID ist kein Identitätswert, sie weist im Vergleich zu einer einfachen Ganzzahl einige Rückzüge auf.
Alejandro
1

Eine andere Möglichkeit, die Identität der von Ihnen eingefügten Zeilen zu gewährleisten, besteht darin, die Identitätswerte anzugeben und das SET IDENTITY_INSERT ONund dann zu verwenden OFF. Dies garantiert, dass Sie genau wissen, was die Identitätswerte sind! Solange die Werte nicht verwendet werden, können Sie diese Werte in die Identitätsspalte einfügen.

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 

Dies kann eine sehr nützliche Technik sein, wenn Sie Daten aus einer anderen Quelle laden oder Daten aus zwei Datenbanken usw. zusammenführen.

Andy Robertson
quelle
0

Obwohl dies ein älterer Thread ist, gibt es eine neuere Möglichkeit, um einige der Fallstricke der IDENTITY-Spalte in älteren Versionen von SQL Server zu vermeiden, z. B. Lücken in den Identitätswerten nach dem Neustart des Servers . Sequenzen sind in SQL Server 2016 und Forward verfügbar. Die neuere Methode besteht darin, ein SEQUENCE-Objekt mit TSQL zu erstellen. Auf diese Weise können Sie in SQL Server ein eigenes numerisches Sequenzobjekt erstellen und steuern, wie es inkrementiert wird.

Hier ist ein Beispiel:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Dann würden Sie in TSQL Folgendes tun, um die nächste Sequenz-ID zu erhalten:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Hier sind die Links zu CREATE SEQUENCE und NEXT VALUE FOR

StevenJe
quelle
Sequenzen haben dieselben Identitätsprobleme wie die Lücken (die eigentlich keine Probleme sind).
Alejandro
-1

Nach Ihrer Einfügeanweisung müssen Sie diese hinzufügen. Und stellen Sie sicher, dass der Tabellenname, in den die Daten eingefügt werden, angezeigt wird. Sie erhalten die aktuelle Zeile, in der die Zeile gerade von Ihrer Einfügeanweisung betroffen ist.

IDENT_CURRENT('tableName')
Khan Ataur Rahman
quelle
2
Haben Sie bemerkt, dass genau dieser Vorschlag schon mehrmals beantwortet wurde?
TT.
Ja. aber ich versuche die lösung auf meine eigene weise zu beschreiben.
Khan Ataur Rahman
Und wenn jemand anderes eine Zeile zwischen Ihrer Einfügeanweisung und Ihrem IDENT_CURRENT () -Aufruf eingefügt hat, erhalten Sie die ID des Datensatzes, den jemand anderes eingefügt hat - wahrscheinlich nicht das, was Sie wollen. Wie in den meisten Antworten oben erwähnt, sollten Sie in den meisten Fällen lieber SCOPE_IDENTITY () verwenden.
Trondster