Найти диапазоны из ряда чисел в SQL/Oracle

У меня есть таблица, содержащая ряд чисел 1,2,3,4,5,11,12,13,14,15,101,102,103,104,105,510,511,512,513,515,516,517.
Мне нужен SQL-запрос, процедура или функция, чтобы я мог получить диапазоны в следующих форматах.

От до:

1-5
11-15
101-105
510-517

OR

1-5, 11-15, 101-105, 510-517

person Hayat Muhammad    schedule 30.07.2015    source источник


Ответы (2)


Вы можете сделать это с помощью аналитической функции ROW_NUMBER. См. Найти диапазон последовательных значений в последовательности чисел или дат.

Например,

Диапазон

SQL> with data(num) as(
  2      select  1 from dual union
  3      select  2 from dual union
  4      select  3 from dual union
  5      select  5 from dual union
  6      select  6 from dual union
  7      select  7 from dual union
  8      select 10 from dual union
  9      select 11 from dual union
 10      select 12 from dual union
 11      select 20 from dual
 12  )
 13  select min(num)||'-'|| max(num) as "range"
 14  from (select num,
 15           num-Row_Number() over(order by num)
 16           as rn
 17           from data)
 18  group by rn
 19  order by min(num);

range
-------------------------------------------------
1-3
5-7
10-12
20-20

SQL>

Список

SQL> with data(num) as(
  2      select  1 from dual union
  3      select  2 from dual union
  4      select  3 from dual union
  5      select  5 from dual union
  6      select  6 from dual union
  7      select  7 from dual union
  8      select 10 from dual union
  9      select 11 from dual union
 10      select 12 from dual union
 11      select 20 from dual
 12  )
 13  SELECT listagg(range, ',') WITHIN GROUP(
 14  ORDER BY min_num) AS "list"
 15  FROM
 16    (SELECT MIN(num) min_num,
 17      MIN(num)
 18      ||'-'
 19      || MAX(num) range
 20    FROM
 21      (SELECT num, num-Row_Number() over(order by num) AS rn FROM DATA
 22      )
 23    GROUP BY rn
 24    );

list
-------------------------------------------------------------------------
1-3,5-7,10-12,20-20

SQL>

Обновление OP хочет, чтобы решение на PL/SQL сохраняло список в переменной PL/SQL.

Настройка

SQL> CREATE TABLE t AS
  2  SELECT *
  3  FROM
  4    ( WITH data(num) AS
  5    ( SELECT 1 FROM dual
  6    UNION
  7    SELECT 2 FROM dual
  8    UNION
  9    SELECT 3 FROM dual
 10    UNION
 11    SELECT 5 FROM dual
 12    UNION
 13    SELECT 6 FROM dual
 14    UNION
 15    SELECT 7 FROM dual
 16    UNION
 17    SELECT 10 FROM dual
 18    UNION
 19    SELECT 11 FROM dual
 20    UNION
 21    SELECT 12 FROM dual
 22    UNION
 23    SELECT 20 FROM dual
 24    )
 25  SELECT * FROM DATA);

Table created.

Блок PL/SQL

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2    v_list VARCHAR2(100);
  3  BEGIN
  4    SELECT listagg(RANGE, ',') WITHIN GROUP(
  5    ORDER BY min_num)
  6    INTO v_list
  7    FROM
  8      (SELECT MIN(num) min_num,
  9        MIN(num)
 10        ||'-'
 11        || MAX(num) range
 12      FROM
 13        (SELECT num, num-Row_Number() over(order by num) AS rn FROM t
 14        )
 15      GROUP BY rn
 16      );
 17    dbms_output.put_line(v_list);
 18  END;
 19  /
1-3,5-7,10-12,20-20

PL/SQL procedure successfully completed.

SQL>
person Lalit Kumar B    schedule 30.07.2015
comment
Результат отображается под заголовком List. список ------------------------------------------------- ------------------------ 1-3,5-7,10-12,20-20 Я хочу сохранить этот вывод в переменной в PL /SQL. - person Hayat Muhammad; 31.07.2015
comment
@HayatMuhammad Поскольку список представляет собой строку, вам просто нужна переменная varchar2. просто сделай SELECT LISTAGG() INTO VARIABLE FROM... - person Lalit Kumar B; 31.07.2015
comment
хорошо, я попробую, но я также разместил отдельный вопрос. Спасибо. - person Hayat Muhammad; 31.07.2015

Обычное решение использует тот факт, что эти значения должны быть последовательными:

select min(serialnum), max(serialnum), count(*)
from
 (
   select 
      serialnum,
      -- this returns a meaningless value, but the same meaningless value for sequential numbers
      serialnum - row_number() over (order by serialnum) as dummy
   from tab
 ) dt
group by dummy
person dnoeth    schedule 30.07.2015