Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I add a moving window to rank() using Oracle SQL

Tags:

sql

oracle

I need to rank values over a moving time window. I got some direction from a blogpost by Tony Hasler at https://tonyhasler.wordpress.com/2012/10/24/model-clause-use-cases/#comment-5116 but the solution of adding a windowing clause to non-windowing functions like median does not work with the rank() or percent_rank() functions which are analytic non-windowing functions.

Working example with median() function:

with a as (
    select 'a' sector, trunc(sysdate) dt, 64 v from dual union all
    select 'a' sector, trunc(sysdate)-1 dt, 2 from dual union all
    select 'a' sector, trunc(sysdate)-2 dt, 4 from dual union all
    select 'a' sector, trunc(sysdate)-3 dt, 128 from dual union all
    select 'a' sector, trunc(sysdate)-4 dt, 8 from dual union all
    select 'a' sector, trunc(sysdate)-5 dt, 16 from dual union all
    select 'a' sector, trunc(sysdate)-6 dt, 32 from dual union all
    select 'a' sector, trunc(sysdate)-7 dt, 256 from dual union all
    select 'a' sector, trunc(sysdate)-8 dt, 1 v from dual union all
    select 'a' sector, trunc(sysdate)-9 dt, 512 from dual union all
    select 'b' sector, trunc(sysdate) dt, 3 from dual union all
    select 'b' sector, trunc(sysdate)-1 dt, 27 from dual union all
    select 'b' sector, trunc(sysdate)-2 dt, 9 from dual union all
    select 'b' sector, trunc(sysdate)-3 dt, 81 from dual 
)
select * from a
model
partition by (sector)
dimension by (dt)
measures (v, 0 mov_rank)
rules 
(
    mov_rank[ANY] = median(v)[dt between CV()-3 and CV()]
)
order by sector, dt
;

The example does not work if we replace median with rank() as in:

with a as (
    select 'a' sector, trunc(sysdate) dt, 64 v from dual union all
    select 'a' sector, trunc(sysdate)-1 dt, 2 from dual union all
    select 'a' sector, trunc(sysdate)-2 dt, 4 from dual union all
    select 'a' sector, trunc(sysdate)-3 dt, 128 from dual union all
    select 'a' sector, trunc(sysdate)-4 dt, 8 from dual union all
    select 'a' sector, trunc(sysdate)-5 dt, 16 from dual union all
    select 'a' sector, trunc(sysdate)-6 dt, 32 from dual union all
    select 'a' sector, trunc(sysdate)-7 dt, 256 from dual union all
    select 'a' sector, trunc(sysdate)-8 dt, 1 v from dual union all
    select 'a' sector, trunc(sysdate)-9 dt, 512 from dual union all
    select 'b' sector, trunc(sysdate) dt, 3 from dual union all
    select 'b' sector, trunc(sysdate)-1 dt, 27 from dual union all
    select 'b' sector, trunc(sysdate)-2 dt, 9 from dual union all
    select 'b' sector, trunc(sysdate)-3 dt, 81 from dual 
)
select * from a
model
partition by (sector)
dimension by (dt)
measures (v, 0 mov_rank)
rules 
(
    mov_rank[ANY] = rank() over (order by v)[dt between CV()-3 and CV()]
)
order by sector, dt
;

I would appreciate any help.

Thanks.

like image 826
rpm Avatar asked Oct 31 '22 17:10

rpm


1 Answers

This might be a little "old-fashioned", but you might be able to achieve an equivalent result using a self-join instead of analytics or model, as in something like:

with a as (
    select 'a' sector, trunc(sysdate) dt, 64 v from dual union all
    select 'a' sector, trunc(sysdate)-1 dt, 2 from dual union all
    select 'a' sector, trunc(sysdate)-2 dt, 4 from dual union all
    select 'a' sector, trunc(sysdate)-3 dt, 128 from dual union all
    select 'a' sector, trunc(sysdate)-4 dt, 8 from dual union all
    select 'a' sector, trunc(sysdate)-5 dt, 16 from dual union all
    select 'a' sector, trunc(sysdate)-6 dt, 32 from dual union all
    select 'a' sector, trunc(sysdate)-7 dt, 256 from dual union all
    select 'a' sector, trunc(sysdate)-8 dt, 1 v from dual union all
    select 'a' sector, trunc(sysdate)-9 dt, 512 from dual union all
    select 'b' sector, trunc(sysdate) dt, 3 from dual union all
    select 'b' sector, trunc(sysdate)-1 dt, 27 from dual union all
    select 'b' sector, trunc(sysdate)-2 dt, 9 from dual union all
    select 'b' sector, trunc(sysdate)-3 dt, 81 from dual 
)
select
    a.sector, 
    a.dt, 
    a.v, 
    count(case when self.v < a.v then self.v end) + 1 mov_rank
from
    a,
    a self
where
    self.sector = a.sector
    and
    self.dt between a.dt - 3 and a.dt + 3
group by 
    a.sector, 
    a.dt, 
    a.v
order by 
    a.sector, 
    a.dt, 
    a.v;
like image 61
Brian Camire Avatar answered Nov 15 '22 05:11

Brian Camire