Oracle
 sql >> база данни >  >> RDS >> Oracle

Решение за пропуски и острови в Oracle - използване на рекурсивно

Това е разновидност на проблема с пропуски и острови, с добавената сложност на максималния брой редове във всеки остров. Това е малко дълготрайно, но можете да започнете, като идентифицирате групите, причинени от реда на последователност:

select t.*,
  row_number() over (partition by "Description" order by "Start") as rn,
  case when lag("SequentialOrder")
    over (partition by "Description" order by "Start") < "SequentialOrder"
    then 1 else 0 end as newblock
from test t
order by "Start";

Start     Description MaximunRow SequentialOrder  RN   NEWBLOCK
--------- ----------- ---------- --------------- --- ----------
12-JUN-15 A                    3               3   1          0
13-JUN-15 A                    3               4   2          1
14-JUN-15 A                    3               5   3          1
01-JUL-15 A                    3               4   4          0
02-JUL-15 A                    3               3   5          0
04-JUL-15 A                    3               4   6          1
01-AUG-15 B                    2               5   1          0
16-AUG-15 B                    2               7   2          1

След това можете да използвате рекурсивен CTE (от 11gR2 нататък) въз основа на това:

with u as (
  select t.*,
    row_number() over (partition by "Description" order by "Start") as rn,
    case when lag("SequentialOrder")
      over (partition by "Description" order by "Start") < "SequentialOrder"
      then 1 else 0 end as newblock
  from test t
),
r ("Start", "Description", "MaximunRow", "SequentialOrder", rn, blocknum,
  pos, lastmaxrow) as (
  select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
    1, 1, u."MaximunRow"
  from u
  where rn = 1
  union all
  select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
    case when r.pos = r.lastmaxrow or u.newblock = 0
      then r.blocknum + 1 else r.blocknum end,
    case when r.pos = r.lastmaxrow or u.newblock = 0
      then 1 else r.pos + 1 end,
    case when r.pos = r.lastmaxrow or u.newblock = 0
      then r.lastmaxrow else u."MaximunRow" end
  from r
  join u on u."Description" = r."Description" and u.rn = r.rn + 1
)
select * from r
order by "Start";

Start     Description MaximunRow SequentialOrder  RN   BLOCKNUM  POS LASTMAXROW
--------- ----------- ---------- --------------- --- ---------- ---- ----------
12-JUN-15 A                    3               3   1          1    1          3
13-JUN-15 A                    3               4   2          1    2          3
14-JUN-15 A                    3               5   3          1    3          3
01-JUL-15 A                    3               4   4          2    1          3
02-JUL-15 A                    3               3   5          3    1          3
04-JUL-15 A                    3               4   6          3    2          3
01-AUG-15 B                    2               5   1          1    1          2
16-AUG-15 B                    2               7   2          1    2          2

Това е присвояване на blocknum към всеки ред, като това започва от едно за всяко описание в анкерния член и се увеличава в рекурсивния член или ако newblock е нула (показва прекъсване на последователност) или броят на членовете в блока е предишният максимум. (Може да не разбирам логиката за „предишен максимум“, тъй като не е ясна във въпроса.)

След това можете да групирате по описанието и генерирания номер на блок:

with u as (
  select t.*,
    row_number() over (partition by "Description" order by "Start") as rn,
    case when lag("SequentialOrder")
      over (partition by "Description" order by "Start") < "SequentialOrder"
      then 1 else 0 end as newblock
  from test t
),
r ("Start", "Description", "MaximunRow", "SequentialOrder", rn, blocknum,
  pos, lastmaxrow) as (
  select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
    1, 1, u."MaximunRow"
  from u
  where rn = 1
  union all
  select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
    case when r.pos = r.lastmaxrow or u.newblock = 0
      then r.blocknum + 1 else r.blocknum end,
    case when r.pos = r.lastmaxrow or u.newblock = 0
      then 1 else r.pos + 1 end,
    case when r.pos = r.lastmaxrow or u.newblock = 0
      then r.lastmaxrow else u."MaximunRow" end
  from r
  join u on u."Description" = r."Description" and u.rn = r.rn + 1
)
select min(r."Start") as "Start", max(r."Start") as "End", r."Description"
from r
group by r."Description", r.blocknum
order by r."Description", r.blocknum;

Start     End       Description
--------- --------- -----------
12-JUN-15 14-JUN-15 A          
01-JUL-15 01-JUL-15 A          
02-JUL-15 04-JUL-15 A          
01-AUG-15 16-AUG-15 B          

Вашите примерни данни не задействат максималното прекъсване на редовете, тъй като така или иначе нямате последователности, по-дълги от 3. С някои допълнителни данни:

Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('15-JUN-15','DD-MON-RR'),'A',3,7);
Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('16-JUN-15','DD-MON-RR'),'A',3,8);
Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('17-JUN-15','DD-MON-RR'),'A',3,10);
Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('18-JUN-15','DD-MON-RR'),'A',3,12);
Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('19-JUN-15','DD-MON-RR'),'A',3,13);

същата заявка получава:

Start     End       Description
--------- --------- -----------
12-JUN-15 14-JUN-15 A          
15-JUN-15 17-JUN-15 A          
18-JUN-15 19-JUN-15 A          
01-JUL-15 01-JUL-15 A          
02-JUL-15 04-JUL-15 A          
01-AUG-15 16-AUG-15 B          

така че можете да видите, че се разделя при промяна на последователността и при удряне на три реда в блока.

Демо на SQL Fiddle .

Бихте могли да се измъкнете само с рекурсивния CTE, а не с предишния междинен, като сравнявате последователния ред директно в операторите за case, вместо да използвате newblock; но с rn да намерите следващия ред е по-лесно, отколкото да се опитвате да намерите следващата дата, тъй като те не са съседни.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да проверя дали стойността на DataReader не е нула?

  2. Вмъкнете UTC/GMT дата в база данни на Oracle с Java и Spring

  3. WHENEVER SQLERROR никога не работи

  4. Oracle Identify Data Type

  5. Интегриране на ServiceNow с Oracle Identity Cloud Service (IDCS)