【问题标题】:How to get the next identity value from SQL Server如何从 SQL Server 获取下一个标识值
【发布时间】:2014-01-15 07:17:30
【问题描述】:

我需要从SQL Server 获取下一个标识值。

我使用这个代码:

SELECT IDENT_CURRENT('table_name') + 1

这是正确的,但是当table_name 为空(并且下一个标识值为“1”)返回“2”但结果为“1”

【问题讨论】:

  • 您无法可靠地找出下一个标识值 - 直到您实际插入一个新行到表中。停止尝试 - 你不会成功 - 只需接受这样一个事实:在行实际插入到表中并且 SQL Server 已分配该值之前,您无法知道 identity 值。
  • 我的问题是为什么。听起来你试图以更冗长的方式完成已经很容易的事情。解释你想做什么,也许有更好的方法。你永远不应该试图得到什么,因为当你使用它时,它可能不再是了。如果您继续,请告诉我它是什么应用程序,这样我就可以确保我以后不再使用。 :)

标签: c# sql-server tsql


【解决方案1】:

我认为您需要寻找一种替代方法来计算下一个可用值(例如setting the column to auto-increment)。

来自IDENT_CURRENT 文档,关于空表:

当 IDENT_CURRENT 值为 NULL 时(因为表从未包含行或已被截断),IDENT_CURRENT 函数返回种子值。

它甚至看起来都不是那么可靠,尤其是如果您最终设计的应用程序有多个人同时写信给桌子。

谨慎使用 IDENT_CURRENT 预测下一个生成的标识值。由于其他会话执行的插入操作,实际生成的值可能与 IDENT_CURRENT 加上 IDENT_INCR 不同。

【讨论】:

  • 您无法可靠地计算下一个可用值 - 这只是不可能。有太多的 if 和 but 涉及 - 你永远不会做对。只是不要这样做-只是不想提前这样做。这不是IDENTITY 在 SQL Server 中的工作方式
  • 谢谢...有没有办法判断它是否为空?
【解决方案2】:

如果您的表为空,则此查询将完美运行。

SELECT
  CASE
    WHEN (SELECT
        COUNT(1)
      FROM tablename) = 0 THEN 1
    ELSE IDENT_CURRENT('tablename') + 1
  END AS Current_Identity;

【讨论】:

  • 案例第一部分不靠谱。可能已经删除了行,但身份不会重置。
【解决方案3】:

我知道已经有答案了,但让我很恼火的是,我所有的“获取下一个身份 sql 服务器”搜索都提出了不稳定的解决方案(比如仅选择当前身份值并添加 1)或“它不能可靠地完成”。

实际上有几种方法可以做到这一点。

SQL 服务器 >= 2012

CREATE SEQUENCE dbo.seq_FooId START WITH 1 INCREMENT BY 1
GO

CREATE TABLE dbo.Foos (
    FooId int NOT NULL 
        DEFAULT (NEXT VALUE FOR dbo.seq_FooId)
        PRIMARY KEY CLUSTERED 
)
GO

// Get the next identity before an insert
DECLARE @next_id = NEXT VALUE FOR dbo.seq_FooId

SQL Server 2012 引入了SEQUENCE 对象。在这种情况下,每次调用NEXT VALUE FOR时序列都会递增,因此您无需担心并发。

SQL 服务器
CREATE TABLE dbo.Foos (
    FooId int NOT NULL 
        IDENTITY (1, 1)
        PRIMARY KEY CLUSTERED 
)
GO

// Get the next identity before an insert
BEGIN TRANSACTION
SELECT TOP 1 1 FROM dbo.Foos WITH (TABLOCKX, HOLDLOCK)
DECLARE @next_id int = IDENT_CURRENT('dbo.Foos') + IDENT_INCR('dbo.Foos');
DBCC CHECKIDENT('dbo.Foos', RESEED, @next_id)
COMMIT TRANSACTION

您可能希望将所有这些都封装在一个存储过程中,尤其是因为DBCC 语句需要提升访问权限,而您可能不希望每个人都拥有这种访问权限。

没有NEXT VALUE FOR 那样优雅,但它应该是可靠的。请注意,如果表中没有行,您的第一个值将得到 2,但如果您打算始终使用此方法来获取下一个身份,您可以在 0 而不是 1 播种身份(使用IDENTITY (0, 1))如果您一心想从 1 开始。

为什么有人要这样做?

我不能代表问题海报,但“领域驱动设计”一书和the 'official' DDD sample uses this technique(或至少暗示它)作为强制实体始终具有有效标识符的一种方式。如果您的实体在将其INSERTed 到数据库中之前有一个伪造的标识符(如-1 或default(int) 或null),则它可能会泄露持久性问题。

【讨论】:

  • 重新设置为标准代码位?除了不寒而栗之外,如果身份存在差距,这将无法可靠地工作
【解决方案4】:

我倾向于同意其他发帖者的观点,即这不是正确的做法,但在某些情况下它可能很方便。有几篇帖子问为什么要这样做,让我举一个例子,对我来说很方便,以及如何以及为什么。

我正在实现一个比特币节点。我希望将区块链存储在 SQL 数据库中。每个块都是从其他节点和矿工从网络接收的。您可以在其他地方找到详细信息。

当接收到一个区块时,它包含一个区块头、任意数量的交易以及每笔交易任意数量的输入和输出。我的数据库中有 4 个表 - 你猜对了 - 一个标题表、事务表、输入表和输出表。交易、输入和输出表中的每一行都通过 ID 相互链接到标题行。

一些区块包含数千个交易。一些交易有数百个输入和/或输出。我需要通过 C# 中的方便调用将它们存储在数据库中,而不会影响完整性(ID 全部链接)并且具有良好的性能 - 当有接近 10000 次提交时,我无法通过逐行提交来获得。

相反,我绝对确保在操作期间在 C# 中同步锁定我的数据库对象(而且我不必担心其他进程也访问数据库),因此我可以方便地对所有 4 个表执行 IDENT_CURRENT ,从存储过程中返回值,填充 4 个 List 中的近 10000 行,同时增加 ID,并使用选项 SqlBulkCopyOptions.KeepIdentity 调用 SqlBulkCopy.WriteToServer 方法,然后在 4 个简单调用中将其全部发送,一个每个表集。

对于真正的大块,性能提升(在 4-5 年的中档笔记本电脑上)从大约 60-90 秒下降到 2-3 秒,所以我很高兴了解 IDENT_CURRENT()。

解决方案可能不优雅,可能不是按照书本说的,但它方便简单。我知道还有其他方法可以实现这一点,但这很简单,需要几个小时才能实现。只要确保您没有并发问题。

【讨论】:

  • 忘记了一个细节,如果有读者想做我所做的事。将在事务中执行所有 SqlBulkCopy.WriteToServer 调用的方法包装起来,如果保存了所有集合,则对其调用 Commit,或者如果出现故障(例如在 try-catch 中),则对其调用 Rollback。快乐编码:)
  • 需要知道下一个 ID 的另一个原因是可测试性。我有有条件地插入一行的代码,如果其他事情失败,则删除一行(由于事务没有帮助的原因)测试人员看不到代码,但可以看到数据库。行数和 max() 不会告诉他们是否发生了插入和删除操作。但是看到“下一个”ID 会告诉他们发生了插入(在某种程度上是真实的)。
【解决方案5】:
SELECT isnull(IDENT_CURRENT('emp') + IDENT_INCR('emp'),1)

【讨论】:

    【解决方案6】:

    与其问“你为什么要这样做???”,我会假设你有充分的理由。

    这是我为一些快速而肮脏的测试 SQL 所做的,它在除一个之外的所有情况下都可以正常工作,即使在清空先前填充的表之后也是如此:

        DECLARE @nextId INTEGER = IDENT_CURRENT('myTable')
        IF (SELECT COUNT(*) FROM myTable) > 0
        OR @nextId > 1
            SET @nextId += 1
    

    然后,我使用它构建了一个状态字符串,并将其放入 myTable 中(我希望它具有新的标识值,该行将被赋予)以验证 SQL 是否按预期工作。

    唯一不能正确处理的情况是:

    1. 创建一个新的(空)表并运行上面的代码。它会正确地告诉您它的 ID 为 1。
    2. 向表格中添加一个单个元素。正如预测的那样,它的 ID 为 1。
    3. 清空表格。
    4. 再次运行上述 SQL。它会错误地告诉您下一个条目的 ID 也是 1。

    这是我找不到解决方法的一种情况。我没问题,因为我的测试的第 1 步涉及向表中添加多行,但请注意这一不太可能但可能导致错误的事件序列。

    可能有一种“更好”的方式,但它不适用于生产代码,所以快速而肮脏就可以了。

    【讨论】:

      【解决方案7】:

      我可以贡献另一种情况,知道下一个值会很有用。我在 Excel 的一行中有数据,例如: A B C X1 X2 Y1 Y2 Z1 Z2 我想将 A B C 放在一个数据库表中,并在另一个表中使用该记录的新主键,我将在其中放置 X1 和 X2,然后是 Y1 和 Y2,然后是 Z1 和 Z2。所以这一行将在第一个表中生成一条记录: xxx A B C

      还有第二张表的三条记录

      yyy xxx X1 X2

      yyy+1 xxx Y1 Y2

      yyy+2 xxx Z1 Z3

      如果我可以在开始处理之前确定 xxx,我可以为正在处理的每条记录增加它。我也犯了一个错误,即只找到最高值 M 并假设 M+1 将是下一个值。这不起作用,因为一些上传由于错误而没有完成,事务被回滚,但是索引的 PK 已经被分配并且不会被重用。手动删除价值最高的 PK 记录会导致类似的问题。

      我的简单解决方案是写一个SP,在上传第一个案例后获得PK的最大值。

      CREATE     PROCEDURE [MIRR].[GetLast_CasesIndexID]
      @GroupNumber as int Output
      AS
      BEGIN
          SELECT @GroupNumber = max(CasesIndexID)  
          FROM MIRR.Cases
      END
      GO
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-09
        • 1970-01-01
        相关资源
        最近更新 更多