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

时间:2013-12-27 17:30:51

标签: c# sql-server tsql

我需要从SQL Server获取下一个身份值。

我使用此代码:

SELECT IDENT_CURRENT('table_name') + 1

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

7 个答案:

答案 0 :(得分:11)

我想您会想要另一种方法来计算下一个可用值(例如setting the column to auto-increment)。

IDENT_CURRENT文档中,关于空表:

  

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

它甚至看起来都不可靠,特别是如果你最终设计的应用程序同时有多个人写到桌面上。

  

使用IDENT_CURRENT预测下一个生成的标识值时要小心。由于其他会话执行插入,实际生成的值可能与IDENT_CURRENT和IDENT_INCR不同。

答案 1 :(得分:5)

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

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

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

某些块包含数千个事务。一些交易包括投入和/或产出。我需要将它们存储在数据库中,方便调用C#而不影响完整性(所有ID都链接起来)并具有良好的性能 - 当接近10000次提交时,我无法通过逐行提交来获取它。

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

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

解决方案可能并不优雅,可能不会出书,但它既方便又简单。我知道,还有其他方法可以实现这一目标,但这只是直截了当,需要几个小时才能实现。只要确保你没有并发问题。

答案 2 :(得分:5)

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

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

答案 3 :(得分:2)

我知道已经有了答案,但我真的很烦我所有的搜索都是"得到下一个身份sql server"提出了片状解决方案(例如仅选择当前的身份值并添加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取代身份1IDENTITY (0, 1))的-1如果你从1开始就死定了。

为什么有人想要这样做?

我不能代表问题海报发言,而是“域驱动设计”这本书。和the 'official' DDD sample uses this technique(或至少暗示它)作为一种强制实体的方式总是有一个有效的标识符。如果您的实体具有虚假标识符(例如default(int)nullINSERT),直到它{{1}}进入数据库,则可能会泄漏持久性问题。

答案 4 :(得分:2)

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

答案 5 :(得分:0)

我可以提供另一种情况,在这种情况下,知道下一个值会很有用。我在 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

答案 6 :(得分:0)

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

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

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