Rank () function over multiple columns in oracle - oracle

I need to rank a table with two columns transID & travel_date
here is my data
transID travel_date
2341 2018-04-04 10:00:00
2341 2018-04-04 11:30:00
2891 2018-04-04 12:30:00
2891 2018-04-04 18:30:00
2341 2018-04-05 11:30:00
2891 2018-04-05 22:30:00
this is the query which i have tried
select transID,travel_date,rn,
dense_rank () over (partition by transID order by EarliestDate,transID) as rn2
from
(SELECT transID,travel_date,
ROW_NUMBER() OVER (PARTITION BY transID ORDER BY travel_date) AS rn,
max(travel_date) OVER (partition by travel_date) as EarliestDate
FROM travel_log_info
) t
order by transID;
Current Output from the above query
transID travel_date rn2
2341 2018-04-04 10:00:00 1
2341 2018-04-04 11:30:00 2
2341 2018-04-05 11:30:00 3
2891 2018-04-04 12:30:00 1
2891 2018-04-04 18:30:00 2
2891 2018-04-05 22:30:00 3
Expected Output
transID travel_date rn2
2341 2018-04-04 10:00:00 1
2341 2018-04-04 11:30:00 2
2341 2018-04-05 11:30:00 1
2891 2018-04-04 12:30:00 1
2891 2018-04-04 18:30:00 2
2891 2018-04-05 22:30:00 1
with this output, I can get the desired output by where condition rn2 = 1 to get the output based on travel date and transId.
I am not getting the desired output as shown above. Kindly provide suggestions to achieve the correct output.
Thanks for your time

The main problem with what you have now is:
max(travel_date) OVER (partition by travel_date)
which includes the time part of each date in the partition - so you're really getting the max of every individual date/time, which is that date/time. You seem to want maximum date/time within each day, so you could partition by each day by using trunc() in the partition-by clause:
max(travel_date) OVER (partition by trunc(travel_date))
Just that change gives you:
TRANSID TRAVEL_DATE RN RN2
---------- ------------------- ---------- ----------
2341 2018-04-04 10:00:00 1 1
2341 2018-04-04 11:30:00 2 1
2341 2018-04-05 11:30:00 3 2
2891 2018-04-04 12:30:00 1 1
2891 2018-04-04 18:30:00 2 1
2891 2018-04-05 22:30:00 3 2
The partitioning in the outer query is also wrong though, you need to partition by that 'earliest' date (actually latest, but doesn't matter for this):
select transID,travel_date,rn,
dense_rank () over (partition by transID,EarliestDate order by travel_date) as rn2
from
(SELECT transID,travel_date,
ROW_NUMBER() OVER (PARTITION BY transID ORDER BY travel_date) AS rn,
max(travel_date) OVER (partition by trunc(travel_date)) as EarliestDate
FROM travel_log_info
) t
order by transID;
TRANSID TRAVEL_DATE RN RN2
---------- ------------------- ---------- ----------
2341 2018-04-04 10:00:00 1 1
2341 2018-04-04 11:30:00 2 2
2341 2018-04-05 11:30:00 3 1
2891 2018-04-04 12:30:00 1 1
2891 2018-04-04 18:30:00 2 2
2891 2018-04-05 22:30:00 3 1
But you don't really need that max, or the outer query you currently have; if you include that truncated day in the row_number() partition (which you currently aren't really using) you get:
SELECT transID,travel_date,
ROW_NUMBER() OVER (PARTITION BY transID, trunc(travel_date) ORDER BY travel_date) AS rn
FROM travel_log_info;
TRANSID TRAVEL_DATE RN
---------- ------------------- ----------
2341 2018-04-04 10:00:00 1
2341 2018-04-04 11:30:00 2
2341 2018-04-05 11:30:00 1
2891 2018-04-04 12:30:00 1
2891 2018-04-04 18:30:00 2
2891 2018-04-05 22:30:00 1
and you can then wrap that in an outer query to filter on rn:
SELECT transID,travel_date
FROM (
SELECT transID,travel_date,
ROW_NUMBER() OVER (PARTITION BY transID, trunc(travel_date) ORDER BY travel_date) AS rn
FROM travel_log_info
)
WHERE rn = 1
ORDER BY transID,travel_date;
TRANSID TRAVEL_DATE
---------- -------------------
2341 2018-04-04 10:00:00
2341 2018-04-05 11:30:00
2891 2018-04-04 12:30:00
2891 2018-04-05 22:30:00
You could also do this without a subquery; this gets the same result using first:
SELECT transID,
min(travel_date) keep (dense_rank first order by travel_date) as travel_date
FROM travel_log_info
GROUP BY transID, trunc(travel_date)
ORDER BY transID, travel_date;

Related

Merge Columns into One Column Oracle PL/SQL

I have the following script and tables where upon running the script produces the output for LOG_ID, YEAR, WA.SUB_DIVISION, AI.SUB_DIVISION, EA.SUB_DIVISION, FI.SUB_DIVISION
Is it possible to Merge four columns into one column
WA.SUB_DIVISION, AI.SUB_DIVISION, EA.SUB_DIVISION, FI.SUB_DIVISION into SUB_DIVISION a single column
Not sure how to proceed.
I have created a sample sql fiddle
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=3c4abb924462dcf5e5f8b0f91019b6b6
select distinct L.LOG_ID,
FC.LOG_YR as YEAR,
WA.SUB_DIVISION,
AI.SUB_DIVISION AS SUB_DIV,
EA.SUB_DIVISION AS SUB_DIV3,
FI.SUB_DIVISION AS SUB_DIV4
FROM FINAL_CALENDAR FC
JOIN LOG L
ON TO_DATE ( TO_CHAR (L.LOG_DATE, 'MM/DD/YYYY'), 'MM/DD/YYYY') = FC.CAL_DATE
LEFT OUTER JOIN LOG_WATER WA
ON WA.LOG_ID = L.LOG_ID
LEFT OUTER JOIN LOG_AIR AI
ON AI.LOG_ID = L.LOG_ID
LEFT OUTER JOIN LOG_EARTH EA
ON EA.LOG_ID = L.LOG_ID
LEFT OUTER JOIN LOG_FIRE FI
ON FI.LOG_ID = L.LOG_ID
Actual Output / ISSUE / Existing Output
LOG_ID YEAR SUB_DIVISION SUB_DIV SUB_DIV3 SUB_DIV4
990741 2020 NULL NULL NULL NULL
990742 2020 NULL NULL NULL NULL
991122 2020 NULL NULL NULL NULL
991123 2020 NULL NULL NULL NULL
994461 2020 NULL 4 NULL NULL
994468 2020 NULL 2 NULL NULL
994466 2020 NULL 2 NULL NULL
994480 2020 8 NULL NULL NULL
994479 2020 8 NULL NULL NULL
994476 2020 6 NULL NULL NULL
994478 2020 6 NULL NULL NULL
994440 2020 NULL NULL NULL NULL
994432 2020 NULL NULL NULL NULL
994450 2020 NULL NULL NULL NULL
994154 2020 NULL NULL NULL NULL
Required / Desired Output
LOG_ID YEAR SUB_DIVISION DISPLAY_NAME
990741 2020 NULL NULL
990742 2020 NULL NULL
991122 2020 NULL NULL
991123 2020 NULL NULL
994461 2020 4 Triangle
994468 2020 2 Circle
994466 2020 2 Circle
994480 2020 8 Rhombus
994479 2020 8 Rhombus
994476 2020 6 Dot
994478 2020 6 Dot
994440 2020 NULL NULL
994432 2020 NULL NULL
994450 2020 NULL NULL
994154 2020 NULL NULL
Table LOG;
LOG_ID, LOG_DATE,
990741, to_date('21-JAN-20','DD-MON-RR')
990742 21-JAN-20
991122 24-JAN-20
991123 25-JAN-20
994461 25-JAN-20
994468 25-JAN-20
994466 25-JAN-20
994480 25-JAN-20
994479 25-JAN-20
994476 25-JAN-20
994478 25-JAN-20
994440 25-JAN-20
994432 25-JAN-20
994450 25-JAN-20
994154 25-JAN-20
TABLE FINAL_CALENDAR;
CAL_DATE CAL_MONTH LOG_YR
21-JAN-20 1 2020
21-JAN-20 1 2020
24-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
TABLE LOG_AIR;
ID LOG_ID SUB_DIVISION
134 994468 2
132 994461 4
133 994466 2
TABLE LOG_WATER;
ID LOG_ID SUB_DIVISION
9345 994480 8
9344 994479 8
9342 994476 6
9343 994478 6
TABLE LOG_EARTH;
ID LOG_ID SUB_DIVISION
0118 994440 null
0117 994432 null
TABLE LOG_FIRE;
ID LOG_ID SUB_DIVISION
706 994450 null
705 994154 null
TABLE Z_SUB_DIVISION_TYPE;
SUB_DIVISION DISPLAY_NAME
1 Parallelogram
2 Circle
3 Square
4 Triangle
5 Tangent
6 Dot
7 Line
8 Rhombus
9 Trapezium
If that's the final result you can merge last four columns assuming one column has value and rest are null(sub division columns)
SELECT log_id,
year,
CASE
WHEN sub_division IS NULL AND SUB_DIV3 IS NULL AND SUB_DIV4 IS NULL THEN sub_div
WHEN sub_division IS NULL AND SUB_DIV IS NULL AND SUB_DIV4 IS NULL THEN sub_div3
WHEN sub_division IS NULL AND SUB_DIV IS NULL AND SUB_DIV3 IS NULL THEN sub_div4
ELSE
sub_division
END as sub_division,
display_name
FROM (SELECT DISTINCT L.log_id,
FC.log_yr AS YEAR,
WA.sub_division,
AI.sub_division AS SUB_DIV,
EA.sub_division
AS SUB_DIV3,
FI.sub_division AS SUB_DIV4,
(SELECT display_name
FROM z_sub_division_type a
WHERE a.sub_division = WA.sub_division
OR a.sub_division = AI.sub_division
OR a.sub_division = EA.sub_division
OR a.sub_division = FI.sub_division
) AS DISPLAY_NAME
FROM final_calendar FC
join log L
ON To_date (To_char (L.log_date, 'MM/DD/YYYY'), 'MM/DD/YYYY') =
FC.cal_date
left outer join log_water WA
ON WA.log_id = L.log_id
left outer join log_air AI
ON AI.log_id = L.log_id
left outer join log_earth EA
ON EA.log_id = L.log_id
left outer join log_fire FI
ON FI.log_id = L.log_id)
Edit 1:-This sql works assuming atleast one column has value and rest are null
Edit 2:- You can replace case clause with coalesce
coalesce(sub_division,sub_div,sub_div3,sub_div4) as sub_division

Oracle query for getting particular Date

I have an oracle query which returns next mont first sunday.
now i have a condition here to check if the date which has passed is first sunday of current month, then we need second sunday in current month.
Else, next month first sunday.
My query:
DEF startDate = to_date('somedate', 'dd/mm/yyyy');
Select next_day(last_day(&startDate),'Sunday' ) from dual ;
Expected output:
if we input 1st july 2018, it has to return 8th july 2018(second sunday) else, any other day apart from first sunday like, (2nd july 2018), it has to return 5th Aug 2018.
Input Expected Output
01-Jul-18 08-Jul-18,
02-Jul-18 05-Aug-18,
05-Aug-18 12-Aug-18,
19-Aug-18 02-Sep-18.
Based on the description in your question and comments you want something like:
case when start_date = next_day(trunc(start_date, 'MM') - 1, 'Sunday') -- date is on first sunday
then next_day(start_date, 'Sunday') -- next Sunday, which is second in month
else next_day(last_day(start_date), 'Sunday') -- first Sunday of next month
end
With some sample dates in a CTE, including some discussed but also others:
with cte (start_date) as (
select date '2018-05-30' from dual
union all select date '2018-06-01' from dual
union all select date '2018-06-02' from dual
union all select date '2018-06-03' from dual
union all select date '2018-06-04' from dual
union all select date '2018-06-30' from dual
union all select date '2018-07-01' from dual
union all select date '2018-07-02' from dual
union all select date '2018-07-03' from dual
union all select date '2018-07-04' from dual
union all select date '2018-07-05' from dual
union all select date '2018-07-06' from dual
union all select date '2018-07-07' from dual
union all select date '2018-07-08' from dual
union all select date '2018-07-31' from dual
union all select date '2018-08-01' from dual
union all select date '2018-08-02' from dual
union all select date '2018-08-03' from dual
union all select date '2018-08-04' from dual
union all select date '2018-08-05' from dual
union all select date '2018-08-06' from dual
)
select start_date,
to_char(start_date, 'Dy') as day,
case when to_char(start_date, 'Dy') = 'Sun'
then 'Yes' else 'No' end as is_sunday,
case when start_date = next_day(trunc(start_date, 'MM') - 1, 'Sunday')
then 'Yes' else 'No' end as is_first_sunday,
next_day(trunc(start_date, 'MM') - 1, 'Sunday') as first_sun_this_month,
next_day(trunc(start_date, 'MM') + 6, 'Sunday') as second_sun_this_month,
next_day(last_day(start_date), 'Sunday') as first_sun_next_month,
case when start_date = next_day(trunc(start_date, 'MM') - 1, 'Sunday') -- date is on first sunday
then next_day(start_date, 'Sunday') -- next Sunday, which is second in month
else next_day(last_day(start_date), 'Sunday') -- first Sunday of next month
end as result
from cte;
gets
START_DATE DAY IS_ IS_ FIRST_SUN_ SECOND_SUN FIRST_SUN_ RESULT
---------- ------------ --- --- ---------- ---------- ---------- ----------
2018-05-30 Wed No No 2018-05-06 2018-05-13 2018-06-03 2018-06-03
2018-06-01 Fri No No 2018-06-03 2018-06-10 2018-07-01 2018-07-01
2018-06-02 Sat No No 2018-06-03 2018-06-10 2018-07-01 2018-07-01
2018-06-03 Sun Yes Yes 2018-06-03 2018-06-10 2018-07-01 2018-06-10
2018-06-04 Mon No No 2018-06-03 2018-06-10 2018-07-01 2018-07-01
2018-06-30 Sat No No 2018-06-03 2018-06-10 2018-07-01 2018-07-01
2018-07-01 Sun Yes Yes 2018-07-01 2018-07-08 2018-08-05 2018-07-08
2018-07-02 Mon No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-03 Tue No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-04 Wed No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-05 Thu No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-06 Fri No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-07 Sat No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-08 Sun Yes No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-07-31 Tue No No 2018-07-01 2018-07-08 2018-08-05 2018-08-05
2018-08-01 Wed No No 2018-08-05 2018-08-12 2018-09-02 2018-09-02
2018-08-02 Thu No No 2018-08-05 2018-08-12 2018-09-02 2018-09-02
2018-08-03 Fri No No 2018-08-05 2018-08-12 2018-09-02 2018-09-02
2018-08-04 Sat No No 2018-08-05 2018-08-12 2018-09-02 2018-09-02
2018-08-05 Sun Yes Yes 2018-08-05 2018-08-12 2018-09-02 2018-08-12
2018-08-06 Mon No No 2018-08-05 2018-08-12 2018-09-02 2018-09-02
The result column is the one you're interested in, the others just try to show the working a bit.
It looks odd to have the result dates out of sequence compared to the input dates - 2018-06-03 going to 2018--06-10 then those both before and after it go to 2018-07-01. But that seems to be what you want.

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

Oracle and DateTime math

I'm writing a query to select some records. I have this data:
Event Table
------------------
Definition
EVE_RID NUMBER(10)
EVE_START_DATE Date
Data:
EVE_RID EVE_START_DATE
156891 11/1/2016
Agenda Table
------------------
Definition:
AGD_EVE_RID NUMBER(10)
AGD_DAY NUMBER(2)
AGD_START_TIME NUMBER(4)
Data:
AGD_EVE_RID AGD_DAY AGD_START_TIME
156891 1 1000
156891 1 1400
156891 8 1000
156891 8 1400
156891 15 1000
156891 15 1400
WAList Table
------------------
Definition:
WAL_STARTTIME DATE
WAL_KEY VARCHAR2(50)
Data:
WAL_STARTTIME WAL_KEY
11/1/2016 10:00:00 AM 6341371019318098180
11/1/2016 2:00:00 PM 7561779448126279684
11/8/2016 10:00:00 AM 6904435321948802820
11/8/2016 2:00:00 PM 7998296559469684996
11/15/2016 10:00:00 AM 4690144247933554180
11/15/2016 2:00:00 PM 7931460546152111876
What I need is some way to match records from the WAList table from the Agenda table.
How could I write my where clause to match the WALList records to the Agenda.AGD_DAY records and return the correct Key for the correct day? This would be result:
EVE_START_DATE AGD_DAY AGD_START_TIME WAL_KEY
11/1/2016 1 1000 6341371019318098180
11/1/2016 1 1400 7561779448126279684
11/1/2016 8 1000 6904435321948802820
11/1/2016 8 1400 7998296559469684996
11/1/2016 15 1000 4690144247933554180
11/1/2016 15 1400 7931460546152111876
Assuming datatypes as —
Sessions.startdate as varchar2 and WAL.startTime as date:
select s.startdate, s.sessionDay, s.startTime, w.key
from sessions s join WAL w
on to_date(to_char(to_date(s.startdate, 'mm/dd/yyyy') + s.sessionDay, 'mm/dd/yyyy')
||s.starttime,'mm/dd/yyyyhh24mi') = w.startTime;

Convert columns to rows in oracle [duplicate]

This question already has answers here:
Oracle SQL pivot query
(4 answers)
Closed 8 years ago.
this is my table in oracle 11g:
**date qty1 qty2 qty3 qty4**
2-Feb-14 61 64 52 54
2-Mar-14 124 130 149 156
i want to convert it into the following table. i.e. add 7 days to the date and transpose the qty. And i have till qty52 such metrics
***date qty***
**2-Feb-14 61**
9-Feb-14 64
16-Feb-14 52
23-Feb-14 54
**2-Mar-14 124**
9-Mar-14 130
16-Mar-14 149
23-Mar-14 156
have a try:
WITH t(my_date, val, val2, val3, val4)
AS (
SELECT to_date('01/01/2014 12:00:00 AM', 'dd/mm/yyyy hh:mi:ss am'), 1,2,3,4 from dual
UNION ALL
SELECT to_date('01/02/2014 12:00:00 AM', 'dd/mm/yyyy hh:mi:ss am'), 5,6,7,8 FROM dual
)
SELECT (my_date-7) + (row_number() OVER (partition by my_date ORDER BY my_date)*7) my_date, value as qty
FROM (
( SELECT my_date, val, val2, val3, val4 FROM t
) unpivot ( value FOR value_type IN (val, val2, val3, val4) ) );
output:
MY_DATE QTY
----------------------- ----------
01/01/2014 12:00:00 AM 1
08/01/2014 12:00:00 AM 2
15/01/2014 12:00:00 AM 3
22/01/2014 12:00:00 AM 4
01/02/2014 12:00:00 AM 5
08/02/2014 12:00:00 AM 6
15/02/2014 12:00:00 AM 7
22/02/2014 12:00:00 AM 8
select date,qty from
(select date,qty1 as qty
from tbl
union
select date+7 as date,qty2 as qty
from tbl
union
select date+14 as date,qty3 as qty
from tbl
union
select date+21 as date,qty4 as qty
from tbl)
order by date
If you've got Oracle 11g, I'd look at doing it with UNPIVOT.
select
start_date + to_number(week_number) * 7,
qty
from (
select *
from quantity_data
unpivot (qty for week_number
in (qty1 as '0', qty2 as '1', qty3 as '2', qty4 as '3'))
)
This is an alternative to the example from ajmalmhd04, using to_number instead of the row_number analytic function. The answer from ajmalmhd04 is probably more generic though
If you haven't got Oracle 11g then try this for an option:
with pivot_data as (
select 0 as pivot_col from dual union all
select 1 from dual union all
select 2 from dual union all
select 3 from dual
)
select
start_date + (7 * pivot_col) as start_date,
case
when pivot_col = 0 then qty1
when pivot_col = 1 then qty2
when pivot_col = 2 then qty3
when pivot_col = 3 then qty4 end as qty
from
quantity_data cross join pivot_data
order by 1
Try this
with tab(date_d,qty1,qty2,qty3,qty4) as (
select '2-Feb-14',61,64,52,54 from dual union all
select '2-Mar-14',124,130,149,156 from dual),
tab2(dd, ss) as (select date_d, qty1||','||qty2||','||qty3||','||qty4 from tab)
select to_date(dd) + ((level-1) * 7) "DATE", regexp_substr(ss, '[^(,)]+', 1, level) "QTY"
from tab2
connect by level <= length(ss) - length(replace(ss, ',')) + 1
and prior ss = ss
and prior sys_guid() is not null
output
| DATE | QTY |
|---------------------------------|-----|
| March, 02 2014 00:00:00+0000 | 124 |
| March, 09 2014 00:00:00+0000 | 130 |
| March, 16 2014 00:00:00+0000 | 149 |
| March, 23 2014 00:00:00+0000 | 156 |
| February, 02 2014 00:00:00+0000 | 61 |
| February, 09 2014 00:00:00+0000 | 64 |
| February, 16 2014 00:00:00+0000 | 52 |
| February, 23 2014 00:00:00+0000 | 54 |
Let me know if it meets your requirement.

Resources