Create rows between two dates - sql

I have the below table. I need to create a row for each month from hire_dt to term_dt
id hire_dt term_dt
1 08/07/2017 02/20/2018
Expected Results:
id hire_dt term_dt Month level_alias
1 08/07/2017 10/20/2017 201708 1
1 08/07/2017 10/20/2017 201709 2
1 08/07/2017 10/20/2017 201710 3
Query:
SELECT DISTINCT
ID,
HIRE_DT,
TERM_DT,
TO_CHAR(ADD_MONTHS(TRUNC(HIRE_DT, 'MM'), LEVEL -1), 'YYYYMM') AS MONTH,
LEVEL AS LEVEL_ALIAS
FROM AHR
JOIN HA ON ID = HA.id --AND RNK = 1
WHERE 1=1
CONNECT BY LEVEL <= 1 + MONTHS_BETWEEN(TRUNC(TERM_DT,'MM'),TRUNC(HIRE_DT,'MM'))
AND PRIOR ID = ID AND PRIOR HIRE_DT=HIRE_DT
AND PRIOR SYS_GUID() IS NOT NULL
AND AHR.ASSOC_AIN_ID = 1
Results:
id hire_dt term_dt Month level_alias
1 08/07/2017 201708 1
1 08/07/2017 10/20/2017 201708 1
1 08/07/2017 10/20/2017 201709 2
1 08/07/2017 10/20/2017 201710 3
Why am I receiving the null term_dt?

Based on (somewhat misleading; 10/20/2017 vs. 02/20/2018) sample data you provided, have a look at this:
SQL> with test (id, hire_dt, term_dt) as
2 (select 1, date '2017-08-07', date '2017-10-20' from dual)
3 select id,
4 hire_dt,
5 term_dt,
6 to_char(add_months(hire_dt, level - 1), 'yyyymm') month,
7 level lvl
8 from test
9 connect by level <= months_between(term_dt, hire_dt) + 1;
ID HIRE_DT TERM_DT MONTH LVL
---------- ---------- ---------- ------ ----------
1 08/07/2017 10/20/2017 201708 1
1 08/07/2017 10/20/2017 201709 2
1 08/07/2017 10/20/2017 201710 3
SQL>
It might need to be modified, but - as I said, with information you provided, that's it (i.e. I have no idea what tables you're joining and why).

Related

PLSQL How to split each row based on begin and end date

Could you please assist me to split each row and create multiple rows for each date between begin_date and end_date.
ID CODE VIEW BEGIN_DATE END_DATE
-------------------------------------------
10400 null 2 17-FEB-20 17-FEB-20
10650 null 2 17-FEB-20 18-FEB-20
10900 null 2 19-FEB-20 21-FEB-20
10901 null 2 21-FEB-20 02-MAR-20
11650 2723 2 02-MAR-20 04-MAR-20
11650 1002 2 02-MAR-20 04-MAR-20
11650 1001 2 02-MAR-20 04-MAR-20
11650 1000 2 02-MAR-20 04-MAR-20
Currently I'm using below query but it doesn't seem to work
select
r.*
from rec r
connect by level <= end_date - begin_date + 1;
what i want is to some thing like this
ID CODE VIEW DATE
----------------------------------
11650 2723 2 02-MAR-20
11650 2723 2 03-MAR-20
11650 2723 2 04-MAR-20
.... continue
Here's one option:
Sample data:
SQL> with test (id, code, cview, begin_date, end_date) as
2 (select 10400, null, 2, date '2020-02-17', date '2020-02-17' from dual union all
3 select 10650, null, 2, date '2020-02-17', date '2020-02-18' from dual union all
4 select 11650, 2723, 2, date '2020-03-02', date '2020-03-04' from dual
5 )
Query begins here:
6 select id,
7 code,
8 cview,
9 begin_date + column_value - 1 as datum
10 from test cross join
11 table(cast(multiset(select level from dual
12 connect by level <= end_date - begin_date + 1
13 ) as sys.odcinumberlist))
14 order by id, datum;
ID CODE CVIEW DATUM
---------- ---------- ---------- ----------
10400 2 17.02.2020
10650 2 17.02.2020
10650 2 18.02.2020
11650 2723 2 02.03.2020
11650 2723 2 03.03.2020
11650 2723 2 04.03.2020
6 rows selected.
SQL>

FORMATING SELECT

I have a select which shows me how much lasts a process, it looks like:
SELECT
info1,
info2,
ifno3...
(DATE,'DD.MM.YYYY')DAY,
to_char(DATE,'HH24:MI') hour_from,
to_char(DATE_UNTIL,''HH24:MI'')hour_until,
REGEXP_SUBSTR((date_until-date)DAY TO SECOND,'d{2}:\d{2}')lasts_time
.....
and if a process lasts for example from 09.06.2021 23:00 to 10.06.2021 07:00, then the output of query from above will be:
info1 info2 info3 DAY hour_from hour_until lasts_time
info1| info2 |info3 |09.06.2021 |23:00 | 07:00 | 08:00
and i want it to change in
if this process lasts over night, then the output should be something like this:
info1 info2 info3 DAY hour_from hour_until lasts_time
info1| info2 |info3 |09.06.2021 |23:00 | 00:00 | 01:00
info1| info2 |info3 |10.06.2021 |00:00 | 07:00 | 07:00
so it will count only until the day ends, and if the process is still on going in the new day the output will come in a new row,
I think I have here to deal with union all, but I'm not sure, if anyone has any idea would be great
Here's one option. Read comments within code.
SQL> with
2 test (id, info1, date_from, date_to) as
3 -- sample data
4 (select 1, 'info1', to_date('09.06.2021 23:00', 'dd.mm.yyyy hh24:mi'),
5 to_date('10.06.2021 07:00', 'dd.mm.yyyy hh24:mi')
6 from dual
7 ),
8 temp as
9 -- row generator, to generate all hours between DATE_FROM and DATE_TO
10 (select id, info1,
11 date_from + (level - 1) / 24 datum,
12 lead(date_from + (level - 1) / 24) over (order by date_from) next_datum
13 from test
14 connect by level <= (date_to - date_from) * 24 + 1
15 ),
16 temp2 as
17 -- find date boundaries (MINDAT and MAXDAT), as well as duration in between (LASTS_TIME)
18 (select id, info1, datum, next_datum,
19 min(datum) mindat,
20 max(case when to_char(next_datum, 'mi') = '00' then next_datum - 1/(24*60*60)
21 else datum
22 end
23 ) maxdat,
24 --
25 round((max(case when to_char(next_datum, 'mi') = '00' then next_datum - 1/(24*60*60)
26 else datum
27 end
28 ) -
29 min(datum)
30 ) * 24) lasts_time
31 from temp
32 group by id, info1, datum, next_datum
33 )
34 -- the final result
35 select id,
36 info1,
37 to_char(datum, 'dd.mm.yyyy') day,
38 min(to_char(datum, 'hh24:mi')) hour_from,
39 max(to_char(next_datum, 'hh24:mi')) hour_until,
40 sum(lasts_time) lasts_time
41 from temp2
42 group by id, info1,
43 to_char(datum, 'dd.mm.yyyy')
44 order by day;
The result is
ID INFO1 DAY HOUR_FROM HOUR_UNTIL LASTS_TIME
---------- ----- ---------- ---------- ---------- ----------
1 info1 09.06.2021 23:00 00:00 1
1 info1 10.06.2021 00:00 07:00 7
SQL>
For #astentx's comment: there's no endless loop in my case for values you suggested:
SQL> set timing on
SQL> with
2 test (id, info1, date_from, date_to) as
3 -- sample data
4 (select 1, 'info1', to_date('10.06.2021 07:00', 'dd.mm.yyyy hh24:mi'),
5 to_date('11.06.2021 07:00', 'dd.mm.yyyy hh24:mi')
6 from dual
7 ),
<snip>
ID INFO1 DAY HOUR_ HOUR_ LASTS_TIME
---------- ----- ---------- ----- ----- ----------
1 info1 10.06.2021 07:00 23:00 17
1 info1 11.06.2021 00:00 07:00 7
Elapsed: 00:00:00.04
SQL>
You can use recursive CTE to generate required amount of rows (which are equal to days distance between dates).
with a as (
select
'info1' as id
, to_date('2021-05-01 10:20:30') as dt_from
, to_date('2021-05-03 07:08:09') as dt_to
from dual
union all
select
'info2'
, to_date('2021-05-02 05:06:07')
, to_date('2021-05-02 10:20:30')
from dual
)
, b(id, dt_from, dt_to, dt) as (
select
id
, dt_from
, dt_to
, trunc(dt_from)
from a
union all
select
id
, dt_from
, dt_to
, dt + 1
from b
where dt < trunc(dt_to)
)
select
id
, dt
, numtodsinterval(
case
when trunc(dt_to) = dt
then dt_to
else dt + 1
end
-
greatest(dt_from, dt)
, 'DAY') as dur_interval
/*Or any rounding function you need*/
, trunc((case
when trunc(dt_to) = dt
then dt_to
else dt + 1
end
-
greatest(dt_from, dt))*24) as dur_hours
, case
when trunc(dt_from) = dt
then to_char(dt_from, 'hh24:mi')
else '00:00'
end as hour_from
, case
when trunc(dt_to) = dt
then to_char(dt_to, 'hh24:mi')
else '23:59'
end as hour_to
from b
order by
id
, dt
ID | DT | DUR_INTERVAL | DUR_HOURS | HOUR_FROM | HOUR_TO
:---- | :------------------ | :---------------------------- | --------: | :-------- | :------
info1 | 2021-05-01 00:00:00 | +000000000 13:39:30.000000000 | 13 | 10:20 | 23:59
info1 | 2021-05-02 00:00:00 | +000000001 00:00:00.000000000 | 24 | 00:00 | 23:59
info1 | 2021-05-03 00:00:00 | +000000000 07:08:09.000000000 | 7 | 00:00 | 07:08
info2 | 2021-05-02 00:00:00 | +000000000 05:14:23.000000000 | 5 | 05:06 | 10:20
db<>fiddle here

How to count the number of entries between a time period

I have a sample table below which shows the ticket number, time when the ticket was opened and time when it was closed.
TKTNUM OPEN_DATE CLOSE_DATE
1234 12-Mar-19 08:36 14-Mar-19 08:36
1235 13-Mar-19 08:36 15-Mar-19 08:36
1236 14-Mar-19 08:36 16-Mar-19 08:36
1237 15-Mar-19 08:36
1238 16-Mar-19 08:36
1239 17-Mar-19 08:36
1240 18-Mar-19 08:36 20-Mar-19 08:36
1241 19-Mar-19 08:36 20-Mar-19 08:36
1242 20-Mar-19 08:36 21-Mar-19 08:36
I need to count the number of open/closed tickets on a given day...
DATE OPEN CLOSED
12-Mar-19 08:36 1 0
13-Mar-19 08:36 2 0
14-Mar-19 08:36 2 1
15-Mar-19 08:36 2 2
16-Mar-19 08:36 2 3
17-Mar-19 08:36 3 3
18-Mar-19 08:36 4 3
19-Mar-19 08:36 5 3
20-Mar-19 08:36 4 5
Any help is greatly appreciated. Thanks
Used the query(c/o Tejash) below on a sample job_history table
EMPLOYEE_ID START_DATE END_DATE JOB_ID DEPARTMENT_ID
----------- -------------------- -------------------- ---------- -------------
200 17/SEP/1995 00:00:00 17/JUN/2001 00:00:00 AD_ASST 90
101 21/SEP/1997 00:00:00 27/OCT/2001 00:00:00 AC_ACCOUNT 110
102 13/JAN/2001 00:00:00 24/JUL/2006 00:00:00 IT_PROG 60
101 28/OCT/2001 00:00:00 15/MAR/2005 00:00:00 AC_MGR 110
200 01/JUL/2002 00:00:00 31/DEC/2006 00:00:00 AC_ACCOUNT 90
201 17/FEB/2004 00:00:00 19/DEC/2007 00:00:00 MK_REP 20
114 24/MAR/2006 00:00:00 31/DEC/2007 00:00:00 ST_CLERK 50
176 24/MAR/2006 00:00:00 31/DEC/2006 00:00:00 SA_REP 80
176 01/JAN/2007 00:00:00 31/DEC/2007 00:00:00 SA_MAN 80
122 01/JAN/2007 00:00:00 31/DEC/2007 00:00:00 ST_CLERK 50
With dates(dt)
As (Select mindt + level - 1 from
(Select min(start_date) mindt, max(end_date) maxdt from job_history)
Connect by level <= maxdt - mindt + 1)
Select dt,
sum(case when dt between start_date and coalesce(end_date,dt) then 1 end) as startdate,
Sum(case when dt >= end_date then 1 end) as enddate
From dates cross join job_history
Group by dt
Order by dt desc
On 17/JUN/2001, the query gave
DT STARTDATE ENDDATE
-------------------- ---------- ----------
31/DEC/2007 00:00:00 3 10
<SNIPPED>
17/JUN/2001 00:00:00 3 1
Instead of
DT STARTDATE ENDDATE
-------------------- ---------- ----------
31/DEC/2007 00:00:00 3 10
<SNIPPED>
17/JUN/2001 00:00:00 2 1
Tried to edit the query and now its giving me
DT STARTDATE ENDDATE
-------------------- ---------- ----------
31/DEC/2007 00:00:00 <<< 10
<snipped>
18/JUN/2001 00:00:00 2 1
17/JUN/2001 00:00:00 2 <<< 1
16/JUN/2001 00:00:00 3 1
You can use dates as cte for total days and join it again with same table as following:
With dates(dt)
As
(
Select mindt + level - 1 from
(Select min(open_date) mindt, max(open_dt) maxdt from your_table)
Connect by level <= maxdt - mindt + 1
)
Select dt,
sum(case when dt between open_date and coalesce(close_date,dt) then 1 end) as open,
Sum(case when dt >= close_date then 1 end) as closed
From dates cross join your_table
Group by dt;
Cheers!!
You can unpivot and aggregate:
select dte, sum(is_open) as num_opens, sum(is_close) as num_closes
from ((select open_date as dte, 1 as is_open, 0 as is_close
from t
) union all
(select close_date, 0 as is_open, 1 as is_close
from t
)
) t
group by dte
order by dte;
Note: It is probably a good idea to truncate the date so it has no time component:
select trunc(dte), sum(is_open) as num_opens, sum(is_close) as num_closes
from ((select open_date as dte, 1 as is_open, 0 as is_close
from t
) union all
(select close_date, 0 as is_open, 1 as is_close
from t
)
) t
where dte is not null
group by trunc(dte)
order by trunc(dte);
And in Oracle 12C you can use a lateral join for this:
select trunc(dte), sum(is_open), sum(is_close)
from t cross join lateral
(select t.open_date as dte, 1 as is_open, 0 as is_close from dual union all
select t.close_date, 0 as is_open, 1 as is_close from dual
) t
group by trunc(dte)
order by trunc(dte);

ORACLE SQL - How to find the number of reliefs each teacher has, each day, 2 months before the teacher resigned?

I need some help in finding the number of reliefs each teacher has, every single day, 2 months before the teacher resigns.
Join_dt - teacher's join date,
Resign_dt - teacher's resign date,
Relief_ID - Relief teacher's ID,
Start_dt - Relief's start date,
End_dt - Relief's end date,
note that there may be overlapping dates between 2 or more different reliefs and so I need to find the number of distinct reliefs each teacher has for each date.
This is what I am given:
Teacher_ID Join_dt Resign_dt Relief_ID Start_dt End_dt
12 2006-08-30 2019-08-01 20 2017-02-07 2019-07-04
12 2006-08-30 2019-08-01 20 2016-11-10 2019-01-30
12 2006-08-30 2019-08-01 103 2016-08-20 2019-07-29
12 2006-08-30 2019-08-01 17 2016-01-30 2017-12-30
23 2017-10-01 2018-11-12 44 2018-10-19 2018-11-11
23 2017-10-01 2018-11-12 29 2018-04-01 2018-12-02
23 2017-10-01 2018-11-12 06 2017-11-25 2018-05-02
05 2015-02-11 2019-10-02 38 2019-01-17 2019-07-21
05 2015-02-11 2019-10-02 11 2018-11-02 2019-02-05
05 2015-02-11 2019-10-02 15 2018-09-30 2018-10-03
Expected result:
Teacher_ID Dates No_of_reliefs
12 2019-07-31 0
12 2019-07-30 0
12 2019-07-29 1
12 2019-07-28 1
12 2019-07-27 1
... ...
12 2019-07-04 2
... ...
12 2016-05-30 2
12 2016-05-29 2
12 2016-05-28 2
12 2016-05-27 2
12 2016-05-26 1
23 2018-10-31 2
... ...
For date 2019-07-29, No_of_reliefs = 1 because of Relief_ID 103.
For date 2017-07-04, No_of_reliefs = 2 because of Relief_ID 20 & 103.
Dates are supposed to start from 1 month before the teacher resigned. For Teacher_ID 23, since she resigned on 2019-11-12, dates shall start from 2019-10-31.
I have tried using connect by but the execution time is really long since it involves a large amount of data.
Any other methods will be greatly appreciated!!
Thank you kind souls!!!
You can use
connect by level <= last_day(add_months(Resign_dt,-1)) - add_months(Resign_dt,-2) clause :
I suppose you mean 2 months before resignment for the starting date, and ending on the last day of the previous month.
with t1(Teacher_ID,Resign_dt,Relief_ID,start_dt,end_dt) as
(
select 12,date'2019-08-01',20 ,date'2017-02-07',date'2019-07-04' from dual union all
select 12,date'2019-08-01',20 ,date'2016-11-10',date'2019-01-30' from dual union all
select 12,date'2019-08-01',103,date'2016-08-20',date'2019-07-29' from dual
......
), t2 as
(
select distinct last_day(add_months(Resign_dt,-1)) - level + 1 as Resign_dt, Teacher_ID
from t1
connect by level <= last_day(add_months(Resign_dt,-1)) - add_months(Resign_dt,-2)
and prior Teacher_ID = Teacher_ID and prior sys_guid() is not null
)
select Teacher_ID, to_char(Resign_dt,'yyyy-mm-dd') as Dates,
(select count(distinct Relief_ID)
from t1
where t2.Resign_dt between start_dt and end_dt
and t2.Teacher_ID = Teacher_ID
)
from t2
order by Teacher_ID, Resign_dt desc;
Demo
select d.dt
, tr.Teacher_ID
--, tr.Join_dt
--, tr.Resign_dt
, count(tr.Relief_ID)
--, tr.Start_dt
--, tr.End_dt
from tr
right outer join (
SELECT dt
FROM (
SELECT DATE '2006-01-01' + ROWNUM - 1 dt
FROM DUAL CONNECT BY ROWNUM < 5000
) q
WHERE EXTRACT(YEAR FROM dt) < EXTRACT(YEAR FROM sysdate) + 2
--order by 1
) d on d.dt between tr.Join_dt and tr.End_dt
and d.dt between tr.Start_dt and tr.Resign_dt
group by d.dt
, tr.Teacher_ID
order by d.dt desc

Range select between two dates

I have an Oracle 11g and I want to know if it is possible to make a select from a date to another date.
For example:
I have two fields called StartDate and EndDate. I want to show the count of rows between EndDate and StartDate.
If my StartDate is 2018-08-01 and my EndDate is 2018-08-10 so my expected table should be:
DATE | rownum
2018-08-01 | 1
2018-08-02 | 2
2018-08-03 | 3
2018-08-04 | 4
2018-08-05 | 5
2018-08-06 | 6
2018-08-07 | 7
2018-08-08 | 8
2018-08-09 | 9
2018-08-10 | 10
Thank you!
You may need a row generator like the following:
select date '2018-08-01' + level -1 as yourDate,
level as yourRowNum
from dual
connect by date '2018-08-01' + level -1 <= date '2018-08-10'
The result:
YOURDATE YOURROWNUM
---------- ----------
2018-08-01 1
2018-08-02 2
2018-08-03 3
2018-08-04 4
2018-08-05 5
2018-08-06 6
2018-08-07 7
2018-08-08 8
2018-08-09 9
2018-08-10 10
To avoid repeating the date values, you can use:
with dateRange(startDate, endDate) as
(
select date '2018-08-01', date '2018-08-10'
from dual
)
select startDate + level -1 as yourDate,
level as yourRowNum
from dateRange
connect by startDate + level -1 <= endDate;
You may easily get what you'd like by using sum(1) over (order by "date"):
select "Date", sum(1) over (order by "Date") "Row Number"
from tab
where "Date" between date'2018-08-01' and date'2018-08-10';
Date Row Number
---------- ----------
2018-08-01 1
2018-08-02 2
2018-08-03 3
2018-08-04 4
2018-08-05 5
2018-08-06 6
2018-08-07 7
2018-08-08 8
2018-08-09 9
2018-08-10 10
Other alternatives count(1), row_number() might be replaced with sum(1), also.
SQL Fiddle Demo
I guess you want something like this...
WITH my_table AS
(SELECT TRUNC(SYSDATE) + LEVEL - 1 AS current_day
FROM DUAL
CONNECT BY LEVEL < 10)
SELECT FIRST_VALUE(current_day) OVER (ORDER BY current_day) first_day
, current_day
, current_day - FIRST_VALUE(current_day) OVER (ORDER BY current_day) days_diff
FROM my_TABLE;
FIRST_DAY CURRENT_DAY DAYS_DIFF
--------- ----------- ----------
29-AUG-18 29-AUG-18 0
29-AUG-18 30-AUG-18 1
29-AUG-18 31-AUG-18 2
29-AUG-18 01-SEP-18 3
29-AUG-18 02-SEP-18 4
29-AUG-18 03-SEP-18 5
29-AUG-18 04-SEP-18 6
29-AUG-18 05-SEP-18 7
29-AUG-18 06-SEP-18 8
9 rows selected.