【问题标题】:SQL Server - Best way to get identity of inserted row?SQL Server - 获取插入行标识的最佳方法?
【发布时间】:2010-09-07 17:51:53
【问题描述】:

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

我知道@@IDENTITYIDENT_CURRENTSCOPE_IDENTITY,但不了解它们各自的优缺点。

有人可以解释一下不同之处以及我应该何时使用它们吗?

【问题讨论】:

  • INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...),或更旧的方法:INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY(); 您可以在 c# 中使用 ExecuteScalar() 获取它。
  • 这比其他答案有什么好处? (另外 - 你为什么不把这个作为答案而不是评论发布)。请写一个完整的答案(并解释为什么这是一个比发布的更好的选择 - 如果特定于版本,请说明)。
  • 这就像一个简短的摘要。 ;D 接受的答案没有提到 OUTPUT 子句语法并且缺少示例。其他帖子中的样本也不是那么干净......
  • @saeedserpooshan - 然后编辑它。你可以这样做,你知道吗?看看这个答案是什么时候发布的?这早于 SQL Server 中的 OUTPUT 子句。

标签: sql sql-server tsql


【解决方案1】:

来自 MSDN

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

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

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

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

【讨论】:

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

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

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

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

    【讨论】:

    • SCOPE_IDENTITY() 返回错误值的已知错误:blog.sqlauthority.com/2009/03/24/… 解决方法是不在多处理器并行计划中运行 INSERT 或使用 OUTPUT 子句
    • 几乎每次我想要“身份”时,我都想知道我刚刚插入的记录的键。如果这是你的情况,你想使用 OUTPUT 子句。如果您想要其他内容,请努力阅读和理解 bdukes 的回复。
    • 使用output,您无需创建临时表来存储和查询结果。只需去掉输出子句的into 部分,它就会将它们输出到结果集。
    • 为了避免其他人恐慌,上述错误已在 SQL Server 2008 R2 Service Pack 1 的累积更新 5 中修复。
    • 请注意,SCOPE_IDENTITY 错误显然已在很久以前在 SQL Server 2008 R2 的 CU5 中修复:请参阅此处,support.microsoft.com/en-us/help/2019779/…
    【解决方案3】:

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

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

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

    【讨论】:

    • 您不应该为此目的使用@@identity。如果有人稍后添加触发器,您将失去数据完整性。 @@identiy 是一种极其危险的做法。
    • "您有 > 插入记录的表的值。"真的吗?
    【解决方案4】:

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

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

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

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

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

    【讨论】:

    • 如果假设 2 或 5 个用户同时创建一条记录,SCOPE_IDENTITY() 会为每个用户提供正确的记录吗?
    • @SlavaCa 它为每个连接的每个 SQL 语句返回正确的记录。如果您有 5 个用户同时创建记录,则很可能会有 5 个不同的数据库连接,因此每个用户都会获得自己的身份。它有效:-)
    【解决方案5】:

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

    【讨论】:

    • 不太从不,但 100 次中有 99 次,您将使用 Scope_Identity()。
    • 你还用过什么别的东西?
    • 如果使用 INSERT-SELECT 插入多行,则需要使用 OUTPUT 子句捕获多个 ID
    • @KM:是的,但我提到了 scope_identity vs @@identity vs ident_current。 OUTPUT 是一个完全不同的类,而且通常很有用。
    • 查看 Orry (stackoverflow.com/a/6073578/2440976) 对这个问题的回答 - 在并行性中,作为最佳实践,您最好遵循他的设置......太棒了!
    【解决方案6】:

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

    【讨论】:

    • 是的,这是正确的方法,如果您不在 SQL Server 2008 上,请仅使用其他方法之一(我们跳过了 2005,因此不确定 OUTPUT 是否可用)
    • @HLGEM 有一个MSDN page for OUTPUT in SQL Server 2005,所以看起来只有 SQL Server 2000 及更早版本没有它
    • 有关获取插入 ID 的非常简洁的示例,请查看: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”,或者名称不明确。',我在其他线程中提出了一个问题,所以如果有人知道解决方案,将不胜感激:@ 987654326@
    【解决方案7】:

    获取新插入行的标识的最佳(阅读:最安全)方法是使用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 以使用 @ 987654324@ 而不是 scope_identity()。我已经删除了答案中关于集群的 FUD。
    • 谢谢,这是我能找到的唯一一个例子,它展示了如何在变量中使用输出值而不是仅仅输出它。
    【解决方案8】:

    添加

    SELECT CAST(scope_identity() AS int);
    

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

    NewId = command.ExecuteScalar()
    

    将检索它。

    【讨论】:

    • 你从哪里得到NewId?它声明的类型是什么?要在其中存储command.ExecuteScalar(),我假设它是Object?
    • @TylerH 在本例中,第一部分是 SQL(select),第二部分是 .NET(ExecuteScaler),因此 NewId 假定为 SQL 返回到 C# 的变量。跨度>
    • @b.pell 我知道第二部分是 C# 而不是 SQL;我要求 OP 解释每个部分的含义,因为他们凭空创建了该部分,并且没有说明如何将其用作假定解决方案的一部分。
    【解决方案9】:

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

    您可以通过using the Timestamp attribute: 在您的实体框架模型中使用乐观并发 ?

    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 你的 OUTPUT 都会出错:

    • 初始插入。时间戳:1
    • OUTPUT 子句输出时间戳:1
    • 触发器修改行。时间戳:2

    如果表上有触发器,则返回的时间戳永远不会正确。所以您必须使用单独的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 子句。不支持尝试使用 OUTPUT 执行 UPDATE,并且会报错:

    做到这一点的唯一方法是使用后续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 :)
    【解决方案10】:

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

    IDENT_CURRENT('tableName')
    

    【讨论】:

    • 你有没有注意到这个完全相同的建议已经被回答了好几次?
    • 是的。但我试图以我自己的方式描述解决方案。
    • 如果其他人在您的插入语句和您的 IDENT_CURRENT() 调用之间插入了一行,您将获得其他人插入的记录的 id - 可能不是您想要的。正如上面大多数回复中所述 - 在大多数情况下,您应该使用 SCOPE_IDENTITY()。
    【解决方案11】:

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

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

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

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

    输出

    ID
    2
    3
    4
    

    【讨论】:

    • 如果您以后想使用它,我想您需要临时表
    • @JohnOsborne 如果您愿意,欢迎您使用临时表,但我的意思是这不是OUTPUT 的要求。如果您不需要临时表,那么您的查询最终会简单得多。
    【解决方案12】:

    创建一个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/uuidconst uuidv4 = require('uuid/v4'); const uuid = uuidv4()
    • GUID 不是标识值,与简单整数相比,它有一些倒退。
    • 此外,如果 UUID 在 SQL 表级别生成为 UNIQUEIDENTIFIER 数据类型,默认为 newid(),那么您将无法使用此方法获取它。因此,您需要插入,将 UUID 留空,然后执行 OUTPUT INSERTED.uuid 以获得它
    【解决方案13】:

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

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

    【讨论】:

      【解决方案14】:

      尽管这是一个较旧的线程,但有一种较新的方法可以避免旧版本 SQL Server like gaps in the identity values after server reboots 中 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 的链接

      【讨论】:

      • 序列有同样的身份问题,比如间隙(这不是真正的问题)。
      • 重新启动 SQL Server 时,身份间隙随机发生。这些间隙不会出现在新的 SEQUENCE 增量中,除非开发人员不使用生成的 SEQUENCE,或者回滚要使用下一个 SEQUENCE id 的事务。来自在线文档:序列对象根据其定义生成数字,但序列对象不控制数字的使用方式。当事务回滚时,插入到表中的序列号可能会有间隙,...或者在分配序列号时没有在表中使用它们。
      猜你喜欢
      • 2011-08-08
      • 1970-01-01
      • 1970-01-01
      • 2021-11-21
      • 2014-08-15
      • 2019-05-28
      • 2011-08-17
      • 2021-08-03
      相关资源
      最近更新 更多