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

时间:2009-03-26 00:52:10

标签: sql sql-server gaps-and-islands

如何在SQL Server列中找到最小的未使用数字?

我准备将大量手动录制的记录从Excel导入SQL Server表。它们都有一个数字ID(称为文档编号),但由于不再适用的原因,它们没有按顺序分配,这意味着从现在开始我的网站记录新记录时,需要为其分配最小的文档编号(尚未采取的大于零。

有没有办法通过纯SQL执行此操作,或者这是TSQL /代码的问题?

谢谢!

修改

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

LINQ

仅供参考 - 这可以通过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

14 个答案:

答案 0 :(得分:54)

找到不存在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的特殊情况,这是一个丑陋的解决方案:

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

答案 1 :(得分:12)

如果按数字ID对它们进行排序,那么您要查找的数字将是ROW_NUMBER()函数不等于ID的第一个数字。

答案 2 :(得分:12)

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

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

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

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

答案 3 :(得分:10)

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给出的相关子查询的答案的替代方案。

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

如果您通过电子邮件向某人发送文档#42的链接,然后删除该文档,会发生什么?稍后,您将重新使用id#42作为新文档。现在,电子邮件的收件人将会看到指向错误文档的链接!

答案 4 :(得分:5)

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个扫描哈希匹配和连接,如最佳答案

答案 5 :(得分:3)

如果序列中存在空白,您可以通过以下方式找到第一个空白:

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

换句话说,找到其后继者不存在的最小ID,并返回该后继者。如果没有间隙,则返回大于现存最大ID的一个。插入占位符ID为0以确保考虑以1开头的ID。

请注意,这至少需要n log n时间。

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

答案 6 :(得分: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%准确,但你应该可以从那里得到它)

答案 7 :(得分:2)

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

答案 8 :(得分:2)

这是一个简单的方法。它可能不会很快。它不会在开头找到缺失的数字。

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

答案 9 :(得分:2)

我们假设您的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

这可以处理我能想到的所有情况 - 包括根本没有现有记录。

我唯一不喜欢这个解决方案的是,其他条件必须包括两次,如:

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

还请注意有关锁定和并发的评论 - 填补空白的要求在大多数情况下是糟糕的设计并且可能导致问题。但是, I 有充分的理由这样做:ID将由人类打印和输入,并且我们不希望在一段时间后拥有多位数的ID,而所有的低一个是免费的......

答案 10 :(得分:1)

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

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

答案 11 :(得分:1)

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

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

答案 12 :(得分:0)

对于Oracle DB,这应该完成工作:

SELECT MIN(NI) FROM
        (SELECT ROWNUM AS NI,YOUR_ID
         FROM (SELECT YOUR_ID
               FROM YOUR_TABLE 
               ORDER BY YOUR_ID ASC))
WHERE NI<>YOUR_ID

答案 13 :(得分:0)

ROW_NUMBER()函数示例:

IF NOT EXISTS (SELECT TOP 1 row_num FROM (SELECT ROW_NUMBER() OVER (ORDER BY Id) row_num, Id FROM table) t WHERE t.Id != t.row_num) SELECT MAX (Id)+1 FROM table ELSE SELECT TOP 1 row_num FROM (SELECT ROW_NUMBER() OVER (ORDER BY Id) row_num, Id FROM table) t WHERE t.Id != t.row_num);