Najlepszy sposób na uzyskanie tożsamości wstawionego wiersza?

1119

Jaki jest najlepszy sposób na uzyskanie IDENTITYwstawionego wiersza?

Wiem o @@IDENTITYi IDENT_CURRENTa SCOPE_IDENTITY, ale nie rozumiem, plusy i minusy przymocowane do siebie.

Czy ktoś może wyjaśnić różnice i kiedy powinienem ich używać?

Oded
źródło
5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)lub starsza metoda: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();możesz pobrać w c # za pomocą ExecuteScalar ().
S.Serpooshan
4
Jak to jest lepsze niż inne odpowiedzi? (także - dlaczego nie publikujesz tego jako odpowiedzi zamiast komentarza). Proszę napisać pełną odpowiedź (i wyjaśnić, dlaczego jest to lepsza opcja niż zamieszczone - jeśli tak, to w przypadku konkretnej wersji).
Oded
to jest jak krótkie podsumowanie. ; D Przyjęta odpowiedź nie wspomina o składni klauzuli OUTPUT i brakuje w niej próbki. Także próbki z innych postów nie są tak czyste ...
S.Serpooshan
2
@saeedserpooshan - następnie edytuj to. Możesz to zrobić, wiesz? Widzisz, kiedy ta odpowiedź została opublikowana? To poprzedza OUTPUTklauzulę w SQL Server.
Oded

Odpowiedzi:

1434
  • @@IDENTITYzwraca ostatnią wartość tożsamości wygenerowaną dla dowolnej tabeli w bieżącej sesji we wszystkich zakresach. Musisz tu być ostrożny , ponieważ jest to w wielu zakresach. Możesz otrzymać wartość z wyzwalacza, zamiast z bieżącego wyciągu.

  • SCOPE_IDENTITY()zwraca ostatnią wartość tożsamości wygenerowaną dla dowolnej tabeli w bieżącej sesji i bieżący zakres. Ogólnie to, czego chcesz użyć .

  • IDENT_CURRENT('tableName')zwraca ostatnią wartość tożsamości wygenerowaną dla określonej tabeli w dowolnej sesji i dowolnym zakresie. Pozwala to określić, z której tabeli chcesz uzyskać wartość, na wypadek gdyby dwie powyższe nie były dokładnie tym, czego potrzebujesz ( bardzo rzadko ). Ponadto, jak wspomniał @ Guy Starbuck , „Możesz użyć tego, jeśli chcesz uzyskać bieżącą wartość TOŻSAMOŚCI dla tabeli, do której nie wstawiłeś rekordu”.

  • OUTPUTKlauzula z INSERToświadczeniem pozwoli Ci uzyskać dostęp do każdego wiersza, który został wstawiony za pośrednictwem tego rachunku. Ponieważ zakres obejmuje określoną instrukcję, jest prostszy niż w przypadku innych powyższych funkcji. Jest to jednak trochę bardziej szczegółowe (musisz wstawić do tabeli zmienna / temp tabela, a następnie zapytać o to) i daje wyniki nawet w scenariuszu błędu, w którym instrukcja jest wycofywana. To powiedziawszy, jeśli twoje zapytanie korzysta z równoległego planu wykonania, jest to jedyna gwarantowana metoda uzyskania tożsamości (bez wyłączania równoległości). Jest on jednak wykonywany przed wyzwalaczami i nie można go użyć do zwrócenia wartości wygenerowanych przez wyzwalacz.

bdukes
źródło
48
znany błąd SCOPE_IDENTITY () zwracający niepoprawne wartości: blog.sqlauthority.com/2009/03/24 /... obejściem tego problemu jest nie uruchomienie INSERT w planie równoległym dla wielu procesorów lub użycie klauzuli OUTPUT
KM.
3
Niemal za każdym razem, gdy chciałem „tożsamości”, chciałem poznać klucz (-y) zapisów, które właśnie wstawiłem. Jeśli taka jest Twoja sytuacja, chcesz użyć klauzuli OUTPUT. Jeśli chcesz czegoś innego, postaraj się przeczytać i zrozumieć odpowiedź bdukes.
Jerry
3
Dzięki outputnie musisz tworzyć tabeli tymczasowej, aby przechowywać i sprawdzać wyniki. Po prostu zostaw intoczęść klauzuli wyjściowej, a wyświetli się ona w zestawie wyników.
spb
96
Aby uratować innych przed panikowaniem, wspomniany wyżej błąd został naprawiony w aktualizacji zbiorczej 5 dla programu SQL Server 2008 R2 z dodatkiem Service Pack 1.
GaTechThomas
1
@niico, myślę, że zalecenie jest takie samo, jak było, to znaczy, że OUTPUTjest „najlepsze”, o ile nie używasz wyzwalaczy i obsługuje się błędy, ale SCOPE_IDENTITYjest najprostsze i bardzo rzadko ma problemy
bdukes
180

Uważam, że najbezpieczniejszym i najdokładniejszym sposobem odzyskania wstawionego identyfikatora byłoby użycie klauzuli wyjściowej.

na przykład (zaczerpnięte z następującego artykułu 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
źródło
3
Tak to jest poprawna metoda idzie do przodu, tylko skorzystać z jednej z innymi, jeśli nie jesteś na SQL Server 2008 (pominęliśmy 2005 więc nie wiem, czy WYJŚCIE była dostępna wtedy)
HLGEM
1
@HLGEM Strona OUTPUTSQL Server 2005 zawiera stronę MSDN , więc wygląda na to, że jest to tylko SQL Server 2000 i wcześniejsze
wersje
6
woohoo! KLAUZULA WYNIKÓW kołysze :) To uprości moje bieżące zadanie. Nie znałem tego stwierdzenia wcześniej. Dzięki chłopaki!
SwissCoder
8
Aby uzyskać bardzo zwięzły przykład, aby uzyskać tylko wstawiony identyfikator, zobacz: stackoverflow.com/a/10999467/2003325
Luke
Twoje użycie INTO z OUTPUT jest dobrym pomysłem. Zobacz: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/... (Od komentarza tutaj: stackoverflow.com/questions/7917695/… )
shlgug
112

Mówię to samo co pozostali, więc wszyscy mają rację, staram się tylko wyjaśnić.

@@IDENTITYzwraca identyfikator ostatniej rzeczy wstawionej przez połączenie twojego klienta z bazą danych.
Przez większość czasu działa to dobrze, ale czasami wyzwalacz pójdzie i wstawi nowy wiersz, o którym nie wiesz, i otrzymasz identyfikator z tego nowego wiersza, zamiast tego, który chcesz

SCOPE_IDENTITY()rozwiązuje ten problem. Zwraca identyfikator ostatniej rzeczy, którą wstawiłeś do kodu SQL wysłanego do bazy danych. Jeśli wyzwalacze przejdą i utworzą dodatkowe wiersze, nie spowodują zwrotu niewłaściwej wartości. Brawo

IDENT_CURRENTzwraca ostatni identyfikator, który został wstawiony przez kogokolwiek. Jeśli jakaś inna aplikacja zdarzy się wstawić kolejny wiersz w niefortunnym czasie, otrzymasz identyfikator tego wiersza zamiast swojego.

Jeśli chcesz grać bezpiecznie, zawsze używaj SCOPE_IDENTITY(). Jeśli będziesz trzymać się, @@IDENTITYa ktoś zdecyduje się dodać wyzwalacz później, cały Twój kod się zepsuje.

Orion Edwards
źródło
64

Najlepszym (czytaj: najbezpieczniejszym) sposobem uzyskania tożsamości nowo wstawionego wiersza jest użycie outputklauzuli:

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
źródło
5
Klastrowanie serwerów SQL jest funkcją wysokiej dostępności i nie ma wpływu na równoległość. Bardzo rzadko zdarza się, że wstawki jednorzędowe (co jest najczęstszym przypadkiem scope_identity()), aby uzyskać równoległe plany. Ten błąd został naprawiony ponad rok przed odpowiedzią.
Martin Smith,
Co rozumiesz przez paralelizm?
user1451111,
@MartinSmith Klient nie był skłonny pozwolić na przestoje ich klastrze serwerów zainstalować CU rozwiązaniem tego problemu (nie żartuje), więc jedynym rozwiązaniem było dla nas, aby przepisać cały SQL do używania outputzamiast scope_identity(). W odpowiedzi usunąłem FUD dotyczący grupowania.
Ian Kemp,
1
Dziękuję, to jest jedyny przykład, jaki udało mi się znaleźć, który pokazuje, jak używać wartości z danych wyjściowych w zmiennej zamiast po prostu ją wyprowadzać.
Sean Ray
26

Dodaj

SELECT CAST(scope_identity() AS int);

na końcu instrukcji wstawiania sql

NewId = command.ExecuteScalar()

odzyska to.

Jim
źródło
18

Gdy używasz Entity Framework, wewnętrznie korzysta z tej OUTPUTtechniki, aby zwrócić nowo wstawioną wartość identyfikatora

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

Wyniki wyjściowe są przechowywane w tymczasowej zmiennej tabeli, łączone z powrotem do tabeli i zwracają wartość wiersza z tabeli.

Uwaga: Nie mam pojęcia, dlaczego EF miałby wewnętrznie dołączyć efemeryczny stół z powrotem do prawdziwego stołu (w jakich okolicznościach te dwa nie pasują).

Ale tak właśnie działa EF.

Ta technika ( OUTPUT) jest dostępna tylko w SQL Server 2008 lub nowszym.

Edytuj - powód przyłączenia

Powodem, dla którego Entity Framework dołącza z powrotem do oryginalnej tabeli, zamiast po prostu używać OUTPUTwartości, jest to, że EF używa tej techniki również w celu uzyskania rowversionnowo wstawionego wiersza.

Z optymistycznej współbieżności można korzystać w modelach struktury encji, używając Timestampatrybutu: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

Po wykonaniu tej czynności Entity Framework będzie potrzebował rowversionnowego wiersza:

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

Aby to odzyskać Timetsamp, nie możesz użyć OUTPUTklauzuli.

To dlatego, że jeśli na stole jest wyzwalacz, każde TimestampWYJŚCIE będzie błędne:

  • Wstępna wstawka. Znacznik czasu: 1
  • Klauzula OUTPUT generuje znacznik czasu: 1
  • wyzwalacz modyfikuje wiersz. Znacznik czasu: 2

Zwrócony znacznik czasu nigdy nie będzie poprawny, jeśli masz wyzwalacz na stole. Więc należy użyć oddzielnego SELECT.

I nawet jeśli byłeś skłonny ponieść niepoprawną konwersję wierszy, innym powodem do wykonania osobnego SELECTjest to, że nie możesz WYKONAĆ rowversionzmiennej zmiennej tabeli:

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

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

Trzecim powodem jest symetria. Podczas wykonywania UPDATEna stole z wyzwalaczem nie można użyć OUTPUTklauzuli. Próba wykonania UPDATEza pomocą OUTPUTnie jest obsługiwana i spowoduje błąd:

Jedynym sposobem, aby to zrobić, jest SELECToświadczenie uzupełniające :

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
źródło
2
Wyobrażam sobie, że pasują do nich, aby zapewnić integralność (np. w trybie optymistycznej współbieżności, gdy wybierasz ze zmiennej tabeli, ktoś mógł usunąć wiersze insertera). Również uwielbiam twój TurboEncabulators:)
zaitsman
16

MSDN

@@ IDENTITY, SCOPE_IDENTITY i IDENT_CURRENT są podobnymi funkcjami, ponieważ zwracają ostatnią wartość wstawioną do kolumny IDENTITY tabeli.

@@ IDENTITY i SCOPE_IDENTITY zwrócą ostatnią wartość tożsamości wygenerowaną w dowolnej tabeli w bieżącej sesji. Jednak SCOPE_IDENTITY zwraca wartość tylko w bieżącym zakresie; @@ TOŻSAMOŚĆ nie jest ograniczona do określonego zakresu.

IDENT_CURRENT nie jest ograniczony zakresem i sesją; jest ograniczony do określonej tabeli. IDENT_CURRENT zwraca wartość tożsamości wygenerowaną dla określonej tabeli w dowolnej sesji i dowolnym zakresie. Aby uzyskać więcej informacji, zobacz IDENT_CURRENT.

  • IDENT_CURRENT to funkcja, która przyjmuje tabelę jako argument.
  • @@ IDENTITY może zwrócić mylące wyniki, gdy masz wyzwalacz na stole
  • SCOPE_IDENTITY jest twoim bohaterem przez większość czasu.
Jakub Šturc
źródło
14

@@ TOŻSAMOŚĆ to ostatnia tożsamość wstawiona przy użyciu bieżącego połączenia SQL. Jest to dobra wartość, aby powrócić z procedury składowanej wstawiania, w której wystarczy wstawić tożsamość dla nowego rekordu, i nie przejmuj się, czy później zostanie dodanych więcej wierszy.

SCOPE_IDENTITY to ostatnia tożsamość wstawiona przy użyciu bieżącego połączenia SQL, aw bieżącym zakresie - to znaczy, jeśli wstawiono drugą tożsamość opartą na wyzwalaczu po wstawieniu, nie byłaby odzwierciedlona w SCOPE_IDENTITY, tylko wstawiona przez Ciebie wstawka . Szczerze mówiąc, nigdy nie miałem powodu, aby z tego korzystać.

IDENT_CURRENT (tablename) to ostatnia wstawiona tożsamość bez względu na połączenie lub zakres. Możesz użyć tego, jeśli chcesz uzyskać bieżącą wartość TOŻSAMOŚCI dla tabeli, do której nie wstawiłeś rekordu.

Guy Starbuck
źródło
2
Nigdy nie należy używać do tego celu tożsamości @@. Jeśli ktoś doda wyzwalacz później, utracisz integralność danych. @@ identiy jest niezwykle niebezpieczną praktyką.
HLGEM,
1
„wartość dla tabeli, do której <<not>> wstawiono rekord.” Naprawdę?
Abdul Saboor
13

Nie mogę rozmawiać z innymi wersjami programu SQL Server, ale w 2012 r. Bezpośrednie wyświetlanie jest w porządku. Nie musisz zawracać sobie głowy tymczasowym stołem.

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

Nawiasem mówiąc, ta technika działa również podczas wstawiania wielu wierszy.

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

Wynik

ID
2
3
4
MarredCheese
źródło
Jeśli chcesz go później użyć, wyobrażam sobie, że potrzebujesz tabeli tymczasowej
JohnOsborne,
@JohnOsborne Jeśli chcesz, możesz skorzystać z tabeli tymczasowej, ale miałem na myśli, że nie jest to wymagane OUTPUT. Jeśli nie potrzebujesz tabeli tymczasowej, Twoje zapytanie jest znacznie prostsze.
MarredCheese
10

ZAWSZE używaj scope_identity (), NIGDY nie potrzebujesz niczego innego.

erikkallen
źródło
13
Nie całkiem nigdy, ale 99 razy na 100, użyjesz Scope_Identity ().
CJM
Do czego kiedykolwiek używałeś czegoś innego?
erikkallen
11
jeśli wstawisz kilka wierszy za pomocą INSERT-SELECT, będziesz musiał przechwycić wiele identyfikatorów za pomocą klauzuli OUTPUT
KM.
1
@KM: Tak, ale odnosiłem się do scope_identity vs @@ identity vs ident_current. WYJŚCIE to zupełnie inna klasa i często przydatna.
erikkallen
2
Sprawdź odpowiedź Orry'ego ( stackoverflow.com/a/6073578/2440976 ) na to pytanie - równolegle i zgodnie z najlepszą praktyką, mądrze byłoby śledzić jego konfigurację ... po prostu genialne!
Dan B
2

Utwórz, uuida także wstaw go do kolumny. Następnie możesz łatwo zidentyfikować swój wiersz za pomocą UUID. To jedyne w 100% działające rozwiązanie, które możesz wdrożyć. Wszystkie pozostałe rozwiązania są zbyt skomplikowane lub nie działają w tych samych przypadkach brzegowych. Na przykład:

1) Utwórz wiersz

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

2) Uzyskaj utworzony wiersz

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
Frank Roth
źródło
Nie zapomnij utworzyć indeksu dla uuidbazy danych. Więc wiersz zostanie znaleziony szybciej.
Frank Roth,
Dla node.js można użyć tego modułu po prostu stworzyć UUID: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth,
Identyfikator GUID nie jest wartością tożsamości, ma pewne wypłaty w porównaniu do prostej liczby całkowitej.
Alejandro
1

Innym sposobem na zagwarantowanie tożsamości wstawianych wierszy jest określenie wartości tożsamości i użycie opcji SET IDENTITY_INSERT ONa następnie OFF. To gwarantuje, że wiesz dokładnie, jakie są wartości tożsamości! Dopóki wartości nie są używane, można je wstawić do kolumny tożsamości.

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 

Może to być bardzo przydatną techniką, jeśli ładujesz dane z innego źródła lub scalasz dane z dwóch baz danych itp.

Andy Robertson
źródło
0

Mimo że jest to starszy wątek, istnieje nowszy sposób, aby to zrobić, co pozwala uniknąć niektórych pułapek kolumny IDENTITY w starszych wersjach programu SQL Server, takich jak luki w wartościach tożsamości po ponownym uruchomieniu serwera . Sekwencje są dostępne w programie SQL Server 2016 i nowszym sposobem jest utworzenie obiektu SEKWENCJA za pomocą TSQL. Umożliwia to tworzenie własnych obiektów sekwencji numerycznych w SQL Server i kontrolowanie ich przyrostów.

Oto przykład:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Następnie w TSQL wykonaj następujące czynności, aby uzyskać identyfikator następnej sekwencji:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Oto linki do UTWÓRZ SEKWENCJĘ i NASTĘPNEJ WARTOŚCI DLA

StevenJe
źródło
Sekwencje mają takie same problemy z tożsamością, jak luki (które tak naprawdę nie są problemami).
Alejandro
-1

Po wyciągnięciu instrukcji musisz to dodać. I upewnij się, że podana jest nazwa tabeli, do której wstawiane są dane. Otrzymasz bieżący wiersz, nie w którym wierszu wpływa teraz instrukcja wstawiania.

IDENT_CURRENT('tableName')
Khan Ataur Rahman
źródło
2
Czy zauważyłeś, że na tę samą sugestię udzielono odpowiedzi już kilka razy?
TT.
tak. ale próbuję opisać rozwiązanie na swój własny sposób.
Khan Ataur Rahman
A jeśli ktoś wstawi wiersz między twoją instrukcją insert a twoim wywołaniem IDENT_CURRENT (), otrzymasz identyfikator rekordu, który wstawił ktoś inny - prawdopodobnie nie to, czego chcesz. Jak zauważono w większości odpowiedzi powyżej - w większości przypadków powinieneś raczej użyć SCOPE_IDENTITY ().
Trondster,