@@IDENTITY: примеры (SQL)
@@IDENTITY: numeric(38,0)Функция @@IDENTITY в MS SQL Server
Функция @@IDENTITY является системной и возвращает последнее значение идентификатора (IDENTITY), сгенерированное в рамках текущего сеанса соединения с сервером. Она применяется после выполнения инструкций INSERT, SELECT INTO или bulk copy, которые добавляют данные в таблицу со столбцом IDENTITY.
Функция не принимает аргументов. Возвращаемое значение имеет тип данных numeric(38,0). Если в течение сеанса не было сгенерировано ни одного значения IDENTITY, функция возвращает NULL. Ключевая особенность — функция возвращает последнее значение, созданное для любой таблицы в текущем сеансе, даже если оно было создано триггером, что иногда приводит к непредвиденным результатам.
Простые примеры использования
Пример вставки строки и получения сгенерированного идентификатора:
CREATE TABLE TestTable (ID INT IDENTITY(1,1), Name NVARCHAR(50));
INSERT INTO TestTable (Name) VALUES ('Пример 1');
SELECT @@IDENTITY AS 'Последний ID';Последний ID ------------ 1
Пример, демонстрирующий получение значения после нескольких вставок:
INSERT INTO TestTable (Name) VALUES ('Пример 2'), ('Пример 3');
SELECT @@IDENTITY AS 'Последний ID';Последний ID ------------ 3
Если вставка не затрагивает столбцы IDENTITY, функция возвращает NULL:
CREATE TABLE TableNoIdentity (Data NVARCHAR(50));
INSERT INTO TableNoIdentity (Data) VALUES ('Тест');
SELECT @@IDENTITY AS 'Последний ID';Последний ID ------------ NULL
Альтернативные функции в MS SQL
В MS SQL Server существуют другие функции для работы со сгенерированными идентификаторами:
- SCOPE_IDENTITY(): возвращает последнее значение IDENTITY, созданное в той же области видимости (scope). Область видимости — это текущий модуль (хранимая процедура, триггер, функция, пакет). Это самый надежный выбор для большинства сценариев, так как не зависит от действий триггеров.
- IDENT_CURRENT('имя_таблицы'): возвращает последнее значение IDENTITY, сгенерированное для конкретной таблицы в любом сеансе и области видимости. Полезно, когда требуется узнать последнее значение, вставленное в таблицу другими процессами.
Предпочтительнее использовать SCOPE_IDENTITY(), чтобы избежать непреднамеренного получения значения из триггера. Функцию @@IDENTITY применяют в простых случаях, когда уверены в отсутствии триггеров или когда именно такое поведение требуется.
Аналоги в других СУБД
В других системах управления базами данных существуют схожие механизмы:
MySQL: функция LAST_INSERT_ID(). Возвращает первое автоматически сгенерированное значение, если инструкция INSERT вставляла несколько строк.
INSERT INTO users (username) VALUES ('user1');
SELECT LAST_INSERT_ID();LAST_INSERT_ID() ---------------- 101
PostgreSQL: используется предложение RETURNING в запросе INSERT или функция currval() для последовательности.
INSERT INTO products (name) VALUES ('Товар') RETURNING id;id ---- 55
Oracle: последовательности (SEQUENCE). Для получения последнего значения применяют sequence_name.CURRVAL в пределах текущего сеанса, но только после вызова NEXTVAL.
INSERT INTO employees (id, name) VALUES (emp_seq.NEXTVAL, 'Иван');
SELECT emp_seq.CURRVAL FROM dual;CURRVAL ------- 1234
SQLite: функция last_insert_rowid(). Возвращает ROWID последней вставленной строки.
INSERT INTO logs (message) VALUES ('Событие');
SELECT last_insert_rowid();last_insert_rowid() ------------------- 5
Sybase ASE: как и в MS SQL, доступна функция @@identity с похожей семантикой.
Распространенные ошибки
Основная ошибка — игнорирование влияния триггеров. Если после вставки в целевую таблицу срабатывает триггер, который вставляет данные в другую таблицу с IDENTITY, @@IDENTITY вернет значение из второй таблицы.
CREATE TABLE MainTable (ID INT IDENTITY(1,1), Data NVARCHAR(50));
CREATE TABLE LogTable (LogID INT IDENTITY(100,1), Action NVARCHAR(50));
GO
CREATE TRIGGER MainTable_Ins ON MainTable AFTER INSERT AS
INSERT INTO LogTable (Action) VALUES ('Insert fired');
GO
INSERT INTO MainTable (Data) VALUES ('Тест');
SELECT @@IDENTITY AS 'Что вернулось?';Что вернулось? -------------- 100
В этом примере возвращается 100 (из LogTable), а не 1 (из MainTable). Также ошибкой является предположение, что функция возвращает значение для конкретной таблицы. Она глобальна для сеанса. Другая проблема — проверка значения без учета NULL. Если в сеансе не было операций, генерирующих IDENTITY, функция вернет NULL, что может вызвать ошибки в логике приложения.
Изменения в последних версиях
Поведение функции @@IDENTITY в современных версиях MS SQL Server (начиная с 2008 и вплоть до 2022) остается стабильным и неизменным. Основные изменения связаны не с самой функцией, а с рекомендациями по ее использованию. В официальной документации Microsoft продолжает советовать применять SCOPE_IDENTITY() или выходной параметр OUTPUT предложения INSERT для более предсказуемого и безопасного получения сгенерированных значений. Сама функция не устарела, но ее применение без понимания контекста считается потенциально рискованным.
Расширенные примеры применения
Пример использования в хранимой процедуре с несколькими вставками. Функция возвращает идентификатор последней операции, независимо от таблицы.
CREATE PROC InsertData @Name NVARCHAR(50) AS
BEGIN
INSERT INTO TableA (Name) VALUES (@Name); -- IDENTITY столбец
INSERT INTO TableB (Description) VALUES ('Лог'); -- Без IDENTITY
DECLARE @LastID NUMERIC(38,0);
SET @LastID = @@IDENTITY;
SELECT @LastID AS 'Последний ID в сеансе';
END;
GO
EXEC InsertData 'Новая запись';Последний ID в сеансе --------------------- 1
Сравнение поведения @@IDENTITY и SCOPE_IDENTITY() в одном пакете с триггером.
CREATE TABLE Parent (PID INT IDENTITY(10,1), PName NVARCHAR(50));
CREATE TABLE Child (CID INT IDENTITY(100,1), PID INT, CName NVARCHAR(50));
GO
CREATE TRIGGER Parent_Ins ON Parent AFTER INSERT AS
INSERT INTO Child (PID, CName) SELECT PID, 'Child for ' + PName FROM inserted;
GO
INSERT INTO Parent (PName) VALUES ('Родитель 1');
SELECT @@IDENTITY AS '@@IDENTITY', SCOPE_IDENTITY() AS 'SCOPE_IDENTITY';@@IDENTITY SCOPE_IDENTITY ---------- --------------- 100 10
Использование в транзакции с откатом. Функция возвращает значение, сгенерированное до отката, так как оно уже было создано в сеансе.
BEGIN TRANSACTION;
INSERT INTO TestTable (Name) VALUES ('В транзакции');
SELECT @@IDENTITY AS 'ID до Rollback';
ROLLBACK TRANSACTION;
SELECT @@IDENTITY AS 'ID после Rollback';ID до Rollback -------------- 4 ID после Rollback ----------------- 4
Вставка через SELECT INTO. Функция также работает с этой инструкцией.
SELECT * INTO NewTable FROM TestTable WHERE 1=0; -- Создается таблица без данных, но со схемой (без IDENTITY, если не указано иначе).
-- Создадим таблицу с IDENTITY через SELECT INTO явно:
SELECT IDENTITY(INT,1,1) AS NewID, Name INTO NewTable2 FROM TestTable;
SELECT @@IDENTITY AS 'После SELECT INTO'; -- Вернет последний ID, сгенерированный при вставке, если она была.