getting other rank value than 1 in oracle in SQL query - sql

There is 1 SQL query when i used like below-
select * from
(select a.id, a.nm, b.pd, b.date, rank() over(partition by a.id order by b.date desc) rnk
from tab1 a, tab2 b where a.id = b.id)
where rnk =1
then getting output like below-
id nm pd date rnk
-- --- --- ---------- ---
11 abc a15 11/04/2022 1
11 abc a15 11/04/2022 1
14 gef a23 11/04/2022 1
14 gef a23 10/04/2022 12
19 lfb a37 11/04/2022 1
19 lfb a37 08/04/2022 21
But I want only one value to be select as order by latest date. Could you please help me on this to resolve it to select one value for 1 id like below-
id nm pd date rnk
-- --- --- ---------- ---
11 abc a15 11/04/2022 1
14 gef a23 11/04/2022 1
19 lfb a37 11/04/2022 1

You need to specify a second column in the order by of the RANK(), so that there are no duplicate pairs of values (e.g. b.id). I've also normalised the JOIN operation.
select * from
(select
a.id,
a.nm,
b.pd,
b.date,
rank() over (
partition by a.id
order by b.[date] desc, b.id asc
) as rnk
from tab1 a
join tab2 b on a.id = b.id
) s
where rnk = 1;

Have you tried like this because sometimes rank() function doesn't work just outside main SQL. Try it, hope it will work.
SELECT id,
nm,
pd, date
FROM
(SELECT *
FROM
(SELECT a.id,
a.nm,
b.pd,
b.date ,
rank() over(PARTITION BY a.id
ORDER BY b.date DESC) rnk
FROM tab1 a,
tab2 b
WHERE a.id = b.id))
WHERE rnk =1

Related

SQL Select a value from the history table based on a date

I have two tables, which one table (table A) contains the users' payment data, and the other (Table B) contains the users' rank history.
Table A
IDNO LName FName Start_Date PayType Current_Rank
------------------------------------------------------------
SJ01 Smith John 11/13/2016 Cert AC
DJ01 Doe Jack 10/20/2020 Assignment BC
Table B
IDNO Date Rank
----------------------
SJ01 10/01/2010 CAP
SJ01 10/01/2016 BC
SJ01 10/01/2020 AC
DJ01 01/01/2010 LT
DJ01 01/01/2015 CAP
DJ01 01/01/2020 BC
I need to show the user's rank according to the start_date from the table A and bring in the rank from the table B. So my end result can look like this:
IDNO LName FName Start_Date PayType Rank
------------------------------------------------------------
SJ01 Smith John 11/13/2016 Cert BC
DJ01 Doe Jack 10/20/2020 Assignment BC
How can I join these two tables and compare the dates, so that I can bring in the rank from the history table based on the start_date from the table A?
Another very simple way is to just select the corresponding Rank directly using a correlated query
select a.*,
(select top(1) [rank] from TableB b
where b.idno=a.idno and a.start_date>b.date
order by b.date desc) as [Rank]
from TableA a
here is one way:
select a.* , c.Rank From TableA a
cross apply (select top 1 * from tableB b
where a.IdNo = b.IDno
and a.Start_Date > b.date
order by b.date desc
) c
;WITH CTE
AS
(
SELECT A.IDNO,A.LName,A.FName,A.Start_Date,A.PayType,B.Rank,RNo=ROW_NUMBER() OVER(PARTITION BY B.IDNO ORDER BY B.DATE DESC)
FROM Table_B B
JOIN Table_A A ON B.IDNO=A.IDNO AND A.Start_Date>=B.Date
) SELECT IDNO,LName,FName,Start_Date,PayType,Rank
FROM CTE WHERE RNo=1
ORDER BY Start_Date
select idno, lname, fname, start_date, paytype, pp.rank current_rank
from
(select xx.idno, xx.rank, min(xx.datediff) from
(select tableA.idno, tableA.start_date - tableB.date datediff, tableB.rank
from tableA, tableB
where tableA.idno = tableB.idno) XX
group by xx.idno, xx.rank)pp,
tableA
where tableA.idno = pp.idno

join two tables with the max date of the filled field

I have a scenario where i should get VAL from table B by joining A and B with the max date of the filled field, e.g:
A:
F1 F2 F3
-- -- --
1 2 t1
2 3 t2
B:
F1 F2 VAL date
---- ---- --- ----------
1 NULL v10 12/30/2020
1 NULL v11 01/31/2020
NULL 2 v20 02/28/2020
NULL 2 v22 03/30/2020
Desired result:
1 2 t1 v11 01/31/2020
2 3 t2 v22 03/30/2020
Thank you in advance.
You can use MAX() and FIRST_VALUE() window functions:
SELECT DISTINCT A.f1, A.f2, A.f3,
FIRST_VALUE(B.VAL) OVER (PARTITION BY COALESCE(B.f1, B.f2) ORDER BY "date" DESC) VAL,
MAX(B."date") OVER (PARTITION BY COALESCE(B.f1, B.f2)) "date"
FROM A INNER JOIN B
ON A.f1 = COALESCE(B.f1, B.f2)
See the demo.
select A.* , maxB.* , B.VAL
from
A
join B
on A.f1 = B.F1
or A.f2 = B.F2
join
(
select
F1, F2 ,VAL , max(date) date
from
B
group by F1,F2
) maxB
on
B.date = maxB.date
and (
maxB.f1 = B.F1
or maxB.f2 = B.F2
)
Try with this
SELECT
R.F1, R.F2, R.F3, R.VAL,R.date
FROM
(
SELECT
A.F1, A.F2, A.F3,
B.VAL,B.date, RANK() OVER(PARTITION BY A.F1,A.F2,A.F3 ORDER BY B.date DESC) as Rank
FROM A
JOIN B ON B.F1 = A.F1 OR B.F2 = A.F1
) R
WHERE
R.Rank = 1
You can use ROW_NUMBER() analytic function within the subquery
WITH AB AS
(
SELECT A.f1, A.f2, B.val, B."date",
ROW_NUMBER() OVER (PARTITION BY NVL(B.f1, B.f2) ORDER BY "date" DESC) AS rn
FROM A
JOIN B
ON A.f1=B.f1 OR A.f1=B.f2
)
SELECT f1,f2,val,"date"
FROM AB
WHERE rn = 1
If ties(equal values) for the date values is the case and all should be included within the result set, then replace ROW_NUMBER() with DENSE_RANK() function.
Demo

How to select rows by max value from another column in Oracle

I have two datasets in Oracle Table1 and Table2.
When I run this:
SELECT A.ID, B.NUM_X
FROM TABLE1 A
LEFT JOIN TABLE2 B ON A.ID=B.ID
WHERE B.BOOK = 1
It returns this.
ID NUM_X
1 10
1 5
1 9
2 2
2 1
3 20
3 11
What I want are the DISTINCT ID where NUM_X is the MAX value, something like this:
ID NUM_x
1 10
2 2
3 20
You can use aggregation:
SELECT A.ID, MAX(B.NUM_X)
FROM TABLE1 A LEFT JOIN
TABLE2 B
ON A.ID = B.ID
WHERE B.BOOK = 1
GROUP BY A.ID;
If you wanted additional columns, I would recommend window functions:
SELECT A.ID, MAX(B.NUM_X)
FROM TABLE1 A LEFT JOIN
(SELECT B.*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY NUM_X DESC) as seqnum
FROM TABLE2 B
) B
ON A.ID = B.ID AND B.seqnum = 1
WHERE B.BOOK = 1
GROUP BY A.ID;

oracle sql running total range

I have two tables tab_a as
SUB_ID AMOUNT
1 10
2 5
3 7
4 15
5 4
2 table tab_b as
slab_number slab_start slab_end
1 12 20
2 21 25
3 26 35
slab_start will always be 1 more than slab_end of previous slab number
If I run the running total for tab_a my result is
select sub_id , sum(amount) OVER(ORDER BY sub_id) run_sum
from tab_a
sub_id run_sum
1 10
2 15
3 22
4 37
5 41
I need to SQL query to check which slab_NUMBER if run_sum is less than first slab_number from then it should be Zero , if run_sum is more than last slab number then blank except the row which crosses the limit .
Expected result is
sub_id run_sum slab_number
1 10 0
2 15 1
3 22 2
4 37 3
5 41 NULL
I have tried this .
First find the running sum which crosses the limit i. e last slab_end
select min( run_sum )
from (select sub_id , sum(amount) OVER(ORDER BY sub_id) run_sum
from tab_a ) where run_sum>=35
then use below query
select sub_id,
run_sum,
case
when run_sum <
(select SLAB_START from tab_b where slab_number = '1') then
0
when run_sum = 37 then
(select max(slab_number) from tab_b)
when run_sum > 37 then
NULL
else
(select slab_number
from tab_b
where run_sum between SLAB_START and slab_end)
end slab_number
from (select sub_id, sum(amount) OVER(ORDER BY sub_id) run_sum from tab_a)
is there any other way to improve.
Somewhat strange requirement :) Use some analytic functions and case when's. Row_number when you need to find something first, max() over() and sum() over() when you need information from over rows:
with
a as (
select sub_id, row_number() over (order by sub_id) rn,
sum(amount) over (order by sub_id) rs
from tab_a),
b as (select tab_b.*, max(slab_number) over () msn from tab_b )
select sub_id, rs,
case when sn is null and row_number() over (partition by sn order by sub_id) = 1
then msn else sn
end sn
from (
select sub_id, rs, max(msn) over () msn,
case when slab_number is null and rn = 1 then 0 else slab_number end sn
from a left join b on rs between slab_start and slab_end)
dbfiddle demo
you could try this:
select a.sub_id , sum(a.amount) OVER(ORDER BY a.sub_id) run_sum
,case when b.slab_number=1 then 0 else lag(b.slab_number,1) over (order by a.sub_id)end slab_number
from tab_a a
left join tab_b b on a.SUB_ID = b.slab_number
I think this is basically a left join with a default value:
select a.*,
(case when a.run_sum < bb.min_slab_num then 0
else b.slab_num
end) as slab_num
from (select sub_id,
sum(amount) over (order by sub_id) as run_sum
from tab_a
) a left join
tab_b b
on a.run_sum between slab_start and slab_end cross join
(select min(slab_start) as min_slab_start
from tab_b
) bb;

Query for Retrieving

I need to build a query which should give me below mentioned output.
My table structure Required output
Depth | Name Level A | Level B | Level C
1 |A A | B | C
2 |B A1 | B1 | C1
3 |C A1 | B1 | C2
1 |A1 A1 | B2 | C3
2 |B1 A1 | B2 | C4
3 |C1
3 |C2
2 |B2
3 |C3
3 |C4
Thanks in advance
Given your particular data, you can come close with:
select a.name as levela, b.name as levelb, c.name as levelc
from (select name, row_number() over (order by id) as seqnum from table where depth = 1
) a full outer join
(select name, row_number() over (order by id) as seqnum from table where depth = 2
) b full outer join
on b.seqnum = a.seqnum
(select name, row_number() over (order by id) as seqnum from table where depth = 3
) c
on c.seqnum = coalesce(a.seqnum, b.seqnum);
This inserts NULLs instead of repeating the final values for the three columns. If you want the final values, this should work:
select coalesce(a.name, maxes.a) as levela,
coalesce(b.name, maxes.b) as levelb,
coalesce(c.name, maxes.c) as levelc
from (select name, row_number() over (order by id) as seqnum from table where depth = 1
) a full outer join
(select name, row_number() over (order by id) as seqnum from table where depth = 2
) b full outer join
on b.seqnum = a.seqnum
(select name, row_number() over (order by id) as seqnum from table where depth = 3
) c
on c.seqnum = coalesce(a.seqnum, b.seqnum) cross join
(select max(case when depth = 1 and id = maxid then name end) as max_a,
max(case when depth = 2 and id = maxid then name end) as max_b,
max(case when depth = 3 and id = maxid then name end) as max_c
from (select t.*,
max(id) over (partition by depth) as maxid
from t
) t
) maxes
Since the relationship between levelA,levelB,levelC is not clear.I have assumed you want to return the max(name) in case the corresponding value is not available.The Sql Fiddle here.You can replace
order by my_table.name into order by unique_seq_column and also max(name) name by the name value in max(unique_seq_column) if it suits your requirement
--Get the max count and max name for each level
with cnt_max1 as (select max(name) name ,count(1) cnt from my_table where depth=1)
,cnt_max2 as (select max(name) name ,count(1) cnt from my_table where depth=2)
,cnt_max3 as (select max(name) name ,count(1) cnt from my_table where depth=3)
--find out the total rows required
,greatest_cnt as (select greatest(cnt_max1.cnt,cnt_max2.cnt,cnt_max3.cnt) cnt from cnt_max1,cnt_max2,cnt_max3)
--Establish relationship between levelA,levelB,levelC using sublevel column
,level_A as (select * from (select rownum sublevel, my_table.name as levela from my_table where depth=1 order by my_table.name)
union
select level+cnt_max1.cnt sublevel,cnt_max1.name levela
from cnt_max1,greatest_cnt connect by level <=(greatest_cnt.cnt - cnt_max1.cnt))
,level_B as (select * from (select rownum sublevel, my_table.name as levelb from my_table where depth=2 order by my_table.name)
union
select level+cnt_max2.cnt sublevel,cnt_max2.name levelb
from cnt_max2,greatest_cnt connect by level <=(greatest_cnt.cnt - cnt_max2.cnt))
,level_C as (select * from (select rownum sublevel, my_table.name as levelc from my_table where depth=3 order by my_table.name)
union
select level+cnt_max3.cnt sublevel,cnt_max3.name levelc
from cnt_max3,greatest_cnt connect by level <=(greatest_cnt.cnt - cnt_max3.cnt))
--Display the data
select levela,levelb,levelc
from level_A join level_b
on level_A.sublevel=level_B.sublevel
join level_c on level_C.sublevel=level_b.sublevel