45

如何在 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

4

15 回答 15

64

查找不存在 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
于 2009-03-26T01:14:33.633 回答
12

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

于 2009-03-26T01:13:13.557 回答
12

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

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

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

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

于 2009-03-26T01:20:52.057 回答
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 重新用于新文档。现在电子邮件的收件人将点击错误文档的链接!

于 2009-03-26T01:15:52.303 回答
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 次扫描哈希匹配和连接,如最佳答案

于 2009-08-27T10:39:49.660 回答
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 大 1 的值。插入占位符 ID 0 以确保考虑以 1 开头的 ID。

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

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

于 2009-03-26T01:12:02.890 回答
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% 准确,但你应该能够从那里得到它)

于 2009-03-26T00:56:53.160 回答
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
于 2009-04-27T13:59:13.373 回答
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
于 2014-10-09T18:05:54.347 回答
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

还请注意关于锁定和并发性的评论——在大多数情况下,填补空白的要求是糟糕的设计,可能会导致问题。但是,这样做是有充分理由的:ID 是由人工打印和键入的,我们不希望一段时间后有很多数字的 ID,而所有低数字的 ID 都是免费的......

于 2016-05-24T09:28:19.203 回答
1

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

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

于 2009-03-26T01:55:20.227 回答
1

我知道这个答案很晚,但您可以使用递归表表达式找到最小的未使用数字:

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
)
于 2015-04-29T21:34:23.580 回答
0

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

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
于 2017-02-27T16:51:56.450 回答
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
于 2019-03-05T17:08:52.837 回答
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;

于 2019-12-17T21:05:41.517 回答