如何检查一系列数字中的任何缺失数字?

时间:2012-06-10 12:01:59

标签: sql oracle plsql gaps-and-islands

我正在做一个为大学创建录取系统的项目;这些技术是Java和Oracle。

在其中一个表格中,存储了预先生成的序列号。之后,根据这些序列号,将输入申请人的表格数据。我的要求是,当输入过程完成后,我将不得不生成一个明智的报告。如果在喂食预先生成的序列号期间,任何序列号都会丢失。

例如,在表中,序列号是7001,7002,7004,7005,7006,7010。 从上面的系列中可以清楚地看出,从7001到7010,缺失的数字是7003,7007,7008和7009

Oracle中是否有任何DBMS功能可以找出这些数字,或者如果任何存储过程可能符合我的目的,请建议一个算法。

我可以在Java中找到一些技术,但为了提高速度,我想在Oracle中找到解决方案。

10 个答案:

答案 0 :(得分:42)

没有硬编码9的解决方案:

select min_a - 1 + level
     from ( select min(a) min_a
                 , max(a) max_a
              from test1
          )
  connect by level <= max_a - min_a + 1
    minus
   select a
     from test1

结果:

MIN_A-1+LEVEL
-------------
         7003
         7007
         7008
         7009

4 rows selected.

答案 1 :(得分:13)

试试这个:

SELECT t1.SequenceNumber + 1 AS "From",
       MIN(t2.SequenceNumber) - 1 AS "To"
FROM MyTable t1
JOIN MyTable t2 ON t1.SequenceNumber < t2.SequenceNumber 
GROUP BY t1.SequenceNumber
HAVING t1.SequenceNumber + 1 < MIN(t2.SequenceNumber)

以下是序列7001,7002,7004,7005,7006,7010的结果:

From  To
7003  7003
7007  7009

答案 2 :(得分:2)

这适用于postgres&gt; = 8.4。通过对CTE语法的一些细微修改,它也可以用于oracle和microsoft。

-- EXPLAIN ANALYZE
WITH missing AS (
    WITH RECURSIVE fullhouse AS (
        SELECT MIN(num)+1 as num
        FROM numbers n0
        UNION ALL SELECT 1+ fh0.num AS num
        FROM fullhouse fh0
        WHERE EXISTS (
                SELECT * FROM numbers ex
                WHERE ex.num > fh0.num
                )
        )
        SELECT * FROM fullhouse fh1
        EXCEPT ( SELECT num FROM numbers nx)
        )
SELECT * FROM missing;

答案 3 :(得分:1)

为您的方案提供答案的一种简单方法是:

create table test1 ( a number(9,0));

insert into test1 values (7001);
insert into test1 values (7002);
insert into test1 values (7004);
insert into test1 values (7005);
insert into test1 values (7006);
insert into test1 values (7010);
commit;

select n.n from (select ROWNUM + 7001 as n from dual connect by level <= 9) n 
   left join test1 t on n.n = t.a where t.a is null;

选择将为您提供示例中的答案。这只是有意义的,如果你事先知道你的数字在哪个范围内,范围不应该太大。第一个数字必须是ROWNUM部分中的偏移量,序列的长度是connect by部分中的水平限制。

答案 4 :(得分:1)

我会建议connect by levelStefan has done,但是,您不能在此声明中使用子查询,这意味着它不适合您,因为您需要知道您的序列的最大值和最小值是什么。

我建议pipe-lined table function可能是生成加入所需数字的最佳方法。为了使其工作,您需要数据库中的对象将值返回到:

create or replace type t_num_array as table of number;

然后功能:

create or replace function generate_serial_nos return t_num_array pipelined is

   l_first number;
   l_last number;

begin

   select min(serial_no), max_serial_no)
     into l_first, l_last 
     from my_table
          ;

   for i in l_first .. l_last loop
      pipe row(i);
   end loop;

   return;

end generate_serial_nos;
/

使用此功能,以下内容将返回最小值和最大值之间的序列号列表。

select * from table(generate_serial_nos);

这意味着您的查询找出缺少的序列号:

select serial_no
  from ( select * 
           from table(generate_serial_nos) 
                ) generator 
  left outer join my_table actual
    on generator.column_value = actual.serial_no
 where actual.serial_no is null

答案 5 :(得分:1)

这有效但选择了第一个序列(起始值),因为它没有前任。在SQL Server中测试但应该在Oracle中工作

SELECT
    s.sequence  FROM seqs s
WHERE
    s.sequence - (SELECT sequence FROM seqs WHERE sequence = s.sequence-1) IS NULL

这是测试结果

  Table
  -------------
  7000
  7001
  7004
  7005
  7007
  7008

  Result
  ----------
  7000
  7004
  7007

要获得未分配的序列,只需执行value[i] - 1,其中i位于第一行,例如(7004 - 1 = 7003 and 7007 - 1 = 7006)可用序列

我认为你可以改进这个简单的查询

答案 6 :(得分:1)

以下是一个解决方案:

  • 依赖Oracle的LAG功能
  • 不需要知道完整序列(但因此不会检测序列中的第一个或最后一个数字是否被遗漏)
  • 列出缺少数字列表周围的值
  • 将缺少的数字列表列为连续组(可能方便报告)
  • 由于listagg限制
  • ,遗憾地失败了很大的缺失数字列表

SQL:

WITH MentionedValues /*this would just be your actual table, only defined here to provide data for this example */
        AS (SELECT *
              FROM (    SELECT LEVEL + 7000 seqnum
                          FROM DUAL
                    CONNECT BY LEVEL <= 10000)
             WHERE seqnum NOT IN (7003,7007,7008,7009)--omit those four per example
                                       ),
     Ranges /*identifies all ranges between adjacent rows*/
        AS (SELECT seqnum AS seqnum_curr,
                   LAG (seqnum, 1) OVER (ORDER BY seqnum) AS seqnum_prev,
                   seqnum - (LAG (seqnum, 1) OVER (ORDER BY seqnum)) AS diff
              FROM MentionedValues)
SELECT Ranges.*,
       (    SELECT LISTAGG (Ranges.seqnum_prev + LEVEL, ',') WITHIN GROUP (ORDER BY 1)
              FROM DUAL
        CONNECT BY LEVEL < Ranges.diff) "MissingValues" /*count from lower seqnum+1 up to lower_seqnum+(diff-1)*/
  FROM Ranges
 WHERE diff != 1 /*ignore when diff=1 because that means the numers are sequential without skipping any*/
;

输出:

SEQNUM_CURR SEQNUM_PREV DIFF MissingValues
7004        7002        2    "7003" 
7010        7006        4    "7007,7008,7009"                  

答案 7 :(得分:0)

 SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
 MINUS
 SELECT a FROM test1 ;

答案 8 :(得分:0)

改进的查询是:

SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
 MINUS
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL < (SELECT Min(a) FROM test1)
 MINUS
SELECT a FROM test1;

注意:a是我们在其中发现缺失值的列。

答案 9 :(得分:-1)

 select    A.ID + 1 As ID
From [Missing] As A
Where A.ID + 1 Not IN (Select ID from [Missing])
And A.ID < n

Data: ID
1
2
5
7
Result: ID
3
4
6