How to count the number of entries between a time period - sql

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

Related

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

SQLQuery for Time In and Time Out attendance in Oracle

I have a table in oracle with the below sample output.
EID | type | Date
24 | IN |03/25/2019 6:45 am
24 | OUT |03/25/2019 8:05 am
24 | IN |03/25/2019 8:06 am
24 | IN |03/25/2019 8:28 am
24 | OUT |03/25/2019 9:48 am
24 | IN |03/25/2019 9:52 am
24 | IN |03/25/2019 9:57 am
24 | IN |03/25/2019 10:44 am
24 | OUT |03/25/2019 12:16 pm
24 | OUT |03/25/2019 1:00 pm
24 | IN |03/25/2019 1:05 pm
24 | OUT |03/25/2019 2:21 pm
I want to build a query to achieve the below results:
EID | TIMEIN | TIMEOUT | DIIF_IN_MIN
24 | 03/25/2019 6:45 am | 03/25/2019 8:05 am | 1
24 | 03/25/2019 8:06 am | null | 0
24 | 03/25/2019 8:28 am | 03/25/2019 9:48 am | 4
24 | 03/25/2019 9:52 am | null | 0
24 | 03/25/2019 9:57 am | null | 0
24 | 03/25/2019 10:44 am | 03/25/2019 12:16 pm | 0
24 | null | 03/25/2019 1:00 pm | 5
24 | 03/25/2019 1:05 pm | 03/25/2019 2:21 pm | 0
You can use such a logic by the contribution of lead window analytic function
with tab(eid, type, dates ) as
(
select 24,'IN' ,timestamp'2019-03-25 06:45:00' from dual union all
select 24,'OUT',timestamp'2019-03-25 08:05:00' from dual union all
select 24,'IN' ,timestamp'2019-03-25 08:06:00' from dual union all
select 24,'IN' ,timestamp'2019-03-25 08:28:00' from dual union all
select 24,'OUT',timestamp'2019-03-25 09:48:00' from dual union all
select 24,'IN' ,timestamp'2019-03-25 09:52:00' from dual
)
select t1.eid, t1.dates as timein, t2.dates as timeout,
nvl(to_number(regexp_substr(to_char(t1.ld_dates - t2.dates),'[^:]+',1,2)),0)
as diff_in_minutes
from ( select lead(dates) over (order by dates) as ld_dates, t.*
from tab t
where type = 'IN' order by dates) t1
full join ( select * from tab where type = 'OUT' order by dates) t2
on t1.dates <= t2.dates and ld_dates > t2.dates
order by t1.dates;
EID TIMEIN TIMEOUT DIFF_IN_MINUTES
24 25.03.2019 06:45:00 25.03.2019 08:05:00 1
24 25.03.2019 08:06:00 NULL 0
24 25.03.2019 08:28:00 25.03.2019 09:48:00 4
24 25.03.2019 09:52:00 NULL 0
Demo
You can do this with the following logic.
You can get all the ins using a lead() query. Then you can get the unmatched outs using a lag():
select t.eid, date as timein,
(case when next_type = 'OUT' then next_date end) as timeout,
((case when next_type = 'OUT' then next_date end) - date) * (24 * 60) as diff_in_minutes
from (select t.*,
lead(type) over (partition by eid order by date) as next_type,
lead(type) over (partition by eid order by date) as next_date
from t
) t
where type = 'IN'
union all
select t.eid, null as timein,
date as timeout, null as diff_in_minutes
from (select t.*,
lag(type) over (partition by eid order by date) as prev_type,
lag(date) over (partition by eid order by date) as prev_date
from t
) t
where type = 'OUT' and (prev_type <> 'IN' or prev_type is null);
Here is a db<>fiddle with all your data, showing that it supports the multiple INs and OUTs.
Note this assumes that the date/time column is really a date. It only converts to a timestamp to show the time component in the result set.

Create rows between two dates

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

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.

How do I write a query to display the months between two dates?

I have a database of IDs with income and start/end dates as below but I have trouble breaking the income per ID per month for the given start/end date range.
A sample of the table data is below:
ID | INCOME | START_DATE | END_DATE
1 | 2000 | 02/01/2016 | 05/31/2016
1 | 1500 | 12/01/2015 | 01/31/2016
2 | 1000 | 01/01/2016 | 04/30/2016
The outcome should be:
ID | INCOME | MONTH
1 | 2000 | 05/2016
1 | 2000 | 04/2016
1 | 2000 | 03/2016
1 | 2000 | 02/2016
1 | 1500 | 01/2016
1 | 1500 | 12/2015
2 | 1000 | 04/2016
2 | 1000 | 03/2016
2 | 1000 | 02/2016
2 | 1000 | 01/2016
How would I write the Oracle SQL such that I am able to produce the above outcome efficiently (assuming the table has thousands of unique IDs)?
You can do this using connect by, like so:
with sample_data as (select 1 id, 2000 income, to_date('01/02/2016', 'dd/mm/yyyy') start_date, to_date('31/05/2016', 'dd/mm/yyyy') end_date from dual union all
select 1 id, 1500 income, to_date('01/12/2015', 'dd/mm/yyyy') start_date, to_date('31/01/2016', 'dd/mm/yyyy') end_date from dual union all
select 2 id, 1000 income, to_date('01/01/2016', 'dd/mm/yyyy') start_date, to_date('30/04/2016', 'dd/mm/yyyy') end_date from dual)
select id,
income,
add_months(trunc(start_date, 'mm'), -1 + level) mnth
from sample_data
connect by prior id = id
and prior income = income
and prior sys_guid() is not null
and add_months(trunc(start_date, 'mm'), -1 + level) <= trunc(end_date, 'mm')
order by id, income desc, mnth desc;
ID INCOME MNTH
---------- ---------- ---------
1 2000 01-MAY-16
1 2000 01-APR-16
1 2000 01-MAR-16
1 2000 01-FEB-16
1 1500 01-JAN-16
1 1500 01-DEC-15
2 1000 01-APR-16
2 1000 01-MAR-16
2 1000 01-FEB-16
2 1000 01-JAN-16
You could use recursive subquery factoring, if you're on 11gR2 or higher:
with r (id, income, this_date, end_date) as (
select id, income, trunc(start_date, 'MM'), trunc(end_date, 'MM')
from your_table
union all
select id, income, this_date + interval '1' month, end_date
from r
where end_date > this_date
)
select id, income, to_char(this_date, 'MM/YYYY') as month
from r
order by id, this_date desc;
ID INCOME MONTH
---------- ---------- -------
1 2000 05/2016
1 2000 04/2016
1 2000 03/2016
1 2000 02/2016
1 1500 01/2016
1 1500 12/2015
2 1000 04/2016
2 1000 03/2016
2 1000 02/2016
2 1000 01/2016
The anchor member gets the starting information - which I'm truncating to the start of the month, probably redundantly, but just in case one starts late enough in the month to cause a problem with interval addition. The recursive member then keeps adding a month to each existing member until it reaches the end date.