【问题标题】:SQL: find missing IDs in a tableSQL:在表中查找缺失的 ID
【发布时间】:2009-09-07 14:12:15
【问题描述】:

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

 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 19 之后创建并被删除的 ID。我目前正在用代码解决这种情况,因为我持有创建的最大 ID。但是,如果查询可以将 MaxID 作为参数,并且还返回当前 max 和 MaxID 之间的那些 ID,那将是一个不错的“奖励”(但肯定不是必须的)。

我目前正在使用 MySQL,但考虑迁移到 SQL Server,因此我希望查询适合两者。另外,如果您使用的任何东西不能在 SQLite 上运行,请注明,谢谢。

【问题讨论】:

    标签: sql


    【解决方案1】:

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

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

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

    简单的解决方案是:

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

    天才。

    【讨论】:

    • 好的,所以我因帮助可能会像我一样登陆此页面的人而被否决。不错。
    • 我可以建议(一年多后)进行一个小的改进,以消除 SQLITE 中返回的最大缺失值,该值始终为 max(id) + 1 :只需在查询末尾添加:AND id
    • 这只是部分有效。如果您有 ID 24,但没有 25 或 26,则此请求将获得 ID 25,而不是 ID 26,因此您将忘记修复 ID 26 的情况。
    • 搜索了几个小时,发现了许多复杂的解决方案。这是一个多么简单而天才的答案。这对我帮助很大。非常感谢@NikBurns
    • 请注意,如果您使用此解决方案,您将丢失值。我对其进行了测试,但就像@conradkleinespel 提到的那样,如果您连续丢失多个身份值,它只会捕获第一个。
    【解决方案2】:

    这个问题经常出现,遗憾的是,最常见(也是最便携)的答案是创建一个临时表来保存 应该 存在的 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
    

    【讨论】:

    • 我不确定这个环境中的场景是什么,但是如果不是 20 条,而是说一千条记录呢? ..这是由同时为 50-60 个用户提供服务的网页上的代码调用的。每次创建和删除这些记录是否有效?考虑到我们省略了创建和删除临时表的部分。
    • @daemonkid:伙计,真是个该死的稻草人。如果您必须一次又一次地解决这个问题,对于 50-60 个用户,您显然需要一个永久表。您显然必须适应您的特定场景,但这是找到丢失 ID 问题的解决方案。
    • +1 我不确定我会接受它,但我会考虑。谢谢埃里克。
    • 虽然它适用于示例数据,但不适用于大量记录。如果想要一个大于 100.000 的范围,单独的“while”将需要几秒钟才能完成
    • 非常适合大量记录。我已经将它与包含 60 万条记录的表上的另一个答案进行了比较。
    【解决方案3】:

    这是 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); 
    

    希望这有帮助。

    【讨论】:

    • MySQL 上的类似查询是什么?
    【解决方案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
    

    【讨论】:

    • 我发现这对 postgres 非常有用。 generate_series 函数的链接如下:postgresql.org/docs/10/functions-srf.html。我很高兴找到了这个解决方案。
    • 谢谢!出于某种原因,ID 以非常随机的顺序出现,所以我在末尾添加了ORDER BY missing_ids
    【解决方案5】:

    我知道这是一个老问题,并且已经有一个公认的答案, 但是使用临时表并不是真的必要。固定格式(抱歉重复发布)。

    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
    

    【讨论】:

      【解决方案6】:

      这是唯一的 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
      

      【讨论】:

        【解决方案7】:

        单个查询可以找到丢失的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)
        

        【讨论】:

        • 注意:这个答案是特定于 SQL Server 的
        • 奇怪.. 在 MS SQL Server 2008 上不起作用。表包含 600k 条记录,此查询仅检查 2150 条。
        • @naXa 那是因为spt_values 只是一个包含一堆随机数的表。这个答案是完全错误的。
        • @Stijn 正是我所暗示的
        【解决方案8】:

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

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

        【讨论】:

        • 小幅改进以获取所有丢失的 id:select gs from generate_series(1, (select MAX(id) from mytable)) as gs where gs not in (select id from mytable)
        【解决方案9】:

        从表中获取缺失的行

        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
        

        【讨论】:

          【解决方案10】:

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

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

          第一行和最后两行只是为了记录花费了多长时间。 150 万个 ID(ish)花了我 57 秒的时间,这是在一个慢速服务器上。在 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)
          

          【讨论】:

            【解决方案11】:

            这个问题只需要一个查询就可以解决

            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 上测试

            【讨论】:

            • 这只查找比现有 ID 大 1 的缺失 ID。在 OP 的情况下,它会遗漏大部分缺失的 ID。 sqlfiddle.com/#!2/45412/1
            【解决方案12】:

            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 ;
            

            【讨论】:

              【解决方案13】:

              几天前,我正在编写一份生产报告,发现缺少一些数字。丢失的号码非常重要,因此我被要求查找所有丢失号码的列表以进行调查。 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 临时表中选择缺失的数据。

              【讨论】:

                【解决方案14】:

                将 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
                

                【讨论】:

                  【解决方案15】:

                  使用@PaulSvirin 的答案,我用UNION 对其进行了扩展,以显示我表中的所有数据,包括NULLs 的缺失记录。

                  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
                  

                  【讨论】:

                    【解决方案16】:

                    对我来说最简单的解决方案:创建一个选择,让所有 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)
                    

                    【讨论】:

                      【解决方案17】:

                      借用@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 
                      

                      【讨论】:

                        【解决方案18】:

                        试试这个查询。这个单一的查询足以得到缺失的数字:(请将 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;
                        

                        【讨论】:

                        • 这不是一个简单的查询
                        【解决方案19】:
                        SELECT DISTINCT id -1
                        FROM users
                        WHERE id != 1 AND id - 1 NOT IN (SELECT DISTINCT id FROM users)
                        

                        解释:(id - 1).....检查表中存在的任何先前的id

                        ( id != 1 ).....当当前 id 为 1 时忽略,因为它的前一个 id 将是 0 零。

                        【讨论】:

                        • 不错的尝试,但错过了一些缺少的 ID
                        【解决方案20】:

                        这是我用来查找一个名为 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,它将只返回第一个。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2022-01-05
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2022-09-27
                          • 1970-01-01
                          相关资源
                          最近更新 更多