How to add a quarter to a date in Oracle - sql

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

Related

Oracle query to Exclude weekends, and 6PM to 9PM

I am trying to achieve a query that returns the time difference between two dates excluding weekends(Saturday and Sunday) and excluding time (6 pm-9 am).
For now, I have a function that is excluding the weekends, But I am unable to exclude time from the query. Can anyone help with this?
The article from which I take help is this
CREATE OR REPLACE FUNCTION get_bus_minutes_between(
p_start_date DATE,
p_end_date DATE
)
RETURN NUMBER
DETERMINISTIC -- ***** Can't hurt
IS
days_diff NUMBER := 0;
end_date DATE := p_end_date;
minutes_diff NUMBER;
start_date DATE := p_start_date;
weeks_diff NUMBER;
BEGIN
IF start_date <= end_date
THEN
-- Move start_date and end_date away from weekends
IF start_date > TRUNC (start_date, 'IW') + 5
THEN -- Use next Monday for start_date
start_date := TRUNC (start_date, 'IW') + 7;
END IF;
IF end_date > TRUNC (end_date, 'IW') + 5
THEN -- Use Friday quitting time
end_date := TRUNC (end_date, 'IW') + 4 + (16.5 / 24);
END IF;
-- Move start_date into the same weeek as end_date
-- (Remember how many weeks we had to move it)
weeks_diff := ( TRUNC (end_date, 'IW')
- TRUNC (start_date, 'IW')
) / 7;
IF weeks_diff > 0
THEN
start_date := start_date + (7 * weeks_diff);
END IF;
-- Make start_date the same day as end_date
-- (Remember how many days we had to move it)
days_diff := TRUNC (end_date) - TRUNC (start_date);
IF days_diff > 0
THEN
start_date := start_date + days_diff;
END IF;
-- Move start_date up to starting time
start_date := GREATEST ( start_date
, TRUNC (start_date) + (8.75 / 24)
);
-- Move end_date back to quitting time
end_date := LEAST ( end_date
, TRUNC (end_date) + ( CASE
WHEN TO_CHAR ( end_date
, 'DY'
, 'NLS_DATE_LANGUAGE=ENGLISH'
) = 'FRI'
THEN 16.5
ELSE 17
END
/ 24
)
);
minutes_diff := ( GREATEST ( 0
, end_date - start_date
)
* 24 * 60
)
+ (days_diff * 495) -- 495 minutes per full day (Mon.-Thu.)
+ (weeks_diff * 2445); -- 2445 minutes per full week
ELSIF start_date > end_date
THEN
minutes_diff := -get_bus_minutes_between (end_date, start_date);
ELSE -- One of the arguments was NULL
minutes_diff := NULL;
END IF;
RETURN ROUND(minutes_diff);
END get_bus_minutes_between;
You can directly calculate the difference in days (adapted from my answer here):
SELECT start_date,
end_date,
ROUND(
(
-- Calculate the full weeks difference from the start of ISO weeks.
( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * (9/24) * (5/7)
-- Add the full days for the final week.
+ LEAST( TRUNC( end_date ) - TRUNC( end_date, 'IW' ), 5 ) * (9/24)
-- Subtract the full days from the days of the week before the start date.
- LEAST( TRUNC( start_date ) - TRUNC( start_date, 'IW' ), 5 ) * (9/24)
-- Add the hours of the final day
+ LEAST( GREATEST( end_date - TRUNC( end_date ) - 9/24, 0 ), 9/24 )
-- Subtract the hours of the day before the range starts.
- LEAST( GREATEST( start_date - TRUNC( start_date ) - 9/24, 0 ), 9/24 )
)
-- Multiply to give minutes rather than fractions of full days.
* 24 * 60
) AS work_day_mins_diff
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name ( start_date, end_date ) AS
SELECT DATE '2020-12-30' + INTERVAL '00' HOUR, DATE '2020-12-30' + INTERVAL '12' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-30' + INTERVAL '18' HOUR, DATE '2020-12-30' + INTERVAL '20' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-30' + INTERVAL '17:30' HOUR TO MINUTE, DATE '2020-12-30' + INTERVAL '21:30' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT DATE '2021-01-01' + INTERVAL '00' HOUR, DATE '2021-01-04' + INTERVAL '00' HOUR FROM DUAL UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '00' HOUR, DATE '2021-01-04' + INTERVAL '00' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-28' + INTERVAL '00' HOUR, DATE '2021-01-04' + INTERVAL '00' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-28' + INTERVAL '00' HOUR, DATE '2020-12-29' + INTERVAL '00' HOUR FROM DUAL;
Outputs:
(Using ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS (DY)';)
START_DATE | END_DATE | WORK_DAY_MINS_DIFF
:------------------------ | :------------------------ | -----------------:
2020-12-30 00:00:00 (WED) | 2020-12-30 12:00:00 (WED) | 180
2020-12-30 18:00:00 (WED) | 2020-12-30 20:00:00 (WED) | 0
2020-12-30 17:30:00 (WED) | 2020-12-30 21:30:00 (WED) | 30
2021-01-01 00:00:00 (FRI) | 2021-01-04 00:00:00 (MON) | 540
2021-01-02 00:00:00 (SAT) | 2021-01-04 00:00:00 (MON) | 0
2020-12-28 00:00:00 (MON) | 2021-01-04 00:00:00 (MON) | 2700
2020-12-28 00:00:00 (MON) | 2020-12-29 00:00:00 (TUE) | 540
db<>fiddle here

Oracle query Round up or down to nearest 15 minute interval

08-SEP-20 08:55:05
08-SEP-20 15:36:13
The query below is working correctly for 15:36:13 in that it rounds to 15:30 but the 8:55:05 is rounding down to 08:45 when it should be rounding to 09:00
select event_date,trunc(event_date,'mi') - numtodsinterval( mod(to_char(event_date,'mi'),15), 'minute' ) as nearest_quarter
from time_source_in_all where empno = '002307718' and event_date between '8-sep-2020' and '9-sep-2020'
I think this will do what you want:
select trunc(event_date, 'HH') + round(extract(minute from event_date) / 15) * interval '15' minute )
. . .
Note: I prefer the extract() because it is standard SQL. But it assumes that the column is a timestamp and not a date.
or the equivalent:
select trunc(event_date, 'HH') + numtodsinterval(round(to_number(to_char(event_date, 'MI')) / 15) * 15, 'minute')
You can use:
SELECT event_date,
TRUNC( event_date, 'HH' )
+ ROUND( EXTRACT( MINUTE FROM CAST( event_date AS TIMESTAMP ) ) / 15 )
* INTERVAL '15' MINUTE
AS rounded_15_event_date
FROM table_name
or:
SELECT event_date,
TRUNC( event_date, 'HH' )
+ ROUND( ( event_date - TRUNC( event_date, 'HH' ) ) * 24 * 4 )
* INTERVAL '15' MINUTE
AS rounded_15_event_date
FROM table_name
Which, for your sample data:
CREATE TABLE table_name ( event_date ) AS
SELECT DATE '2020-09-08' + INTERVAL '08:55:05' HOUR TO SECOND FROM DUAL UNION ALL
SELECT DATE '2020-09-08' + INTERVAL '15:36:13' HOUR TO SECOND FROM DUAL
Both output:
EVENT_DATE | ROUNDED_15_EVENT_DATE
:------------------ | :--------------------
2020-09-08 08:55:05 | 2020-09-08 09:00:00
2020-09-08 15:36:13 | 2020-09-08 15:30:00
db<>fiddle here

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

Get current day count from current month except holidays

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)

How to get the list of dates between two dates for particular month in oracle

For example consider this two dates "2012-04-02" "2012-04-30"
I want to get output as shown below but it should not include this dates "2012-04-02" "2012-04-30"
**COLA**
2012-04-01
2012-04-03
2012-04-04
2012-04-05
2012-04-06
2012-04-07
..
..
..
2012-04-29
2012-04-31
Use a hierarchical query:
SELECT start_date + LEVEL - 1
FROM (
SELECT DATE '2012-04-02' + 1 AS start_date,
DATE '2012-04-30' - 1 AS end_date
FROM DUAL
)
CONNECT BY start_date + LEVEL - 1 <= end_date;
or, a recursive sub-query factoring clause:
WITH dates ( value ) AS (
SELECT CAST( DATE '2014-04-02' + 1 AS DATE ) FROM DUAL
UNION ALL
SELECT value + 1 FROM dates WHERE value + 1 < DATE '2014-04-30'
)
SELECT * FROM dates;
or, if you are doing it for the entire month except those dates:
WITH boundaries ( start_date, end_date ) AS (
SELECT DATE '2014-04-02', DATE '2014-04-30' FROM DUAL
)
SELECT TRUNC( start_date, 'MM' ) + LEVEL - 1
FROM boundaries
WHERE TRUNC( start_date, 'MM' ) + LEVEL - 1 NOT IN ( start_date, end_date )
CONNECT BY TRUNC( start_date, 'MM' ) + LEVEL - 1 <= LAST_DAY( end_date );
or:
WITH boundaries ( start_date, end_date ) AS (
SELECT DATE '2014-04-02', DATE '2014-04-30' FROM DUAL
),
dates ( value ) AS (
SELECT TRUNC( start_date, 'MM' ) FROM boundaries
UNION ALL
SELECT value + 1 FROM dates WHERE value + 1 <= LAST_DAY( value )
)
SELECT *
FROM dates d
WHERE NOT EXISTS ( SELECT 1 FROM boundaries WHERE d.value IN ( start_date, end_date ) );