How to display Oracle query output in Matrix/Pivot format - sql

I would like to display the output in Matrix/Pivot format of the following query.
Query :
SELECT
SUBSTR(mon, 4, 6) month,
rmbs_cd,
scdta,
cl_nm,
br_cd,
brd_nm,
prod,
prod_nm,
SUM(sale_net) sales
FROM (SELECT
LAST_DAY(x.deli_dt) mon,
x.rmbs_cd,
x.sc_cd || x.dist_cd || x.tha_cd || x.un_cd || x.cl_id scdta,
INITCAP(cl_nm) cl_nm,
a.br_cd,
brd_nm,
a.cat_cd || a.prd_cd prod,
prod_nm,
sale_cd,
Nvl(sum(a.sale_net) - sum(rt_qty * flat_rt), 0) sale_net
FROM bill_det a, bill_mas x, cl_info c, inv_brand d, inv_prod p
WHERE (a.bill_no = x.bill_no AND a.sc_cd = x.sc_cd)
AND x.fl_mvh IN ('1', '4')
AND x.deli_dt BETWEEN '01-JUL-15' AND '31-DEC-15'
AND a.br_cd = d.br_cd AND d.div_cd = '1'
AND a.typ_cd || a.cat_cd || a.prd_cd = p.typ_cd || p.cat_cd || p.prd_cd
AND p.typ_cd = '09'
AND x.sc_cd = c.sc_cd (+)
AND x.dist_cd = c.dist_cd (+)
AND x.tha_cd = c.tha_cd (+)
AND x.un_cd = c.un_cd (+)
AND x.cl_id = c.cl_id (+)
AND c.div_cd IN ('1', '4')
AND sale_cd IN ('IM', 'IC', 'IN')
AND cancl IS NULL
GROUP BY
LAST_DAY(x.deli_dt),
x.rmbs_cd,
x.sc_cd || x.dist_cd || x.tha_cd || x.un_cd || x.cl_id,
cl_nm,
a.br_cd,
brd_nm,
a.cat_cd || a.prd_cd,
prod_nm,
sale_cd
)
GROUP BY SUBSTR(mon, 4, 6), rmbs_cd, scdta, cl_nm, br_cd, brd_nm, prod, prod_nm
ORDER BY 1, 2, 3
Result :
Expected Output :
I would like to display each individual month in different vertical columns sequentially in Oracle.

Hisomething like this (tested on Oracle 11gr2)? Using Pivot function:
WITH T AS(SELECT 'A' AS PROD, 1 AS VAL, 1 AS myMONTH FROM DUAL
UNION ALL
SELECT 'A' AS PROD, 20 AS VAL, 2 AS myMONTH FROM DUAL
UNION ALL
SELECT 'A' AS PROD, 33 AS VAL, 3 AS myMONTH FROM DUAL
UNION ALL
SELECT 'A' AS PROD, 13 AS VAL, 4 AS myMONTH FROM DUAL
UNION ALL
SELECT 'B' AS PROD, 3211 AS VAL, 5 AS myMONTH FROM DUAL
UNION ALL
SELECT 'C' AS PROD, 1 AS VAL, 6 AS myMONTH FROM DUAL
UNION ALL
SELECT 'D' AS PROD, 1 AS VAL, 7 AS myMONTH FROM DUAL
UNION ALL
SELECT 'D' AS PROD, 32 AS VAL, 7 AS myMONTH FROM DUAL
UNION ALL
SELECT 'E' AS PROD, 1 AS VAL, 8 AS myMONTH FROM DUAL
UNION ALL
SELECT 'B' AS PROD, 1 AS VAL, 9 AS myMONTH FROM DUAL
UNION ALL
SELECT 'G' AS PROD, 2131 AS VAL, 9 AS myMONTH FROM DUAL
UNION ALL
SELECT 'A' AS PROD, 1 AS VAL, 10 AS myMONTH FROM DUAL
UNION ALL
SELECT 'H' AS PROD, 1 AS VAL, 11 AS myMONTH FROM DUAL
UNION ALL
SELECT 'J' AS PROD, 234 AS VAL, 1 AS myMONTH FROM DUAL
UNION ALL
SELECT 'J' AS PROD, 432 AS VAL, 3 AS myMONTH FROM DUAL
UNION ALL
SELECT 'J' AS PROD, 22 AS VAL, 5 AS myMONTH FROM DUAL
UNION ALL
SELECT 'J' AS PROD, 25546 AS VAL, 5 AS myMONTH FROM DUAL)
SELECT *
FROM (
SELECT PROD, VAL, MYMONTH
FROM T)
PIVOT(SUM(VAL) FOR MYMONTH IN (1 AS JAN, 2 AS FEB, 3 AS MAR, 4 AS APR, 5 AS MAY, 6 AS JUN,
7 AS JUL, 8 AS AGO, 9 AS SEPT, 10 AS OCT, 11 AS NOV, 12 AS DIC)
)
ORDER BY
PROD;
Igor

Related

Divide by couples of elements in a table

I have this table:
ELEMENTO VALOR
-------------------------
ELEMENT1_SUFFIX1 2
ELEMENT1_SUFFIX2 4
ELEMENT2_SUFFIX1 5
ELEMENT2_SUFFIX2 15
I would like to generate new entries by dividing each couple of elements and suffixes. For each partition of element, I would like to divide suffix2/suffix1 and generate an entry with element_result. Like the below.
ELEMENTO VALOR
-------------------------
ELEMENT1_SUFFIX1 2
ELEMENT1_SUFFIX2 4
ELEMENT2_SUFFIX1 5
ELEMENT2_SUFFIX2 15
ELEMENT1_RESULT 2
ELEMENT1_RESULT 3
The table can be generated with the below code.
with aux (elemento, valor) as
( select 'ELEMENT1_SUFFIX1', 2 from dual
UNION ALL
select 'ELEMENT1_SUFFIX2', 4 from dual
UNION ALL
select 'ELEMENT2_SUFFIX1', 5 from dual
UNION ALL
select 'ELEMENT2_SUFFIX2', 10 from dual
)
select aux.* from aux;
This is what I've tried so far, but I am pretty sure there must be a better solution.
with aux (elemento, valor) as
( select 'ELEMENT1_SUFFIX1', 2 from dual
UNION ALL
select 'ELEMENT1_SUFFIX2', 4 from dual
UNION ALL
select 'ELEMENT2_SUFFIX1', 5 from dual
UNION ALL
select 'ELEMENT2_SUFFIX2', 15 from dual
),
aux2 as
(select aux.*,
valor/max(
case
when regexp_substr(elemento, '[^_]+', 1, 2) = 'SUFFIX1'
then valor
end) over (partition by REGEXP_SUBSTR(elemento,'[^_]+',1,1)) new_value
from aux
)
select * from aux
union all
select REGEXP_SUBSTR(elemento,'[^_]+',1,1)
|| '_RESULT',
new_value valor
from aux2
where regexp_substr(elemento, '[^_]+', 1, 2) = 'SUFFIX2';
Yet another variation.
Sample data:
SQL> WITH
2 aux (elemento, valor)
3 AS
4 (SELECT 'ELEMENT1_SUFFIX1', 2 FROM DUAL
5 UNION ALL
6 SELECT 'ELEMENT1_SUFFIX2', 4 FROM DUAL
7 UNION ALL
8 SELECT 'ELEMENT2_SUFFIX1', 5 FROM DUAL
9 UNION ALL
10 SELECT 'ELEMENT2_SUFFIX2', 15 FROM DUAL
11 )
Query:
12 -- existing rows
13 select elemento, valor
14 from aux
15 union all
16 -- calculated rows
17 select
18 substr(elemento, 1, instr(elemento, '_') - 1) ||'_RESULT' as elemento,
19 max(case when substr(elemento, -1) = 2 then valor end) /
20 max(case when substr(elemento, -1) = 1 then valor end) as valor
21 from aux
22 group by substr(elemento, 1, instr(elemento, '_') - 1);
ELEMENTO VALOR
----------------------- ----------
ELEMENT1_SUFFIX1 2
ELEMENT1_SUFFIX2 4
ELEMENT2_SUFFIX1 5
ELEMENT2_SUFFIX2 15
ELEMENT1_RESULT 2
ELEMENT2_RESULT 3
6 rows selected.
SQL>
We can use an aggregation/pivot approach along with a union here:
WITH cte AS (
SELECT SUBSTR(ELEMENTO, 1, INSTR(ELEMENTO, '_') - 1) || '_RESULT' AS ELEMENTO,
MAX(CASE WHEN SUBSTR(ELEMENTO, INSTR(ELEMENTO, '_') + 1) = 'SUFFIX2'
THEN VALOR END) /
MAX(CASE WHEN SUBSTR(ELEMENTO, INSTR(ELEMENTO, '_') + 1) = 'SUFFIX1'
THEN VALOR END) AS VALOR
FROM yourTable
GROUP BY SUBSTR(ELEMENTO, 1, INSTR(ELEMENTO, '_') - 1)
)
SELECT ELEMENTO, VALOR
FROM
(
SELECT ELEMENTO, VALOR, 1 AS pos FROM yourTable
UNION ALL
SELECT ELEMENTO, VALOR, 2 FROM cte
) t
ORDER BY pos, ELEMENTO;
Try this:
with aux (elemento, valor) as
( select 'ELEMENT1_SUFFIX1', 2 from dual
UNION ALL
select 'ELEMENT1_SUFFIX2', 4 from dual
UNION ALL
select 'ELEMENT2_SUFFIX1', 5 from dual
UNION ALL
select 'ELEMENT2_SUFFIX2', 15 from dual
), rawdata (elemento, valor, group_id, row_id) AS
(
select aux.*
,DENSE_RANK() OVER (ORDER BY SUBSTR(elemento, 1, INSTR(elemento, '_')-1))
,ROW_NUMBER() OVER (PARTITION BY SUBSTR(elemento, 1, INSTR(elemento, '_')-1) ORDER BY SUBSTR(elemento, INSTR(elemento, '_')+1))
from aux
)
SELECT *
FROM aux
UNION ALL
SELECT CONCAT(CONCAT('ELEMENT', A.group_id),'_RESULT')
,B.valor / a.valor
FROM rawdata A
INNER JOIN rawdata B
ON A.group_id = b.group_id
AND A.row_id = b.row_id -1

ORACLE SQL | If a column contains a value, then it will exclude a different value from the same column

I have this query that returns the data below it
select LISTAGG(d.DOCUMENT_TYPE_CD, ',') WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d;
VALUE
---------
CI,ECI,POA
now I'm trying to add a condition whenever 'ECI' value is present, it should exclude 'CI' in the result like this one below
VALUE
---------
ECI,POA
I tried using case statement in where condition it prompted an error
select LISTAGG(d.DOCUMENT_TYPE_CD, ',')
WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d
where CASE d.DOCUMENT_TYPE_CD
WHEN 'ECI' THEN d.DOCUMENT_TYPE_CD <> 'CI'
END;
ORA-00905: missing keyword
00905. 00000 - "missing keyword"
*Cause:
*Action:
Error at Line: 7 Column: 36
is there any other way I could resolve this?
See if this helps; read comments within code.
SQL> with
2 test (id, document_type_cd) as
3 -- sample data
4 (select 1, 'ECI' from dual union all
5 select 1, 'CI' from dual union all
6 select 1, 'POA' from dual union all
7 --
8 select 2, 'CI' from dual union all
9 select 2, 'POA' from dual union all
10 --
11 select 3, 'XYZ' from dual union all
12 select 3, 'ABC' from dual
13 ),
14 temp as
15 -- see whether CI and ECI exist per each ID
16 (select id,
17 sum(case when document_type_cd = 'CI' then 1 else 0 end) sum_ci,
18 sum(case when document_type_cd = 'ECI' then 1 else 0 end) sum_eci
19 from test
20 group by id
21 ),
22 excl as
23 -- exclude CI rows if ECI exist for that ID
24 (select a.id,
25 a.document_type_cd
26 from test a join temp b on a.id = b.id
27 where a.document_type_cd <> case when b.sum_ci > 0 and b.sum_eci > 0 then 'CI'
28 else '-1'
29 end
30 )
31 -- finally:
32 select e.id,
33 listagg(e.document_type_cd, ',') within group (order by e.document_type_cd) result
34 from excl e
35 group by e.id;
ID RESULT
---------- --------------------
1 ECI,POA
2 CI,POA
3 ABC,XYZ
SQL>
Something like this:
select LISTAGG(d.DOCUMENT_TYPE_CD, ',')
WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d,
(select sum (case when DOCUMENT_TYPE_CD = 'CI' then 1 else 0 end) C
from test_table) A
where d.DOCUMENT_TYPE_CD <> case when A.c > 0 then 'CI' when A.c = 0 then ' ' end;
DEMO
You may identify the presence of both the values with two conditional aggregations in the same group by and then replace CI inside the result of listagg in one pass.
with a(id, cd) as (
select 1, 'ABC' from dual union all
select 1, 'ECI' from dual union all
select 1, 'CI' from dual union all
select 1, 'POA' from dual union all
select 2, 'XYZ' from dual union all
select 2, 'ECI' from dual union all
select 2, 'CI' from dual union all
select 2, 'POA' from dual union all
select 3, 'CI' from dual union all
select 3, 'POA' from dual union all
select 4, 'ABC' from dual union all
select 4, 'DEF' from dual
)
select
id,
ltrim(
/*Added comma in case CI will be at the beginning*/
replace(
',' || listagg(cd, ',') within group (order by cd asc),
decode(
/*If both are present, then replace CI. If not, then do not replace anything*/
max(decode(cd, 'CI', 1))*max(decode(cd, 'ECI', 1)),
1,
',CI,'
),
','
),
','
) as res
from a
group by id
ID | RES
-: | :----------
1 | ABC,ECI,POA
2 | ECI,POA,XYZ
3 | CI,POA
4 | ABC,DEF
db<>fiddle here
Instead of using GROUP BY, you can also use windowing (aka analytic) functions to check the presence of ECI per group (test data shamelessly stolen from #littlefoot):
with
test (id, document_type_cd) as
-- sample data
(select 1, 'ECI' from dual union all
select 1, 'CI' from dual union all
select 1, 'POA' from dual union all
--
select 2, 'CI' from dual union all
select 2, 'POA' from dual union all
--
select 3, 'XYZ' from dual union all
select 3, 'ABC' from dual
),
temp as
(select id,
document_type_cd,
sum(case when document_type_cd = 'ECI' then 1 else 0 end) over (partition by id) as sum_eci
from test
)
select a.id,
listagg(a.document_type_cd, ',') within group (order by a.document_type_cd) result
from temp a
where a.document_type_cd != 'CI' or sum_eci = 0
group by a.id;

How to count consecutive duplicates in a table?

I have below question:
Want to find the consecutive duplicates
SLNO NAME PG
1 A1 NO
2 A2 YES
3 A3 NO
4 A4 YES
6 A5 YES
7 A6 YES
8 A7 YES
9 A8 YES
10 A9 YES
11 A10 NO
12 A11 YES
13 A12 NO
14 A14 NO
We will consider the value of PG column and I need the output as 6 which is the count of maximum consecutive duplicates.
It can be done with Tabibitosan method. Run this, to understand it:
with a as(
select 1 slno, 'A' pg from dual union all
select 2 slno, 'A' pg from dual union all
select 3 slno, 'B' pg from dual union all
select 4 slno, 'A' pg from dual union all
select 5 slno, 'A' pg from dual union all
select 6 slno, 'A' pg from dual
)
select slno, pg, newgrp, sum(newgrp) over (order by slno) grp
from(
select slno,
pg,
case when pg <> nvl(lag(pg) over (order by slno),1) then 1 else 0 end newgrp
from a
);
Newgrp means a new group is found.
Result:
SLNO PG NEWGRP GRP
1 A 1 1
2 A 0 1
3 B 1 2
4 A 1 3
5 A 0 3
6 A 0 3
Now, just use a group by with count, to find the group with maximum number of occurrences:
with a as(
select 1 slno, 'A' pg from dual union all
select 2 slno, 'A' pg from dual union all
select 3 slno, 'B' pg from dual union all
select 4 slno, 'A' pg from dual union all
select 5 slno, 'A' pg from dual union all
select 6 slno, 'A' pg from dual
),
b as(
select slno, pg, newgrp, sum(newgrp) over (order by slno) grp
from(
select slno, pg, case when pg <> nvl(lag(pg) over (order by slno),1) then 1 else 0 end newgrp
from a
)
)
select max(cnt)
from (
select grp, count(*) cnt
from b
group by grp
);
with test as (
select 1 slno,'A1' name ,'NO' pg from dual union all
select 2,'A2','YES' from dual union all
select 3,'A3','NO' from dual union all
select 4,'A4','YES' from dual union all
select 6,'A5','YES' from dual union all
select 7,'A6','YES' from dual union all
select 8,'A7','YES' from dual union all
select 9,'A8','YES' from dual union all
select 10,'A9','YES' from dual union all
select 11,'A10','NO' from dual union all
select 12,'A11','YES' from dual union all
select 13,'A12','NO' from dual union all
select 14,'A14','NO' from dual),
consecutive as (select row_number() over(order by slno) rr, x.*
from test x)
select x.* from Consecutive x
left join Consecutive y on x.rr = y.rr+1 and x.pg = y.pg
where y.rr is not null
order by x.slno
And you can control output with condition in where.
where y.rr is not null query returns duplicates
where y.rr is null query returns "distinct" values.
Just for completeness, here's the actual Tabibitosan method:
with sample_data as (select 1 slno, 'A1' name, 'NO' pg from dual union all
select 2 slno, 'A2' name, 'YES' pg from dual union all
select 3 slno, 'A3' name, 'NO' pg from dual union all
select 4 slno, 'A4' name, 'YES' pg from dual union all
select 6 slno, 'A5' name, 'YES' pg from dual union all
select 7 slno, 'A6' name, 'YES' pg from dual union all
select 8 slno, 'A7' name, 'YES' pg from dual union all
select 9 slno, 'A8' name, 'YES' pg from dual union all
select 10 slno, 'A9' name, 'YES' pg from dual union all
select 11 slno, 'A10' name, 'NO' pg from dual union all
select 12 slno, 'A11' name, 'YES' pg from dual union all
select 13 slno, 'A12' name, 'NO' pg from dual union all
select 14 slno, 'A14' name, 'NO' pg from dual)
-- end of mimicking a table called "sample_data" containing your data; see SQL below:
select max(cnt) max_pg_in_queue
from (select count(*) cnt
from (select slno,
name,
pg,
row_number() over (order by slno)
- row_number() over (partition by pg
order by slno) grp
from sample_data)
where pg = 'YES'
group by grp);
MAX_PG_IN_QUEUE
---------------
6
SELECT MAX(consecutives) -- Block 1
FROM (
SELECT t1.pg, t1.slno, COUNT(*) AS consecutives -- Block 2
FROM test t1 INNER JOIN test t2 ON t1.pg = t2.pg
WHERE t1.slno <= t2.slno
AND NOT EXISTS (
SELECT * -- Block 3
FROM test t3
WHERE t3.slno > t1.slno
AND t3.slno < t2.slno
AND t3.pg != t1.pg
)
GROUP BY t1.pg, t1.slno
);
The query calculates the result in following way:
Extract all couples of records that don't have a record with different value of PG in between (blocks 2 and 3)
Group them by PG value and starting SLNO value -> this counts the consecutive values for any [PG, (starting) SLNO] couple (block 2);
Extract Maximum value from query 2 (block 1)
Note that the query may be simplified if the slno field in table contains consecutive values, but this seems not your case (in your example record with SLNO = 5 is missing)
Only requiring a single aggregation query and no joins (the rest of the calculation can be done with ROW_NUMBER, LAG and LAST_VALUE):
SELECT MAX( num_before_in_queue ) AS max_sequential_in_queue
FROM (
SELECT rn - LAST_VALUE( has_changed ) IGNORE NULL OVER ( ORDER BY ROWNUM ) + 1
AS num_before_in_queue
FROM (
SELECT pg,
ROW_NUMBER() OVER ( ORDER BY slno ) AS rn,
CASE pg WHEN LAG( pg ) OVER ( ORDER BY slno )
THEN NULL
ELSE ROW_NUMBER() OVER ( ORDER BY sl_no )
END AS change
FROM table_name
)
WHERE pg = 'Y'
);
Try to use row_number()
select
SLNO,
Name,
PG,
row_number() over (partition by PG order by PG) as 'Consecutive'
from
<table>
order by
SLNO,
NAME,
PG
This is should work with minor tweaking.
--EDIT--
Sorry, partiton by PG.
The partitioning tells the row_number when to start a new sequence.

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

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.
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;

How can I rank sequential rows with a null value in a query?

I would like to rank sequential rows with the null value inside a group of the same natural key. The database is oracle.
Here's the example :
NAT_KEY ATTRIBUTE_A ORDERED_FIELD RANK
A A 1
A 2 1
A A 4
A I 6
A 8 1
A 10 2
A 11 3
B 2 1
B 3 2
B A 5
B A 6
B 9 1
C A 1
C A 5
C I 6
C 7 1
C 8 2
There must be a way with row_number() function, level and connect by or another one.
My guess is that you want something like
select key,
attr,
order_by,
(case when rnk1 is not null
then rank() over (partition by key order by rnk1)
else null
end) rnk
from (
select x.*,
(case when attr is null
then row_number() over (partition by key order by order_by)
else null
end) rnk1
from <<table name>> x
)
order by key, order_by
That produces output like this
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 'A' key, 'A' attr, 1 order_by from dual union all
3 select 'A', null, 2 from dual union all
4 select 'A', 'A', 4 from dual
5 )
6 select key,
7 attr,
8 order_by,
9 (case when rnk1 is not null
10 then rank() over (partition by key order by rnk1)
11 else null
12 end) rnk
13 from (
14 select x.*,
15 (case when attr is null
16 then row_number() over (partition by key order by order_by)
17 else null
18 end) rnk1
19 from x
20 )
21* order by key, order_by
SQL> /
K A ORDER_BY RNK
- - ---------- ----------
A A 1
A 2 1
A A 4
I finally figured it out with Andriy M's link.
Here's the solution :
select grouped_table.nat_key,
grouped_table.attr,
grouped_table.order_by,
case
when attr is null
then row_number() over (partition by nat_key, attr, grp order by order_by)
else null
end rowrank
from (
select the_table.*,
row_number() over (partition by nat_key order by order_by) - row_number() over (partition by nat_key, nvl2(attr, 1, 0) order by order_by) grp
from (
select 'A' nat_key, 'A' attr, 1 order_by from dual
union all
select 'A' nat_key, null attr, 2 order_by from dual
union all
select 'A' nat_key, 'A' attr, 4 order_by from dual
union all
select 'A' nat_key, 'I' attr, 6 order_by from dual
union all
select 'A' nat_key, null attr, 8 order_by from dual
union all
select 'A' nat_key, null attr, 10 order_by from dual
union all
select 'A' nat_key, null attr, 11 order_by from dual
union all
select 'B' nat_key, null attr, 2 order_by from dual
union all
select 'B' nat_key, null attr, 3 order_by from dual
union all
select 'B' nat_key, 'A' attr, 5 order_by from dual
union all
select 'B' nat_key, 'A' attr, 6 order_by from dual
union all
select 'B' nat_key, null attr, 9 order_by from dual
union all
select 'C' nat_key, 'A' attr, 1 order_by from dual
union all
select 'C' nat_key, 'A' attr, 5 order_by from dual
union all
select 'C' nat_key, 'I' attr, 6 order_by from dual
union all
select 'C' nat_key, null attr, 7 order_by from dual
union all
select 'C' nat_key, null attr, 8 order_by from dual
) the_table
) grouped_table
order by nat_key, order_by
Thanks!