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

Sql 如何从一系列数字中检查任何缺失的数字?,sql,oracle,plsql,gaps-and-islands,Sql,Oracle,Plsql,Gaps And Islands,我正在做一个项目,为一所大学创建招生系统;这些技术是Java和Oracle 其中一个表中存储了预生成的序列号。稍后,将根据这些序列号输入申请人的表格数据。我的要求是,当进入过程完成后,我将不得不生成一份非常明智的报告。如果在输入预生成的序列号期间,任何序列号丢失 例如,在一个表中,序列号是7001、7002、7004、7005、7006、7010。 从上述系列中可以清楚地看出,从7001到7010,缺失的数字是7003、7007、7008和7009 Oracle中是否有任何DBMS函数可用于查找

我正在做一个项目,为一所大学创建招生系统;这些技术是Java和Oracle

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

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

Oracle中是否有任何DBMS函数可用于查找这些数字,或者是否有任何存储过程可满足我的目的,请建议一种算法


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

一个简单的方法是:

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部分中的偏移量,序列的长度是connectby部分中级别的限制。

我会建议按级别连接,但是,您不能在此语句中使用子查询,这意味着它并不真正适合您,因为您需要知道序列的最大值和最小值是什么

我建议a可能是生成加入所需号码的最佳方式。为了使其工作,您需要数据库中的一个对象将值返回到:

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

不硬编码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

这起作用,但选择第一个序列开始值,因为它没有前置值。在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
要获得未分配序列,只需执行值[i]-1,其中i大于第一行,例如7004-1=7003和7007-1=7006,这是可用序列


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

这适用于postgres>=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;
试试这个:

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)

以下是一个解决方案:

依赖于Oracle的滞后功能 不需要完整序列的知识,但因此无法检测序列中的第一个或最后一个数字是否丢失 列出缺少的数字列表周围的值 将缺失的数字列表列为连续组,可能便于报告 不幸的是,由于Listag的限制,对于非常大的缺失数字列表,失败了 SQL:

改进的查询是:


从dual CONNECT BY LEVEL中选择ROWNUM Missing_Numbers,您需要知道该值为9。你怎么知道的?这就是我写的:你需要知道序列的范围。如果我正确理解任务,这可能是已知的。还是我误解了你?我加上了gaps and islands标签。搜索它可能会产生足够多的现有技术,包括递归查询。看,这让我的答案看起来过于复杂了+1我自己探索了一段时间,认为这样浪费时间是不必要的。我想我应该让谷歌成为一个好的实践。因此,这个答案是+1。这里的级别是什么?伪列级别:这预先假定了一个包含存储在其中的所有序列号的表。在Oracle中没有必要这样做。Oracle的connect by性能会比这更好吗?这里投票率最高的答案使用2个聚合函数-性能如何?我喜欢这个简单的解决方案,但如果您在序列7、8、9中有几个遗漏的数字,它只会检测其中一个,请解释。这个问题被标记为“sql”,它应该是标准sql。CTE是其中的一部分。不是我投了反对票,但公平地说,它也被标记为Oracle,此语法不正确。我被告知CTE是在Oracle中实现的,请参阅:。当然,connectby/previor构造已经存在了几年,但是CTE语法至少是标准的一部分,并且总是有一些多样性的原因,即使它是标准的。正如我在回答中所说:在语法上可能存在一些细微的差异,比如省略递归关键字。最后:至少查询对我有效,对其他人也有一些更改。此处发布的其他答案不起作用。缺少序号。请编辑您的帖子以提供答案的上下文。只使用代码的答案只是部分有用:不是向下投票,因为如果您更改了 e a到1,2,3…10的值,而不是OP中提到的7001-7010,但是…如果问题中提到的数字是7001-7010,那么它就不起作用了。
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)
From  To
7003  7003
7007  7009
 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
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"                  
 SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
 MINUS
 SELECT a FROM test1 ;