1280

获取IDENTITY插入行的最佳方法是什么?

我知道@@IDENTITY和但不了解每个的优点和缺点IDENT_CURRENTSCOPE_IDENTITY

有人可以解释一下差异以及我应该何时使用它们吗?

4

14 回答 14

1615
  • @@IDENTITY返回在所有范围内为当前会话中的任何表生成的最后一个标识值。 您需要在这里小心,因为它是跨范围的。您可以从触发器而不是当前语句中获取值。

  • SCOPE_IDENTITY()返回为当前会话和当前范围中的任何表生成的最后一个标识值。 一般你想用什么

  • IDENT_CURRENT('tableName')返回为任何会话和任何范围内的特定表生成的最后一个标识值。这使您可以指定要从哪个表中获取值,以防上述两个不是您所需要的(非常罕见)。此外,正如@ Guy Starbuck所提到的,“如果您想获取尚未插入记录的表的当前 IDENTITY 值,您可以使用它。”

  • 该语句的OUTPUT子句INSERT将允许您访问通过该语句插入的每一行。由于它的范围仅限于特定语句,因此它比上面的其他函数更直接。但是,它有点冗长(您需要插入表变量/临时表然后查询它),即使在语句回滚的错误情况下也会给出结果。也就是说,如果您的查询使用并行执行计划,这是获取标识的唯一保证方法(没有关闭并行性)。但是,它在触发器之前执行,不能用于返回触发器生成的值。

于 2008-09-03T21:38:23.863 回答
206

我相信检索插入的 id 的最安全和最准确的方法是使用输出子句。

例如(取自以下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
于 2011-05-20T14:43:54.040 回答
124

我说的和其他人一样,所以每个人都是正确的,我只是想让它更清楚。

@@IDENTITY返回客户端与数据库的连接插入的最后一个东西的 id。
大多数情况下这工作正常,但有时触发器会插入一个你不知道的新行,你会从这个新行获取 ID,而不是你想要的

SCOPE_IDENTITY()解决了这个问题。它返回您在发送到数据库的 SQL 代码中插入的最后一个内容的 ID。如果触发器去创建额外的行,它们不会导致返回错误的值。万岁

IDENT_CURRENT返回任何人插入的最后一个 ID。如果某个其他应用碰巧在不幸的时间插入了另一行,您将获得该行的 ID 而不是您的 ID。

如果您想安全使用,请始终使用SCOPE_IDENTITY(). 如果您坚持下去@@IDENTITY并且有人决定稍后添加触发器,那么您的所有代码都会中断。

于 2008-09-03T21:44:50.060 回答
72

获取新插入行的标识的最佳(阅读:最安全)方法是使用以下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)
于 2013-04-29T07:22:55.720 回答
34

添加

SELECT CAST(scope_identity() AS int);

到您的插入 sql 语句的末尾,然后

NewId = command.ExecuteScalar()

将检索它。

于 2015-03-30T18:25:01.697 回答
23

来自 MSDN

@@IDENTITY、SCOPE_IDENTITY 和 IDENT_CURRENT 是相似的函数,因为它们返回插入到表的 IDENTITY 列中的最后一个值。

@@IDENTITY 和 SCOPE_IDENTITY 将返回当前会话中任何表中生成的最后一个标识值。但是,SCOPE_IDENTITY 只返回当前范围内的值;@@IDENTITY 不限于特定范围。

IDENT_CURRENT 不受范围和会话限制;它仅限于指定的表。IDENT_CURRENT 返回为任何会话和任何范围内的特定表生成的标识值。有关详细信息,请参阅 IDENT_CURRENT。

  • IDENT_CURRENT 是一个将表作为参数的函数。
  • 当您在表上有触发器时,@@IDENTITY 可能会返回令人困惑的结果
  • SCOPE_IDENTITY 大多数时候都是你的英雄。
于 2008-09-03T21:37:02.137 回答
22

当您使用实体框架时,它在内部使用该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; }
}

当你这样做时,实体框架将需要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是您不能将rowversiona 输出到表变量中:

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使用 an 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
于 2016-11-04T15:05:48.960 回答
17

@@IDENTITY是使用当前 SQL 连接插入的最后一个标识。这是从插入存储过程返回的一个很好的值,您只需要为新记录插入标识,而无需关心之后是否添加了更多行。

SCOPE_IDENTITY是使用当前 SQL 连接插入的最后一个标识,并且在当前范围内——也就是说,如果在您插入之后基于触发器插入了第二个标识,它不会反映在 SCOPE_IDENTITY 中,只有您执行的插入. 坦率地说,我从来没有理由使用它。

IDENT_CURRENT(tablename)是最后插入的身份,无论连接或范围如何。如果您想获取尚未插入记录的表的当前 IDENTITY 值,则可以使用它。

于 2008-09-03T21:42:55.693 回答
15

我无法与其他版本的 SQL Server 交谈,但在 2012 年,直接输出就可以了。您无需为临时表烦恼。

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

顺便说一句,这种技术在插入多行时也有效。

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

输出

ID
2
3
4
于 2018-06-06T16:58:15.353 回答
12

始终使用 scope_identity(),永远不需要其他任何东西。

于 2009-10-09T20:35:00.793 回答
3

创建一个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';
于 2019-03-13T12:55:06.863 回答
3

保证您插入的行的标识的另一种方法是指定标识值并使用SET IDENTITY_INSERT ONand then 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 

如果您从另一个源加载数据或合并来自两个数据库的数据等,这可能是一种非常有用的技术。

于 2019-12-10T12:01:05.037 回答
1

尽管这是一个较旧的线程,但有一种较新的方法可以避免旧版本 SQL Server 中 IDENTITY 列的一些缺陷,例如服务器重新启动后标识值的间隙。序列在 SQL Server 2016 和转发中可用,这是较新的方法是使用 TSQL 创建一个 SEQUENCE 对象。这允许您在 SQL Server 中创建自己的数字序列对象并控制其递增方式。

这是一个例子:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

然后在 TSQL 中,您将执行以下操作来获取下一个序列 ID:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

以下是CREATE SEQUENCENEXT VALUE FOR的链接

于 2020-02-19T17:20:54.893 回答
-1

在您的插入语句之后,您需要添加它。并确保数据插入的表名。您将获得当前行,而不是您的插入语句刚刚影响的行。

IDENT_CURRENT('tableName')
于 2017-12-31T06:04:43.100 回答