Get current day count from current month except holidays - sql

I am a beginner in SQL. I have to fetch current day count(Day Number) from current system month, which should not consider the weekend (Saturday and Sunday).
For example if I am executing the query today (05-Dec-2018) then my output should be 3 (current date is 05-12-2018, here 1st Dec is Saturday and 2nd dec is Sunday. I don't want to include the weekends in this calculation. So Monday (3rd Dec) will be 1, Tue (4th Dec) will be 2 and Wed (5 Dec) will be 3.
Any help for this highly appreciated.

You do not need to use a hierarchical query and can do it independent of the NLS settings using TRUNC( date_value, 'IW' ) to find the start of the ISO week, which is always a Monday.
So:
TRUNC( SYSDATE, 'IW' ) - TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' )
Will find the number of days between the start of the ISO week containing the first day of the month and the start of the current ISO week. Multiplying this by 5/7 will give the number of week days.
Then all you need to find is how many of those days occurred in the previous month and subtract them. This can be found using:
LEAST( TRUNC( SYSDATE, 'MM' ) - TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ), 5 )
and how many days need to be added on for the current week; which is given by:
LEAST( TRUNC( SYSDATE ) - TRUNC( SYSDATE, 'IW' ) + 1, 5 )
So the total can be found using:
SELECT ( TRUNC( SYSDATE, 'IW' ) - TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) ) * 5 / 7
+ LEAST( TRUNC( SYSDATE ) - TRUNC( SYSDATE, 'IW' ) + 1, 5 )
- LEAST( TRUNC( SYSDATE, 'MM' ) - TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ), 5 )
AS Num_Week_Days
FROM DUAL;
An example with multiple days:
WITH calendar ( date_value ) AS (
SELECT DATE '2018-12-01' + LEVEL - 1
FROM DUAL
CONNECT BY LEVEL <= 15
)
SELECT date_value,
TO_CHAR( date_value, 'DY' ) AS day,
( TRUNC( date_value, 'IW' ) - TRUNC( TRUNC( date_value, 'MM' ), 'IW' ) ) * 5 / 7
+ LEAST( TRUNC( date_value ) - TRUNC( date_value, 'IW' ) + 1, 5 )
- LEAST( TRUNC( date_value, 'MM' ) - TRUNC( TRUNC( date_value, 'MM' ), 'IW' ), 5 )
AS Num_Week_Days
FROM Calendar;
Output:
DATE_VALUE DAY NUM_WEEK_DAYS
---------- --- -------------
2018-12-01 SAT 0
2018-12-02 SUN 0
2018-12-03 MON 1
2018-12-04 TUE 2
2018-12-05 WED 3
2018-12-06 THU 4
2018-12-07 FRI 5
2018-12-08 SAT 5
2018-12-09 SUN 5
2018-12-10 MON 6
2018-12-11 TUE 7
2018-12-12 WED 8
2018-12-13 THU 9
2018-12-14 FRI 10
2018-12-15 SAT 10

This type of query is really easy to write if you maintain a calendar table in your database. Here is a query that generates a partial calendar of all days from the start of the month up to and including today. Then we can count the number of weekdays.
select count(*)
from (select trunc(sysdate, 'MM') - 1 + level as d
from dual
connect by level <= trunc(sysdate, 'DD') + 1 -- Today
- trunc(sysdate, 'MM') -- First day of current month
)
-- Exclude weekends
where to_char(d, 'DY', 'nls_date_language=american') not in('SAT', 'SUN')
;

You might use this sql select statement with connect by level clause :
select sum(dy) "Total Day"
from
(
select (case when to_char(sysdate-level+1,'D','nls_date_language=turkish') in (6,7)
then 0
else 1 end ) as dy
from dual
connect by level <= to_number(to_char(sysdate,'DD'))
);
Total Day
---------
3
You can try the other cases by replacing both sysdate keywords with sysdate + 3, sysdate + 4 , sysdate + 5 ... etc.

This should work for SQL Server
SET #today= '2018-12-14'
SET #firstDate = (SELECT DATEADD(month, DATEDIFF(month, 0, #today), 0) AS StartOfMonth);
WITH allDates AS
(
SELECT
#firstDate AS DT
UNION ALL
SELECT
DATEADD(dd, 1, DT)
FROM
allDates as S
WHERE
DATEADD(dd, 1, DT) <= #today
)
SELECT count(*) FROM allDates where DATENAME(dw,DT) <> 'Saturday' and DATENAME(dw,DT) <> 'Sunday' option (maxrecursion 0)

Related

Split date range in weeks in oracle SQL

I need to split the following data in Oracle SQL:
WITH sample_data AS
(SELECT DATE '2020-12-16' Start_Date, DATE '2021-01-07' End_Date FROM DUAL)
in week ranges for every working week(from Monday to Friday) of this given period. The final result it should look like this:
NEW_STARTDATE NEW_END_DATE
2020-12-16 2020-12-18
2020-12-21 2020-12-25
2020-12-28 2021-01-01
2021-01-04 2021-01-07
So in this example, the first row starts with the initial start date (2020-12-16) which is on Wednesday and continues with the new end date(2020-12-18) which is the next Friday, and so on with ranges of working weeks until the actual end date of this period.
You can use:
WITH sample_data ( start_date, end_date ) AS (
SELECT DATE '2020-12-16', DATE '2021-01-07' FROM DUAL
),
weeks ( start_date, week_start, week_end, end_date ) AS (
SELECT start_date,
TRUNC( start_date, 'IW' ),
LEAST( TRUNC( start_date, 'IW' ) + INTERVAL '4' DAY, end_date ),
end_date
FROM sample_data
UNION ALL
SELECT start_date,
week_start + INTERVAL '7' DAY,
LEAST( week_end + INTERVAL '7' DAY, end_date ),
end_date
FROM weeks
WHERE week_start + INTERVAL '7' DAY <= end_date
)
SELECT GREATEST( week_start, start_date ) AS new_start_date,
week_end AS new_end_date
FROM weeks
WHERE GREATEST( week_start, start_date ) <= week_end;
Which outputs (where the NLS_DATE_FORMAT is YYYY-MM-DD (DY)):
NEW_START_DATE
NEW_END_DATE
2020-12-16 (WED)
2020-12-18 (FRI)
2020-12-21 (MON)
2020-12-25 (FRI)
2020-12-28 (MON)
2021-01-01 (FRI)
2021-01-04 (MON)
2021-01-07 (THU)
db<>fiddle here
Here is one way - compute the Monday dates for the ISO week of the input dates (start_date and end_date), while also keeping track of which is the first and which is the last such Monday in the same hierarchical (connect by) query. Then produce the requested output; for the first week, check that the start_date is not a Saturday or a Sunday (if it is, that "first week" should not produce a row in the output); that is done in the where clause. This is illustrated in the sample dates I used for testing - the input "start date" is a Saturday, so the first "new start date" is the following Monday.
with
sample_data (start_date, end_date) as (
select date '2020-12-12', date '2021-01-02' from dual
)
, mondays (dt, rn, mx) as (
select trunc(start_date, 'iw') + 7 * (level - 1), level, max(level) over ()
from sample_data
connect by level <= 1 + (trunc(end_date, 'iw') - trunc(start_date, 'iw'))/7
)
select case rn when 1 then greatest(start_date, dt)
else dt end as new_start_date,
case rn when mx then least(dt + 4, end_date)
else dt + 4 end as new_end_date
from sample_data cross join mondays
where rn >= 2 or start_date <= dt + 4
;
NEW_START_DATE NEW_END_DATE
--------------- ---------------
MON 14-DEC-2020 FRI 18-DEC-2020
MON 21-DEC-2020 FRI 25-DEC-2020
MON 28-DEC-2020 FRI 01-JAN-2021

Oracle SQL counting the days of the week of the same month

How can I count the days of each week for a specific month that the user will give to my SQL query? For example, if the user gives April 2021, the result will be:
If the user gives May 2021 the result will be:
Here is a direct computation of the same. The input is given as a string, such as May 2021; you can use a bind variable in its place. Just keep in mind the possibility that the user may be in a non-English-speaking locale; as long as they use their local language in passing the month to the query, everything should work fine.
with
inputs (mth) as (select 'May 2021' from dual)
, first_day (dt) as (select to_date(mth, 'fmMonth yyyy') from inputs)
, mondays (dt, ord, lst) as (
select trunc(dt, 'iw') + 7 * (level - 1), level, max(level) over ()
from first_day
connect by level <= 1 + (trunc(add_months(dt, 1), 'iw') - trunc(dt, 'iw')) / 7
)
select to_number(to_char(dt, 'iw')) as week_number,
case ord when 1 then dt + 7 - trunc(dt + 7, 'mm')
when lst then last_day(dt) + 1 - dt
else 7 end as week_days
from mondays
order by week_number
;
WEEK_NUMBER WEEK_DAYS
----------- ----------
17 2
18 7
19 7
20 7
21 7
22 1
I think this is the query you are looking for:
SELECT WEEK AS WEEK_NUMBER, COUNT(*) AS WEEK_DAYS
FROM (SELECT TO_CHAR(FIRST_DAY + (LEVEL-1), 'IW') AS WEEK
FROM (SELECT supplied_date AS FIRST_DAY, LAST_DAY(supplied_date) - SUPPLIED_DATE+1 AS DAYS
FROM (SELECT TO_DATE('05/2021', 'MM/YYYY') AS SUPPLIED_DATE
FROM DUAL))
CONNECT BY LEVEL <= DAYS)
GROUP BY WEEK
ORDER BY WEEK;
Just replace the inner TO_DATE with your date.
Edit 1: Additional Column (See Comments)
SELECT MONTH_NAME, WEEK AS WEEK_NUMBER, COUNT(*) AS WEEK_DAYS
FROM (SELECT TO_CHAR(FIRST_DAY, 'MONTH') AS MONTH_NAME, TO_CHAR(FIRST_DAY + (LEVEL-1), 'IW') AS WEEK
FROM (SELECT supplied_date AS FIRST_DAY, TO_CHAR(LAST_DAY(supplied_date), 'DD') AS DAYS
FROM (SELECT TO_DATE('05/2021', 'MM/YYYY') AS SUPPLIED_DATE
FROM DUAL))
CONNECT BY LEVEL <= DAYS)
GROUP BY MONTH_NAME, WEEK
ORDER BY WEEK;
Note: I also changed the way I calculate the number of days in the month.
You can generate the weeks (without needing to use any aggregation) using:
WITH input ( month ) AS (
SELECT DATE '2021-04-01' FROM DUAL
),
weeks_of_month ( month_start, week_start, end_day ) AS (
SELECT TRUNC( month, 'MM' ),
TRUNC( month, 'IW' ),
LAST_DAY( TRUNC( month ) )
FROM input
UNION ALL
SELECT month_start,
week_start + INTERVAL '7' DAY,
end_day
FROM weeks_of_month
WHERE week_start + INTERVAL '7' DAY <= end_day
)
SELECT TO_CHAR( week_start, 'IW' ) AS iso_week,
GREATEST( month_start, week_start ) AS first_day_of_week,
LEAST( end_day, week_start + INTERVAL '6' DAY ) AS last_day_of_week,
LEAST( end_day + 1, week_start + INTERVAL '7' DAY )
- GREATEST( month_start, week_start ) AS days
FROM weeks_of_month;
Which outputs (with the NLS_DATE_FORMAT set to YYYY-MM-DD (DY)):
ISO_WEEK
FIRST_DAY_OF_WEEK
LAST_DAY_OF_WEEK
DAYS
13
2021-04-01 (THU)
2021-04-04 (SUN)
4
14
2021-04-05 (MON)
2021-04-11 (SUN)
7
15
2021-04-12 (MON)
2021-04-18 (SUN)
7
16
2021-04-19 (MON)
2021-04-25 (SUN)
7
17
2021-04-26 (MON)
2021-04-30 (FRI)
5
db<>fiddle here

How to add a quarter to a date in Oracle

I am looking for a way in oracle to add a quarter to a date.
For e.g 28-FEB-21 should become 28-MAY-20
Currently i am using ADD_MONTHS('28-FEB-21',3) to add a quarter but its changing the date to 31-MAY-21
Regards,
You can use:
LEAST(
ADD_MONTHS( value, 3 ),
ADD_MONTHS( TRUNC( value, 'MM' ), 3 )
+ ( value - TRUNC( value, 'MM' ) ) DAY TO SECOND
)
So, if you have the test data:
CREATE TABLE table_name ( value ) AS
SELECT DATE '2019-11-30' FROM DUAL UNION ALL
SELECT DATE '2021-02-28' + INTERVAL '01:23:45' HOUR TO SECOND FROM DUAL;
Then the query:
SELECT value,
LEAST(
ADD_MONTHS( value, 3 ),
ADD_MONTHS( TRUNC( value, 'MM' ), 3 )
+ ( value - TRUNC( value, 'MM' ) ) DAY TO SECOND
) AS value_plus_quarter
FROM table_name;
Outputs:
VALUE | VALUE_PLUS_QUARTER
:------------------ | :------------------
2019-11-30 00:00:00 | 2020-02-29 00:00:00
2021-02-28 01:23:45 | 2021-05-28 01:23:45
db<>fiddle here

Return date value from nth week of month in SQL Developer without creating function

I am writing a select query which has to return date.
Eg: if i need 4th week of friday for current month, then i am expecting 2019-09-27.
But my challenge is i don't want to use any function.
I have tried below code
select next_day( (trunc(to_date('nov','mon'),'mm')-1), 'fri' ) + 21 from dual
Above code gives last week's date, but i am expecting any week's date.
It depends on how you determine which week is in a month.
If want the first week to be the iso-week where any day of the week is in the month:
TRUNC( SYSDATE, 'MM' ) you will get the first day of the month.
TRUNC( first_day_of_month, 'IW' ) will truncate the date to the start of the iso-week (i.e. Monday).
NEXT_DAY( monday_of_week_including_first_day_of_month, 'FRIDAY' ) will find the friday in the iso-week containing the first day of the month.
If you want the Friday of the 4th iso-week then add 3 weeks:
I.e.:
SELECT NEXT_DAY( TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) - INTERVAL '1' DAY, 'FRIDAY' ) + 3 * INTERVAL '7' DAY AS friday4,
NEXT_DAY( TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) - INTERVAL '1' DAY, 'MONDAY' ) + 0 * INTERVAL '7' DAY AS monday1,
NEXT_DAY( TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) - INTERVAL '1' DAY, 'SUNDAY' ) + 0 * INTERVAL '7' DAY AS sunday1
FROM DUAL;
FRIDAY4 | MONDAY1 | SUNDAY1
:-------- | :-------- | :--------
20-SEP-19 | 26-AUG-19 | 01-SEP-19
If you want to use iso-weeks but start where the first week of the month is the iso-week which contains the first Thursday of the month (as is used to calculate when a week is within an iso-year) then you need to use some additional logic to determine this:
SELECT NEXT_DAY(
TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' )
+ CASE
WHEN TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) + INTERVAL '3' DAY >= TRUNC( SYSDATE, 'MM' )
THEN INTERVAL '-1' DAY
ELSE INTERVAL '6' DAY
END,
'FRIDAY'
) + 3 * INTERVAL '7' DAY
AS friday4,
NEXT_DAY(
TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' )
+ CASE
WHEN TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) + INTERVAL '3' DAY >= TRUNC( SYSDATE, 'MM' )
THEN INTERVAL '-1' DAY
ELSE INTERVAL '6' DAY
END,
'MONDAY'
) + 0 * INTERVAL '7' DAY
AS monday4,
NEXT_DAY(
TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' )
+ CASE
WHEN TRUNC( TRUNC( SYSDATE, 'MM' ), 'IW' ) + INTERVAL '3' DAY >= TRUNC( SYSDATE, 'MM' )
THEN INTERVAL '-1' DAY
ELSE INTERVAL '6' DAY
END,
'SUNDAY'
) + 0 * INTERVAL '7' DAY
AS sunday4
FROM DUAL;
FRIDAY4 | MONDAY1 | SUNDAY1
:-------- | :-------- | :--------
27-SEP-19 | 02-SEP-19 | 08-SEP-19
If you want start the counting from the first day of the month (regardless of which day of the week it is):
SELECT NEXT_DAY( TRUNC( SYSDATE, 'MM' ) - INTERVAL '1' DAY, 'FRIDAY' ) + 3 * INTERVAL '7' DAY AS friday4,
NEXT_DAY( TRUNC( SYSDATE, 'MM' ) - INTERVAL '1' DAY, 'MONDAY' ) + 0 * INTERVAL '7' DAY AS monday1,
NEXT_DAY( TRUNC( SYSDATE, 'MM' ) - INTERVAL '1' DAY, 'SUNDAY' ) + 0 * INTERVAL '7' DAY AS sunday1
FROM DUAL;
FRIDAY4 | MONDAY1 | SUNDAY1
:-------- | :-------- | :--------
27-SEP-19 | 02-SEP-19 | 01-SEP-19
db<>fiddle here
Here's one option; calendar CTE I created refers to this September.
SQL> set ver off
SQL>
SQL> with calendar as
2 (select trunc(sysdate, 'mm') + level - 1 datum
3 from dual
4 connect by level <= 30
5 ),
6 inter as
7 (select datum,
8 to_char(datum, 'w') week_in_month,
9 to_char(datum, 'dy', 'nls_date_language=english') day
10 from calendar
11 )
12 select datum
13 from inter
14 where day = '&day'
15 and week_in_month = &week;
Enter value for day: tue
Enter value for week: 2
DATUM
----------
10.09.2019
SQL> /
Enter value for day: fri
Enter value for week: 3
DATUM
----------
20.09.2019
SQL>

Oracle query to get the number of business days between 2 dates, excluding holidays

We are using Oracle 11.
In our CASE WHEN statement, I need to check if the number of days between the 2 dates are > 3 business days (so excluding weekends and holidays).
CASE WHEN end_date - start_date > 3 THEN 0 --> this includes weekend
and holidays
WHEN CODE = 1 THEN 1
WHEN CODE =2 THEN 2
ELSE 3
END AS MyColumn
Say I have a holiday calendar table that has column HolidayDates that contains all the holidays, for ex: 12/25/2018, 12/31/2018, etc.
HolidayDates
12/25/2018
12/31/2018
So, if
Date1 = 1/2/19 (Wednesday)
Date2 = 12/27/18 (Thursday)
The number of business days in between Date1 and Date2 is 3 days (12/27, 12/28 and 12/31).
The below query will get the number of business days excluding weekends.
How do I also exclude holidays in this query ?
SELECT TO_CHAR( start_date, 'YYYY-MM-DD "("DY")"') AS start_date,
( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( TRUNC( end_date ) - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( TRUNC( start_date ) - TRUNC( start_date, 'IW' ), 5 )
AS Num_Week_Days
FROM table_name;
Thank you.
Taking the code in this previous answer and converting it from a function to a query gives:
Oracle Setup:
CREATE TABLE Holidays ( HolidayDates ) AS
SELECT DATE '2018-12-25' FROM DUAL UNION ALL
SELECT DATE '2018-12-31' FROM DUAL;
CREATE TABLE table_name ( start_date, end_date ) AS
SELECT DATE '2018-12-21', DATE '2018-12-26' FROM DUAL UNION ALL
SELECT DATE '2018-12-28', DATE '2019-01-01' FROM DUAL;
Query:
SELECT t.*,
( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( TRUNC( end_date ) - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( TRUNC( start_date ) - TRUNC( start_date, 'IW' ), 5 )
- ( SELECT COUNT(1)
FROM holidays
WHERE HolidayDates BETWEEN t.start_date AND t.end_date
-- Exclude any weekend holidays so we don't double count.
AND TRUNC( HolidayDates ) - TRUNC( HolidayDates, 'IW' ) <= 5
)
AS Num_Week_Days
FROM table_name t;
Output:
START_DATE | END_DATE | NUM_WEEK_DAYS
:--------- | :-------- | ------------:
21-DEC-18 | 26-DEC-18 | 3
28-DEC-18 | 01-JAN-19 | 2
01-JAN-19 | 07-JAN-19 | 5
db<>fiddle here