有没有人知道重用身份价值的巧妙技巧?

时间:2009-09-26 10:25:15

标签: sql sql-server tsql

通常,当您指定标识列时,您将在SQL Server中获得一个方便的接口,用于询问特定行。

SELECT * FROM $IDENTITY = @pID

如果是标识列,你真的不需要关心自己的名字,因为只有一个。

但是如果我有一张主要由临时数据组成的表怎么办?大量插入和大量删除。我有一种简单的方法可以重用身份值。

最好我希望能够编写一个函数,将NEXT_SMALLEST($IDENTITY)作为下一个标识值返回,并以故障安全方式执行。

基本上找到未使用的最小值。这并不是一件容易的事,但我想要的是能够告诉SQL Server这是我的函数,它将生成身份值。但我所知道的是,没有这样的功能......

我想......

实现全局数据库ID,我需要提供一个我可以控制的默认值。

我的想法是基于我应该能够拥有一个包含所有已知ID的表,然后来自其他需要全局ID的表的每一行ID都会引用该表。默认值将由

之类的提供
INSERT INTO GlobalID
RETURN SCOPE_IDENTITY()

6 个答案:

答案 0 :(得分:5)

没有;如果可以重复使用它并不是唯一的。

为什么要重复使用它们?你为什么要关心这个领域?如果你想控制它,不要把它作为一个身份;创建自己的方案并使用它。

答案 1 :(得分:4)

不要重复使用身份,你只需要用脚射击自己。使用足够大的值,使其永不翻转(64位大整数)。

要查找数字序列中缺失的间隙,请将表格与自身联系起来,并加上+/- 1差异:

SELECT a.id
FROM table AS a
LEFT OUTER JOIN table AS b ON a.id = b.id+1
WHERE b.id IS NULL;

此查询将在id序列中找到id-1不在表中的数字,即。连续序列起始编号。然后,您可以使用SET IDENTITY INSERT OFF插入特定ID和reuse一个数字。与普通的基于身份的插入相比,这样做的代价是压倒性的(运行时和代码复杂性)。

答案 2 :(得分:1)

如果您确实要将身份值重置为最低值,请执行  这是您可以通过DBCC CHECKIDENT

使用的技巧

基本上,在sql语句之后重置标识值,以便从最低可能的数字重新开始标识值

create table TT (id int identity(1, 1))
GO
insert TT default values
GO 10
select * from TT
GO
delete TT where id between 5 and 10
GO
--; At this point, next ID will be 11, not 5
select * from TT
GO
insert TT default values
GO
--; as you can see here, next ID is indeed 11
select * from TT
GO
--; Now delete ID = 11 
--; so that we can reseed next highest ID to 5
delete TT where id = 11
GO

--; Now, let''s reseed identity value to the lowest possible identity number
declare @seedID int
select  @seedID = max(id) from TT
print   @seedID --; 4

--; We reseed identity column with "DBCC CheckIdent" and pass a new seed value
--; But we can't pass a seed number as argument, so let's use dynamic sql.
declare @sql nvarchar(200)
set @sql = 'dbcc checkident(TT, reseed, ' + cast(@seedID as varchar) + ')'
exec sp_sqlexec @sql
GO

--; Now the next 
insert TT default values
GO
--; as you can see here, next ID is indeed 5
select * from TT
GO

答案 3 :(得分:1)

我想我们真的需要知道你为什么要重用你的身份栏。我能想到的唯一原因是因为您的数据的临时性质可能会耗尽身份的可能值。这不太可能,但如果您担心这一点,则可以使用uniqueidentifiers(guids)作为表中的主键。

函数newid()将创建一个新的guid,可以在insert语句(或其他语句)中使用。然后,当您删除该行时,您的密钥中没有任何“漏洞”,因为无论如何都不按该顺序创建guid。

答案 4 :(得分:1)

[语法假设SQL2008 ....]

是的,这是可能的。您需要在每个参与表上使用两个管理表和两个触发器。

首先,管理表:

-- this table should only ever have one row
CREATE TABLE NextId (Id INT) 
INSERT NextId VALUES (1)
GO

CREATE TABLE RecoveredIds (Id INT NOT NULL PRIMARY KEY)
GO 

然后,触发器,每个表上两个:

CREATE TRIGGER tr_TableName_RecoverId ON TableName 
FOR DELETE AS BEGIN
   IF @@ROWCOUNT = 0 RETURN
   INSERT RecoveredIds (Id) SELECT Id FROM deleted
END
GO

CREATE TRIGGER tr_TableName_AssignId ON TableName 
INSTEAD OF INSERT AS BEGIN
  DECLARE @rowcount INT = @@ROWCOUNT 
  IF @rowcount = 0 RETURN 
  DECLARE @required INT = @rowcount 
  DECLARE @new_ids TABLE (Id INT PRIMARY KEY)
  DELETE TOP (@required) OUTPUT DELETED.Id INTO @new_ids (Id) FROM RecoveredIds
  SET @rowcount = @@ROWCOUNT
  IF @rowcount < @required BEGIN 
    DECLARE @output TABLE (Id INT)
    UPDATE NextId SET Id = Id + (@required-@rowcount) 
    OUTPUT DELETED.Id INTO @output
    -- this assumes you have a numbers table around somewhere
    INSERT @new_ids (Id) 
    SELECT n.Number+o.Id-1 FROM Numbers n, @output o
    WHERE n.Number BETWEEN 1 AND @required-@rowcount
  END
  SET IDENTITY_INSERT TableName ON
  ;WITH inserted_CTE AS (SELECT _no = ROW_NUMBER() OVER (ORDER BY Id), * FROM inserted)
      , new_ids_CTE  AS (SELECT _no = ROW_NUMBER() OVER (ORDER BY Id), * FROM @new_ids)
  INSERT TableName (Id, Attr1, Attr2)
  SELECT n.Id, i.Attr1, i.Attr2 
  FROM inserted_CTE i JOIN new_ids_CTE n ON i._no = n._no
  SET IDENTITY_INSERT TableName OFF
END

您可以从系统表中轻松编写触发器脚本。

您可能希望测试此并发性。它应该按原样工作,尽管语法错误:OUTPUT子句保证id lookup-&gt;递增的原子性为一步,并且由于触发器,整个操作发生在事务中。

TableName.Id仍然是标识列。所有常见的习语,如$ IDENTITY和SCOPE_IDENTITY()仍然有效。

桌子上没有ids的中央表,但你可以很容易地创建一个。

答案 5 :(得分:-1)

我没有任何帮助来查找未使用的值,但如果你真的想找到它们并自己设置它们,你可以使用

set identity_insert on ....

在你的代码中这样做。

我和其他人在一起。何必?您是否有业务问题需要解决?