简体   繁体   中英

Oracle SQL find consecutive (at least 5) Available numbers from table

Requirement is to search at a table of numbers that have different states each, like "Available", "Reserved", "Disconnected", etc., to find the "Available" only numbers that are consecutive and at least 50 consecutive every time, and return those. Example data:

Number State
124    "Reserved"
125    "Available"
126    "Available"
127    "Disconnected"
128    "Available"
129    "Available"
130    "Available"
131    "Available"
132    "Available"
133    "Reserved"
.
.
.

So, at above case, at least the 128 - 132 should get returned since they are 5 "Available" numbers. Then next consecutive "Available" could be 7 or 10 or 15, those should also get returned, as soon as they are 5 or more. Hope request is clear. Thank you.

If you are using Oracle 12.1 or higher, the match_recognize solution in Vamsi Prabhala's Answer is probably the most efficient.

For older versions, a solution using the fixed differences (aka Tabibitosan) method is probably best. I show the count for each sequence (although strictly speaking you may not need that).

with
  sample_data as (
    select 124 as num, 'Reserved' as state     from dual union all
    select 125       , 'Available'             from dual union all
    select 126       , 'Available'             from dual union all
    select 127       , 'Disconnected'          from dual union all
    select 128       , 'Available'             from dual union all
    select 129       , 'Available'             from dual union all
    select 130       , 'Available'             from dual union all
    select 131       , 'Available'             from dual union all
    select 132       , 'Available'             from dual union all
    select 133       , 'Reserved'              from dual union all
    select 135       , 'Troubled'              from dual union all
    select 136       , 'Taken'                 from dual union all
    select 137       , 'Available'             from dual union all
    select 138       , 'Available'             from dual union all
    select 139       , 'Available'             from dual union all
    select 140       , 'Available'             from dual union all
    select 141       , 'Available'             from dual union all
    select 142       , 'Available'             from dual
  )
select   num, ct
from     (
           select num, count(*) over (partition by grp) ct
           from   (
                    select num, num - row_number() over (order by num) as grp
                    from   sample_data
                    where  state = 'Available'
                  )
         )
where    ct >= 5
order by num
;

Output:

       NUM         CT
---------- ----------
       128          5
       129          5
       130          5
       131          5
       132          5
       137          6
       138          6
       139          6
       140          6
       141          6
       142          6

One method uses a bunch of lags:

select t.*
from (select t.*,
             lag(state, 1) over (order by number) as state_1,
             lag(state, 2) over (order by number) as state_2,
             lag(state, 3) over (order by number) as state_3,
             lag(state, 4) over (order by number) as state_4
      from t
     ) t
where state = 'Available' and state_1 = state and state_2 = state and state_3 = state and state_4 = state;

This returns only the 5 (and subsequent rows), but it is easy enough to figure out the earlier ones.

With that in mind, there is a simpler method:

select t.*
from (select t.*,
             lag(number, 4) over (partition by state order by number) as number_state_4
      from t
     ) t
where state = 'Available' and
      number_state_4 = number - 4;

This is saying that the 4th row back for 'Available' is the current row minus 4 -- or that there are 5 "Available"s in a row.

Of course, you can also approach this as gaps and islands:

select state, min(number), max(number)
from (select t.*,
             row_number over (partition by state order by number) as seqnum
      from t
     ) t
where state = 'Available' 
group by state, number - seqnum
having count(*) >= 5

If you are using Oracle version 12c, there is an option to use match_recognize , which does a pattern matching to get rows with a specified pattern.

select *
from tbl
MATCH_RECOGNIZE (
         ORDER BY "number"
         ALL ROWS PER MATCH
         AFTER MATCH SKIP TO LAST AVAILABLE
         PATTERN(available{5,})
         DEFINE 
         available AS (status='Available')
       ) MR
ORDER BY "number"

You can do this easily via PLsql block if this can help you:

DECLARE

CuRSOR L1 is 
SELECT * from tab1;

L_COUNT number:= 0;
 l_number_seats number := &1;
l_start_seat number;
l_end_seat number;
BEGIN
 l_start_seat := 0;
l_end_seat := 0;
 for i in l1
loop
   if L_COUNT = l_number_seats THEN 
     EXIT;
   end if;

 if i.state = 'Available' then
    if L_COUNT =0 then
       l_start_seat := i;
    else
      l_end_seat := i;
    end if;
   L_COUNT := L_COUNT +1;
else
L_COUNT := 0;
 end if;


END loop;
dbms_output.put_line(l_start_seat||' - '||l_end_seat);
END;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM