sql oracle ignore holidays - sql

I am using this code to calculate the difference between two dates ignoring weekends:
SELECT To_date(SYSDATE) -
To_date('01.07.2014', 'DD.MM.YYYY')
- 2 * ( TRUNC(Next_day(To_date(SYSDATE) - 1, 'FRI'))
- TRUNC( Next_day(To_date('01.07.2014' , 'DD.MM.YYYY')
- 1, 'FRI')) ) / 7 AS DAYS_BETWEEN
FROM dual
I have another table called table1 in which the column "date" exists (its type is "DATE") in which all dates where a holiday is are written down.
Example table 1:
DATES
12.06.2011
19.06.2014
09.05.2013
...
I am trying to make my code check this table and that if one date is between the two dates above it makes -1 day in the output.

It should be easy if you divide it into following tasks:
Generate all the dates between the two given dates using Row Generator method as shown here.
Ignore the dates which are weekend, i.e. Saturdays and Sundays
Check whether the dates in the range are having any match in the holiday table.
The following row generator query will give you the total count of weekdays, i.e. not including Saturdays and Sundays:
SQL> WITH dates 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(weekday) weekday_count
7 FROM
8 (SELECT
9 CASE
10 WHEN TO_CHAR(date1+LEVEL-1, 'DY','NLS_DATE_LANGUAGE=AMERICAN')
11 NOT IN ('SAT', 'SUN')
12 THEN 1
13 ELSE 0
14 END weekday
15 FROM dates
16 CONNECT BY LEVEL <= date2-date1+1
17 )
18 /
WEEKDAY_COUNT
-------------
261
SQL>
Now, based on above row generator query, let's see a test case.
The following query will calculate the count of working days between 1st Jan 2014 and 31st Dec 2014 excluding the holidays as mentioned in the table.
The WITH clause is only to use it as tables, in your case you can simply use your holiday table.
SQL> WITH dates
2 AS (SELECT To_date('01/01/2014', 'DD/MM/YYYY') date1,
3 To_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual),
5 holidays
6 AS (SELECT To_date('12.06.2011', 'DD.MM.YYYY') holiday FROM dual UNION ALL
7 SELECT To_date('19.06.2014', 'DD.MM.YYYY') holiday FROM dual UNION ALL
8 SELECT To_date('09.05.2013', 'DD.MM.YYYY') holiday FROM dual),
9 count_of_weekdays
10 AS (SELECT SUM(weekday) weekday_count
11 FROM (SELECT CASE
12 WHEN To_char(date1 + LEVEL - 1, 'DY',
13 'NLS_DATE_LANGUAGE=AMERICAN')
14 NOT IN (
15 'SAT',
16 'SUN' ) THEN 1
17 ELSE 0
18 END weekday
19 FROM dates
20 CONNECT BY LEVEL <= date2 - date1 + 1)),
21 count_of_holidays
22 AS (SELECT Count(*) holiday_count
23 FROM holidays
24 WHERE holiday NOT BETWEEN To_date('01/01/2015', 'DD/MM/YYYY') AND
25 To_date('31/03/2015', 'DD/MM/YYYY'))
26 SELECT weekday_count - holiday_count as working_day_count
27 FROM count_of_weekdays,
28 count_of_holidays
29 /
WORKING_DAY_COUNT
-----------------
258
SQL>
There were total 261 weekdays, out of which there were 3 holidays in holiday table. So, total count of working days in the output is 261 - 3 = 258.

SELECT To_date(sysdate)- To_date('01.07.2014','DD.MM.YYYY')
- (2 * (to_char(To_date(sysdate), 'WW') - to_char(To_date('01.07.2014','DD.MM.YYYY'), 'WW'))) AS DAYS_BETWEEN
FROM dual

Related

How to count number of records for each week, from last month activity on a table?

I'm working with Oracle and I have a table with a column of type TIMESTAMP. I was wondering how can I extract the records from last 4 weeks of activity on the database, partitioned by week.
Following rows are inserted on week 1
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
Following rows are inserted on week2
cv 1 12-10-2021
ck 5 14-10-2021
Following rows are inserted on week3
vv 7 19-10-2021
Following rows are inserted on week4
vx 7 29-10-2021
Table now has
SQL>select * from tab;
NAME VALUE TIMESTAMP
-------------------- ----------
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
cv 1 12-10-2021
ck 5 14-10-2021
vv 7 19-10-2021
vx 7 29-10-2021
I would like a query which would give me the number of rows added each week, in the last 4 weeks.
This is what I would like to see
numofrows week
--------- -----
3 1
2 2
1 3
1 4
One option is to use to_char function and its iw parameter:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 )
10 select to_char(datum, 'iw') week,
11 count(*)
12 from test
13 where datum >= add_months(sysdate, -1) --> the last month
14 group by to_char(datum, 'iw');
WE COUNT(*)
-- ----------
42 1
43 1
40 3
41 2
SQL>
Line #13: I intentionally used "one month" instead of "4 weeks" as I thought (maybe wrongly) that you, actually, want that (you know, "a month has 4 weeks" - not exactly, but close, sometimes not close enough).
If you want 4 weeks, what is that, then? Sysdate minus 28 days (as every week has 7 days)? Then you'd modify line #13 to
where datum >= trunc(sysdate - 4*7)
Or, maybe it is really the last 4 weeks:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 ),
10 temp as
11 (select to_char(datum, 'iw') week,
12 count(*) cnt,
13 row_number() over (order by to_char(datum, 'iw') desc) rn
14 from test
15 group by to_char(datum, 'iw')
16 )
17 select week, cnt
18 from temp
19 where rn <= 4
20 order by week;
WE CNT
-- ----------
40 3
41 2
42 1
43 1
SQL>
Now you have several options, see which one fits the best (if any).
I "simulated" missing data (see TEST CTE), created a calendar (calend) and ... did the job. Read comments within code:
SQL> with test (name, datum) as
2 -- sample data
3 (select 'vv', date '2021-10-19' from dual union all
4 select 'vx', DATE '2021-10-29' from dual
5 ),
6 calend as
7 -- the last 31 days; 4 weeks are included, obviously
8 (select max_datum - level + 1 datum
9 from (select max(a.datum) max_datum from test a)
10 connect by level <= 31
11 ),
12 joined as
13 -- joined TEST and CALEND data
14 (select to_char(c.datum, 'iw') week,
15 t.name
16 from calend c left join test t on t.datum = c.datum
17 ),
18 last4 as
19 -- last 4 weeks
20 (select week, count(name) cnt,
21 row_number() over (order by week desc) rn
22 from joined
23 group by week
24 )
25 select week, cnt
26 from last4
27 where rn <= 4
28 order by week;
WE CNT
-- ----------
40 0
41 0
42 1
43 1
SQL>

Get count days in current year by day name or day id

name id
----------------
Mon 1
Thu 2
Wen 3
Thr 4
Fri 5
Sat 6
San 7
How get count day where id in eg. (1,2,3,4) and year is 2021
The result should be 208.
Actually, it is 208 for year 2021.
SQL> WITH
2 year AS (SELECT &par_year year FROM DUAL),
3 calendar
4 AS
5 ( SELECT TRUNC (TO_DATE (y.year, 'yyyy'), 'yyyy') + LEVEL - 1 datum
6 FROM year y
7 CONNECT BY LEVEL <=
8 ADD_MONTHS (TRUNC (TO_DATE (y.year, 'yyyy'), 'yyyy'), 12)
9 - TRUNC (TO_DATE (y.year, 'yyyy'), 'yyyy'))
10 SELECT SUM (CASE
11 WHEN TO_NUMBER (TO_CHAR (c.datum, 'd')) IN (1,
12 2,
13 3,
14 4)
15 THEN
16 1
17 ELSE
18 0
19 END) result
20 FROM calendar c;
Enter value for par_year: 2021
RESULT
----------
208
SQL> /
Enter value for par_year: 2020
RESULT
----------
210
SQL>
What does it do?
YEAR CTE contains year you're interested in
CALENDAR CTE creates all dates in that particular year
SUM function conditionally adds 1 if TO_CHAR(datum, 'd') is 1, 2, 3 or 4
that's all

How can I generate 4-week ranges of dates?

I have to generate date range for 1 year in gap of 4 weeks ( or 28 days ) from a fixed date backward and going forward. For example I have DATE '2016-02-20'. I need to generate the below.
Start date = Sunday , End-date = Saturday
No Start_date End_date
==== ========= =======
1 1/24/2016 2/20/2016
2 12/27/2015 1/23/2016
3 11/29/2015 12/26/2015
4 .....
13 2/22/2015 3/21/2015
14 1/25/2015 2/21/2015
But,when 03/20/2016(Sunday) comes,it should add
1. 2/21/2016 3/19/2016 & remove 14. 1/25/2015 2/21/2015
and so on after every 4 weeks.
I have written the below, but I need help to iterate in minimal code( if possible.)
SELECT LEVEL,
DATE '2016-02-20'-27*LEVEL-LEVEL+1 AS start_date,
DATE '2016-02-20'-28*(LEVEL-1) AS end_date
FROM DUAL
Connect BY LEVEL < 15;
It seems you want to have a rolling window of a year's worth of four-week ranges, based from the current date. To do that you need a fixed known period start (or end) date you can work from. Picking one that happens to be January 1st you can do:
SELECT DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
Which will find 54 periods up to today. On March 21st it will find 55 periods, etc. You only want those that are in the last year, so use that as an inline view and restrict the range:
SELECT ROW_NUMBER() OVER (ORDER BY start_date DESC) AS no, start_date, end_date
FROM (
SELECT DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
)
WHERE end_date >= ADD_MONTHS(TRUNC(sysdate), -12)
ORDER BY start_date DESC;
NO START_DATE END_DATE
---------- ---------- ----------
1 01/24/2016 02/20/2016
2 12/27/2015 01/23/2016
3 11/29/2015 12/26/2015
...
11 04/19/2015 05/16/2015
12 03/22/2015 04/18/2015
13 02/22/2015 03/21/2015
The ROW_NUMBER() just generates your NO column, as the LEVEL is now in the wrong order.
If you always want exactly 14 rows in the result set you can move the ROW_NUMBER() into the inline view:
SELECT no, start_date, end_date
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY LEVEL DESC) AS no,
DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
)
WHERE no <= 14
ORDER BY no;
NO START_DATE END_DATE
---------- ---------- ----------
1 01/24/2016 02/20/2016
2 12/27/2015 01/23/2016
3 11/29/2015 12/26/2015
...
12 03/22/2015 04/18/2015
13 02/22/2015 03/21/2015
14 01/25/2015 02/21/2015
14 rows selected

Calculating holidays: number of saturdays and sundays within the given date range query in Oracle

I want to calculate holidays: the number of Saturdays and Sundays within the given date range query in Oracle.
You could use the ROW GENERATOR technique to first generate the dates for a given range, and then count only the SATURDAYs and SUNDAYs.
For example, this query will give me the total count of saturdays and sundays between 1st Jan 2014 and 31st Dec 2014 -
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') IN ('SAT', 'SUN')
11 THEN 1
12 ELSE 0
13 END holiday
14 FROM data
15 CONNECT BY LEVEL <= date2-date1+1
16 )
17 /
HOLIDAY_COUNT
-------------
104
SQL>

Number of days in a month

I have a monthly amount that I need to spread equally over the number of days in the month. The data looks like this:
Month Value
----------- ---------------
01-Jan-2012 100000
01-Feb-2012 121002
01-Mar-2012 123123
01-Apr-2012 118239
I have to spread the Jan amount over 31 days, the Feb amount over 29 days and the March amount over 31 days.
How can I use PL/SQL to find out how many days there are in the month given in the month column?
SELECT CAST(to_char(LAST_DAY(date_column),'dd') AS INT)
FROM table1
Don't use to_char() and stuff when doing arithmetics with dates.
Strings are strings and dates are dates. Please respect the data types and use this instead:
1+trunc(last_day(date_column))-trunc(date_column,'MM')
Indeed, this is correct. It computes the difference between the value of the last day of the month and the value of the first (which is obviously always 1 and therefore we need to add this 1 again).
You must not forget to use the trunc() function if your date columns contains time, because last_day() preserves the time component.
SELECT EXTRACT(DAY FROM LAST_DAY(SYSDATE)) num_of_days FROM dual;
/
SELECT SYSDATE, TO_CHAR(LAST_DAY(SYSDATE), 'DD') num_of_days FROM dual
/
-- Days left in a month --
SELECT SYSDATE, LAST_DAY(SYSDATE) "Last", LAST_DAY(SYSDATE) - SYSDATE "Days left"
FROM DUAL
/
You can add a month and subtract the two dates
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2012-01-01' dt from dual union all
3 select date '2012-02-01' from dual union all
4 select date '2012-03-01' from dual union all
5 select date '2012-01-31' from dual
6 )
7 select dt, add_months(trunc(dt,'MM'),1) - trunc(dt,'MM')
8* from x
SQL> /
DT ADD_MONTHS(TRUNC(DT,'MM'),1)-TRUNC(DT,'MM')
--------- -------------------------------------------
01-JAN-12 31
01-FEB-12 29
01-MAR-12 31
31-JAN-12 31
select add_months(my_date, 1)-my_date from dual;
SELECT TO_CHAR(LAST_DAY(SYSDATE), 'fmday-mon-rr dd') as LastDayOfMonth
FROM dual;
Use the following Oracle query:
select to_number(to_char(last_day(sysdate),'dd')) TotalDays from dual
Date_Parameter='01-Oct-2017'
select to_number(to_char(last_day('Date_Parameter'),'dd')) TotalDays from dual