Как лучше всего получить IDENTITY вставленной строки?
Я знаю о @@IDENTITY, IDENT_CURRENT и SCOPE_IDENTITY, но не понимаю плюсов и минусов каждого из них.
Может ли кто-нибудь объяснить различия и когда я должен их использовать?
Чем это лучше других ответов? (также - почему вы не публикуете это как ответ вместо комментария). Пожалуйста, напишите полный ответ (и объясните, почему это лучший вариант, чем опубликованные - если версия специфична, скажите так).
это просто краткое изложение. ; D В принятом ответе не упоминается синтаксис предложения OUTPUT и отсутствует образец. Также образцы в других сообщениях не такие чистые ...
@saeedserpooshan - тогда отредактируйте это. Вы можете это сделать, понимаете? Видите, когда был опубликован этот ответ? Это предшествует предложению OUTPUT в SQL Server.


Из MSDN
@@IDENTITY, SCOPE_IDENTITY, and IDENT_CURRENT are similar functions in that they return the last value inserted into the IDENTITY column of a table.
@@IDENTITY and SCOPE_IDENTITY will return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; @@IDENTITY is not limited to a specific scope.
IDENT_CURRENT is not limited by scope and session; it is limited to a specified table. IDENT_CURRENT returns the identity value generated for a specific table in any session and any scope. For more information, see IDENT_CURRENT.
@@IDENTITY возвращает последнее значение идентификатора, созданное для любой таблицы в текущем сеансе, во всех областях. Здесь нужно быть осторожным, так как это вне границ. Вы можете получить значение из триггера вместо текущего оператора.
SCOPE_IDENTITY() возвращает последнее значение идентификатора, созданное для любой таблицы в текущем сеансе и текущей области. Вообще то, что вы хотите использовать.
IDENT_CURRENT('tableName') возвращает последнее значение идентификатора, созданное для конкретной таблицы в любом сеансе и любой области. Это позволяет вам указать, из какой таблицы вы хотите получить значение, на случай, если две приведенные выше не совсем то, что вам нужно (очень редкий). Кроме того, как упоминалось в @ Гай Старбак: «Вы можете использовать это, если хотите получить текущее значение IDENTITY для таблицы, в которую вы не вставляли запись».
Пункт OUTPUT оператора INSERT позволит вам получить доступ к каждой строке, которая была вставлена с помощью этого оператора. Поскольку он привязан к конкретному оператору, он более простой, чем другие функции, указанные выше. Однако это небольшой более подробный (вам нужно вставить в таблицу переменную / временную таблицу, а затем запросить ее), и он дает результаты даже в сценарии ошибки, когда оператор откатывается. Тем не менее, если ваш запрос использует план параллельного выполнения, это только гарантированный метод для получения идентификатора (за исключением отключения параллелизма). Однако он выполняет триггеры перед и не может использоваться для возврата значений, созданных триггером.
известная ошибка с SCOPE_IDENTITY (), возвращающей неправильные значения: blog.sqlauthority.com/2009/03/24/… решение состоит в том, чтобы не запускать INSERT в многопроцессорном параллельном плане или использовать предложение OUTPUT
@@ Identity хуже использовать Scope_identity. Практически никогда не следует использовать @@ identity.
identity_current чрезвычайно опасен для целостности данных. Кто-то использовал это, и одновременные действия с базой данных привели к тому, что дочерние таблицы были назначены не тому человеку. Когда это происходит, исправить данные практически невозможно.
Почти каждый раз, когда мне нужна была «идентичность», я хотел знать ключ (и) только что вставленной записи (ов). Если это ваша ситуация, вы хотите использовать предложение OUTPUT. Если вы хотите чего-то еще, приложите усилия, чтобы прочитать и понять ответ bdukes.
Еще одна причина не использовать @@ Indentity заключается в том, что, по моему опыту, это O (n), а SCOPE_IDENTITY () - O (1). Он появился только на действительно большом столе.
С output вам не нужно создавать временную таблицу для хранения и запроса результатов. Просто оставьте часть into в предложении вывода, и он выведет их в набор результатов.
Чтобы спасти других от паники, упомянутая выше ошибка была исправлена в накопительном обновлении 5 для SQL Server 2008 R2 с пакетом обновления 1.
Почему ограничение SCOPE_IDENTITY текущей областью действия не делает его безопасным в использовании?
Можем ли мы получить идентичность с помощью нескольких вставок?
Обратите внимание, что кажется, что вы не можете использовать OUTPUT, когда две таблицы имеют отношение fkey, если вы не используете промежуточную временную таблицу (которая должна быть уже определена перед оператором INSERT).
Предложение OUTPUT также содержит ошибки, поскольку не учитывает выполнение триггеров, а также имеет проблемы с отметками времени. Мне пришлось изменить возврат значений с OUTPUT обратно на SCOPE_IDENTITY в дополнительном SELECT именно для этой ошибки. См. Также: stackoverflow.com/a/13198916/2557263
@niico, я думаю, что рекомендация такая же, как и раньше: OUTPUT - "лучший", если вы не используете триггеры и не обрабатываете ошибки, но SCOPE_IDENTITY - самый простой и очень редко вызывает проблемы.
Практически: сгенерируйте uuid и вставьте его. Тогда вы легко сможете определить свой ряд.
Обратите внимание, что ошибка SCOPE_IDENTITY была явно исправлена давно в CU5 SQL Server 2008 R2: см. Здесь, support.microsoft.com/en-us/help/2019779/…
Также обратите внимание, что большое преимущество OUTPUT перед другими методами состоит в том, что он может захватыватьнесколькосгенерированные значения идентичности при вставке нескольких строк.
@@ЛИЧНОСТЬ - это последний идентификатор, вставленный с использованием текущего соединения SQL. Это хорошее значение для возврата из хранимой процедуры вставки, где вам просто нужно, чтобы идентификатор был вставлен для вашей новой записи, и не заботиться о том, были ли добавлены другие строки после этого.
SCOPE_IDENTITY - это последний идентификатор, вставленный с использованием текущего соединения SQL и в текущей области - то есть, если после вашей вставки был вставлен второй идентификатор IDENTITY на основе триггера, он не будет отражен в SCOPE_IDENTITY, только выполненная вами вставка . Честно говоря, у меня никогда не было причин использовать это.
IDENT_CURRENT (имя таблицы) - это последний идентификатор, вставленный независимо от соединения или области действия. Вы можете использовать это, если хотите получить текущее значение IDENTITY для таблицы, в которую вы не вставляли запись.
Никогда не используйте для этой цели @@ identity. Если позже кто-то добавит триггер, вы потеряете целостность данных. @@ identity - чрезвычайно опасная практика.
"значение для таблицы, в которую вы <<не>> вставили запись." Действительно?
Я говорю то же, что и другие парни, так что все правы, я просто пытаюсь прояснить это.
@@IDENTITY возвращает идентификатор последнего элемента, который был вставлен клиентом при подключении к базе данных.
В большинстве случаев это работает нормально, но иногда срабатывает триггер и вставляет новую строку, о которой вы не знаете, и вы получите идентификатор из этой новой строки вместо того, который вам нужен.
SCOPE_IDENTITY() решает эту проблему. Он возвращает в базу данных идентификатор последнего элемента ты вставил в SQL-коде ты отправил. Если триггеры создают дополнительные строки, они не вернут неправильное значение. Ура
IDENT_CURRENT возвращает последний ID, который был вставлен кем-либо. Если какое-то другое приложение вставит еще одну строку в непредвиденное время, вы получите идентификатор этой строки вместо своего.
Если вы хотите перестраховаться, всегда используйте SCOPE_IDENTITY(). Если вы будете придерживаться @@IDENTITY, и кто-то решит добавить триггер позже, весь ваш код сломается.
Что, если предположим, что 2 или 5 пользователей будут создавать запись одновременно, предоставит ли SCOPE_IDENTITY () нужную запись для каждого пользователя, или?
ВСЕГДА используйте scope_identity (), НИКОГДА не нужно ничего другого.
Не совсем никогда, но в 99 случаях из 100 вы будете использовать Scope_Identity ().
Для чего вы еще что-нибудь использовали?
если вы вставляете несколько строк с помощью INSERT-SELECT, вам нужно будет захватить несколько идентификаторов с помощью предложения OUTPUT
@KM: Да, но я сослался на scope_identity vs @@ identity vs identify_current. OUTPUT - это совершенно другой класс, который часто бывает полезен.
Ознакомьтесь с ответом Орри (stackoverflow.com/a/6073578/2440976) на этот вопрос - параллельно, и в качестве наилучшей практики было бы разумно следовать его настройке ... просто великолепно!
Почему это лучше, чем Output Inserted.Id?
Я считаю, что наиболее безопасным и точным методом получения вставленного идентификатора было бы использование предложения output.
например (взято из следующей статьи 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
Да, это правильный метод в будущем, используйте только один из других, если вы не используете SQL Server 2008 (мы пропустили 2005 год, поэтому не уверены, был ли тогда доступен OUTPUT)
@HLGEM Есть Страница MSDN для OUTPUT в SQL Server 2005, так что похоже, что это просто SQL Server 2000 и более ранние версии, которые без него
Для действительно краткого примера, чтобы просто получить вставленный идентификатор, посмотрите: stackoverflow.com/a/10999467/2003325
Использование INTO с OUTPUT - хорошая идея. См .: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (Из комментария здесь: stackoverflow.com/questions/7917695/…)
Я только что узнал об этой функции OUTPUT INSERT, которая выглядит как настоящий ответ, очень хорошо работает в sqlserver, но не работает с классом SqlClient, она выдает исключение System.Data.SqlClient.SqlException: «Не удается найти ни столбец INSERTED», ни пользовательская функция или агрегат «INSERTED.Id», или имя неоднозначно. ', я поднимаю вопрос в другом потоке, поэтому, если кто-нибудь знает решение, буду признателен: stackoverflow.com/questions/49786640/…
Можно ли получить столбцы из части VALUES (или SELECT, если вы ее используете)? Примеры в MSDN показывают, что в операторе DELETE, но я не могу заставить его работать с SELECT / VALUES
Лучший (читай: самый безопасный) способ получить идентификацию вновь вставленной строки - использовать предложение 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)
Кластеризация SQL-серверов - это функция высокой доступности, не имеющая отношения к параллелизму. В любом случае, очень редко для вставок одной строки (наиболее распространенный случай для scope_identity()) получаются параллельные планы. И эта ошибка была исправлена более чем за год до этого ответа.
Что вы имеете в виду под параллелизмом?
@MartinSmith. Клиент не хотел допускать простоя на своем кластере серверов для установки CU, исправляющего эту проблему (не шутка), поэтому единственным решением для нас было переписать весь SQL, чтобы использовать output вместо scope_identity(). Я удалил FUD о кластеризации в ответе.
Спасибо, это единственный пример, который мне удалось найти, который показывает, как использовать значение из вывода в переменной, а не просто выводить его.
Добавлять
SELECT CAST(scope_identity() AS int);
до конца вашей инструкции insert sql, затем
NewId = command.ExecuteScalar()
получит его.
Откуда у вас NewId? А какой у него заявленный тип? Чтобы хранить в нем command.ExecuteScalar(), я предполагаю, что это Object?
Когда вы используете 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, который вы ВЫВОДИТЕ, будет неправильным:
Возвращенная временная метка будет правильной никогда, если у вас есть триггер в таблице. Итак, вы должен используете отдельный 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
Я предполагаю, что они соответствуют им, чтобы гарантировать целостность (например, в режиме оптимистичного параллелизма, когда вы выбираете переменную таблицы, кто-то мог удалить строки средства вставки). Также мне нравится ваш TurboEncabulators :)
После вашего заявления о вставке вам необходимо добавить это. И убедитесь в названии таблицы, в которую вставляются данные. Вы не получите текущую строку там, где строка, затронутая только что оператором вставки.
IDENT_CURRENT('tableName')
Вы заметили, что на это предложение уже несколько раз приходили ответы?
да. но я пытаюсь описать решение по-своему.
И если кто-то еще вставил строку между вашим оператором вставки и вашим вызовом IDENT_CURRENT (), вы получите идентификатор записи, которую вставил кто-то другой - вероятно, не то, что вы хотите. Как отмечалось в большинстве ответов выше, в большинстве случаев вам лучше использовать SCOPE_IDENTITY ().
Я не могу говорить с другими версиями SQL Server, но в 2012 году прямой вывод работает нормально. Вам не нужно возиться с временной таблицей.
INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES (...)
Кстати, этот прием работает и при вставке нескольких строк.
INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES
(...),
(...),
(...)
Выход
ID
2
3
4
Если вы хотите использовать его позже, я полагаю, вам понадобится временная таблица
@JohnOsborne Вы можете использовать временную таблицу, если хотите, но я хочу сказать, что это не требование OUTPUT. Если вам не нужна временная таблица, тогда ваш запрос будет намного проще.
Создайте 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 не является значением идентификатора, он имеет некоторые отступы по сравнению с простым целым числом.
Кроме того, если UUID создается на уровне таблицы SQL как тип данных UNIQUEIDENTIFIER со значением по умолчанию newid(), вы не сможете получить его с помощью этого метода. Таким образом, вам нужно будет ВСТАВИТЬ, оставив поле UUID пустым, а затем выполнить OUTPUT INSERTED.uuid, чтобы получить его.
Еще один способ гарантировать идентичность вставляемых строк - указать значения идентичности и использовать 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
Это может быть очень полезным методом, если вы загружаете данные из другого источника или объединяете данные из двух баз данных и т. д.
Несмотря на то, что это более старый поток, есть новый способ сделать это, который позволяет избежать некоторых ошибок столбца 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
Вот ссылки на СОЗДАТЬ ПОСЛЕДОВАТЕЛЬНОСТЬ и СЛЕДУЮЩЕЕ ЗНАЧЕНИЕ ДЛЯ
Последовательности имеют те же проблемы идентичности, как пробелы (которые на самом деле не являются проблемами).
Пробелы в идентификаторах возникали случайным образом при перезапуске SQL Server. Эти пробелы не возникают в новых приращениях ПОСЛЕДОВАТЕЛЬНОСТИ, если разработчик не использует генерируемую ПОСЛЕДОВАТЕЛЬНОСТЬ или откатывает транзакцию, которая должна была использовать следующий идентификатор ПОСЛЕДОВАТЕЛЬНОСТИ. Из онлайн-документации: объект последовательности генерирует числа в соответствии со своим определением, но объект последовательности не контролирует использование чисел. Порядковые номера, вставленные в таблицу, могут иметь пропуски при откате транзакции ... или когда порядковые номера выделяются без использования их в таблицах.
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)или более старый метод:INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();, вы можете получить его в C# с помощью ExecuteScalar ().