Как проверить любое пропущенное число из ряда чисел?

Я делаю проект по созданию системы приема в колледж; технологии - Java и Oracle.

В одной из таблиц хранятся предварительно сгенерированные серийные номера. Позже против этих серийных номеров будут введены данные формы заявителя. Мое требование состоит в том, что, когда процесс ввода будет завершен, мне нужно будет создать отчет по лоту. Если во время подачи предварительно сгенерированных серийных номеров пропали какие-либо порядковые номера.

Например, скажем, в таблице порядковые номера 7001, 7002, 7004, 7005, 7006, 7010. Из приведенного выше ряда видно, что от 7001 до 7010 отсутствуют номера 7003, 7007, 7008 и 7009.

Есть ли какая-либо функция СУБД, доступная в Oracle, чтобы узнать эти числа, или если какая-либо хранимая процедура может выполнить мою задачу, то, пожалуйста, предложите алгоритм.

Я могу найти некоторые методы в Java, но для скорости я хочу найти решение в Oracle.


person Samcoder    schedule 10.06.2012    source источник
comment
Я добавил тег пробелов и островов. поиск его, вероятно, даст достаточное количество предшествующего уровня техники, включая рекурсивные запросы.   -  person wildplasser    schedule 10.06.2012


Ответы (10)


Решение без жесткого кодирования 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.
person Rob van Wijk    schedule 10.06.2012
comment
Это делает мой ответ смехотворно сложным! +1 - person Ben; 10.06.2012
comment
Я сам какое-то время изучал логику и решил, что зря трачу время. Я предполагаю, что я должен был сделать Google хорошей практикой. Поэтому +1 к этому ответу. - person 4 Leave Cover; 28.10.2015
comment
что здесь LEVEL? - person beginner; 05.10.2020
comment
УРОВЕНЬ псевдостолбца: docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ - person Rob van Wijk; 06.10.2020

Попробуй это:

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
person Patrick    schedule 28.08.2014

Это работает на postgres >= 8.4. С некоторыми небольшими изменениями синтаксиса CTE его можно заставить работать и с оракулом, и с микрософтом.

-- 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;
person wildplasser    schedule 10.06.2012
comment
Для downvoter: пожалуйста, объясните. Этот вопрос помечен как «sql», который является (должным быть) стандартным sql. CTE являются частью этого. - person wildplasser; 11.06.2012
comment
Не я проголосовал против, но, честно говоря, он также помечен как Oracle, и этот синтаксис неверен. - person Ben; 11.06.2012
comment
Ну, мне сказали, что CTE реализован в оракуле, см.: stackoverflow.com/questions/6064970/oracle-cte -объединить. Конечно, конструкция connect-by/preor существует уже несколько лет, но синтаксис CTE является, по крайней мере, частью стандарта, и всегда есть причина для некоторого разнообразия, даже если он стандартен. Как я сказал в своем ответе: могут существовать незначительные различия в синтаксисе (например, пропуск ключевого слова RECURSIVE). И наконец: по крайней мере, запрос работает для меня (может быть, с некоторыми изменениями и для других). Здесь были опубликованы другие ответы, которые не работают. - person wildplasser; 11.06.2012

Вот решение, которое:

  • Полагается на функцию Oracle LAG
  • Не требует знания всей последовательности (но, таким образом, не определяет, были ли пропущены самые первые или последние числа в последовательности)
  • Перечисляет значения, окружающие отсутствующие списки чисел
  • Перечисляет отсутствующие списки номеров как непрерывные группы (возможно, удобно для отчетности)
  • Трагически терпит неудачу для очень больших списков отсутствующих чисел из-за ограничений списка.

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"                  
person James Daily    schedule 14.11.2017

Один простой способ получить ответ для вашего сценария:

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.

person Stefan    schedule 10.06.2012
comment
Вам нужно знать, что значение равно 9. Откуда вы это знаете? - person Ben; 10.06.2012
comment
Вот что я написал: Вам нужно знать диапазон вашей последовательности. Если я правильно понял задачу, то это, наверное, известно. Или я вас неправильно понял? - person Stefan; 10.06.2012

Я бы предложил connect by level, как это сделал Стефан, однако вы не можете использовать подзапрос в этом операторе, который означает, что это не очень подходит для вас, так как вам нужно знать, каковы максимальные и минимальные значения вашей последовательности.

Я бы предположил, что табличная функция с вертикальной подкладкой может быть лучшим способом генерировать числа вам нужно сделать соединение. Чтобы это работало, вам понадобится объект в вашей базе данных для возврата значений:

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
person Ben    schedule 10.06.2012

Это сработало, но выбирается первая последовательность (начальное значение), поскольку у нее нет предшественника. Протестировано в 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) доступные последовательности

Я думаю, вы можете улучшить этот простой запрос

person codingbiz    schedule 10.06.2012
comment
Это предполагает наличие таблицы со всеми хранящимися в ней номерами последовательностей. В Oracle этого делать не нужно. - person Ben; 10.06.2012
comment
будет ли Oracle connect by работать лучше, чем это? - person codingbiz; 10.06.2012
comment
ответ с наибольшим количеством голосов здесь использует 2 агрегатные функции - как насчет производительности? - person codingbiz; 10.06.2012
comment
Мне нравится это простое решение, но если у вас есть несколько пропущенных чисел в последовательности (7,8,9), оно обнаружит только одно из них. - person Troglo; 14.03.2014

Улучшенный запрос:

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;

Примечание: это столбец, в котором мы находим пропущенное значение.

person Sumit Bansal    schedule 21.12.2019

person    schedule
comment
Не понижать голосование, потому что оно действительно работает, если вы измените значения a на 1, 2, 3... 10, а не на 7001-7010, отмеченные OP, но... ну, это не так. не работает, если речь идет о числах 7001-7010, как указано в вопросе. dbfiddle здесь - person Bob Jarvis - Reinstate Monica; 29.10.2018

person    schedule
comment
Отсутствующие порядковые номера. - person Harish H.N; 23.08.2016
comment
Пожалуйста, отредактируйте свой пост, чтобы предоставить контекст для вашего ответа. Ответы только на код полезны лишь частично: stackoverflow.com/help/how-to-answer - person Uwe Allner; 23.08.2016