在SQL Server中查找最小的未使用的数字

如何findSQL Server列中最小的未使用的数字?

我即将从Excel导入大量手动录制的logging到SQL Server表中。 它们都有一个数字ID(称为文档号),但是由于不再适用的原因,它们没有按顺序分配,这意味着从现在开始,当我的网站logging一个新的logging时,需要给它分配尽可能小的文档编号大于零)还没有被采取。

有没有办法通过普通的SQL来做到这一点,或者这是一个TSQL /代码的问题?

谢谢!

编辑

特别感谢WW提出的并发问题。 鉴于这是一个Web应用程序,它是multithreading的定义,任何人面对同样的问题应该考虑代码或数据库级锁来防止冲突。

LINQ

FYI – 这可以通过LINQ使用以下代码完成:

var nums = new [] { 1,2,3,4,6,7,9,10}; int nextNewNum = ( from n in nums where !nums.Select(nu => nu).Contains(n + 1) orderby n select n + 1 ).First(); 

nextNewNum == 5

find不存在与Id + 1行的第一行

 SELECT TOP 1 t1.Id+1 FROM table t1 WHERE NOT EXISTS(SELECT * FROM table t2 WHERE t2.Id = t1.Id + 1) ORDER BY t1.Id 

编辑:

为了处理现有最低ID不是1的特殊情况,这里是一个丑陋的解决scheme:

 SELECT TOP 1 * FROM ( SELECT t1.Id+1 AS Id FROM table t1 WHERE NOT EXISTS(SELECT * FROM table t2 WHERE t2.Id = t1.Id + 1 ) UNION SELECT 1 AS Id WHERE NOT EXISTS (SELECT * FROM table t3 WHERE t3.Id = 1)) ot ORDER BY 1 

到目前为止,在任何答案中都没有提及locking或并发性。

考虑这两个用户几乎同时添加一个文档:

 User 1 User 2 Find Id Find Id Id = 42 Id = 42 Insert (42..) Insert (42..) Error! 

您可能需要:a)处理该错误并再次循环查找下一个可用的Id,或者b)在stream程开始时locking,因此只有1个用户在特定时间查找ID

如果您使用数字ID对它们进行sorting,那么您正在查找的数字将是ROW_NUMBER()函数不等于ID的第一个数字。

 SELECT TOP 1 t1.id+1 FROM mytable t1 LEFT OUTER JOIN mytable t2 ON (t1.id + 1 = t2.id) WHERE t2.id IS NULL ORDER BY t1.id; 

这是使用@Jeffrey Hantlin和@Darrel Miller给出的相关子查询的答案的替代scheme。

但是,您所描述的政策并不是一个好主意。 ID值应该是唯一的,但不应该被要求是连续的。

如果您通过电子邮件向文档#42链接,然后删除文档,会发生什么情况? 稍后,您将重新使用ID#42作为新文档。 现在电子邮件的收件人将跟随链接到错误的文件

如果序列中有空白,你可以find像这样的第一个缺口:

 select top 1 (found.id + 1) nextid from (select id from items union select 0) found where not exists (select * from items blocking where blocking.id = found.id + 1) order by nextid asc 

换句话说,find后继者不存在的最小ID,并返回后继者。 如果没有差距,则返回比现存ID最大的一个。 插入占位符ID为0以确保从1开始的ID被考虑。

请注意,这将至less需要n个时间。

Microsoft SQL允许在insert语句中使用from子句,因此您可能不需要诉诸程序代码。

 declare @value int select @value = case when @value is null or @value + 1 = idcolumn then idcolumn else @value end from table order by idcolumn select @value + 1 

是1表扫描而不是2扫描一个哈希匹配和一个连接,如顶部的答案

是否有一个原因,它必须是尽可能最小的数字? 为什么你需要填补漏洞?

编辑广告的答案,因为这是一个业务规则。

 DECLARE @counter int DECLARE @max SET @counter = 0 SET @max = SELECT MAX(Id) FROM YourTable WHILE @counter <= @max BEGIN SET @counter = @counter + 1 IF NOT EXISTS (SELECT Id FROM YourTable WHERE Id = @counter) BREAK END END 

(我没有一个数据库方便,所以这可能不是100%准确的,但你应该能够从那里得到它)

 select MIN(NextID) NextUsableID from ( select (case when c1 = c2 then 0 else c1 end) NextID from ( select ROW_NUMBER() over (order by record_id) c1, record_id c2 from myTable) ) where NextID > 0 

这是一个简单的方法。 它可能不会很快。 它在开始时不会find缺失的数字。

 SELECT MIN(MT1.MyInt+1) FROM MyTable MT1 LEFT OUTER JOIN MyTable MT2 ON (MT1.MyInt+1)=MT2.MyInt WHERE MT2.MyInt Is Null 

你真的应该尝试将列转换为IDENTITY。 首先备份,然后使用ROW_NUMBER来更新文档ID,以便从1开始,直到文档计数。 您应该在WHILE中执行此操作,因为如果在其他表中使用数字列作为引用(外键),SQL Server将尝试更新外键,并可能因冲突而失败。 最后只需启用列的标识规范。

:)现在做更多的工作,但以后会为您节省很多麻烦。

我知道这个答案已经晚了,但你可以通过recursionexpression式find最小的未使用的数字:

 CREATE TABLE Test ( ID int NOT NULL ) --Insert values here ;WITH CTE AS ( --This is called once to get the minimum and maximum values SELECT nMin = 1, MAX(ID) + 1 as 'nMax' FROM Test UNION ALL --This is called multiple times until the condition is met SELECT nMin + 1, nMax FROM CTE WHERE nMin < nMax ) --Retrieves all the missing values in the table. Removing TOP 1 will --list all the unused numbers up to Max + 1 SELECT TOP 1 nMin FROM CTE WHERE NOT EXISTS ( SELECT ID FROM Test WHERE nMin = ID ) 

假设您的ID应始终以1开头:

 SELECT MIN(a.id) + 1 AS firstfree FROM (SELECT id FROM table UNION SELECT 0) a LEFT JOIN table b ON b.id = a.id + 1 WHERE b.id IS NULL 

这处理所有我能想到的情况 – 包括根本不存在的logging。

我不喜欢这个解决scheme的唯一的事情就是必须包含两个额外的条件,比如:

 SELECT MIN(a.id) + 1 AS firstfree FROM (SELECT id FROM table WHERE column = 4711 UNION SELECT 0) a LEFT JOIN table b ON b.column = 4711 AND b.id = a.id + 1 WHERE b.id IS NULL 

请注意关于locking和并发性的注释 – 填补空白的要求在大多数情况下是不好的devise,可能会导致问题。 然而, 有一个很好的理由去做:身份证是由人类打印和input的,我们不想在一段时间后有很多数字的身份证,而所有的身份证都是免费的。

我遇到了类似的问题,并提出了这个问题:

 Select Top 1 IdGapCheck From (Select Id, ROW_NUMBER() Over (Order By Id Asc) AS IdGapCheck From dbo.table) F Where Id > IdGapCheck Order By Id Asc