繁体   English   中英

如何从 SQL Server 获取下一个标识值

[英]How to get the next identity value from SQL Server

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

我使用这个代码:

SELECT IDENT_CURRENT('table_name') + 1

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

我认为您需要寻找一种替代方法来计算下一个可用值(例如将列设置为 auto-increment )。

IDENT_CURRENT文档中,关于空表:

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

它甚至看起来都不那么可靠,特别是如果您最终设计的应用程序有不止一个人同时在桌子上写字。

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

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

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

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

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

SQL Server >= 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 Server <= 2008

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 开始。

为什么会有人想要这样做?

我不能代表问题海报,但“域驱动设计”一书和“官方”DDD 示例使用这种技术(或至少暗示它)作为强制实体始终具有有效标识符的一种方式。 如果您的实体在将其INSERT插入数据库之前具有虚假标识符(如-1default(int)null ),则它可能会泄漏持久性问题。

我倾向于同意其他海报,这不是正确的方法,但在某些情况下可能很方便。 一些帖子询问为什么要这样做,让我举一个例子,说明它对我来说很方便,以及如何和为什么。

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

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

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

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

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

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

SELECT isnull(IDENT_CURRENT('emp') + IDENT_INCR('emp'),1)

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

以及第二个表中的三个记录

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

与其问“你到底为什么要这样做???”,我会假设你有一个很好的理由。

这是我为一些快速而肮脏的测试 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 步涉及向表中添加多行,但请注意这个不太可能但可能导致错误的事件序列。

可能有一个“更好”的方法,但它不适用于生产代码,所以快速和肮脏是好的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM