SQL:在表中查找缺少的ID

时间:2009-09-07 14:12:15

标签: sql

我的表有一个唯一的自动增量主键。随着时间的推移,可能会从表中删除条目,因此该字段的值中存在“漏洞”。例如,表格数据可能如下:

 ID  | Value    | More fields...
---------------------------------
 2   | Cat      | ... 
 3   | Fish     | ...
 6   | Dog      | ...
 7   | Aardvark | ...
 9   | Owl      | ...
 10  | Pig      | ...
 11  | Badger   | ...
 15  | Mongoose | ...
 19  | Ferret   | ...

我对将返回表中缺少ID列表的查询感兴趣。对于上述数据,预期结果为:

 ID 
----
 1
 4
 5
 8
 12
 13
 14
 16
 17
 18

注意:

  1. 假设初始第一个ID是1
  2. 应检查的最大ID是最后一个ID,即可以假设在当前最后一个之后没有其他条目(请参阅下面的其他数据)
  3. 上述要求的缺点是列表不会返回在ID 19之后创建且已删除的ID。我目前正在代码中解决这个问题,因为我持有创建的最大ID。但是,如果查询可以作为参数MaxID,并且还返回当前max和MaxID之间的ID,那将是一个很好的“奖励”(但肯定不是必须的)。

    我目前正在使用MySQL,但考虑转移到SQL Server,所以我希望查询适合两者。另外,如果您使用的是无法在SQLite上运行的任何内容,请提及它,谢谢。

20 个答案:

答案 0 :(得分:29)

这个问题经常出现,遗憾的是,最常见(也是最便携)的答案是创建一个临时表来保存所在的ID,并进行左连接。 MySQL和SQL Server之间的语法非常相似。唯一真正的区别是临时表语法。

在MySQL中:

declare @id int
declare @maxid int

set @id = 1
select @maxid = max(id) from tbl

create temporary table IDSeq
(
    id int
)

while @id < @maxid
begin
    insert into IDSeq values(@id)

    set @id = @id + 1
end

select 
    s.id 
from 
    idseq s 
    left join tbl t on 
        s.id = t.id 
 where t.id is null

 drop table IDSeq

在SQL Server中:

declare @id int
declare @maxid int

set @id = 1
select @maxid = max(id) from tbl

create table #IDSeq
(
    id int
)

while @id < @maxid --whatever you max is
begin
    insert into #IDSeq values(@id)

    set @id = @id + 1
end

select 
    s.id 
from 
    #idseq s 
    left join tbl t on 
        s.id = t.id 
 where t.id is null

 drop table #IDSeq

答案 1 :(得分:19)

以下是SQL Server的查询:

;WITH Missing (missnum, maxid)
AS
(
 SELECT 1 AS missnum, (select max(id) from @TT)
 UNION ALL
 SELECT missnum + 1, maxid FROM Missing
 WHERE missnum < maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN @TT tt on tt.id = Missing.missnum
WHERE tt.id is NULL
OPTION (MAXRECURSION 0); 

希望这有用。

答案 2 :(得分:17)

我登陆此页面希望找到SQLITE的解决方案,因为这是我在为SQLITE搜索同样的问题时找到的唯一答案。

我找到的最终解决方案来自这篇文章 Float Middle Blog - SQLITE answer

希望它可以帮助其他人: - )

简单的解决方案是:

SELECT DISTINCT id +1
FROM mytable
WHERE id + 1 NOT IN (SELECT DISTINCT id FROM mytable);

天才。

答案 3 :(得分:10)

我知道这是一个老问题,已经有一个接受的答案, 但是使用临时表并不是必需的。修正格式化(对不起双重帖子)。

DECLARE @TEST_ID integer, @LAST_ID integer, @ID integer

SET @TEST_ID = 1 -- start compare with this ID 
SET @LAST_ID = 100 -- end compare with this ID

WHILE @TEST_ID <= @LAST_ID 
BEGIN 
  SELECT @ID = (SELECT <column> FROM <table> WHERE <column> = @TEST_ID) 
  IF @ID IS NULL 
  BEGIN 
    PRINT 'Missing ID: ' + CAST(@TEST_ID AS VARCHAR(10)) 
  END 
  SET @TEST_ID = @TEST_ID + 1 
END

答案 4 :(得分:4)

这是Oracle唯一的解决方案。它没有解决完整的问题,但留给可能正在使用Oracle的其他人。

select level id           -- generate 1 .. 19
from dual
connect by level <= 19

minus                     -- remove from that set

select id                 -- everything that is currently in the 
from table                -- actual table

答案 5 :(得分:4)

仅限PostgreSQL,受到其他答案的启发。

SELECT all_ids AS missing_ids
FROM generate_series((SELECT MIN(id) FROM your_table), (SELECT MAX(id) FROM your_table)) all_ids
EXCEPT 
SELECT id FROM your_table

答案 6 :(得分:2)

我刚刚找到了 Postgres 的解决方案:

select min(gs) 
from generate_series(1, 1999) as gs 
where gs not in (select id from mytable)

答案 7 :(得分:1)

单个查询可以找到缺少的ID ..

SELECT distinct number

FROM master..spt_values

WHERE number BETWEEN 1 and (SELECT max(id) FROM MyTable)

AND number NOT IN (SELECT id FROM MyTable)

答案 8 :(得分:1)

  

从表中获取缺失的行

DECLARE @MaxID INT = (SELECT MAX(ID) FROM TABLE1)
SELECT SeqID AS MissingSeqID
FROM (SELECT ROW_NUMBER() OVER (ORDER BY column_id) SeqID from sys.columns) LkUp
LEFT JOIN dbo.TABLE1 t ON t.ID = LkUp.SeqID
WHERE t.ID is null and SeqID < @MaxID

答案 9 :(得分:0)

借用@Eric 提案的修改版本。这是针对 SQL Server 的,并将缺失范围的开始和结束值保存在临时表中。如果差距只是一个值,它会将 NULL 作为最终值以便于可视化。

它会产生这样的输出

|StartId| EndId |
|-------|-------|
|     1 | 10182 |
| 10189 | NULL  |
| 10246 | 15000 |

这是脚本,其中 myTableid 需要替换为您的表和标识列。

declare @id bigint
declare @endId bigint
declare @maxid bigint
declare @previousid bigint=0

set @id = 1
select @maxid = max(id) from myTable

create table #IDGaps
(
    startId bigint,
    endId bigint
)

while @id < @maxid
begin
    if NOT EXISTS(select id from myTable where id=@id)
    BEGIN
        SET @previousid=@id
        select top 1 @endId=id from myTable where id>@id

        IF @id=@endId-1
            insert into #IDGaps values(@id,null)
        ELSE
            insert into #IDGaps values(@id,@endId-1)

        SET @id=@endId
        
    END
    ELSE
        set @id = @id + 1
end

select * from #IDGaps

drop table #IDGaps 

答案 10 :(得分:0)

对我来说最简单的解决方案:创建一个选择,使所有ID达到最大序列值(例如:1000000),然后进行过滤:

with listids as (
Select Rownum idnumber From dual Connect By Rownum <= 1000000)

select * from listids
where idnumber not in (select id from table where id <=1000000)

答案 11 :(得分:0)

SELECT DISTINCT id -1
FROM users
WHERE id != 1 AND id - 1 NOT IN (SELECT DISTINCT id FROM users)

说明:(id - 1).....检查表格中存在的任何先前的身份

(id!= 1).....忽略当前id为1,因为其先前的id将为0。

答案 12 :(得分:0)

使用@PaulSvirin的答案,我已使用UNION对其进行了展开,以显示表格中的所有数据,包括NULL s的缺失记录

WITH Missing(missnum, maxid) AS
          (SELECT (SELECT MIN(tmMIN.TETmeetingID)
                   FROM tblTETMeeting AS tmMIN)
                      AS missnum,
                  (SELECT MAX(tmMAX.TETmeetingID)
                   FROM tblTETMeeting AS tmMAX)
                      AS maxid
           UNION ALL
           SELECT missnum + 1, maxid
           FROM Missing
           WHERE missnum < maxid)
SELECT missnum AS TETmeetingID,
       tt.DateID,
       tt.WeekNo,
       tt.TETID
FROM Missing LEFT JOIN tblTETMeeting tt ON tt.TETmeetingID = Missing.missnum
WHERE tt.TETmeetingID IS NULL
UNION
SELECT tt.TETmeetingID,
       tt.DateID,
       tt.WeekNo,
       tt.TETID
FROM tblTETMeeting AS tt
OPTION ( MAXRECURSION 0 )

工作很棒!

TETmeetingID    DateID  WeekNo  TETID
29  3063    21  1
30  null    null    null
31  null    null    null
32  null    null    null
33  null    null    null
34  3070    22  1
35  3073    23  1

答案 13 :(得分:0)

将SQL CTE(从Paul Svirin)转换为Oracle版本,它看起来像这样(用表名替换:YOURTABLE):

WITH Missing (missnum,maxid) as (
  SELECT 1 missnum, (select max(id) from :YOURTABLE) maxid from dual
  UNION ALL
  SELECT m.missnum + 1,m.maxid 
  FROM Missing m
  WHERE m.missnum < m.maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN :YOURTABLE tt on tt.id = Missing.missnum
WHERE tt.id is NULL

答案 14 :(得分:0)

几天前,我正在编写一份生产报告,发现有些数据丢失了。丢失的数字非常重要,因此我被要求查找所有缺失数字的列表以供调查。 I posted a blog entry here, with a full demo, including a script to find missing numbers/IDs in a sample table.

建议的脚本很长,所以我不会在这里包含它。以下是使用的基本步骤:

  1. 创建一个临时表并存储所有不同的数字。
  2. 查找之前缺少某些内容的NextID。存储到一个TempTable中。
  3. 创建一个临时表以存储缺失的数字详细信息。
  4. 使用WHILE循环开始查找缺少的ID。
  5. 从#MissingID临时表中选择缺少的数据。

答案 15 :(得分:0)

尝试此查询。这个单一查询足以得到丢失的数字:(请将TABLE_NAME替换为您正在使用的表名称)

select sno as missing from(SELECT @row := @row + 1 as sno FROM 
(select 0 union all select 1 union all select 3 union all select 4 union all 
select 5 union all select 6 union all select 6 union all select 7 union all 
select 8 union all select 9) t,(select 0 union all select 1 union all select 3 
union all select 4 union all select 5 union all select 6 union all select 6 
union all select 7 union all select 8 union all select 9) t2,(select 0 
union all select 1 union all select 3 union all select 4 union all select 5 
union all select 6 union all select 6 union all select 7 union all select 8 
union all select 9) t3, (select 0 union all select 1 union all select 3 union 
all select 4 union all select 5 union all select 6 union all select 6 union all 
select 7 union all select 8 union all select 9) t4, 
(SELECT @row:=0) as b where @row<1000) as a where a.sno  not in 
  (select distinct b.no from 
(select b.*,if(@mn=0,@mn:=b.no,@mn) as min,(@mx:=b.no) as max from 
  (select ID as no from TABLE_NAME as a) as b,
        (select @mn:=0,@mx:=0) as x order by no) as b) and 
         a.sno between @mn and @mx;

答案 16 :(得分:0)

MySQL

中尝试
DELIMITER ||
DROP PROCEDURE IF EXISTS proc_missing ||
CREATE PROCEDURE proc_missing()
BEGIN 
SET @minID = (SELECT MIN(`id`) FROM `tbl_name` WHERE `user_id`=13);
SET @maxID = (SELECT MAX(`id`) FROM `tbl_name` WHERE `user_id`=13);
REPEAT 
    SET @tableID = (SELECT `id` FROM `tbl_name` WHERE `id` = @minID);
    IF (@tableID IS NULL) THEN
        INSERT INTO temp_missing SET `missing_id` = @tableID;
    END IF;
    SET @minID = @minID + 1;
UNTIL(@minID <= @maxID)
END REPEAT;
END ||
DELIMITER ;

答案 17 :(得分:0)

更新:这个方法花了太长时间,所以我写了一个linux命令来查找文本文件中的空白。它以相反的顺序执行,因此首先将所有id转储到文本文件中,如此;

nohup mysql --password=xx -e 'select id from tablename order by id desc' databasename > /home/ids.txt &

第一行和最后两行只是为了跟踪它花了多长时间。 150万ID(ish)花了我57秒&amp;这是在一个缓慢的服务器上。在i中设置最大ID并拥有它。

T="$(date +%s)"; \
i=1574115; \
while read line; do \
    if  [[ "$line" != "$i" ]] ; then \
        if [[ $i -lt 1 ]] ; then break; fi; \
        if  [[ $line -gt 1 ]] ; then \
            missingsequenceend=$(( $line + 1 )); \
            minusstr="-"; \
            missingsequence="$missingsequenceend$minusstr$i"; \
            expectnext=$(( $line - 1 )); \
            i=$expectnext; \
            echo -e "$missingsequence"; \
        fi; \
    else \
        i=$(( $i - 1 )); \
    fi; \
done \
< /home/ids.txt; \
T="$(($(date +%s)-T))"; \
echo "Time in seconds: ${T}"

示例输出:

1494505-1494507
47566-47572
Time in seconds: 57

另外,我从Eric的答案中得到了语法错误,但在更改分隔符后,在适当的位置使用分号并将其存储在过程中,它可以正常工作。

确保设置正确的最大ID,数据库名称和表名称(它位于选择查询中)。如果您想更改程序名称,请在所有3个位置更改它。

use dbname;
drop procedure if exists dorepeat;
delimiter #
CREATE PROCEDURE dorepeat()
BEGIN
set @id = 1;
set @maxid = 1573736;
drop table if exists IDSeq;
create temporary table IDSeq
(
    id int
);

WHILE @id < @maxid DO
    insert into IDSeq values(@id);
    set @id = @id + 1;
END WHILE;

select 
    s.id 
from 
    IDSeq s 
    left join tablename t on 
        s.id = t.id 
 where t.id is null;

drop table if exists IDSeq;

END#
delimiter ;
CALL dorepeat;

我也发现了这个查询,但我还没有测试过它。

SELECT a.id+1 AS start, MIN(b.id) - 1 AS end
    FROM tablename AS a, tablename AS b
    WHERE a.id < b.id
    GROUP BY a.id
    HAVING start < MIN(b.id)

答案 18 :(得分:-1)

这是我以前找到一个名为tablename

的表的缺失id

select a.id+1 missing_ID from tablename a where a.id+1 not in (select id from tablename b where b.id=a.id+1) and a.id!=(select id from tablename c order by id desc limit 1)

它将返回缺少的ID。 如果有两(2)个或更多连续缺失id,它将仅返回第一个。

答案 19 :(得分:-1)

只需一个查询即可解决此问题

select lft.id + 1 as missing_ids
from tbl as lft left outer join tbl as rght on lft.id + 1 = rght.id
where rght.id is null and lft.id between 1 and (Select max(id)-1 from tbl)

在Mysql上测试