Как сгенерировать динамическую последовательность в Oracle - PullRequest
2 голосов
/ 03 октября 2019

У меня есть таблица A, которая представляет действительную последовательность чисел, которая выглядит примерно так:

| id | start |   end | step |
|----|-------|-------|------|
|  1 |  4000 |  4999 |    4 |
|  2 |     3 | 20000 |    1 | 

A[1], таким образом, представляет последовательность [4000, 4004, 4008, ...4996]

и другую Bиз «занятых» чисел, которые выглядят так:

|  id | number | ... |
|-----|--------|-----|
|   1 |   4000 | ... |
|   2 |   4003 | ... |
| ... |    ... | ... |

Я хочу построить запрос, который, используя A и B, находит первый незанятый номер для определенной последовательности.

Я былпопытка - и неуспешная - сделать это - создать список действительных чисел из строки в A, а затем left outer join table B on B.number = valid_number where B.id is null, из чего я мог бы затем select min(...).

Ответы [ 3 ]

1 голос
/ 03 октября 2019

Как насчет этого?

Я упростил ваш тестовый пример (значение END не так уж и высоко), чтобы сэкономить место (в противном случае мне пришлось бы использовать шрифт меньшего размера:)).

Что он делает?

  • CTE A и B - ваши выборочные данные
  • FULL_ASEQ создает последовательность чисел из таблицы A
    • если вы хотите, чтобы он возвращал, удалите все из строки # 17 и - вместо него - выполните select * from full_aseq
  • , последний запрос возвращает первый доступный порядковый номерто есть тот, который еще не использовался (строки # 19 - 23).

Вот, пожалуйста,

SQL> with
  2  a (id, cstart, cend, step) as
  3    (select 1, 4000,  4032, 4 from dual union all
  4     select 2,    3,    20, 1 from dual
  5    ),
  6  b (id, cnumber) as
  7    (select 1, 4000 from dual union all
  8     select 1, 4004 from dual union all
  9     select 2, 4003 from dual
 10    ),
 11  full_aseq as
 12    (select a.id, a.cstart + column_value * a.step seq_val
 13     from a cross join table(cast(multiset(select level from dual
 14                                           connect by level <= (a.cend - a.cstart) / a.step
 15                                          ) as sys.odcinumberlist))
 16    )
 17  select f.id, min(f.seq_val) min_seq_val
 18  from full_aseq f
 19  where not exists (select null
 20                    from b
 21                    where b.id = f.id
 22                      and b.cnumber = f.seq_val
 23                   )
 24  group by f.id;

        ID MIN_SEQ_VAL
---------- -----------
         1        4008
         2           4

SQL>
0 голосов
/ 03 октября 2019

Я спрошу очевидное и предложу, почему бы не использовать фактическую последовательность?

SQL> set timing on
SQL> CREATE SEQUENCE SEQ_TEST_A
START WITH 4000
INCREMENT BY 4
MINVALUE 4000
MAXVALUE 4999
NOCACHE 
NOCYCLE 
ORDER
Sequence created.
Elapsed: 00:00:01.09
SQL> CREATE SEQUENCE SEQ_TEST_B
START WITH 3
INCREMENT BY 1
MINVALUE 3
MAXVALUE 20000
NOCACHE 
NOCYCLE 
ORDER
Sequence created.
Elapsed: 00:00:00.07

SQL> -- get nexvals from A
SQL> select seq_test_a.nextval from dual

   NEXTVAL
----------
      4000
1 row selected.
Elapsed: 00:00:00.09
SQL> select seq_test_a.nextval from dual

   NEXTVAL
----------
      4004
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_a.nextval from dual

   NEXTVAL
----------
      4008
1 row selected.
Elapsed: 00:00:00.08
SQL> -- get nextvals from B
SQL> select seq_test_b.nextval from dual

   NEXTVAL
----------
         3
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual

   NEXTVAL
----------
         4
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual

   NEXTVAL
----------
         5
1 row selected.
Elapsed: 00:00:00.08
0 голосов
/ 03 октября 2019

Вы можете использовать LEAD для вычисления разницы между упорядоченными строками в таблице B. Любая строка, имеющая разницу (до следующей строки), которая превышает значение step для этой последовательности, является пробелом.

Вот эта концепция, реализованная (ниже). Я добавил идентификатор последовательности «3», который не имеет значений в таблице B, чтобы проиллюстрировать, что он генерирует правильное первое значение.

with
   a (id, cstart, cend, step) as
     (select 1, 4000,  4032, 4 from dual union all
      select 2,    3, 20000, 1 from dual union all
      select 3,  100,   200, 3 from dual
     ),
   b (id, cnumber) as
     (select 1, 4000 from dual union all
      select 1, 4004 from dual union all
      select 1, 4012 from dual union all
      select 2, 4003 from dual
     ),
work1 as (
select a.id, 
       b.cnumber cnumber, 
       lead(b.cnumber,1) over ( partition by b.id order by b.cnumber ) - b.cnumber diff,
       a.step,
       a.cstart,
       a.cend
from   a left join b on b.id = a.id )
select w1.id, 
       CASE WHEN min(w1.cnumber) is null THEN w1.cstart 
            WHEN min(w1.cnumber)+w1.step < w1.cend THEN min(w1.cnumber)+w1.step 
            ELSE null END next_cnumber
from work1 w1
where ( diff is null or diff > w1.step )
group by w1.id, w1.step, w1.cstart, w1.cend
order by w1.id
+----+--------------+
| ID | NEXT_CNUMBER |
+----+--------------+
|  1 |         4008 |
|  2 |         4004 |
|  3 |          100 |
+----+--------------+

Вы можете дополнительно улучшитьрезультаты исключаются строки в таблице B, которые невозможно для последовательности. Например, исключить строку для идентификатора # 1, скажем, со значением 4007.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...