Find number of Wednesdays (or other weekday) in a month - Oracle SQL - oracle

I found this query for finding the number of Sundays in a month.
I have been tinkering with it but cannot figure out how to change it to get, say, the number of Wednesdays in a month, for example. Can you show me how?
with
months as (
select add_months(trunc(sysdate,'YEAR'),level-13) month
from dual
connect by level <= 36
)
select to_char(month,'YYYY') year,
to_char(month,'Month') month,
to_char(month,'Day') first_day,
to_char(last_day(month),'Day DD') last_day,
4+
case
when to_char(last_day(month),'DD') - decode(to_char(month,'D'),1,0,8 -to_char(month,'D')) >= 29
then 1
else 0
end nb_sunday
from months

Here's the game: You give me a year (like 2015) and a day of the week, in the form of a three-letter string (like 'Wed'). I will return a table with each month of that year and with the count of days-of-week equal to your input in each month.
Simply implementing here the suggestion from my Comment to MT0's Answer. I am hard-coding the year and the day-of-week (in a CTE) since "how to pass parameters to a query" (through bind variables and such) is not the focus in this thread.
with
inputs ( yr, day_of_week ) as (
select 2015, 'Wed' from dual
),
prep ( dec31 ) as (
select to_date(to_char(yr - 1) || '-12-31', 'yyyy-mm-dd') from inputs
)
select to_char(add_months(dec31, level), 'Mon-yyyy') as mth,
( next_day(add_months(dec31, level) , day_of_week) -
next_day(add_months(dec31, level - 1), day_of_week) ) / 7 as cnt
from inputs cross join prep
connect by level <= 12;
MTH CNT
-------- ----
Jan-2015 4
Feb-2015 4
Mar-2015 4
Apr-2015 5
May-2015 4
Jun-2015 4
Jul-2015 5
Aug-2015 4
Sep-2015 5
Oct-2015 4
Nov-2015 4
Dec-2015 5
12 rows selected.

The last wednesday of the month is given by:
TRUNC( NEXT_DAY( LAST_DAY( :month ) - INTERVAL '7' DAY, 'WEDNESDAY' ) )
The first wednesday of the month is given by:
NEXT_DAY( TRUNC( :month, 'MM' ) - INTERVAL '1' DAY, 'WEDNESDAY' )
Subtracting gives the number of days between them. Divide by 7 and add 1 and you get the number of Wednesdays:
SELECT ( TRUNC( NEXT_DAY( LAST_DAY( :month ) - INTERVAL '7' DAY, 'WEDNESDAY' ) )
- NEXT_DAY( TRUNC( :month, 'MM' ) - INTERVAL '1' DAY, 'WEDNESDAY' )
) / 7 + 1
AS number_of_wednesdays
FROM DUAL;
Or you can just use the difference between the first Wednesday of the month and of the next month as suggested by #mathguy
SELECT ( NEXT_DAY(
ADD_MONTHS( TRUNC( :month, 'MM' ), 1 ) - INTERVAL '1' DAY,
'WEDNESDAY'
)
- NEXT_DAY(
TRUNC( :month, 'MM' ) - INTERVAL '1' DAY,
'WEDNESDAY'
)
) / 7
AS number_of_wednesdays
FROM DUAL;

Related

Calculate only working hours between dates in ORACLE

Hello everyone I have written an oracle query which is calculating working according to 8 hours, but I want according to 8.5 hours result, there is a minor change but I am not getting it please help. Now according to the start and end date, it should return 8.5 working house, but it is returning 8 working hours please assist.
Query
with dates as (
select to_date('20-oct-2022 09:00:00','dd-mon-yyyy hh24:mi:ss') start_dt,
to_date('20-oct-2022 17:30:00','dd-mon-yyyy hh24:mi:ss') end_dt
from dual
),
-- get work hours for each date
t as (
select case level
when 1 then greatest(start_dt,trunc(start_dt) + 8 / 24)
else trunc(start_dt) + level - 16 / 24
end start_dt,
case connect_by_isleaf
when 1 then least(end_dt,trunc(end_dt) + 17 / 24)
else trunc(start_dt) + level - 7 / 24
end end_dt
from dates
connect by level <= trunc(end_dt) - trunc(start_dt) + 1
)
select sum(greatest(end_dt - start_dt,0)) * 24 work_hours
from t
where trunc(start_dt) - trunc(start_dt,'iw') < 5
You do not need to generate all the days; you can directly calculate the number of hours:
SELECT start_dt,
end_dt,
ROUND(
(
-- Calculate the full weeks difference from the start of ISO weeks.
( TRUNC( end_dt, 'IW' ) - TRUNC( start_dt, 'IW' ) ) * 8.5 * (5/7)
-- Add the full days for the final week.
+ LEAST( TRUNC( end_dt ) - TRUNC( end_dt, 'IW' ), 5 ) * 8.5
-- Subtract the full days from the days of the week before the start date.
- LEAST( TRUNC( start_dt ) - TRUNC( start_dt, 'IW' ), 5 ) * 8.5
-- Add the hours of the final day
+ CASE
WHEN end_dt - TRUNC( end_dt, 'IW' ) < 5 -- Weekday
THEN LEAST(
GREATEST(
end_dt - (TRUNC( end_dt ) + INTERVAL '09:00' HOUR TO MINUTE),
0
) * 24,
8.5
)
ELSE 0
END
-- Subtract the hours of the day before the range starts.
- CASE
WHEN start_dt - TRUNC( start_dt, 'IW' ) < 5 -- Weekday
THEN LEAST(
GREATEST(
start_dt - (TRUNC( start_dt ) + INTERVAL '09:00' HOUR TO MINUTE),
0
) * 24,
8.5
)
ELSE 0
END
),
15 -- Number of decimal places
) AS work_hours_diff
FROM dates;
Which, for the sample data:
CREATE TABLE dates (start_dt, end_dt) AS
SELECT DATE '2022-10-20' + INTERVAL '09:00:00' HOUR TO SECOND,
DATE '2022-10-20' + INTERVAL '17:30:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-10-20' + INTERVAL '10:00:00' HOUR TO SECOND,
DATE '2022-10-21' + INTERVAL '17:30:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-19' + INTERVAL '23:46:00' HOUR TO SECOND,
DATE '2022-11-21' + INTERVAL '12:06:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-18' + INTERVAL '17:30:00' HOUR TO SECOND,
DATE '2022-11-21' + INTERVAL '09:00:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-19' + INTERVAL '12:26:45' HOUR TO SECOND,
DATE '2022-11-21' + INTERVAL '11:02:15' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-21' + INTERVAL '11:02:15' HOUR TO SECOND,
DATE '2022-11-19' + INTERVAL '12:26:45' HOUR TO SECOND
FROM DUAL;
Outputs:
START_DT
END_DT
WORK_HOURS_DIFF
2022-10-20 09:00:00 (THU)
2022-10-20 17:30:00 (THU)
8.5
2022-10-20 10:00:00 (THU)
2022-10-21 17:30:00 (FRI)
16
2022-11-19 23:46:00 (SAT)
2022-11-21 12:06:00 (MON)
3.1
2022-11-18 17:30:00 (FRI)
2022-11-21 09:00:00 (MON)
0
2022-11-19 12:26:45 (SAT)
2022-11-21 11:02:15 (MON)
2.0375
2022-11-21 11:02:15 (MON)
2022-11-19 12:26:45 (SAT)
-2.0375
Note: the negative value is valid as the start date is after the end date for that row.
fiddle

How to get last workday before holiday in Oracle [duplicate]

This question already has answers here:
How to get the previous working day from Oracle?
(4 answers)
Closed 1 year ago.
need help for some oracle stuff ..
I need to get Day-1 from sysdate, holiday and weekend will be excluded .
And for holiday, we need to get the range to get the last workday before holiday.
The start date and end date will coming from my holiday table.
ex :
Holiday Table
HolidayName
Start_date
End_Date
holiday1
5th Aug'21
6th Aug'21
condition :
this query run on 9th Aug 2021
expected result :
4th Aug'21
I've tried some query and function but I just can't get what I need.
Thanks a lot for help!
Here's one way to do it.
select max(d) as last_workday
from (select trunc(sysdate)-level as d from dual connect by level < 30) prior_month
where to_char(d, 'DY') not in ('SAT','SUN')
and not exists (select holidayname from holiday_table
where prior_month.d between start_date and end_date)
;
Without seeing your Holiday table, it's hard to say how many days back you would need to look to find the last workday. If you have a holiday that lasts for more than 30 days, you'll need to change the 30 to a larger number.
You can use a simple case expression to determine what day of the week the start of your holiday is, then subtract a number of days based on that.
WITH
holiday (holidayname, start_date, end_date)
AS
(SELECT 'holiday1', DATE '2021-8-5', DATE '2021-8-6' FROM DUAL
UNION ALL
SELECT 'Christmas', DATE '2021-12-25', DATE '2021-12-26' FROM DUAL
UNION ALL
SELECT 'July 4th', DATE '2021-7-4', DATE '2021-7-5' FROM DUAL)
SELECT holidayname,
start_date,
end_date,
start_date - CASE TO_CHAR (start_date, 'Dy') WHEN 'Mon' THEN 3 WHEN 'Sun' THEN 2 ELSE 1 END AS prior_business_day
FROM holiday;
HOLIDAYNAME START_DATE END_DATE PRIOR_BUSINESS_DAY
______________ _____________ ____________ _____________________
holiday1 05-AUG-21 06-AUG-21 04-AUG-21
Christmas 25-DEC-21 26-DEC-21 24-DEC-21
July 4th 04-JUL-21 05-JUL-21 02-JUL-21
You can use a recursive sub-query factoring clause from this answer:
WITH start_date (dt) AS (
SELECT DATE '2021-05-02' FROM DUAL
),
days ( dt, day, found ) AS (
SELECT dt,
TRUNC(dt) - TRUNC(dt, 'IW'),
0
FROM start_date
UNION ALL
SELECT dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END,
CASE WHEN day IN (0, 6, 5) THEN 4 ELSE day - 1 END,
CASE WHEN h.start_date IS NULL THEN 1 ELSE 0 END
FROM days d
LEFT OUTER JOIN holidays h
ON ( dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END
BETWEEN h.start_date AND h.end_date )
WHERE found = 0
)
SELECT dt
FROM days
WHERE found = 1;
Which, for the sample data:
CREATE TABLE holidays (HolidayName, Start_date, End_Date) AS
SELECT 'holiday1', DATE '2021-08-05', DATE '2021-08-06' FROM DUAL;
Outputs:
DT
2021-08-04 00:00:00
db<>fiddle here
Don't know if it's very efficient. Did it just for fun
create table holidays (
holiday_name varchar2(100) primary key,
start_date date not null,
end_date date not null
)
/
Table created
insert into holidays (holiday_name, start_date, end_date)
values ('holiday1', date '2021-08-05', date '2021-08-06');
1 row inserted
with days_before(day, wrk_day) as
(select trunc(sysdate - 1) d,
case
when h.holiday_name is not null then 0
when to_char(trunc(sysdate - 1), 'D') in ('6', '7') then 0
else 1
end work_day
from dual
left join holidays h
on trunc(sysdate - 1) between h.start_date and h.end_date
union all
select db.day - 1,
case
when h.holiday_name is not null then 0
when to_char(db.day - 1, 'D') in ('6', '7') then 0
else 1
end work_day
from days_before db
left join holidays h
on db.day - 1 between h.start_date and h.end_date
where db.wrk_day = 0) search depth first by day set order_no
select day from days_before where wrk_day = 1;
DAY
-----------
04.08.2021

How can I return the employee names from table hr.employees who were hired on first half of the month in last month of every quarter

i need to get the employees that hired in the first half in the last month of a quarters
like i need the employees that hired from(1/3 to 15/3) and (1/6 to 15/6) same in months 9 and 12
You can use MONTHS_BETWEEN and TRUNC:
If you want the rows where the instant of the hire_date is in the first half of the month of the 3rd month of the quarter then:
SELECT *
FROM hr.employees
WHERE MONTHS_BETWEEN( hire_date, TRUNC( hire_date, 'Q' ) ) BETWEEN 2 AND 2.5;
If you want the rows where the entirety of the day of the hire_date is within the first half of the month of the 3rd month of the quarter then:
SELECT *
FROM employees
WHERE MONTHS_BETWEEN(
TRUNC( hire_date ) + INTERVAL '23:59:59' HOUR TO SECOND,
TRUNC( hire_date, 'Q' )
) BETWEEN 2 AND 2.5;
or, if you want to manually set the bounds:
SELECT *
FROM employees
WHERE EXTRACT( MONTH FROM hire_date ) IN ( 3, 6, 9, 12 )
AND EXTRACT( DAY FROM hire_date ) BETWEEN 1 AND 15;
db<>fiddle here

Oracle: Days between two date and Exclude weekdays how to handle negative numbers

I have two date columns and trying to measure days between the two dates excluding weekends. I'm getting a negative number and need help solving.
Table
CalendarDate DayNumber FirstAssgn FirstCnt DayNumber2 Id BusinessDays
5/21/2017 Sunday 5/21/17 5/21/17 Sunday 1 -1
Query:
TRUNC(TO_DATE(A.FIRST_CONTACT_DT, 'DD/MM/YYYY')) - TRUNC(TO_DATE(A.FIRST_ASSGN_DT, 'DD/MM/YYYY'))
- ((((TRUNC(A.FIRST_CONTACT_DT,'D'))-(TRUNC(A.FIRST_ASSGN_DT,'D')))/7)*2)
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english') ='SUN' THEN 1 ELSE 0 END)
- (CASE WHEN TO_CHAR(A.FIRST_CONTACT_DT,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END)
- (SELECT COUNT(1) FROM HUM.CALENDAR CAL
WHERE 1=1
AND CAL.CALENDAR_DATE >= A.FIRST_ASSGN_DT
AND CAL.CALENDAR_DATE < A.FIRST_CONTACT_DT
--BETWEEN A.FIRST_ASSGN_DT AND A.FIRST_CONTACT_DT
AND CAL.GRH_HOLIDAY_IND = 'Y'
) AS Business_Days
Looks like below piece needs editing...
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english')='SUN' THEN 1 ELSE 0 END)
Adapted from my answer here:
Get the number of days between the Mondays of both weeks (using TRUNC( datevalue, 'IW' ) as an NLS_LANGUAGE independent method of finding the Monday of the week) then add the day of the week (Monday = 1, Tuesday = 2, etc., to a maximum of 5 to ignore weekends) for the end date and subtract the day of the week for the start date. Like this:
SELECT ( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( end_date - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( start_date - TRUNC( start_date, 'IW' ) + 1, 5 )
AS WeekDaysDifference
FROM your_table
With RANGE_TEMP as (
SELECT
STARTPERIOD start_date,
ENDPERIOD end_date
FROM
TABLE_DATA -- YOUR TABLE WITH ALL DATA DATE
), DATE_TEMP AS (
SELECT
(start_date + LEVEL) DATE_ALL
FROM
RANGE_TEMP
CONNECT BY LEVEL <= (end_date - start_date)
), WORK_TMP as (
SELECT
COUNT(DATE_ALL) WORK_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('1','7')
), BUSINESS_TMP as (
SELECT
COUNT(DATE_ALL) BUSINESS_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('1','7')
)
SELECT
L.WORK_DATE,
H.BUSINESS_DATE
FROM
BUSINESS_TMP H,
WORK_TMP L
;

Between MOnths In oracle when date is greater than 10 of every month

My question is basically i want to increment months by one USING MONTHS_BETWEEN IN ORACLE
when date is greater than 10 of every month my query is :
CASE
when
TRUNC( months_between(TO_DATE(K.RECORD_DATE,'DD/MM/YYYY'),TO_DATE(K.DUE_DATE,'DD/MM/YYYY')) ) <= 0 then 0
when
--to_number(to_char(K.RECORD_DATE,'dd')) >10
TO_NUMBER(TO_CHAR( TO_DATE(k.RECORD_DATE,'DD/MM/YYYY'),'DD')) > 10
then
TRUNC( months_between(K.RECORD_DATE,K.DUE_DATE) )+1
else
TRUNC( months_between(K.RECORD_DATE,K.DUE_DATE) )
end as mths
FROM
TBL_PAYMENT_DTL K
Use to_date() in months_between().
select
CASE
when
TRUNC( months_between(TO_DATE(K.RECORD_DATE,'DD/MM/YYYY'),TO_DATE(K.DUE_DATE,'DD/MM/YYYY')) ) <= 0 then 0
when
--to_number(to_char(K.RECORD_DATE,'dd')) >10
TO_NUMBER(TO_CHAR( TO_DATE(k.RECORD_DATE,'DD/MM/YYYY'),'DD')) > 10
then
TRUNC( months_between(TO_DATE(k.RECORD_DATE,'DD/MM/YYYY'),TO_DATE(k.DUE_DATE,'DD/MM/YYYY')) )+1
else
TRUNC( months_between(TO_DATE(k.RECORD_DATE,'DD/MM/YYYY'),TO_DATE(k.DUE_DATE,'DD/MM/YYYY')) )
end as mths
FROM
TBL_PAYMENT_DTL K
You can use the EXTRACT function to get the day part of the date, as below:
SELECT
CASE
when
extract(day from K.RECORD_DATE) > 10 then K.RECORD_DATE
else
add_months(K.RECORD_DATE, 1)
end as mths
from
TBL_PAYMENT_DTL K

Resources