[英]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)或“它可以”不能可靠地完成”。
有几种方法可以实际做到这一点。
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
时序列都会递增,因此您无需担心并发性。
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
插入数据库之前具有虚假标识符(如-1
或default(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 步涉及向表中添加多行,但请注意这个不太可能但可能导致错误的事件序列。
可能有一个“更好”的方法,但它不适用于生产代码,所以快速和肮脏是好的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.