获得插入行身份的最佳方法?


1118

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

我了解@@IDENTITY和了解IDENT_CURRENTSCOPE_IDENTITY但不了解它们各自的优缺点。

有人可以解释这些差异,以及何时应使用它们?


5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)或更旧的方法:INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();您可以使用ExecuteScalar()在c#中获取它。
S.Serpooshan

4
有什么比其他答案更好的吗?(另外-为什么您不将其发布为答案而不是评论)。请写下完整的答案(并说明为什么这是比发布的方法更好的选择-如果特定于版本,请这样说)。
Oded

就像一个简短的摘要一样。; D接受的答案没有提到OUTPUT子句语法,并且缺少示例。另外,其他帖子中的样本也不是那么干净...
S.Serpooshan

2
@saeedserpooshan-然后进行编辑。您可以这样做,知道吗?看到答案何时发布?那早OUTPUT于SQL Server中的子句。
Oded

Answers:


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

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

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

  • OUTPUT条款的的INSERT声明将让您访问每一个经该语句插入行。由于它是针对特定语句的,因此它比上面的其他函数更直接。但是,它有点冗长(您需要将其插入到表变量/临时表中,然后对其进行查询),即使在语句回滚的错误情况下,它也可以提供结果。也就是说,如果您的查询使用并行执行计划,则这是获得身份的唯一保证方法(缺少关闭并行性)。但是,它触发器之前执行不能用于返回触发器生成的值。


48
SCOPE_IDENTITY()的已知错误返回错误值:blog.sqlauthority.com/2009/03/24/…解决方法是不在多处理器并行计划中运行INSERT或使用OUTPUT子句
KM。

3
几乎每次我想要“身份”时,我都想知道我刚刚插入的记录的键。如果是这种情况,则要使用OUTPUT子句。如果您还需要其他内容,请努力阅读和理解bdukes的响应。
jerry 2012年

3
这样,output您就不需要创建临时表来存储和查询结果。只需省略into输出子句的一部分,它将把它们输出到结果集。
spb 2014年

96
为了拯救他人从panicing,错误上面提到固定在累积更新5为SQL Server 2008 R2的Service Pack 1
GaTechThomas

1
@niico,我认为建议与以往相同,即OUTPUT只要您不使用触发器并处理错误,这就是“最佳”建议,但这SCOPE_IDENTITY是最简单且很少
遇到的

180

我相信检索插入的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

3
是的,这是正确的方法,如果您不在SQL Server 2008上,请仅使用其他方法之一(我们跳过了2005,因此不确定是否可以使用OUTPUT)
HLGEM 2011年

1
@HLGEM SQL Server 2005中有一个MSDN页面OUTPUT,因此看起来只有SQL Server 2000和更早版本没有它
-bdukes

6
呜呼!输出子句:)这将简化我当前的任务。以前不知道那句话。感谢大伙们!
SwissCoder 2011年

8
对于仅获取插入的ID的一个非常简洁的示例,请查看:stackoverflow.com/a/10999467/2003325
路加福音

您将INTO与OUTPUT结合使用是一个好主意。请参阅:blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (来自此处的评论: stackoverflow.com/questions/7917695/…
shlgug

112

我说的是与其他人相同的话,所以每个人都是对的,我只是想让事情变得更清楚。

@@IDENTITY返回客户端连接到数据库所插入的最后一件事的ID。
在大多数情况下,这可以正常工作,但有时会触发一个触发器,插入您不知道的新行,并且您将从新行中获取ID,而不是您想要的行

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

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

如果您想安全播放,请务必使用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 Server群集是一项高可用性功能,与并行性无关。scope_identity()无论如何,单行插入(的最常见情况)很少得到并行计划。并且此错误已在此答案得到解决的一年以上。
马丁·史密斯

您所说的并行性是什么意思。
user1451111

@MartinSmith客户端不愿意允许其服务器群集上的停机时间来安装CU来解决此问题(不是在开玩笑),因此唯一的解决方案是我们重写所有要使用的SQL output而不是scope_identity()。我在答案中删除了有关群集的FUD。
伊恩·坎普

1
谢谢,这是我能够找到的唯一示例,该示例显示了如何在变量中使用输出值,而不仅仅是输出它。
肖恩·雷

26

SELECT CAST(scope_identity() AS int);

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

NewId = command.ExecuteScalar()

将检索它。


18

当您使用实体框架时,它在内部使用该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或更高版本上可用。

编辑 -加入的原因

实体框架联接回原始表而不是简单地使用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在表变量中输出a :

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子句。尝试做UPDATEOUTPUT不支持,并会给出错误:

唯一的方法是通过后续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's

16

MSDN

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

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

IDENT_CURRENT不受范围和会话的限制;它仅限于指定的表。IDENT_CURRENT返回为任何会话和任何作用域中的特定表生成的标识值。有关更多信息,请参见IDENT_CURRENT。


14

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

SCOPE_IDENTITY是使用当前SQL连接在当前作用域中插入的最后一个标识,也就是说,如果在插入后基于触发器插入了第二个IDENTITY,它将不会反映在SCOPE_IDENTITY中,仅反映您执行的插入。坦白说,我从来没有理由使用它。

IDENT_CURRENT(表名)是最后插入的标识,无论连接或作用域如何。如果要获取未插入记录的表的当前IDENTITY值,可以使用此方法。


2
为此,请勿使用@@ identity。如果以后有人添加触发器,您将失去数据完整性。@@ identiy是非常危险的做法。
HLGEM 2011年

1
“您在其中插入了<<< >>的表的值。” 真?
阿卜杜勒·萨博尔

13

我不能说其他版本的SQL Server,但在2012年,直接输出就可以了。您无需为临时表而烦恼。

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

顺便说一句,该技术在插入多行时也适用。

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

输出量

ID
2
3
4

但是,如果您以后想使用它,我想您需要临时表
JohnOsborne

@JohnOsborne如果愿意,欢迎使用临时表,但我的意思是,这不是的要求OUTPUT。如果不需要临时表,那么查询最终会变得简单得多。
MarredCheese

10

总是使用scope_identity(),就不需要其他任何东西了。


13
并非永远不会,但是有100的99倍,您将使用Scope_Identity()。
CJM

您还用了什么呢?
erikkallen,2009年

11
如果您插入使用INSERT-SELECT几行,你就需要捕获使用OUTPUT子句多个ID
KM。

1
@KM:是的,但我提到了scope_identity与@@ identity与ident_current。输出是完全不同的类,通常很有用。
erikkallen 2010年

2
查看Orry的(stackoverflow.com/a/6073578/2440976)对这个问题的回答-并行处理,并且作为最佳实践,您应该明智地遵循他的设置……真是太好了!
Dan B

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在数据库中创建索引。因此,将更快地找到该行。
Frank Roth

对于node.js中您可以使用此模块来简单地创建一个UUID: https://www.npmjs.com/package/uuidconst uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth

GUID不是标识值,与简单整数相比,它具有一些缺点。
亚历杭德罗

1

保证所插入行的身份的另一种方法是指定身份值SET IDENTITY_INSERT ON,然后使用and OFF。这样可以保证您确切知道身份值是什么!只要不使用这些值,就可以将这些值插入到identity列中。

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

即使这是一个较旧的线程,也有一种更新的方法可以避免旧版本的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的链接


序列具有相同的身份问题,例如缺口(并不是真正的问题)。
亚历杭德罗

-1

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

IDENT_CURRENT('tableName')

2
您是否注意到之前曾多次回答过这个完全相同的建议?
TT。

是。但我试图以自己的方式描述解决方案。
Khan Ataur Ra​​hman

而且,如果其他人在您的插入语句和IDENT_CURRENT()调用之间插入了一行,则您将获得其他人插入的记录的ID,可能不是您想要的。如上述大多数答复所述-在大多数情况下,您应该使用SCOPE_IDENTITY()。
Trondster
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.