oracle sql - numbering group of rows - sql

i have the following table with different prices in every week and need a numbering like in the last column. consecutive rows with same prices should have the same number like in weeks 11/12 or 18/19. but on the other side weeks 2 and 16 have the same prices but are not consecutive so they should get a different number.
w | price | r1 | need
===========================
1 167,93 1 1
2 180 1 2
3 164,72 1 3
4 147,42 1 4
5 133,46 1 5
6 145,43 1 6
7 147 1 7
8 147,57 1 8
9 150,95 1 9
10 158,14 1 10
11 170 1 11
12 170 2 11
13 166,59 1 12
14 161,06 1 13
15 162,88 1 14
16 180 2 15
17 183,15 1 16
18 195 1 17
19 195 2 17
i have already experimented with the analytics functions (row_number, rank, dens_rank), but didn't found a solution for this problem so far.
(oracle sql 10,11)
does anyone have a hint? thanks.

Simulating your table first:
SQL> create table mytable (w,price,r1)
2 as
3 select 1 , 167.93, 1 from dual union all
4 select 2 , 180 , 1 from dual union all
5 select 3 , 164.72, 1 from dual union all
6 select 4 , 147.42, 1 from dual union all
7 select 5 , 133.46, 1 from dual union all
8 select 6 , 145.43, 1 from dual union all
9 select 7 , 147 , 1 from dual union all
10 select 8 , 147.57, 1 from dual union all
11 select 9 , 150.95, 1 from dual union all
12 select 10, 158.14, 1 from dual union all
13 select 11, 170 , 1 from dual union all
14 select 12, 170 , 2 from dual union all
15 select 13, 166.59, 1 from dual union all
16 select 14, 161.06, 1 from dual union all
17 select 15, 162.88, 1 from dual union all
18 select 16, 180 , 2 from dual union all
19 select 17, 183.15, 1 from dual union all
20 select 18, 195 , 1 from dual union all
21 select 19, 195 , 2 from dual
22 /
Table created.
Your need column is calculated in two parts: first compute a delta column which denotes whether the previous price-column differs from the current rows price column. If you have that delta column, the second part is easy by computing the sum of those deltas.
SQL> with x as
2 ( select w
3 , price
4 , r1
5 , case lag(price,1,-1) over (order by w)
6 when price then 0
7 else 1
8 end delta
9 from mytable
10 )
11 select w
12 , price
13 , r1
14 , sum(delta) over (order by w) need
15 from x
16 /
W PRICE R1 NEED
---------- ---------- ---------- ----------
1 167.93 1 1
2 180 1 2
3 164.72 1 3
4 147.42 1 4
5 133.46 1 5
6 145.43 1 6
7 147 1 7
8 147.57 1 8
9 150.95 1 9
10 158.14 1 10
11 170 1 11
12 170 2 11
13 166.59 1 12
14 161.06 1 13
15 162.88 1 14
16 180 2 15
17 183.15 1 16
18 195 1 17
19 195 2 17
19 rows selected.

You can nest your analytic functions using inline views, so you first group the consecutive weeks with same prices and then dense_rank using those groups:
select w
, price
, r1
, dense_rank() over (
order by first_w_same_price
) drank
from (
select w
, price
, r1
, last_value(w_start_same_price) ignore nulls over (
order by w
rows between unbounded preceding and current row
) first_w_same_price
from (
select w
, price
, r1
, case lag(price) over (order by w)
when price then null
else w
end w_start_same_price
from your_table
)
)
order by w
The innermost inline view with LAG function lets the starting week of every consecutive group get it's own week number, but every consecutive week with same price gets null (weeks 12 and 19 in your data.)
The middle inline view with LAST_VALUE function then use the IGNORE NULLS feature to give the consecutive weeks the same value as the first week within each group. So week 11 and 12 both gets 11 in first_w_same_price and week 18 and 19 both gets 18 in first_w_same_price.
And finally the outer query use DENSE_RANK to give the desired result.

For each row you should count previous rows where (w-1) row price isn't the same as (w) price:
select T1.*,
(SELECT count(*)
FROM T T2
JOIN T T3 ON T2.w-1=T3.w
WHERE T2.Price<>T3.Price
AND T2.W<=T1.W)+1 rn
from t T1
SQLFiddle demo

Try this:
with tt as (
select t.*, decode(lag(price) over(order by w) - price, 0, 1, 0) diff
from t
)
select w
, price
, r1
, row_number() over (order by w) - sum(diff) over(order by w rows between UNBOUNDED PRECEDING and current row) need
from tt

SELECT w, price, r1,
ROW_NUMBER () OVER (PARTITION BY price ORDER BY price) row_column
FROM TABLE

Related

SQL Query to get flag as Y for specific column values of a column

I have a query that is fetching data as follows between two dates. The query is already fetching data as follows. Since the query has lot of calculations
& is long I am just writing the final table output I am getting -
person_number OVT Hour3_code hour3_amount
10 UNP 7
10 PUB 8
10 STR 16
8 unp 7
8 PUB 8
16 UNP 8
17 10 vac 9
17 STR 8
15 UNP 6.5
15 STH 5
I want to create a query on top of this table such that if for a person number there is only "UNP" ,"STH" AND PUB in hour3_code, then cancel flag (added column) should be Y,
If there are more values in HOUR3_CODE like -STR or there is value for OVT column then it should be N.
So expected output is -
person_number OVT Hour3_code hour3_amount CANCEL
10 UNP 7 N
10 PUB 8
10 STR 16
8 unp 7 Y
8 PUB 8
16 UNP 8 Y
17 10 vac 9 N
17 STR 8
15 UNP 6.5 Y
15 STH 5
So, since 8 has just UNP, PUB it has cancel_pay Y , Since 15 has only UNP STH it has Y , since 16 has UNP it has Y.
How to achieve this ?
Here's one option. Read comments within code.
Sample data:
SQL> with person (person, ovt, hour3) as
2 (select 10, null, 'unp' from dual union all
3 select 10, null, 'pub' from dual union all
4 select 10, null, 'str' from dual union all
5 --
6 select 8, null, 'unp' from dual union all
7 select 8, null, 'pub' from dual union all
8 --
9 select 16, null, 'unp' from dual union all
10 --
11 select 17, 10, 'vac' from dual union all
12 select 17, null, 'str' from dual union all
13 --
14 select 15, null, 'unp' from dual union all
15 select 15, null, 'sth' from dual
16 ),
Query begins here:
17 fails as
18 -- which PERSON rows have a wrong value in HOUR3 (or have
19 -- something in OVT) column?
20 (select distinct a.person
21 from person a
22 where exists (select null
23 from person b
24 where b.person = a.person
25 and ( b.hour3 not in ('unp', 'sth', 'pub')
26 or b.ovt is not null
27 )
28 )
29 )
30 -- CANCEL is "N" if PERSON is found in FAILS CTE; "Y" otherwise
31 select p.person, p.ovt, p.hour3,
32 case when f.person is not null then 'N' else 'Y' end as cancel
33 from person p left join fails f on f.person = p.person
34 order by p.person, p.hour3;
PERSON OVT HOUR3 CANCEL
---------- ---------- ----- ------
8 pub Y
8 unp Y
10 pub N
10 str N
10 unp N
15 sth Y
15 unp Y
16 unp Y
17 str N
17 10 vac N
10 rows selected.
SQL>

Merge Missing Dates With The Actual Query

I am working on a query where a database may have all dates with corresponding data or may not have. Data in the
table are as follows:
ID DATE
1 6/1/2021
1 6/2/2021
1 6/3/2021
1 6/4/2021
1 6/5/2021
1 6/8/2021
2 6/4/2021
2 6/5/2021
2 6/8/2021
Expected Output:
ID DATE
1 6/1/2021
1 6/2/2021
1 6/3/2021
1 6/4/2021
1 6/5/2021
1 6/6/2021
1 6/7/2021
1 6/8/2021
2 6/1/2021
2 6/2/2021
2 6/3/2021
2 6/4/2021
2 6/5/2021
2 6/6/2021
2 6/7/2021
2 6/8/2021
So I tried the following query with LEFT JOIN that'll return all required date:
WITH all_dates AS (SELECT TO_DATE('01-JUN-2021') + ROWNUM - 1 AS d FROM dual CONNECT BY ROWNUM <= ADD_MONTHS(TO_DATE('01-JUN-2021'), 12 ) - TO_DATE('01-JUN-2021'))
SELECT T.ID, T.DATE FROM all_dates LEFT JOIN TABLE_HERE t on T.DATE = all_dates.d WHERE all_dates.d <= '08-JUN-2021' AND T.ID ('1', '2') AND T.DATE >= '01-JUN-2021' AND T.DATE <= '08-JUN-2021' ORDER BY all_dates.d;
Unfortunately this only returns data with matching dates, not the missing one (Missing one will be merged with the actual). Is there anything that I require to do to make it work?
To me, it looks as the following query; read comments within code:
SQL> with
2 your (id, datum) as
3 -- your sample data
4 (select 1, date '2021-06-01' from dual union all
5 select 1, date '2021-06-02' from dual union all
6 select 1, date '2021-06-08' from dual union all
7 --
8 select 2, date '2021-06-08' from dual union all
9 select 2, date '2021-06-04' from dual union all
10 select 2, date '2021-06-08' from dual
11 ),
12 calendar as
13 -- you already know how to create a calendar; I'm using only 10 days for simplicity
14 (select date '2021-06-01' + level - 1 datum
15 from dual
16 connect by level <= 10
17 ),
18 ids (id) as
19 -- distinct ID values from your sample table (returns two rows; "1" and "2")
20 (select distinct id from your)
21 -- final query: cross join of calendar and distinct ID values
22 select c.datum, i.id
23 from calendar c cross join ids i
24 order by i.id, c.datum;
The result is
DATUM ID
-------- ----------
01.06.21 1
02.06.21 1
03.06.21 1
04.06.21 1
05.06.21 1
06.06.21 1
07.06.21 1
08.06.21 1
09.06.21 1
10.06.21 1
01.06.21 2
02.06.21 2
03.06.21 2
04.06.21 2
05.06.21 2
06.06.21 2
07.06.21 2
08.06.21 2
09.06.21 2
10.06.21 2
20 rows selected.
SQL>

Filtering SQL using a oracle database

I would like to know if the following is possible
For example I have a shoe factory. In this factory I have a production line, Every step in this production line is recorded into the oracle database.
if the shoe has completed a production step the result is = 1
example table
Shoe_nr production step result
1 1 1
1 2 1
1 3
2 1 1
2 2 1
2 3
3 1
3 2
3 3
Now the question, is it possible to filter out production step 3 where only the shoes have passed production step 2 which is equal to 1 in result.
I know if it can be done it's probably very easy but if you dont know i found out it's a little bit tricky.
Thanks,
Chris
Yes, you can do it with IN and a Subselect
select *
from shoes
where shoe.id in (
select shoe.id
from shoes
where production_step = 2
and result = 1
)
and production_step = 3
This might be one option; see comments within code (lines #1 - 12 represent sample data; you already have that and don't type it. Query you might be interested in begins at line #13).
SQL> with shoes (shoe_nr, production_step, result) as
2 -- sample data
3 (select 1, 1, 1 from dual union all
4 select 1, 2, 1 from dual union all
5 select 1, 3, null from dual union all
6 select 2, 1, 1 from dual union all
7 select 2, 2, 1 from dual union all
8 select 2, 3, null from dual union all
9 select 3, 1, null from dual union all
10 select 3, 2, null from dual union all
11 select 3, 3, null from dual
12 ),
13 -- which shoes' production step #3 should be skipped?
14 skip as
15 (select shoe_nr
16 from shoes
17 where production_step = 2
18 and result = 1
19 )
20 -- finally:
21 select a.shoe_nr, a.production_step, a.result
22 from shoes a
23 where (a.shoe_nr, a.production_step) not in (select b.shoe_nr, 3
24 from skip b
25 )
26 order by a.shoe_nr, a.production_step;
SHOE_NR PRODUCTION_STEP RESULT
---------- --------------- ----------
1 1 1
1 2 1
2 1 1
2 2 1
3 1
3 2
3 3
7 rows selected.
SQL>
If you just want the shoe_nr that satisfy the condition, you can use aggregation and a having clause:
select shoe_nr
from mytable
group by shoe_nr
having
max(case when production_step = 2 then result end) = 0
and max(case when production_step = 3 then 1 end) = 1
If you want the entire row corresponding to this shoe_nr at step 3, use window functions instead:
select 1
from (
select
t.*,
max(case when production_step = 2 then result end)
over(partition by shoe_nr) as has_completed_step_2
from mytable t
) t
where production_step = 3 and has_completed_step_2 = 0

Find closest or higher values in SQL

I have a table:
table1
rank value
1 10
25 120
29 130
99 980
I have to generate the following table:
table2
rank value
1 10
2 10
3 10
4 10
5 10
6 10
7 10
8 10
9 10
10 10
11 10
12 10
13 120
14 120
15 120
.
.
.
.
25 120
26 120
27 130
28 130
29 130
30 130
.
.
.
.
.
62 980
63 980
.
.
.
99 980
100 980
So, table2 should have all values from 1 to 100. There are 3 cases:
If it's an exact match, for ex. rank 25, value would be 120
Find closest, for ex. for rank 9 in table2, we do NOT have exact match, but 1 is closest to 9 (9-1 = 8 whereas 25-9 = 16), so assign value of 1
If there is equal distribution from both sides, use higher rank value, for ex. for rank 27, we have 25 as well as 29 which are equally distant, so take higher value which is 29 and assign value.
something like
-- your testdata
with table1(rank,
value) as
(select 1, 10
from dual
union all
select 25, 120
from dual
union all
select 29, 130
from dual
union all
select 99, 980
from dual),
-- range 1..100
data(rank) as
(select level from dual connect by level <= 100)
select d.rank,
min(t.value) keep(dense_rank first order by abs(t.rank - d.rank) asc, t.rank desc)
from table1 t, data d
group by d.rank;
If I understand well your need, you could use the following:
select num, value
from (
select num, value, row_number() over (partition by num order by abs(num-rank) asc, rank desc) as rn
from table1
cross join ( select level as num from dual connect by level <= 100) numbers
)
where rn = 1
This joins your table with the [1,100] interval and then keeps only the first row for each number, ordering by the difference and keeping, in case of equal difference, the greatest value.
Join hierarchical number generator with your table and use lag() with ignore nulls clause:
select h.rank, case when value is null
then lag(value ignore nulls) over (order by h.rank)
else value
end value
from (select level rank from dual connect by level <= 100) h
left join t on h.rank = t.rank
order by h.rank
Test:
with t(rank, value) as (
select 1, 10 from dual union all
select 25, 120 from dual union all
select 29, 130 from dual union all
select 99, 980 from dual )
select h.rank, case when value is null
then lag(value ignore nulls) over (order by h.rank)
else value
end value
from (select level rank from dual connect by level <= 100) h
left join t on h.rank = t.rank
order by h.rank
RANK RANK
---------- ----------
1 10
2 10
...
24 10
25 120
26 120
27 120
28 120
29 130
30 130
...
98 130
99 980
100 980
Here's an alternative that doesn't need a cross join (but does use a couple of analytic functions, so you'd need to test whether this is more performant for your set of data than the other solutions):
WITH sample_data AS (SELECT 1 rnk, 10 VALUE FROM dual UNION ALL
SELECT 25 rnk, 120 VALUE FROM dual UNION ALL
SELECT 29 rnk, 130 VALUE FROM dual UNION ALL
SELECT 99 rnk, 980 VALUE FROM dual)
SELECT rnk + LEVEL - 1 rnk,
CASE WHEN rnk + LEVEL - 1 < rnk + (next_rank - rnk)/2 THEN
VALUE
ELSE next_value
END VALUE
FROM (SELECT rnk,
VALUE,
LEAD(rnk, 1, 100 + 1) OVER (ORDER BY rnk) next_rank,
LEAD(VALUE, 1, VALUE) OVER (ORDER BY rnk) next_value
FROM sample_data)
CONNECT BY PRIOR rnk = rnk
AND PRIOR sys_guid() IS NOT NULL
AND LEVEL <= next_rank - rnk;
RNK VALUE
---------- ----------
1 10
2 10
... ...
12 10
13 120
... ...
24 120
25 120
26 120
27 130
28 130
29 130
30 130
... ...
63 130
64 980
65 980
... ...
98 980
99 980
100 980
N.B, I'm not sure why you have 62 and 63 as having a value of 980 - the mid point between 29 and 99 is 64.
Also, you'll see that I've used 100 + 1 instead of 101 - this is because if you wanted to parameterise things, you would replace 100 with the parameter - e.g. v_max_rank + 1
You can use cross join. Use the below query to get your result:
select t1.* from table1 t1
cross join
(select * from table1) t2
on (t1.rank=t2.rank);

UNPIVOT the results of a delimited string on several rows

I am struggling with some complex hierarchical data. I have successfully used a CONNECT BY query to limit the rows down to the subset that i want - and i have used SYS_CONNECT_BY_PATH to return the full tree up to the nodes of interest.
this gives me essentially some rows like this (delimited by '|'):
id path
-------------------
1, '|10|11|12|13'
2, '|10|14|15'
3, '|16|11|12|13'
4, '|16|17'
now - my challenge is to unwrap or UNPIVOT these values back into a structure like this:
id ord node
-------------
1, 1, 10
1, 2, 11
1, 3, 12
1, 4, 13
2, 1, 10
2, 2, 14
2, 3, 15
3, 1, 16
3, 2, 11
3, 3, 12
3, 4, 13
4, 1, 16
4, 2, 17
I think i am unable to use UNPIVOT directly as that is working on a fixed set of columns - which this is not.
I am playing with a PIPELINE function to unwrap this, but frankly - passing all these rows to the function is an issue since they come from another query. I am wondering if anyone has a way to UNPIVOT the values from a SYS_CONNECT_BY_PATH result set back into rows that is maybe a pure sql solution - probably with REGEX parsing...
help always appreciated - thanks
Yes, UNPIVOT operator wont do much here to help you produce the desired output.
As one of the approaches you could user regexp_count()(11g R1 version and up) regular
expression function to count all occurrences of numbers and then use regexp_substr() regular
expression function to extract the numbers as follows:
-- sample of data
SQL> with t1(id1, path1) as(
2 select 1, '|10|11|12|13' from dual union all
3 select 2, '|10|14|15' from dual union all
4 select 3, '|16|11|12|13' from dual union all
5 select 4, '|16|17' from dual
6 ),
7 occurrences(ocr) as( -- occurrences
8 select level
9 from ( select max(regexp_count(path1, '[^|]+')) as mx_ocr
10 from t1
11 ) t
12 connect by level <= t.mx_ocr
13 )
14 select id1
15 , row_number() over(partition by id1 order by id1) as ord
16 , node
17 from ( select q.id1
18 , regexp_substr(q.path1, '[^|]+', 1, o.ocr) as node
19 from t1 q
20 cross join occurrences o
21 )
22 where node is not null
23 order by id1, 2, node
24 ;
Result:
ID1 ORD NODE
---------- ---------- ------------------------------------------------
1 1 10
1 2 11
1 3 12
1 4 13
2 1 10
2 2 14
2 3 15
3 1 11
3 2 12
3 3 13
3 4 16
4 1 16
4 2 17
13 rows selected
As another approach, starting from 10g version and up, you could use model clause:
SQL> with t1(id1, path1) as(
2 select 1, '|10|11|12|13' from dual union all
3 select 2, '|10|14|15' from dual union all
4 select 3, '|16|11|12|13' from dual union all
5 select 4, '|16|17' from dual
6 )
7 select id1
8 , ord
9 , node
10 from t1
11 model
12 partition by ( rownum as id1)
13 dimension by ( 1 as ord)
14 measures( path1
15 , cast(null as varchar2(11)) as node
16 , nvl(regexp_count(path1, '[^|]+'), 0) as ocr )
17 rules(
18 node[for ord from 1 to ocr[1] increment 1] =
19 regexp_substr(path1[1], '[^|]+', 1, cv(ord))
20 )
21 order by id1, ord, node
22 ;
Result:
ID1 ORD NODE
---------- ---------- -----------
1 1 10
1 2 11
1 3 12
1 4 13
2 1 10
2 2 14
2 3 15
3 1 16
3 2 11
3 3 12
3 4 13
4 1 16
4 2 17
13 rows selected
SQLFiddle Demo