Найкращий спосіб отримати ідентифікацію вставленого рядка?


1119

Який найкращий спосіб отримати IDENTITYвставлений рядок?

Я знаю , про @@IDENTITYта IDENT_CURRENTі , SCOPE_IDENTITYале не розумію , плюси і мінуси , приєднані до кожного.

Може хтось, будь ласка, пояснить відмінності, і коли я повинен використовувати кожен?


5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)або старіший метод: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();ви можете отримати його в c # за допомогою ExecuteScalar ().
S.Serpooshan

4
Як це краще, ніж інші? (також - чому ви не публікуєте це як відповідь замість коментаря). Будь ласка, напишіть повну відповідь (і поясніть, чому це кращий варіант, ніж розміщений варіант - якщо це версія, скажіть так).
Одід

це просто як короткий підсумок. ; D У прийнятій відповіді не згадується синтаксис пункту OUTPUT і не вистачає вибірки. Також зразки на інших посадах не такі чисті ...
С.Серпоошан

2
@saeedserpooshan - тоді відредагуйте це. Ви можете це зробити, знаєте? Бачите, коли цю відповідь було опубліковано? Це передує OUTPUTумові в SQL Server.
Одід

Відповіді:


1434
  • @@IDENTITYповертає останнє значення ідентичності, згенеровану для будь-якої таблиці в поточному сеансі, за всіма областями. Ви повинні бути обережними тут , оскільки це в різних сферах. Ви можете отримати значення за допомогою тригера замість поточного оператора.

  • SCOPE_IDENTITY()повертає останнє значення ідентичності, сформоване для будь-якої таблиці в поточному сеансі та поточній області. Взагалі те, що ви хочете використовувати .

  • IDENT_CURRENT('tableName')повертає останнє значення ідентичності, згенеровану для конкретної таблиці в будь-якому сеансі та будь-якій області. Це дозволяє вам вказати, з якої таблиці ви хочете значення, якщо два вище не дуже потрібні ( дуже рідко ). Також, як згадував @ Guy Starbuck , "Ви можете використовувати це, якщо хочете отримати поточне значення IDENTITY для таблиці, в яку ви не вставили запис".

  • OUTPUTПоложення про INSERTзаяві дозволить вам отримати доступ до кожної рядку , який був вставлений з допомогою цієї заяви. Оскільки він підпадає під конкретний вислів, він є більш простим, ніж інші функції вище. Однак, це трохи більш багатослівний (вам потрібно буде вставити в таблицю змінну / таблицю temp, а потім зробити це запит), і це дає результати навіть у сценарії помилок, коли оператор відкочується назад. Однак, якщо ваш запит використовує паралельний план виконання, це єдиний гарантований метод отримання ідентичності (окрім відключення паралелізму). Однак він виконується перед тригерами і не може бути використаний для повернення створених тригером значень.


48
відома помилка з SCOPE_IDENTITY (), що повертає неправильні значення: blog.sqlauthority.com/2009/03/24/…, навколо чого не можна запускати INSERT у паралельному плані багатопроцесорного пристрою або використовувати пункт OUTPUT
KM.

3
Майже кожен раз, коли мені хотілося «ідентичності», я хотів знати ключ (и) запису, який я щойно вставив. Якщо це ваша ситуація, ви хочете використовувати пункт OUTPUT. Якщо ви хочете чогось іншого, застосуйте зусилля, щоб прочитати та зрозуміти відповідь bdukes.
Джеррі

3
З цим outputвам не потрібно створювати темп-таблицю для зберігання та запиту результатів. Просто залиште intoчастину вихідного пункту, і це виведе їх до набору результатів.
spb

96
Щоб врятувати інших від паніки, згадана вище помилка була виправлена ​​в накопичувальному оновлення 5 для пакета оновлень 1 для SQL Server 2008 R2 1.
GaTechThomas

1
@niico, я думаю, що рекомендація така ж, як і раніше, а саме, OUTPUTце "найкраще", поки ви не використовуєте тригери та обробляєте помилки, але SCOPE_IDENTITYце найпростіший і дуже рідко виникає проблеми
bdukes

180

Я вважаю, що найбезпечнішим і найточнішим способом отримання вставленого ідентифікатора буде використання вихідного пункту.

наприклад (взято з наступної статті 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

3
Так, це правильний метод, який рухається вперед, використовуйте лише один з інших, якщо ви не на SQL Server 2008 (ми пропустили 2005 рік, тому не впевнені, чи був
вихід

1
@HLGEM Існує сторінка MSDN для OUTPUTSQL Server 2005 , тому схоже, що без неї просто SQL Server 2000 і більш ранні
bdukes

6
вуоху! ВИХІДНИЙ КЛАУЗ скелі :) Це спростить моє поточне завдання. Раніше не знав цього твердження. Дякую, хлопці!
SwissCoder

8
Для дійсно стислого прикладу просто отримати вставлений ідентифікатор, подивіться на: stackoverflow.com/a/10999467/2003325
Лука

Використання INTO з OUTPUT - хороша ідея. Див: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11 / ... (З коментаря тут: stackoverflow.com/questions/7917695 / ... )
shlgug

112

Я кажу те саме, що й інші хлопці, тому всі правильно, я просто намагаюся зробити це більш зрозумілим.

@@IDENTITYповертає ідентифікатор останнього, що був вставлений підключенням вашого клієнта до бази даних.
У більшості випадків це працює добре, але іноді тригер буде вставляти новий рядок, про який ви не знаєте, і ви отримаєте ідентифікатор з цього нового рядка замість потрібного

SCOPE_IDENTITY()вирішує цю проблему. Він повертає ідентифікатор останнього, що ви вставили у код SQL, який ви надіслали до бази даних. Якщо тригери йдуть та створюють додаткові рядки, вони не спричинять повернення неправильного значення. Ура

IDENT_CURRENTповертає останній ідентифікатор, який хтось вставив. Якщо в інший додаток буде вставлено інший рядок в непридатний час, ви отримаєте ідентифікатор цього рядка замість вашого.

Якщо ви хочете грати в безпеку, завжди використовуйте SCOPE_IDENTITY(). Якщо ви дотримуєтесь, @@IDENTITYі хтось вирішить пізніше додати тригер, весь ваш код порушиться.


64

Найкращий (прочитаний: найбезпечніший) спосіб отримати ідентифікацію щойно вставленого рядка - це за допомогою outputпункту:

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)

5
Кластеризація сервера SQL - це функція високої доступності і не має відношення до паралелізму. Дуже рідко для однорядних вставок (найчастіший випадок scope_identity()) все-таки отримувати паралельні плани. І ця помилка була виправлена ​​ще за рік до цієї відповіді.
Мартін Сміт

Що ви маєте на увазі під паралелізмом.
user1451111

@MartinSmith Клієнт не бажав допускати простоїв на своєму серверному кластері для встановлення CU, що виправляє цю проблему (не жартуючи), тому єдиним рішенням було для нас переписати весь SQL, який використовується outputзамість scope_identity(). Я видалив FUD щодо кластеризації у відповіді.
Ян Кемп

1
Дякую, це єдиний приклад, який мені вдалося знайти, який показує, як використовувати значення з виводу у змінній, а не просто виводити її.
Шон Рей

26

Додайте

SELECT CAST(scope_identity() AS int);

до кінця вставки sql, тоді

NewId = command.ExecuteScalar()

відновить його.


18

Коли ви використовуєте Entity Framework, він використовує внутрішню OUTPUTтехніку для повернення щойно вставленого значення ID

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

Результати виводу зберігаються у тимчасовій змінній таблиці, приєднуються назад до таблиці і повертають значення рядка з таблиці.

Примітка. Я не маю поняття, чому EF внутрішньо приєднає ефемерну таблицю до реальної таблиці (за яких обставин вони не збігаються).

Але це робить EF.

Ця методика ( OUTPUT) доступна лише на SQL Server 2008 або новіших версіях.

Редагувати - причина приєднання

Причина того, що Entity Framework приєднується до початкової таблиці, а не просто використовувати OUTPUTзначення, полягає в тому, що EF також використовує цю техніку для отримання rowversionщойно вставленого рядка.

Ви можете використовувати оптимістичну сумісність у моделях вашої структури, використовуючи Timestampатрибут: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

Коли ви це зробите, Entity Framework потребуватиме rowversionщойно вставлений рядок:

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

І щоб отримати це, Timetsampви не можете використовувати OUTPUTпункт.

Це тому, що якщо на столі є тригер, будь- Timestampякий вивід буде помилятися:

  • Початкова вставка. Часова мітка: 1
  • Стаття OUTPUT видає часову позначку: 1
  • тригер змінює рядок. Часова мітка: 2

Повернута мітка часу ніколи не буде правильною, якщо у вас є тригер на столі. Тому потрібно використовувати окремий SELECT.

І навіть якщо ви були готові зазнати неправильну версію версії, інша причина, щоб виконати окрему, SELECTполягає в тому, що ви не можете вивести А в табличну rowversionзмінну:

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

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

Третя причина цього - симетрія. Виконуючи UPDATEтаблицю за допомогою тригера, ви не можете використовувати OUTPUTпункт. Спроба зробити UPDATEз OUTPUTне підтримується, і це призведе до помилки:

Єдиний спосіб зробити це за допомогою подальшої 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

2
Я думаю, вони відповідають їм для забезпечення цілісності (наприклад, в оптимістичному режимі одночасності, коли ви вибираєте зі змінної таблиці, хтось, можливо, видалив рядки вставки). Також любите свого TurboEncabulators:)
zaitsman

16

MSDN

@@ IDENTITY, SCOPE_IDENTITY та IDENT_CURRENT є подібними функціями, оскільки вони повертають останнє значення, вставлене у стовпець IDENTITY таблиці.

@@ IDENTITY та SCOPE_IDENTITY повернуть останнє значення ідентичності, згенерованого в будь-якій таблиці поточного сеансу. Однак SCOPE_IDENTITY повертає значення лише в поточній області; @@ Ідентичність не обмежується певним обсягом.

IDENT_CURRENT не обмежений обсягом та сеансом; він обмежений вказаною таблицею. IDENT_CURRENT повертає значення ідентичності, згенерованої для конкретної таблиці в будь-якому сеансі та будь-якій області. Для отримання додаткової інформації див. IDENT_CURRENT.

  • IDENT_CURRENT - це функція, яка приймає таблицю як аргумент.
  • @@ IDENTITY може повернути заплутаний результат, якщо у вас є тригер на столі
  • SCOPE_IDENTITY - це ваш герой більшість часу.

14

@@ IDENTITY - це остання ідентифікація, вставлена ​​за допомогою поточного підключення SQL. Це гарне значення для повернення зі збереженої процедури вставки, де вам просто потрібна ідентифікація, вставлена ​​для вашого нового запису, і не хвилюється, чи буде додано більше рядків після цього.

SCOPE_IDENTITY - це остання ідентифікація, вставлена ​​за допомогою поточного підключення SQL, і в поточному діапазоні - тобто, якщо після вставки після вставки вставився другий ідентифікатор, він не відображатиметься в SCOPE_IDENTITY, лише вставку, яку ви виконали . Чесно кажучи, у мене ніколи не було приводу використовувати це.

IDENT_CURRENT (ім'я таблиці) - це остання введена ідентифікація, незалежно від з'єднання чи області. Ви можете використовувати це, якщо хочете отримати поточне значення IDENTITY для таблиці, в яку ви не вставили запис.


2
Ніколи не використовуйте для цієї мети особу @@. Якщо хтось додасть тригер пізніше, ви втратите цілісність даних. @@ identiy - надзвичайно небезпечна практика.
HLGEM

1
"значення для таблиці, у яку ви <<not>> вставили запис." Дійсно?
Абдул Сабур

13

Я не можу спілкуватися з іншими версіями SQL Server, але в 2012 році виведення безпосередньо працює просто чудово. Вам не потрібно турбуватися з тимчасовим столом.

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

До речі, ця техніка працює і при вставці декількох рядків.

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

Вихідні дані

ID
2
3
4

Якщо ви хочете скористатися ним пізніше, я думаю, вам потрібна таблиця темп
JohnOsborne

@JohnOsborne Ви можете використовувати темп-таблицю, якщо хочете, але, на мою думку, це не є вимогою OUTPUT. Якщо вам не потрібна таблиця темп, то ваш запит стає набагато простішим.
MarredCheese

10

ЗАВЖДИ використовуйте domain_identity (), НІКОЛИ не потрібно нічого іншого.


13
Не зовсім ніколи, але 99 разів із 100, ви будете використовувати Scope_Identity ().
CJM

Для чого ти коли-небудь ще використовував?
erikkallen

11
якщо ви вставите кілька рядків INSERT-SELECT, вам потрібно буде зафіксувати кілька ідентифікаторів за допомогою пункту OUTPUT
KM.

1
@KM: Так, але я посилався на domain_identity vs @@ identitet vs ident_current. OUTPUT - це зовсім інший клас і часто корисний.
erikkallen

2
Ознайомтеся з відповіддю Оррі ( stackoverflow.com/a/6073578/2440976 ) на це запитання - паралелізм, і як найкраща практика, було б розумно слідувати його налаштуванню ... просто геніально!
Дан Б

2

Створіть а, uuidа також вставте його у стовпчик. Тоді ви можете легко ідентифікувати свій рядок з uuid. Це єдине 100% робоче рішення, яке ви можете реалізувати. Усі інші рішення занадто складні або не працюють у тих самих крайових випадках. Наприклад:

1) Створити рядок

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

2) Отримайте створений рядок

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';

Не забудьте створити індекс для uuidбази даних. Так рядок буде знайдено швидше.
Френк Рот

Для node.js ви можете використовувати цей модуль, щоб просто створити uuid : https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Френк Рот

GUID не є значенням ідентичності, він має деякі зворотні звороти порівняно з простим цілим числом.
Алехандро

1

Ще один спосіб гарантувати ідентичність рядків, які ви вставляєте, - це вказати значення ідентичності та використовувати SET IDENTITY_INSERT ONі потім OFF. Це гарантує, що ви точно знаєте, що таке ідентичність! Поки ці значення не використовуються, ви можете вставити ці значення в стовпець ідентичності.

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 

Це може бути дуже корисною технікою, якщо ви завантажуєте дані з іншого джерела або об'єднуєте дані з двох баз даних тощо.


0

Незважаючи на те, що це старший потік, є більш новий спосіб зробити це, що дозволяє уникнути деяких підводних каменів стовпця IDENTITY у старих версіях SQL Server, як , наприклад, прогалини у значеннях ідентичності після перезавантаження сервера. . Послідовності доступні в SQL Server 2016 і вперед, що новішим способом є створення об’єкта SEQUENCE за допомогою TSQL. Це дозволяє створювати власний об'єкт числової послідовності в SQL Server і контролювати його збільшення.

Ось приклад:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Тоді в TSQL ви зробите наступне, щоб отримати наступний ідентифікатор послідовності:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Ось посилання на СТВОРИТИ ПОСЛІДОВНІСТЬ та НАСТУПНУ ЦІННУ ДЛЯ


Послідовності мають ті самі проблеми ідентичності, як і прогалини (які насправді не є проблемами).
Алехандро

-1

Після Вашої вставки потрібно додати це. І переконайтесь у назві таблиці, куди вводяться дані. Ви отримаєте поточний рядок, не той, на який саме рядок зараз впливає ваш оператор вставлення.

IDENT_CURRENT('tableName')

2
Ви помітили, що на цю саму пропозицію вже відповіли кілька разів?
ТТ.

так. але я намагаюся описати рішення по-своєму.
Хан Атаур Рахман

І якщо хтось інший вставив рядок між вашим оператором вставки та вашим викликом IDENT_CURRENT (), ви отримаєте ідентифікатор запису, який хтось інший вставив - мабуть, не те, що ви хочете. Як зазначено у більшості відповідей вище - у більшості випадків вам слід скористатися SCOPE_IDENTITY ().
Трондстер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.