Count days between two segments - sql

I have two tables below. I want to count the number of days, Monday-Friday only between Hire_dt and end of calendar month the hire date falls under.
TableA
Hire_DT Id
09/26/2018 1
TableCalendar:
Date WorkDay(M-F) EOM WorkDay
09/26/2018 Wednesday 9/30/2018 1
09/27/2018 Thursday 09/30/2018 1
09/28/2018 Friday 09/30/2018 1
09/29/2018 Saturday 09/30/2018 0
09/30/2018 Sunday 09/30/2018 0
Expected Results
Hire_dt WorkDaysEndMonth WorkDaysEndMonth --counting hire_dt
09/26/2018 2 3

Here is one way to do the calculation - WITHOUT using a calendar table. The only input data is what comes from your first table (ID and HIRE_DATE), which I included in a WITH clause (not part of the query that answers your question!). Everything else is calculated. I show how to compute the number of days INCLUDING the hire date; if you don't need that, subtract 1 at the end.
TRUNC(<date>, 'iw') is the Monday of the week of <date>. The query computes how many days are in the EOM week, between Monday and EOM, but no more than 5 (in case EOM may be a Saturday or Sunday). It does a similar calculation for HIRE_DATE, but it counts the days from Monday to HIRE_DATE excluding HIRE_DATE. The last part is adding 5 days for each full week between the Monday of HIRE_DATE and the Monday of EOM.
with
sample_data(id, hire_date) as (
select 1, to_date('09/26/2018', 'mm/dd/yyyy') from dual union all
select 2, to_date('07/10/2018', 'mm/dd/yyyy') from dual
)
select id, to_char(hire_date, 'Dy mm/dd/yyyy') as hire_date,
to_char(eom, 'Dy mm/dd/yyyy') as eom,
least(5, eom - eom_mon + 1) - least(5, hire_date - hire_mon)
+ (eom_mon - hire_mon) * 5 / 7 as workdays
from (
select id, hire_date, last_day(hire_date) as eom,
trunc(hire_date, 'iw') as hire_mon,
trunc(last_day(hire_date), 'iw') as eom_mon
from sample_data
)
;
ID HIRE_DATE EOM WORKDAYS
---------- ----------------------- ----------------------- ----------
1 Wed 09/26/2018 Sun 09/30/2018 3
2 Tue 07/10/2018 Tue 07/31/2018 16

You may use the following routine ( where last_day function is a great contributor ):
SQL> alter session set NLS_TERRITORY="AMERICA";
SQL> create table TableA( ID int, Hire_DT date );
SQL> insert into TableA values(1,date'2018-09-26');
SQL> select sum(case when mod(to_char(myDate,'D'),7) <= 1 then 0 else 1 end )
as "WorkDaysEndMonth"
from
(
select Hire_DT + level - 1 myDate
from TableA
where ID = 1
connect by level <= last_day(Hire_DT) - Hire_DT + 1
);
WorkDaysEndMonth
----------------
3
P.S. integer value comes from to_char(<date>,'D') depends on the NLS_TERRITORY setting. Here I used AMERICA for which Saturday is the 7th and Sunday is the 1st day, while for UNITED KINGDOM(or my country TURKEY) setting those are 6th and 7th respectively.
Rextester Demo

Related

SQL script to show current week and first week of every month

I have written sql code to pull all the data that I need but the startdate is in weeks. I need to see the current week and then for future dates I need to see the first date of each month. (Like I said these are in weeks and the date is each Monday. (ex. 03-JUN-19, 10-JUN-19, 17-JUN-19....)
select
d.ITEM,
i.DESCRIPTION,
d.MARKET,
(CASE
WHEN d.LOC like 'U%' THEN 'US'
WHEN d.LOC like 'M%' THEN 'MX'
WHEN d.LOC like 'C%' THEN 'CA'
ELSE 'EXP' END) as COUNTRY,
d.START_DATE as STARTDATE,
SUM(d.DEMANDQTY) as QTY
from DEMAND d, ITEM i
where d.ITEM = i.ITEM
GROUP BY d.ITEM, i.DESCRIPTION, d.MARKET, d.LOC,d.START_DATE
So if I pulled this data today (7-JUN-19) I would need to only pull dates that are...03-JUN-19, 01-JUL-19, 05-AUG-19, 02-SEP-19, 07-OCT-19, 04-NOV-19, 02-DEC-19, 06-JAN-20 on and on until the data is done.
You need a calendar which will return desired dates - the first Monday (that follows today), and then the first Monday in every month. Here's how:
SQL> with
2 this_year as
3 (select trunc(sysdate) + level - 1 datum
4 from dual
5 connect by level <= date '2019-12-31' - trunc(sysdate) + 1
6 ),
7 formatted as
8 (select datum,
9 to_char(datum, 'd') day_num,
10 to_char(datum, 'dy', 'nls_date_language = english') day_name
11 from this_year
12 )
13 select *
14 from formatted f
15 where 1 = 1
16 and f.datum = (select min(f1.datum)
17 from formatted f1
18 where f1.day_num = 1
19 and to_char(f1.datum, 'yyyymm') = to_char(f.datum, 'yyyymm')
20 );
DATUM D DAY_NAME
---------- - ------------
10.06.2019 1 mon
01.07.2019 1 mon
05.08.2019 1 mon
02.09.2019 1 mon
07.10.2019 1 mon
04.11.2019 1 mon
02.12.2019 1 mon
7 rows selected.
SQL>
What does it do?
this_year CTE returns all dates from "today" to Dec 31st 2019. If there's some MAX date you can use - use it
formatted CTE is an intermediate step; if you run it, you'll see that day_num represents ordinal number of a day (1, 2, ... 7), while day_name returns their abbreviate names (mon, tue, ...). We are interested in day number 1 as it represents Mondays
the final select returns desired result - the first Monday in every month
Once you have those dates, use them in your current query. How? A simple option is to create a view based on such a query, e.g.
SQL> create or replace view v_mondays as
2 with
3 this_year as ...
<snip>
View created.
SQL> select * From v_mondays;
DATUM D DAY_NAME
---------- - ------------
10.06.2019 1 mon
01.07.2019 1 mon
<snip>
7 rows selected.
SQL>
Expanded, your query might look like this:
select
d.item,
i.description,
d.market,
case
when d.loc like 'U%' then 'US'
when d.loc like 'M%' then 'MX'
when d.loc like 'C%' then 'CA'
else 'EXP'
end as country,
d.start_date as startdate,
sum(d.demandqty) as qty
from demand d join item i on d.item = i.item
join v_mondays m on m.datum = d.start_date --> join with Mondays
group by d.item, i.description, d.market, d.loc,d.start_date;

Count in sql birthday on a weekend

I'm trying to write a program in pl/sql (oracle) that must calculate how many times someone 's birthday was on a weekend.
This is what i got, but im missing somthing like an extraction at everyloop (-1 year) from 2018 to 1990 for example.
Can someone help me out please?
SET SERVEROUTPUT ON;
DECLARE
v_counter number default 0;
v_real_birthdate date default to_date('01/01/1990', 'DD/MM/YYYY');
v_birthdate date default to_date('01/01/2018', 'DD/MM/YYYY');
BEGIN
WHILE v_counter < 28
LOOP
v_leeftijd := v_leeftijd +1;
dbms_output.put_line( ( TO_CHAR( v_birthdate, 'DAY' ) ) );
END LOOP;
END;
If we suppose that I was born on today's day 2010 (which would then be 2010-09-12 (yyyy-mm-dd)), the result would be as follows, step by step.
MY_BIRTHDAY represents what we agreed to be my birthday
YEARS uses hierarchical query and produces my birthdays from 2010 to current year (2018)
DAYS extracts day name from my birthday for every year, using English language
the final result filters out weekends (sat, sun)
If you're interested in finding out what every CTE returns, run it one by one and you'll see.
SQL> with
2 my_birthday as
3 (select date '&par_birthday' birthday from dual),
4 years as
5 (select to_date((extract(year from birthday) + level - 1) ||'-'||
6 case when to_char(birthday, 'mm-dd') = '02-29' then '02-28'
7 else to_char(birthday, 'mm-dd')
8 end,
9 'yyyy-mm-dd'
10 ) birthday_yr
11 from my_birthday
12 connect by level <= extract(year from sysdate) -
13 extract(year from birthday) + 1
14 ),
15 days as
16 (select birthday_yr,
17 to_char(birthday_yr, 'dy', 'nls_date_language=english') dy
18 from years
19 )
20 select birthday_yr, dy
21 from days
22 where dy in ('sat', 'sun');
Enter value for par_birthday: 2010-09-12
BIRTHDAY_Y DY
---------- ---
2010-09-12 sun
2015-09-12 sat
SQL> /
Enter value for par_birthday: 2012-02-29
BIRTHDAY_Y DY
---------- ---
2015-02-28 sat
2016-02-28 sun
SQL>
You can use the following query (assuming Feb 06, 1981 is the birthday):
WITH b AS (SELECT TO_DATE('02/06/1981', 'MM/DD/RRRR') birthday FROM dual)
SELECT SUM(CASE WHEN TO_CHAR(birthday + (INTERVAL '1' YEAR) * (LEVEL -1), 'FMD') IN ('1','7') THEN 1 END)
FROM b
CONNECT BY birthday + (INTERVAL '1' YEAR) * (LEVEL - 1) <= sysdate
Where TO_CHAR(...,'FMD') gives the day of the week from 1 = Sunday till 7 = Saturday

OR clause with subquery taking too much time

Date Range query taking too much time.
Just i removed the one condition then it working fine taking 2 second. If adding
then 30 seconds.
SELECT UserName,COUNT ('t') TOTAL
FROM TRANSACTIONS E1
WHERE E1.START_DATE BETWEEN TO_DATE ('20130101', 'YYYYMMDD') AND TO_DATE ('20140101', 'YYYYMMDD')
AND
(
TO_CHAR (E1.START_DATE, 'D') in ( 7)
OR Exists(SELECT 1 FROM HOLIDAYS TT
WHERE E1.START_DATE BETWEEN TT.DATE_FROM AND TT.DATE_TO )
)
AND EXISTS (SELECT 't' FROM TRANSACTIONS_ORG E2 WHERE E1.TRANTYPE = E2.tran_type)
GROUP BY UserName;
HOLIDAYS table
Id FromDate ToDate Description
1 1-Feb-11 3-Feb-11 Maintance
3 2-Sep-09 5-Sep-09 Eid Holiday Fine Block
4 3-Dec-09 4-Dec-09 Due to System Problem
5 4-Dec-07 04-Dec-07 National Day
EIDTED
I figured out that the issue is not in the date range. but the OR clause in the
TO_CHAR (E1.START_DATE, 'D') in ( 5,6)
OR
Exists(SELECT 1 FROM HOLIDAYS TT
WHERE E1.START_DATE BETWEEN TT.DATE_FROM AND TT.DATE_TO )
if removed OR and put AND then fine and if shuffle conditions with OR still same issue.
The problem is likely with the OR <subquery> construct.
If there can only be one holiday for a particular date, then you could use the following:
select username
,count(*)
from transactions e1
left join holidays tt on(e1.start_date between tt.date_from and tt.date_to)
where e1.start_date between date '2017-02-01' and date '2018-02-01'
and ( to_char(e1.start_date, 'D') in(5, 6)
or tt.date_from is not null)
)
and exists(
select *
from transactions_org e2
where e1.trantype = e2.tran_type
)
group
by username;
This entire category of problems can be solved by implementing a Calendar table. If you had such a table with one record per date, you could easily add columns indicating day of week and holiday flags and such. If your calendar table looked something like this:
DAY DAYNAME IS_WEEKEND IS_WEEKDAY HOLINAME
---------- --------- ---------- ---------- ------------
2017-02-01 WEDNESDAY 0 1
2017-02-02 THURSDAY 0 1
2017-02-03 FRIDAY 0 1 Some holiday
2017-02-04 SATURDAY 1 0
2017-02-05 SUNDAY 1 0
2017-02-06 MONDAY 0 1
2017-02-07 TUESDAY 0 1
2017-02-08 WEDNESDAY 0 1
Your query could be rewritten as:
from transactions e1
join calendar c on(c.day = trunc(e1.start_date, 'DD')) -- Remove hours, minutes
where e1.start_date between date '2017-02-01' and date '2018-02-01'
and ( c.weekday in('THURSDAY', 'FRIDAY') -- Either specific weekdays
or c.holiname is not null -- or there is a holiday
)
and exists(
select *
from transactions_org e2
where e1.trantype = e2.tran_type
)

Counting the number of days excluding sunday between two dates

I am trying to calculate number of days betwen two dates excluding sundays. This is my query,
SELECT F_PLANHM_END_DT
- F_PLANHM_ST_DT
- 2
* (TO_CHAR (F_PLANHM_END_DT, 'WW') - TO_CHAR (F_PLANHM_ST_DT, 'WW'))
FROM VW_S_CURV_PROC
WHERE HEAD_MARK = 'IGG-BLH-BM 221';
SELECT COUNT (*)
FROM (SELECT SYSDATE + l trans_date
FROM ( SELECT LEVEL - 1 l
FROM VW_S_CURV_PROC
CONNECT BY LEVEL <= ( (SYSDATE + 7) - SYSDATE)))
WHERE TO_CHAR (trans_date, 'dy') NOT IN ('sun');
I am retrieving date from a view called VW_S_CURV_PROC with start date : F_PLANHM_ST_DT and end date F_PLANHM_END_DT. Somehow i cant make this to work. Please help me...
You could use the ROW GENERATOR technique to first generate the dates for a given range, and then exclude the SUNDAYs.
For example, this query will give me the total count of days between 1st Jan 2014 and 31st Dec 2014, excluding the Sundays -
SQL> WITH DATA AS
2 (SELECT to_date('01/01/2014', 'DD/MM/YYYY') date1,
3 to_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual
5 )
6 SELECT SUM(holiday) holiday_count
7 FROM
8 (SELECT
9 CASE
10 WHEN TO_CHAR(date1+LEVEL-1, 'DY','NLS_DATE_LANGUAGE=AMERICAN') <> 'SUN'
11 THEN 1
12 ELSE 0
13 END holiday
14 FROM data
15 CONNECT BY LEVEL <= date2-date1+1
16 )
17 /
HOLIDAY_COUNT
-------------
313
SQL>

Oracle sql sort week days by current day

I am trying to sort the days based on the order: Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday.
I am trying using case:
select day,
CASE day
WHEN 1 THEN 1
WHEN 2 THEN 2
WHEN 3 THEN 3
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 6
WHEN 7 THEN 7
else 0
END as day_nr
from week where day in (1,2,3,4,5,6,7)
order by day_nr asc
This is ok when I select all the days of the week. But if I want only for the day 1,5,6 the ordering is not correct. Gets the first day -Monday. How to proceed?
If you're trying to sort a set of dates by day of the week, with Saturday being the first, then consider ordering by a modified date:
create table t1(my_date date);
insert into t1
select trunc(sysdate)+rownum
from dual
connect by level <= 20
select
my_date,
to_char(my_date,'Day'),
to_char(my_date,'D')
from
t1
order by
to_char(my_date + 1,'D');
http://sqlfiddle.com/#!4/5940b/3
The downside is that it's not very intuitive, so add a code comment if you use this method.
Edit: Where you have a list of numbers, order by a case statement with either a list conversion:
case day
when 1 then 3
when 2 then 4
when 3 then 5
when 4 then 6
when 5 then 7
when 6 then 1 -- saturday
when 7 then 2
end
... or the more compact, but not as intuitive:
case
when day <= 5 then day + 2
else day - 5
end
order by case
In Oracle day 1 is Sunday by default.
SELECT * FROM
(
SELECT trunc(sysdate) + LEVEL-1 my_dt
, TO_CHAR(trunc(sysdate) + LEVEL-1, 'DY') Wk_Day
, TO_CHAR(trunc(sysdate) + LEVEL-2, 'D' ) Day#
FROM dual
CONNECT BY LEVEL <= 10
)
WHERE Day# IN (1,5,6)
ORDER BY my_dt, Day#
/
MY_DT WK_DAY DAY#
------------------------
5/10/2013 FRI 5
5/11/2013 SAT 6
5/13/2013 MON 1
5/17/2013 FRI 5
5/18/2013 SAT 6