web-dev-qa-db-ja.com

一連の番号から欠落している番号を確認する方法は?

私は大学の入学システムを作成するプロジェクトを行っています。テクノロジーはJavaおよびOracleです。

いずれかのテーブルには、事前に生成されたシリアル番号が保存されています。後で、それらのシリアル番号に対して、申請者のフォームデータが入力されます。私の要件は、エントリプロセスが完了したら、ロットごとのレポートを生成する必要があることです。事前に生成されたシリアル番号の供給中に、シーケンス番号が欠落した場合。

たとえば、表では、シーケンス番号は7001、7002、7004、7005、7006、7010です。上記のシリーズから、7001から7010までの番号が7003、7007、7008、7009であることは明らかです。

これらの数値を見つけるためにOracleで使用できるDBMS関数はありますか、またはストアドプロシージャが私の目的を達成できる場合は、アルゴリズムを提案してください。

Javaでいくつかのテクニックを見つけることができますが、速度のためにOracleで解決策を見つけたいです。

23
Samcoder

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.
43
Rob van Wijk

これを試して:

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
13
Patrick

これは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;
2
wildplasser

これは機能しましたが、先行タスクがないため、最初のシーケンス(開始値)を選択します。 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)使用可能なシーケンス

この単純なクエリを改善できると思います

1
codingbiz

connect by level as Stefanが行った ただし、このステートメントでサブクエリを使用することはできません。つまり、最大値と最小値を知る必要があるため、実際には適切ではありません。シーケンスの値は次のとおりです。

パイプラインテーブル関数 を使用することをお勧めします。これは、結合に必要な数字を生成するための最良の方法です。これが機能するためには、データベースに値を返すオブジェクトが必要です:

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
1
Ben

以下がその解決策です。

  • 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"                  
1
James Daily

シナリオの答えを得る簡単な方法の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部分のレベルの制限です。

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