Лучший способ вставить последнюю личность в таблицу

35

Какой из них является лучшим вариантом для получения значения идентификатора, которое я только что сгенерировал посредством вставки? Каково влияние этих заявлений с точки зрения производительности?

  1. SCOPE_IDENTITY()
  2. Агрегатная функция MAX()
  3. ВЫБЕРИТЕ TOP 1IdentityColumn FROM TableNameORDER BY IdentityColumn DESC
AA.SC
источник
1
Используйте postgreSQL, и вы получите его с полки postgresql.org/docs/9.1/static/sql-insert.html
Евгений Афанасьев
Параметр Leftfield - если у вас есть столбец Guid в таблице, и вы можете сгенерировать новый Guid и вставить его в новый столбец во время вставки - вы можете затем выбрать строку с этим Guid, чтобы вытащить сгенерированный идентификатор int.
niico

Ответы:

56

Используйте,SCOPE_IDENTITY() если вы вставляете одну строку и хотите получить сгенерированный идентификатор.

CREATE TABLE #a(identity_column INT IDENTITY(1,1), x CHAR(1));

INSERT #a(x) VALUES('a');

SELECT SCOPE_IDENTITY();

Результат:

----
1

Используйте это OUTPUTпредложение, если вы вставляете несколько строк и вам нужно получить набор сгенерированных идентификаторов.

INSERT #a(x) 
  OUTPUT inserted.identity_column 
  VALUES('b'),('c');

Результат:

----
2
3

и почему это лучший вариант быстрее?

Помимо производительности, это единственные, которые гарантированно будут правильными на уровне изоляции по умолчанию и / или для нескольких пользователей. Даже если вы игнорируете аспект правильности, SQL Server хранит вставленное значение в SCOPE_IDENTITY()памяти, поэтому, естественно, это будет быстрее, чем выполнение и выполнение собственного изолированного запроса к таблице или системным таблицам.

Игнорирование аспекта правильности похоже на сообщение почтальону, что он хорошо справился с доставкой сегодняшней почты - он закончил свой маршрут на 10 минут быстрее, чем его среднее время, проблема в том, что ни одна почта не была доставлена ​​в нужный дом.

Не используйте ничего из следующего:

  • @@IDENTITY - поскольку это нельзя использовать во всех сценариях, например, когда в таблице со столбцом идентификаторов есть триггер, который также вставляется в другую таблицу со своим собственным столбцом идентификаторов, - вы получите неверное значение.
  • IDENT_CURRENT()- Я подробно расскажу об этом здесь , и комментарии также полезны для чтения, но, по сути, при параллельности вы часто получаете неправильный ответ.
  • MAX()или TOP 1- вам нужно защитить два оператора с помощью сериализуемой изоляции, чтобы гарантировать, что MAX()вы получите не чужие. Это намного дороже, чем просто использовать SCOPE_IDENTITY().

Эти функции также завершаются сбоем всякий раз, когда вы вставляете две или более строки, и им требуются все значения идентификаторов, которые генерируются - ваш единственный вариант - это OUTPUTпредложение.

Аарон Бертран
источник
Еще один вопрос возник в моей памяти. Когда нам когда-нибудь понадобится получить последний идентификатор, сгенерированный в конкретной таблице любым сеансом или пользователем, единственный правильный и лучший способ - это MAX () этого столбца?
AA.SC
как насчет того, чтобы при вставке нескольких строк в таблицу SCOPE_IDENTITY () всегда возвращал последний сгенерированный идентификатор? Что если столбец является первичным ключом, а не столбцом идентификаторов?
AA.SC
@ AA.SC да, он вернет последний. Если это не столбец идентификации, нет, ни одна из этих функций не будет работать. Откуда берется значение PK в этом случае?
Аарон Бертран
Я видел это для столбца в нашем приложении, где столбец имеет тип INT, и разработчики используют MAX (имя столбца) +1 всякий раз, когда им нужно вставить новую запись
AA.SC
Тогда они уже знают, какое значение они только что вставили. У SQL Server нет никакого способа сказать вам это (вы не можете рассчитывать на повторное использование MAX после факта, если только вы не полностью изолировали всю транзакцию, что не скажется на производительности или параллелизме).
Аарон Бертран
7

Помимо производительности, все они имеют довольно разные значения.

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

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

@@IDENTITYдает вам последнее значение идентификатора, сгенерированное самым последним оператором INSERT для текущего соединения, независимо от таблицы или области. (Примечание: Access использует эту функцию и поэтому имеет некоторые проблемы с триггерами, которые вставляют значения в таблицы со столбцами идентификаторов.)

Использование MAX()или TOP 1может дать вам совершенно неверные результаты, если таблица имеет шаг отрицательной идентификации или в нее вставлены строки SET IDENTITY_INSERT. Вот скрипт, демонстрирующий все это:

CREATE TABLE ReverseIdent (
    id int IDENTITY(9000,-1) NOT NULL PRIMARY KEY CLUSTERED,
    data char(4)
)

INSERT INTO ReverseIdent (data)
VALUES ('a'), ('b'), ('c')

SELECT * FROM ReverseIdent

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9000

SET IDENTITY_INSERT ReverseIdent ON

INSERT INTO ReverseIdent (id, data)
VALUES (9005, 'd')

SET IDENTITY_INSERT ReverseIdent OFF

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9005

Резюме: палка SCOPE_IDENTITY(), IDENT_CURRENT()или @@IDENTITY, и убедитесь , что вы используете тот , который возвращает то , что вы на самом деле нужно.

db2
источник
1
Почему вы поощряете использование IDENT_CURRENT()и @@IDENTITYкогда ваш собственный скрипт демонстрирует, что они выдают неверные результаты?
Аарон Бертран
1
@AaronBertrand Я не уверен, что следую. Последнее сгенерированное значение идентификатора было 8998 (обратите внимание, шаг равен -1), и это то, что IDENT_CURRENT()возвращается. MAX () никогда не возвращает правильное значение за пределами первой строки, так как id считается в обратном направлении, а при IDENTITY_INSERTвключении 9005 не является сгенерированным значением идентификатора, поэтому не отражается IDENT_CURRENT(). Но он может вернуть «неправильные» результаты, если вы действительно после того, что SCOPE_IDENTITY()возвращает. Выберите правильный инструмент для работы.
db2
Похоже, что OP находится после введенного ими значения идентификатора - в этом случае 8998 неверно. На мой взгляд, крайние случаи (обратное увеличение и IDENTITY_INSERT включены ) еще больше спорят против использования IDENT_CURRENT, и @@ IDENTITY никогда не следует использовать из-за опасности триггеров (сейчас или добавленных позже). Я все еще пытаюсь понять, почему именно IDENT_CURRENT будет тем, который ОП хотел бы использовать (особенно в условиях параллелизма), или почему @@ IDENTITY когда-либо будет использоваться кем-либо, когда существуют гораздо более надежные методы.
Аарон Бертран
@AaronBertrand Это не на 100% ясно из вопроса, что желаемый результат - последняя вставка из текущей области (вариант 1 отличается от 2 и 3 в этом отношении), поэтому я подумал, что было бы неплохо описать и то, и другое. отличаются. Но я согласен, что @@IDENTITYэто почти никогда не идеальный способ получить сгенерированные значения идентичности. Суть в том, что MAX()или TOP 1они похожи на менее надежную версию IDENT_CURRENT(), что прекрасно подходит для использования, если вы понимаете, что она делает. Может быть полезно для работ по техническому обслуживанию или что-то.
db2