Some of the complexity is lost while I obscure and simplify the problem (it involved a hierarchical query), but I think the key elements remain.
Consider a table of codes with an order sequence.
create table my_values (code varchar2(10), order_seq number(5)); insert into my_values values ('A', 10); insert into my_values values ('A', 20); insert into my_values values ('A', 30); insert into my_values values ('B', 40); insert into my_values values ('B', 50); insert into my_values values ('C', 60); insert into my_values values ('C', 70); insert into my_values values ('C', 80); insert into my_values values ('D', 90);My requirement was to see the next and previous code. For instance, when listing B records, I wanted to see A and C in the same row - the solution screamed analytical functions so I started with my favourite:
select code, order_seq ,row_number() over (partition by code order by order_seq) rn from my_values order by order_seq; CODE ORDER_SEQ RN ---------- --------- -- A 10 1 A 20 2 A 30 3 B 40 1 B 50 2 C 60 1 C 70 2 C 80 3 D 90 1I struck the results down to just the first row for each code, incorporating lag/lead to get the info I needed.
select s.code ,lag(s.code) over (order by s.order_seq) my_lag ,lead(s.code) over (order by s.order_seq) my_lead from ( select code, order_seq ,row_number() over (partition by code order by order_seq) rn from my_values order by order_seq ) s where s.rn = 1 order by order_seq; CODE MY_LAG MY_LEAD ---------- ---------- ---------- A B B A C C B D D CFinally, to combine my results I created an in-line view with a subquery factoring statement.
with sub as ( select s.code ,lag(s.code) over (order by s.order_seq) my_lag ,lead(s.code) over (order by s.order_seq) my_lead from ( select code, order_seq ,row_number() over (partition by code order by order_seq) rn from my_values order by order_seq ) s where s.rn = 1) select m.*, sub.my_lag, sub.my_lead from my_values m, sub where m.code = sub.code; CODE ORDER_SEQ MY_LAG MY_LEAD ---------- --------- ---------- ---------- A 10 B A 20 B A 30 B B 40 A C B 50 A C C 60 B D C 70 B D C 80 B D D 90 CSo my question is - any ideas for a simpler solution?
Not really simpler, just another way
ReplyDeleteselect
code
,order_seq
,last_value(preceding ignore nulls) over(order by order_seq) preceding
,last_value(following ignore nulls) over(order by order_seq desc) following
from (
select t.*
, nullif(lead(code) over(order by order_seq),code) following
, nullif(lag(code) over(order by order_seq),code) preceding
from my_values t
) t
order by order_seq
Best regards
Maxim
The Maxims solution may be improved to do only two window sorts:
ReplyDeleteselect
code
,order_seq
,last_value(preceding ignore nulls) over(order by order_seq) preceding
,first_value(following ignore nulls) over(order by order_seq rows between current row and unbounded following) following
from (
select t.*
, nullif(lead(code) over(order by order_seq),code) following
, nullif(lag(code) over(order by order_seq),code) preceding
from my_values t
) t
order by order_seq
;
And with a dirty interpretation of the test data it is possible with only one window sort:
select t.*
, nullif(first_value(code) over(order by ascii(code) range 1 preceding),code) pr
, nullif(last_value(code) over(order by ascii(code) range between current row and 1 following),code) fo
from my_values t
;