This question already has answers here:
Oracle How to list last days of months between 2 dates
(5 answers)
Closed 1 year ago.
I need to create a calendar in Oracle with only last days of month between two dates.
I tried to do with this but it creates a calendar with all days between two dates and I only need the last dayS of the month.
begin
begin_date := TO_NUMBER(TO_CHAR(TO_DATE('2021-01-01', 'yyyy-MM-dd'), 'j'));
end_date := TO_NUMBER(TO_CHAR(ADD_MONTHS(LAST_DAY(TO_DATE(sysdate, 'yyyy-MM-dd')),-1), 'j'));
WITH calendar AS (
SELECT to_date(begin_date, 'mm/dd/yyyy') + ROWNUM - 1 c_date
FROM dual
CONNECT BY LEVEL <= to_date(end_date, 'mm/dd/yyyy')
- to_date(begin_date, 'mm/dd/yyyy') + 1
)
SELECT c_date "date", ID
FROM (SELECT c.c_date, EXPE.ID AS ID
FROM EXPEDIENTE EXPE, calendar c
WHERE EXPE.ID=1)
ORDER BY 1,2;
How can i do that?
No need for PL/SQL.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with
2 period as
3 (select date '2020-11-07' start_date,
4 date '2021-04-15' end_date
5 from dual
6 )
7 select last_day(add_months(start_date, level - 1)) mon
8 from period
9 connect by level <= months_between(end_date, start_date) + 1
10 order by mon;
MON
----------
30.11.2020
31.12.2020
31.01.2021
28.02.2021
31.03.2021
30.04.2021
6 rows selected.
SQL>
Related
I have a database structure as below.
period
month
start_date
1
April
2022-04-01
2
May
2022-05-07
3
June
2022-06-04
4
July
2022-07-02
5
August
2022-08-06
6
September
2022-09-03
7
October
2022-10-01
8
November
2022-11-05
9
December
2022-12-03
10
January
2023-01-01
11
February
2023-02-04
12
March
2023-03-04
End date of the year is 2023-03-31.
Based on current_date, how do I select the query to return where the current date falls under Period 6.
My current query as below.
SELECT period FROM table1 as a
WHERE
a.start_date = (SELECT MAX(start_date) FROM table1 as b WHERE
b.start_date <=current_date) and ROWNUM <= 1
Is there anyway to improve the current query which to avoid using subquery?
Today is September 22nd, so - would this do?
Some sample data:
SQL> with test (period, month, start_date) as
2 (select 1, 'april' , date '2022-04-01' from dual union all
3 select 5, 'august' , date '2022-08-06' from dual union all
4 select 6, 'september', date '2022-09-03' from dual union all
5 select 7, 'october' , date '2022-10-01' from dual union all
6 select 10, 'january' , date '2023-01-01' from dual union all
7 select 12, 'march' , date '2023-03-04' from dual
8 ),
Query begins here:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= sysdate
14 )
15 select period
16 from temp
17 where rn = 1
18 /
PERIOD
----------
6
SQL>
It still uses a subquery (or a CTE, as in my example), but - as opposed to your approach, it selects from the source table only once, so performance should be improved.
A few more tests: instead of sysdate (line #13), presume that today is September 2nd (which means that it is in period #5):
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-09-02'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Or, if today were August 7th:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-08-07'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Your rule for the start_date appears to be:
If the month is January (first month of the calendar year) or April (typically, first month of the financial year) then use the 1st of that month;
Otherwise use the 1st Saturday of the month.
If that is the case then you can calculate the start date of the next month and use the query:
SELECT *
FROM table1
WHERE start_date <= SYSDATE
AND SYSDATE < CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(start_date, 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN TRUNC(ADD_MONTHS(start_date, 1), 'MM')
ELSE NEXT_DAY(TRUNC(ADD_MONTHS(start_date, 1), 'MM') - 1, 'SATURDAY')
END
Then, for your sample data:
CREATE TABLE table1 (
period NUMBER(2,0),
month VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(start_date, 'FXMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
),
start_date DATE
);
INSERT INTO table1 (period, start_date)
SELECT LEVEL,
CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(DATE '2022-04-01', LEVEL - 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN ADD_MONTHS(DATE '2022-04-01', LEVEL - 1)
ELSE NEXT_DAY(ADD_MONTHS(DATE '2022-04-01', LEVEL - 1) - 1, 'SATURDAY')
END
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
PERIOD
MONTH
START_DATE
6
September
2022-09-03 00:00:00
fiddle
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
I have a schedule of my training, three times a week, for example -
MON, WED,FRI. I need to generate records for my schedule table with dates till the end of the current year when I have training.
The schedule table is:
CREATE TABLE trainingSchedule (
id NUMBER,
training_date DATE
);
If training date already exist - don’t insert a record.
Here's one option. Read comments within code.
SQL> CREATE TABLE trainingSchedule
2 (id NUMBER,
3 training_date DATE
4 );
Table created.
SQL> create sequence seq_tra;
Sequence created.
SQL> -- initial insert (just to show that MERGE will skip it
SQL> insert into trainingschedule values (seq_tra.nextval, date '2021-03-22');
1 row created.
MERGE will skip rows that are already inserted. I understood that you want to insert only dates that follow today's date; if that's not so, just remove the last condition.
SQL> merge into trainingschedule t
2 using (-- this is a calendar for current year
3 select trunc(sysdate, 'yyyy') + level - 1 datum
4 from dual
5 connect by level <= add_months(trunc(sysdate, 'yyyy'), 12) - trunc(sysdate, 'yyyy')
6 ) c
7 on (c.datum = t.training_date)
8 when not matched then insert (id, training_date) values (seq_tra.nextval, c.datum)
9 -- insert only Mondays, Wednesdays and Fridays
10 where to_char(c.datum, 'dy', 'nls_date_language = english') in ('mon', 'wed', 'fri')
11 -- insert only dates that follow today's date ("till the end of the current year")
12 and datum >= trunc(sysdate);
122 rows merged.
SQL>
What's in there?
SQL> select id,
2 to_char(training_date, 'dd.mm.yyyy, dy', 'nls_date_language = english') tr_date
3 from trainingschedule
4 order by training_date;
ID TR_DATE
---------- ------------------------
1 22.03.2021, mon --> see? No duplicates
311 24.03.2021, wed
309 26.03.2021, fri
207 29.03.2021, mon
354 31.03.2021, wed
321 02.04.2021, fri
<skip>
I wanna roll six months dates starting from 2016 to current date, output should be like below
Year Start_Date End_Date
2016 1/1/2016 30/6/2016
2016 1/7/2016 31/12/2016
2017 ... like this for 2017 & 2018
I tried like fetch first date of the year like
SELECT TRUNC(to_date(Date_key, 'YYYYMMDD'), 'YEAR')
FROM Table;
and adding six months from first date, but in this case end date will be problem. Is there a function I can do this without loop?
Something like this?
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select extract (year from add_months (date '2016-01-01', (level - 1) * 6)) year,
2 add_months (date '2016-01-01', (level - 1) * 6) start_date,
3 add_months (date '2016-01-01', (level) * 6) - 1 end_date
4 from dual
5 connect by level <= (extract (year from sysdate) - 2016 + 1) * 2;
YEAR START_DATE END_DATE
---------- ---------- ----------
2016 01.01.2016 30.06.2016
2016 01.07.2016 31.12.2016
2017 01.01.2017 30.06.2017
2017 01.07.2017 31.12.2017
2018 01.01.2018 30.06.2018
2018 01.07.2018 31.12.2018
6 rows selected.
SQL>
You can also try this,
SELECT :p_from_year + CEIL(ROWNUM/2)-1,
ADD_MONTHS(TRUNC(TO_DATE(:p_from_year + CEIL(rownum/2)-1, 'YYYY'), 'YYYY'),6-(MOD(rownum, 2)*6)) from_date,
ADD_MONTHS(TRUNC(TO_DATE(:p_from_year + CEIL(rownum/2)-1, 'YYYY'), 'YYYY'),12-(MOD(rownum, 2)*6))-1 to_date
FROM dual
CONNECT BY CEIL(rownum/2)-1 <= (:p_to_year - :p_from_year)
I am woking on a query which can give back the count divided by month about the offices that will be closed this summer.
SELECT
qa.tmonth,
COUNT(qa.tmonth) AS qtn
FROM
(
SELECT TO_CHAR(CLOSURE_DATE, 'yyyymm') AS tmonth
FROM Holidays
WHERE CLOSURE_DATE >= TO_DATE('20160501', 'YYYY-MM-DD') AND
CLOSURE_DATE <= TO_DATE('20160901', 'YYYY-MM-DD')
) qa
GROUP BY qa.tmonth;
Since the months: May, June, August and September no office will be closed the output is the following:
TMONTH|QTN
201607|80
But I need a thing like this
TMONTH|QTN
201605|0
201606|0
201607|80
201608|0
201609|0
How could I achieve that?
Thanks to all!
You can try with something like this:
SQL> with holidays(closure_date) as
2 (
3 select date '2016-07-01' from dual union all
4 select date '2016-07-02' from dual union all
5 select date '2016-07-03' from dual union all
6 select date '2016-07-04' from dual union all
7 select date '2016-07-05' from dual
8 )
9 select count(closure_date) as closure_days, to_char(day, 'yyyymm') as month
10 from (
11 select date '2016-05-01' + level -1 as day
12 from dual
13 connect by date '2016-05-01' + level -1 <= date '2016-09-30'
14 ) days
15 left outer join holidays
16 on (day = closure_date)
17 group by to_char(day, 'yyyymm') ;
CLOSURE_DAYS MONTH
------------ ------
0 201608
5 201607
0 201606
0 201605
0 201609
SQL>
This uses a query to build the list of all the days between a starting and an ending date; I used 01/05 and 30/09 and called it days.
Then it queries days with the holidays table in outer join; this way you can count only the days for which there is a corrensponding value in the closure days list, thus counting the closure days for each day, month year; the aggregation for year and month completes the job
A similar approach like above. Tip: You can execute the two sub-queries separately, to analyse the logic.
select to_char (m.month, 'yyyymm') as TMONTH, m.month,
nvl (h.qtn, 0) as QTN
from
(
SELECT add_months(trunc (SYSDATE, 'MONTH'), -(LEVEL-1)) as MONTH
FROM dual
CONNECT BY LEVEL <= 12 -- generate a list of the last 12 month
) m
left join
(
SELECT trunc (closure_date, 'MONTH') as MONTH,
count (*) as QTN
FROM Holidays
group by trunc (closure_date, 'MONTH')
) h
on m.MONTH = h.MONTH
where m.month between DATE '2016-01-01' and sysdate
order by TMONTH desc;