Oracle - Rank by two fields - oracle

I don't write too many queries. So, it might be a simple question for you. I need to rank a set based on a date in desc order and by a sequence number in asc order. For example:
ID
Date
Seq
Rank
1
Aug 1
0
1
1
Aug 1
1
2
1
Jul 1
0
3
1
Jun 1
0
4
1
May 1
0
5
How would I achieve that? The Rank would only allow you to order by multiple fields but by just either asc or desc but not both, right?
Thank you in advance

You can try the below query -
SELECT ID, "Date", Seq, ROW_NUMBER() OVER(ORDER BY TO_DATE("Date", 'Mon dd') DESC, Seq) Rank
FROM YOUR_TABLE;

Related

How to correlate data and counts from columns to rows in Oracle (19c)?

I believe there is probably an easy way to solve these problems with pivots or partitions but I can't seem to find the proper solutions. I have a round about solution for problem 1 by using a long list of select sum()s and a long solution for problem 2 where I just select the count(*) from table B where id = id from table A multiple times in a (select) blocks but if I have a large number of IDs both of those solution equal very long SQL that gets very tedious and I'm sure there is a better way it is just eluding me.
I would really like solutions that would allow me to include a large set of multiple IDs or supply the solution with a table of IDs to evaluate.
Problem 1:
Table:
------------------
ID DESC YEAR
1 A 2021
1 B 2021
1 C 2021
2 A 2021
2 B 2021
2 C 2021
3 A 2019
3 B 2019
I would like to have the count of the ID's for each DESC by year.
Expected Result:
------------------
Year CountA CountB CountC
2019 1 1 0
2021 2 2 2
Problem 2:
Table A:
------------------
ID DESC
1 A
2 B
3 C
Table B:
------------------
SET ID
10 1
10 1
12 1
13 2
14 3
I would like to see (1) how many of each ID from Table A can be found in each SET in Table B and (2) how many of each ID from Table A can be found in each SET in Table B and not in any other SET of Table B (unique matches).
Expected Result 1:
------------------
ID Count10 Count12 Count13 Count14
1 2 1 0 0
2 0 0 1 0
3 0 0 0 1
Expected Result 2:
------------------
ID UniqueCount10 UniqueCount12 UniqueCount13 UniqueCount14
1 0 0 0 0
2 0 0 1 0
3 0 0 0 1
Thank you for any and all assistance.
All three problems can be solved with pivoting (calling Problem 2 "two different problems"), although it is not clear what purpose Result 2 would serve (in the second problem; see my comments to you).
Note that desc and set are reserved keywords, and year is a keyword, so they shouldn't be used as column names. I changed to descr, set_ (with an underscore) and yr. Also, I do not use double-quotes for column names in the output; all-caps column names are just fine.
In the second problem it is not clear why you need Table A. Could you have some id values that don't appear at all in Table B, but you still want them in the final output? If so, you will need to change my semi-joins to outer joins; left as an exercise, since it's a different (and much more basic) type of question.
In the first problem, you must pivot the result of a subquery, which selects only the relevant columns from the base table. There is no such need for the second problem (unless your tables have other columns that should not be considered - left for you to figure out).
Problem 1
Data:
create table tbl (id, descr, yr) as
select 1, 'A', 2021 from dual union all
select 1, 'B', 2021 from dual union all
select 1, 'C', 2021 from dual union all
select 2, 'A', 2021 from dual union all
select 2, 'B', 2021 from dual union all
select 2, 'C', 2021 from dual union all
select 3, 'A', 2019 from dual union all
select 3, 'B', 2019 from dual
;
Query and output:
select *
from (select descr, yr from tbl)
pivot (count(*) for descr in ('A' as count_a, 'B' as count_b, 'C' as count_c))
order by yr
;
YR COUNT_A COUNT_B COUNT_C
---- ------- ------- -------
2019 1 1 0
2021 2 2 2
Problem 2
Data:
create table table_a (id, descr) as
select 1, 'A' from dual union all
select 2, 'B' from dual union all
select 3, 'C' from dual
;
create table table_b (set_, id) as
select 10, 1 from dual union all
select 10, 1 from dual union all
select 12, 1 from dual union all
select 13, 2 from dual union all
select 14, 3 from dual
;
Part 1 - Query and result:
select *
from table_b
pivot (count(*) for set_ in (10 as count_10, 12 as count_12,
13 as count_13, 14 as count_14))
where id in (select id from table_a) -- is this needed?
order by id -- if needed
;
ID COUNT_10 COUNT_12 COUNT_13 COUNT_14
-- -------- -------- -------- --------
1 2 1 0 0
2 0 0 1 0
3 0 0 0 1
Part 2 - Query and result:
select *
from (
select id, case count(distinct set_) when 1 then max(set_) end as set_
from table_b
where id in (select id from table_a) -- is this needed?
group by id
)
pivot (count(*) for set_ in (10 as unique_ct_10, 12 as unique_ct_12,
13 as unique_ct_13, 14 as unique_ct_14))
order by id -- if needed
;
ID UNIQUE_CT_10 UNIQUE_CT_12 UNIQUE_CT_13 UNIQUE_CT_14
-- ------------ ------------ ------------ ------------
1 0 0 0 0
2 0 0 1 0
3 0 0 0 1
In this last part of the second problem, you might as well just take the subquery and run it separately - what's the purpose of pivoting its output?

rownum in Oracle sql with group by

I need to build a query to retrieve information group by Members and an expiration Date but I need to have a sequence number for every Member..
So for example:
If Member "A" has 3 records to expire, "B" has only 1 and "C" has 2, I need a result like this:
Number Member ExpDate
1 A 01/01/2020
2 A 02/01/2020
3 A 03/01/2020
1 B 01/01/2020
1 C 01/01/2020
2 C 02/01/2020
My query now is:
SELECT ROW_NUMBER() OVER(ORDER BY TRUNC(EXPIRATION_DT) ASC) AS SEQUENCE, MEMBER_ID AS MEMBER, SUM(ACCRUALED_VALUE) - SUM(USED_VALUE) AS POINTS, trunc(EXPIRATION_DT) AS EXPDATE
FROM TABLE1
WHERE EXPIRATION_DT > SYSDATE AND EXPIRATION_DT < SYSDATE + 90
GROUP BY MEMBER_ID, TRUNC(EXPIRATION_DT)
HAVING SUM(ACCRUALED_VALUE) - SUM(USED_VALUE) > 0
ORDER BY 4 ASC;
But I cant' "group" the sequence number.... The result now is:
Seq Mem Points Date
1 1-O 188 2018-03-01 00:00:00
2 1-C 472 2018-03-01 00:00:00
3 1-A 485 2018-03-01 00:00:00
4 1-1 267 2018-03-01 00:00:00
5 1-E 500 2018-03-01 00:00:00
6 1-P 55 2018-03-01 00:00:00
7 1-E 14 2018-03-01 00:00:00
I think you need a DENSE_RANK window function. try this -
SELECT DENSE_RANK() OVER (PARTITION BY MEMBER ORDER BY TRUNC(EXPIRATION_DT) ASC) AS SEQUENCE
,MEMBER_ID AS MEMBER
,SUM(ACCRUALED_VALUE) - SUM(USED_VALUE) AS POINTS
,trunc(EXPIRATION_DT) AS EXPDATE
FROM TABLE1
WHERE EXPIRATION_DT > SYSDATE AND EXPIRATION_DT < SYSDATE + 90
GROUP BY MEMBER_ID
,TRUNC(EXPIRATION_DT)
HAVING SUM(ACCRUALED_VALUE) - SUM(USED_VALUE) > 0
ORDER BY 4 ASC;
with g as (
select *
From TABLE1 g
group by MEMBER_ID
,TRUNC(EXPIRATION_DT)
HAVING SUM(ACCRUALED_VALUE) - SUM(USED_VALUE) > 0 ---- etc
)
select rownum, g.* From g
this select return first column with sequence number

Distinct Dates Group BY Year

How to find the distinct dates in a year using Oracle?
id sent_date
1 2017-05-01
1 2017-05-01
1 2017-06-01
1 2016-06-01
Ignore the duplicate sent_date for a Id in the same year.
Output
count(*) id year
2 1 2017
1 1 2016
EDIT:
Thisis my query
select distinct(count(sent_date)), id , extract (year from sent_date)
from test
GROUP BY id, extract (year from sent_date).
3 1 2017 (wrong)-- expecting the count as 2 1 1 2016 –
The DISTINCT is wrongly positioned in your query; you simply need:
select count(distinct sent_date), id , extract (year from sent_date)
from test
group by id, extract (year from sent_date)
Also, DISTINCT is not a function, so the syntax DISTINCT(...) does not make sense.

Calculating values in date ranges in Oracle (possibly with recursive CTE)

I have a problem which can be handled by a recursive CTE, but not within an acceptable period of time. Can anyone point me at ways to improve the performance and/or get the same result a different way?
Here's my scenario!
I have : A large table which contains in each row an id, a start date, an end date, and a ranking number. There are multiple rows for each id and the date ranges often overlap. Dates are from 2010 onward.
I want: A table which contains a row for each combination of id + date which falls inside any date range for that id from the previous table. Each row should have the lowest ranking number for that id and day.
Eg:
ID Rank Range
1 1 1/1/2010-1/4/2010
1 2 1/2/2010-1/5/2010
2 1 1/1/2010-1/2/2010
becomes
ID Rank Day
1 1 1/1/2010
1 1 1/2/2010
1 1 1/3/2010
1 1 1/4/2010
1 2 1/5/2010
2 1 1/1/2010
2 1 1/2/2010
I can do this with a recursive CTE, but the performance is terrible (20-25 minutes for a relatively small data set which produces a final table with 31 million rows):
with enc(PersonID, EncounterDate, EndDate, Type_Rank) as (
select PersonID, EncounterDate, EndDate, Type_Rank
from Big_Base_Table
union all
select PersonID, EncounterDate + 1, EndDate, Type_Rank
from enc
where EncounterDate + 1 <= EndDate
)
select PersonID, EncounterDate, min(Type_Rank) Type_Rank
from enc
group by PersonID, EncounterDate
;
You could extract all possible dates from the table once in a CTE, and then join that back to the table:
with all_dates (day) as (
select start_date + level - 1
from (
select min(start_date) as start_date, max(end_date) as end_date
from big_base_table
)
connect by level <= end_date - start_date + 1
)
select bbt.id, min(bbt.type_rank) as type_rank, to_char(ad.day, 'YYYY-MM-DD') as day
from all_dates ad
join big_base_table bbt
on bbt.start_date <= ad.day
and bbt.end_date >= ad.day
group by bbt.id, ad.day
order by bbt.id, ad.day;
ID TYPE_RANK DAY
---------- ---------- ----------
1 1 2010-01-01
1 1 2010-01-02
1 1 2010-01-03
1 1 2010-01-04
1 2 2010-01-05
2 1 2010-01-01
2 1 2010-01-02
7 rows selected.
The CTE gets all dates from the lowest for any ID, up to the highest for any ID. You could also use a static calendar table for that if you have one, to save hitting the table twice (and getting min/max at the same time is slow in some versions at least).
You could also write it the other way round, as:
...
from big_base_table bbt
join all_dates ad
on ad.day >= bbt.start_date
and ad.day <= bbt.end_date
...
but I think the optimisier will probably end up treating them the same, with a single full scan of your base table; worth checking the plan it actually comes up with for both though, and if one is more efficnet that the other.

Make cumulative sum in view oracle

I want to make a view in oracle, how can I get value of 'saldo' that I supposed like in this screenshoot with oracle? can someone give me the script?
The result that I want:
dateacct period_id startdate enddate debit credit saldo
7/10/2015 1 1/10/2015 30/10/2015 25 0 25
8/10/2015 1 1/10/2015 30/10/2015 0 5 20
9/10/2015 1 1/10/2015 30/10/2015 0 3 17
10/10/2015 1 1/10/2015 30/10/2015 4 0 21
Notes: dateacct,period_id,startdate,enddate,credit,debit are the column that I take from 'akun' table.
Try this:
SELECT DATEACCT, PERIOD_ID, STARTDATE, ENDDATE, CREDIT, DEBIT,
SUM (DEBIT-CREDIT) OVER (ORDER BY DATEACCT,PERIOD_ID,STARTDATE,ENDDATE) saldo
from AKUN
ORDER BY DATEACCT, PERIOD_ID, STARTDATE, ENDDATE;
There's a bunch of things you can do with Oracle Analytics functions, especially made for that purpose.
Oracle Analytic functions
You can also use the PARTITION BY to have cumul for the day or the period:
SELECT DATEACCT, PERIOD_ID, STARTDATE, ENDDATE, CREDIT, DEBIT,
SUM (DEBIT-CREDIT) OVER (PARTITION BY DATEACCT ORDER BY DATEACCT,PERIOD_ID,STARTDATE,ENDDATE) CUMULDAY,
SUM (DEBIT-CREDIT) OVER (PARTITION BY PERIOD_ID ORDER BY DATEACCT,PERIOD_ID,STARTDATE,ENDDATE) CUMULPERIOD
from AKUN
ORDER BY DATEACCT, PERIOD_ID, STARTDATE, ENDDATE;
DATEACCT PERIOD_ID STARTDATE ENDDATE CREDIT DEBIT CUMULDAY CUMULPERIOD
-------- ---------- --------- -------- ---------- ---------- ---------- -----------
07/10/15 1 01/10/15 30/10/15 0 25 25 25
08/10/15 1 01/10/15 30/10/15 5 0 -5 20
09/10/15 1 01/10/15 30/10/15 3 0 -3 17
10/10/15 1 01/10/15 30/10/15 0 4 4 21

Resources