Лучший способ получить идентичность вставленной строки?

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 ().
С.Серпушан
4
Чем это лучше, чем другие ответы? (также - почему вы не публикуете это как ответ вместо комментария). Пожалуйста, напишите полный ответ (и объясните, почему этот вариант лучше, чем опубликованный - если это зависит от версии, скажите так).
Одед
это просто как краткое резюме. ; D В принятом ответе не упоминается синтаксис предложения OUTPUT и отсутствует образец. Также образцы в других постах не так чисты ...
С.Серпушан
2
@saeedserpooshan - затем отредактируйте это. Вы можете сделать это, вы знаете? Видите, когда этот ответ был опубликован? Это предшествует OUTPUTпредложению в SQL Server.
Одед

Ответы:

1435
  • @@IDENTITYвозвращает последнее значение идентификатора, сгенерированное для любой таблицы в текущем сеансе, во всех областях. Вы должны быть осторожны здесь , так как это выходит за рамки. Вы можете получить значение из триггера вместо текущего оператора.

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

  • IDENT_CURRENT('tableName')возвращает последнее значение идентификатора, сгенерированное для конкретной таблицы в любом сеансе и любой области видимости. Это позволяет вам указать, из какой таблицы вы хотите получить значение, в случае, если два приведенных выше не совсем то, что вам нужно ( очень редко ). Кроме того, как заметил @ Гай Старбак : «Вы можете использовать это, если хотите получить текущее значение IDENTITY для таблицы, в которую вы не вставили запись».

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

bdukes
источник
48
известная ошибка с SCOPE_IDENTITY (), возвращающая неправильные значения: blog.sqlauthority.com/2009/03/24/… Обходной путь - не запускать INSERT в многопроцессорном параллельном плане или использовать предложение OUTPUT
KM.
3
Почти каждый раз, когда я хотел «идентичность», я хотел знать ключ (и) записи (ий), которую я только что вставил. Если это ваша ситуация, вы хотите использовать предложение OUTPUT. Если вы хотите что-то еще, приложите усилия, чтобы прочитать и понять ответ bdukes.
Джерри
3
При этом outputвам не нужно создавать временную таблицу для хранения и запроса результатов. Просто оставьте intoчасть предложения output, и он выведет их в набор результатов.
Спб
96
Чтобы уберечь других от паники, упомянутая выше ошибка была исправлена ​​в накопительном обновлении 5 для SQL Server 2008 R2 с пакетом обновления 1 (SP1).
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, поэтому не уверены, что OUTPUT был доступен тогда)
HLGEM
1
@HLGEM В SQL Server 2005 есть страница MSDN дляOUTPUT , так что похоже, что это просто SQL Server 2000 и более ранние
версии
6
Woohoo! OUTPUT CLAUSE качается :) Это упростит мою текущую задачу. Не знал этого заявления раньше. Спасибо вам, ребята!
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технику для возврата вновь вставленного значения идентификатора.

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 возвращает значение только в пределах текущей области; @@ IDENTITY не ограничивается конкретной областью применения.

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

  • IDENT_CURRENT - это функция, которая принимает таблицу в качестве аргумента.
  • @@ IDENTITY может дать неверный результат, если у вас есть триггер на столе
  • SCOPE_IDENTITY - ваш герой большую часть времени.
Якуб Штурц
источник
14

@@ IDENTITY - это последний идентификатор, вставленный с использованием текущего соединения SQL. Это хорошее значение для возврата из хранимой процедуры вставки, где вам просто нужно вставить идентификатор для вашей новой записи, и не волнует, будет ли добавлено больше строк после этого.

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

IDENT_CURRENT (имя таблицы) является последним идентификатором, вставленным независимо от соединения или области действия. Вы можете использовать это, если хотите получить текущее значение IDENTITY для таблицы, в которую вы не вставили запись.

Гай Старбак
источник
2
Вы никогда не должны использовать @@ identity для этой цели. Если кто-то добавит триггер позже, вы потеряете целостность данных. @@ identiy - чрезвычайно опасная практика.
HLGEM
1
msgstr "значение для таблицы, в которую << не >> вставлена ​​запись." В самом деле?
Абдул Сабур
13

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

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

Кстати, эта техника также работает при вставке нескольких строк.

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

Вывод

ID
2
3
4
MarredCheese
источник
Если вы хотите использовать его позже, я думаю, вам нужна временная таблица
JohnOsborne
@JohnOsborne Вы можете использовать временную таблицу, если хотите, но я хотел сказать, что это не требование OUTPUT. Если вам не нужна временная таблица, тогда ваш запрос окажется намного проще.
MarredCheese
10

ВСЕГДА используйте scope_identity (), НИКОГДА больше не нужно ничего.

erikkallen
источник
13
Не совсем никогда , но в 99 раз из 100, вы будете использовать scope_identity ().
CJM
Для чего ты когда-нибудь использовал что-нибудь еще?
erikkallen
11
если вы вставите несколько строк с помощью INSERT-SELECT, вам нужно будет захватить несколько идентификаторов, используя предложение OUTPUT
KM.
1
@KM: Да, но я ссылался на объем_идентичности против @@ идентичности против id_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

Вот ссылки на CREATE SEQUENCE и NEXT VALUE FOR

StevenJe
источник
Последовательности имеют те же проблемы идентичности, как и пробелы (которые на самом деле не являются проблемами).
Алехандро
-1

После вашей вставки вы должны добавить это. И убедитесь, что имя таблицы, куда вставляются данные. Вы получите текущую строку, а не строку, на которую сейчас влияет оператор вставки.

IDENT_CURRENT('tableName')
Хан Атаур Рахман
источник
2
Вы заметили, что на это же самое предложение уже отвечали несколько раз раньше?
ТТ.
да. но я пытаюсь описать решение по-своему.
Хан Атаур Рахман
И если кто-то еще вставил строку между вашим оператором вставки и вашим вызовом IDENT_CURRENT (), вы получите идентификатор записи, которую еще кто-то вставил - вероятно, не то, что вам нужно. Как отмечено в большинстве ответов выше - в большинстве случаев вам лучше использовать SCOPE_IDENTITY ().
Trondster