How to get the full row of data based on max value? - sql

I have an Oracle table with a usage counter. I need to get the full row of data for each sensor with the max counter value?
For ALO I need data in row 2.
For AMA I need data in row 10.
For A11 I need data in row 9658.
For MSP I need data in row 9659.
Any help would be greatly appreciated!
Thanks,
Dave

You can use window function.
selcet * from (
select a.*, row_number() over (partition by facility_id_n
order by usage_counter_n desc) rn
from your_tbl a)
where rn =1;
Or use the First window-funcrion.

ROW_NUMBER won't return correct result if there are two rows with the same USAGE_COUNTER_N value per FACILITY_ID_N. For example:
SQL> WITH test
2 AS (SELECT 9640 ID_n, 'ALO' sensor_id_c, 317 usage_counter_n FROM DUAL
3 UNION
4 SELECT 9641, 'ALO', 18 FROM DUAL
5 UNION
6 SELECT 9642, 'ALO', 0 FROM DUAL
7 UNION
8 SELECT 9659, 'MSP', 25 FROM DUAL --> MAX for MSP ...
9 UNION
10 SELECT 9660, 'MSP', 10 FROM DUAL
11 UNION
12 SELECT 1000, 'MSP', 25 FROM DUAL --> ... but this is also MAX for MSP
13 )
14 SELECT *
15 FROM (SELECT a.*,
16 ROW_NUMBER ()
17 OVER (PARTITION BY sensor_id_c
18 ORDER BY usage_counter_n DESC) rn
19 FROM test a)
20 WHERE rn = 1;
ID_N SEN USAGE_COUNTER_N RN
---------- --- --------------- ----------
9640 ALO 317 1
1000 MSP 25 1
SQL>
RANK might be better:
SQL> l16
16* ROW_NUMBER ()
SQL> c/row_number/rank
16* rank ()
SQL> /
ID_N SEN USAGE_COUNTER_N RN
---------- --- --------------- ----------
9640 ALO 317 1
1000 MSP 25 1
9659 MSP 25 1
SQL>
Or, using the oldfashioned way:
14 SELECT *
15 FROM test t
16 WHERE t.usage_counter_n = (SELECT MAX (t1.usage_counter_n)
17 FROM test t1
18 WHERE t1.sensor_id_c = t.sensor_id_c)
19 ORDER BY sensor_id_c;
ID_N SEN USAGE_COUNTER_N
---------- --- ---------------
9640 ALO 317
9659 MSP 25
1000 MSP 25
SQL>

Depending on your requirements to handle duplicates, the KEEP clause might also be a useful option
SQL> with test
2 AS (SELECT 9640 ID_n, 'ALO' sensor_id_c, 317 usage_counter_n FROM DUAL
3 UNION
4 SELECT 9641, 'ALO', 18 FROM DUAL
5 UNION
6 SELECT 9642, 'ALO', 0 FROM DUAL
7 UNION
8 SELECT 9659, 'MSP', 25 FROM DUAL --> MAX for MSP ...
9 UNION
10 SELECT 9660, 'MSP', 10 FROM DUAL
11 UNION
12 SELECT 1000, 'MSP', 25 FROM DUAL --> ... but this is also MAX for MSP
13 )
14 SELECT sensor_id_c,
15 min(id_n) keep ( dense_rank first order by usage_counter_n DESC ) id_n,
16 min(usage_counter_n) keep ( dense_rank first order by usage_counter_n DESC ) usage_counter_n
17 from test
18 group by sensor_id_c;
SEN ID_N USAGE_COUNTER_N
--- ---------- ---------------
ALO 9640 317
MSP 1000 25

Related

Select top 10 records of a certain key sql

I have 2 tables connected with each other, for simplicity lets say ID of the 1st is connected to UserCreated_FK of the 2nd table.
Table A:
ID
NAME
237
Gal
240
blah
250
blah2
in the parallel table ( aka table B ) I have the following:
UserCreated_FK
col2
237
10/10
20 more rows of 237
20 more rows of 237
240
11/10
5 more rows of 240
20 more rows of 240
250
12/10
14 more rows of 250
20 more rows of 250
Result wanted:
id.237 x10 last(might be sorted by date col I have).
no 240 (cause less than 10).
id.250 x10 last records(might be sorted by date col I have).
My task is to check which of those IDs(person'sIds) have more then 10 records on table B ( because table A is just the ids and names not the actual records of them.
So for example, ID no.237 got 19 records on table B I want to be able to pull the last 10 records of that user.
And again, the condition is only users that have more than 10 records, then to pull the last of 10 of those..
Hope I was understandable, thanks a lot, any help will be appreciated!
You can join the two tables and then use analytic functions:
SELECT *
FROM (
SELECT a.id,
a.name,
b.col2,
b.datetime,
COUNT(*) OVER (PARTITION BY a.id) AS b_count,
ROW_NUMBER() OVER (PARTITION BY a.id ORDER BY b.datetime DESC) AS rn
FROM table_a a
INNER JOIN table_b b
ON (a.id = b.user_created_fk)
)
WHERE b_count >= 10
AND rn <= 10;
If you just want the values from table_b then you do not need to use table_a:
SELECT *
FROM (
SELECT b.*,
COUNT(*) OVER (PARTITION BY b.user_created_fk) AS b_count,
ROW_NUMBER() OVER (
PARTITION BY b.user_created_fk ORDER BY b.datetime DESC
) AS rn
FROM table_b b
)
WHERE b_count >= 10
AND rn <= 10;
The WITH clause is here just to generate some sample data and it is not a part of the answer.
WITH
tbl_a AS
(
Select 237 "ID", 'Gal' "A_NAME" From Dual Union All
Select 240 "ID", 'blah' "A_NAME" From Dual Union All
Select 250 "ID", 'blah2' "A_NAME" From Dual
),
tbl_b AS
(
Select 237 "FK_ID", SYSDATE - (21 - LEVEL) "A_DATE" From Dual Connect By LEVEL <= 20 Union All
Select 240 "FK_ID", SYSDATE - (6 - LEVEL) "A_DATE" From Dual Connect By LEVEL <= 5 Union All
Select 250 "FK_ID", SYSDATE - (15 - LEVEL) "A_DATE" From Dual Connect By LEVEL <= 14
)
Select
a.ID,
a.A_NAME,
b.TOTAL_COUNT "TOTAL_COUNT",
b.RN "RN",
b.A_DATE
From
tbl_a a
Inner Join
( Select FK_ID, A_DATE, ROW_NUMBER() OVER(Partition By FK_ID Order By FK_ID, A_DATE DESC) "RN", Count(*) OVER(Partition By FK_ID) "TOTAL_COUNT" From tbl_b ) b ON(b.FK_ID = a.ID)
WHERE
b.RN <= 10 And b.TOTAL_COUNT > 10
With your sample data the main SQL joins tbl_a with a subquery by FK_ID = ID. The subquery uses analytic functions to get total rows per FK_ID (in tbl_b) and gives row numbers (partitioned by FK_ID) to the dataset. All the rest is in the Where clause.
Result:
/*
ID A_NAME TOTAL_COUNT RN A_DATE
---------- ------ ----------- ---------- ---------
237 Gal 20 1 29-OCT-22
237 Gal 20 2 28-OCT-22
237 Gal 20 3 27-OCT-22
237 Gal 20 4 26-OCT-22
237 Gal 20 5 25-OCT-22
237 Gal 20 6 24-OCT-22
237 Gal 20 7 23-OCT-22
237 Gal 20 8 22-OCT-22
237 Gal 20 9 21-OCT-22
237 Gal 20 10 20-OCT-22
250 blah2 14 1 29-OCT-22
250 blah2 14 2 28-OCT-22
250 blah2 14 3 27-OCT-22
250 blah2 14 4 26-OCT-22
250 blah2 14 5 25-OCT-22
250 blah2 14 6 24-OCT-22
250 blah2 14 7 23-OCT-22
250 blah2 14 8 22-OCT-22
250 blah2 14 9 21-OCT-22
250 blah2 14 10 20-OCT-22
*/
Regards...

Aggregate vertices-as-rows to string

I have multi-part polyline vertices stored as individual rows in an Oracle 18c table.
ASSET_ID PART_NUM VERTEX_NUM X Y M
---------- ---------- ---------- ---------- ---------- ----------
001 1 1 0 5 0
001 1 2 10 10 11.18
001 1 3 30 0 33.54
001 2 1 50 10 33.54
001 2 2 60 10 43.54
Sample data:
db<>fiddle
I want to aggregate the rows into a single text value (without the single quotes):
'MULTILINESTRING ((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))'
How can I do that using Oracle SQL?
Use Listagg function
with cte as (
select 001 c1,1 c2,1 c3,0 c4,5 c5,0 c6 from dual union all
select 001,1,2,10,10,11.18 from dual union all
select 001,1,3,30,0,33.54 from dual union all
select 001,2,1,50,10,33.54 from dual union all
select 001,2,2,60,10,43.54 from dual
), cte1 as
(
select c2,listagg(c4||' '||c5||' '||c6,',')
s1 from cte group by c2 order by c2 asc)
select 'MULTILINESTRING ('||listagg('('||s1||')',',')
within group (order by c2) ||')' as result from cte1;
fiddle

Transpose in ORACLE SQL (Convert 6 columns into 3 Columns)

I've an output like the below.
I want that to be converted into the below
Where ID is the count of TOTAL, HIGH, SENT, WAITING, DATE. I have been contemplating for a while and couldn't get what I want. Can any one please help in ORACLE SQL?
Thanks in advance.
Here's one option:
SQL> with test (cdate, total, high, sent, waiting, loc) as
2 (select 1012018, 23, 4, 35, 45, 13456 from dual union all
3 select 1212018, 74, 2, 77, 82, 98765 from dual
4 ),
5 temp as
6 (select 5 rn, loc, cdate as value from test union all
7 select 1 rn, loc, total from test union all
8 select 2 rn, loc, high from test union all
9 select 3 rn, loc, sent from test union all
10 select 4 rn, loc, waiting from test
11 )
12 select rn, value, loc
13 from temp
14 order by loc, rn;
RN VALUE LOC
---------- ---------- ----------
1 23 13456
2 4 13456
3 35 13456
4 45 13456
5 1012018 13456
1 74 98765
2 2 98765
3 77 98765
4 82 98765
5 1212018 98765
10 rows selected.
SQL>

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

oracle sql - numbering group of rows

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