View in Oracle to display rows from last weekday - oracle

I need to make a view in Oracle that will display all rows from last weekday.
So for Tuesday it should be like this cause I need all entries from Monday:
select * from table_1 where to_char(Mydate,'yyyy-mm-dd') = to_char(sysdate-**1**,'yyyy-mm-dd');
But if it is Monday then I need all entries from Friday.
select * from table_1 where to_char(Mydate,'yyyy-mm-dd') = to_char(sysdate-**3**,'yyyy-mm-dd');
How can I make one view that will always display correct rows from last weekday?

SELECT *
FROM table_1
WHERE mydate >= SYSDATE - CASE WHEN TO_CHAR(SYSDATE, 'D') > 6 THEN 3 ELSE 1 END
AND mydate < SYSDATE - CASE WHEN TO_CHAR(SYSDATE, 'D') > 6 THEN 3 ELSE 1 END + 1

You could use CASE:
SQL> WITH table_1 AS
2 (SELECT SYSDATE - 20 + ROWNUM mydate FROM dual CONNECT BY ROWNUM <= 40)
3 SELECT *
4 FROM table_1
5 WHERE trunc(Mydate) =
6 CASE WHEN to_char(SYSDATE, 'D') = to_char(DATE '2000-01-02', 'D')
7 THEN -- sunday
8 trunc(SYSDATE - 2)
9 WHEN to_char(SYSDATE, 'D') = to_char(DATE '2000-01-03', 'D')
10 THEN -- monday
11 trunc(SYSDATE - 3)
12 ELSE
13 trunc(SYSDATE - 1)
14 END;
MYDATE
-----------
08/01/2010
Note: Depending upon your NLS setting, to_char(X, 'D') may return 1 for mondays or sundays. Relying on a known monday (eg: 2000-01-03 in my case) will make this query work in any setting.

Related

How to create a statement for subtract a date omitting weekends and holidays in Oracle PLSQL

I need to make a query where from a final date specific days are subtracted, omitting weekends (Saturday and Sunday) and holidays,
Once the operation is done, it proceeds to display the date resulting from that operation, for example:
(3/1/2023 - 7) = 2/20/2023
On March 3, 7 days are subtracted, if I apply that statement it should show 2/23/2023 but since within that operation it went through Saturday and Sunday, it must omit them and continue subtracting as long as the days are between
Monday to Friday.
I've a table where I already have the festive dates, I would only check if there is also a festive date, it is omitted and continues subtracting.
Is possible to create that statement?
Here's one option; it uses holidays table (which you have) and a function (which creates a calendar, marks weekends and holidays and skips them in a loop).
Holidays:
SQL> select * from holidays order by datum;
DATUM
----------
01.11.2022
25.12.2022
26.12.2022
01.01.2023
06.01.2023
Function:
SQL> create or replace function f_result
2 (par_datum in date, par_number_of_days in number)
3 return date
4 is
5 retval date := par_datum;
6 i number := 0;
7 begin
8 for cur_r in
9 -- calendar
10 (with
11 temp (datum) as
12 (select par_datum - level + 1
13 from dual
14 connect by level <= par_number_of_days + 20
15 )
16 -- mark weekends and holidays
17 select t.datum,
18 case when to_char(t.datum, 'dy') in ('sat', 'sun') then 1 else 0 end cb_weekend,
19 case when t.datum = h.datum then 1 else 0 end cb_holiday
20 from temp t left join holidays h on h.datum = t.datum
21 order by t.datum desc
22 ) loop
23 retval := cur_r.datum;
24 -- skip weekends and holidays
25 i := i + case when cur_r.cb_weekend = 1 or cur_r.cb_holiday = 1 then 0 else 1 end;
26 exit when i > par_number_of_days;
27 end loop;
28 return retval;
29 end;
30 /
Function created.
Calendar (January 2023; names are in Croatian, but disregard that. Weekends and holidays are marked in different color. Today's date is 20.01.2023 (dd.mm.yyyy)):
Testing:
SQL> select f_result(date '2023-01-20', 10) from dual;
F_RESULT(D
----------
05.01.2023
SQL> select f_result(date '2023-01-10', 7) from dual;
F_RESULT(D
----------
29.12.2022
SQL>
If you'd want to move forward, slightly change function code:
SQL> create or replace function f_result
2 (par_datum in date, par_number_of_days in number)
3 return date
4 is
5 retval date := par_datum;
6 i number := 0;
7 begin
8 for cur_r in
9 -- calendar
10 (with
11 temp (datum) as
12 (select par_datum + level - 1
13 from dual
14 connect by level <= par_number_of_days + 20
15 )
16 -- mark weekends and holidays
17 select t.datum,
18 case when to_char(t.datum, 'dy') in ('sat', 'sun') then 1 else 0 end cb_weekend,
19 case when t.datum = h.datum then 1 else 0 end cb_holiday
20 from temp t left join holidays h on h.datum = t.datum
21 order by t.datum
22 ) loop
23 retval := cur_r.datum;
24 -- skip weekends and holidays
25 i := i + case when cur_r.cb_weekend = 1 or cur_r.cb_holiday = 1 then 0 else 1 end;
26 exit when i > par_number_of_days;
27 end loop;
28 return retval;
29 end;
30 /
Function created.
For example:
SQL> select f_result(date '2022-12-30', 1) res_1,
2 f_result(date '2023-01-04', 7) res_2,
3 f_result(date '2023-03-03', 7) res_3,
4 f_result(date '2023-03-01', 7) res_4
5 from dual;
RES_1 RES_2 RES_3 RES_4
---------- ---------- ---------- ----------
02.01.2023 16.01.2023 14.03.2023 10.03.2023
SQL>
Specifically, RES_3 you mentioned in comment (as 3rd January 2023 + 7 days) is:
03.01.2023 + 7 =
day 1: 04.01.
day 2: 05.01.
skip 06.01. (holiday), 07.01. and 08.01. (weekend)
day 3: 09.01.
day 4: 10.01.
day 5: 11.01.
day 6: 12.01.
day 7: 13.01. --> RES_3
declare
v_result_date date := to_date('03/01/2023','mm/dd/yyyy');
v_days number := 7;
begin
while v_days > 0
loop
if to_char(v_result_date,'dy') not in ('sat','sun')
then
v_days := v_days - 1;
end if;
v_result_date := v_result_date - 1;
end loop;
dbms_output.put_line(v_result_date);
end;
This could be done with SQL using model clause. You could try this.
SAMPLE data for holydays and your DATE 2023-03-01 with 7 days backward count taking care of weekends and holydays:
WITH
holydays (A_DATE) AS
(
Select DATE '2023-01-01' From Dual Union All
Select DATE '2023-01-06' From Dual Union All
Select DATE '2023-04-09' From Dual Union All
Select DATE '2023-04-10' From Dual Union All
Select DATE '2023-06-08' From Dual Union All
Select DATE '2023-06-22' From Dual Union All
Select DATE '2023-08-05' From Dual Union All
Select DATE '2023-08-15' From Dual Union All
Select DATE '2023-11-01' From Dual Union All
Select DATE '2023-11-18' From Dual Union All
Select DATE '2023-12-25' From Dual Union All
Select DATE '2023-12-26' From Dual
),
test_period AS
(
Select To_Date('2023-03-01', 'yyyy-mm-dd') "REF_DATE", 7 "NUM_OF_DAYS" From Dual
),
Now, using Oracle SQL MODEL Clause, we could createCTE returning a dataset that will have a kind of reversed set of dates excluding weekend and holyday days.
days AS
( SELECT IDX, REF_DATE, ROWNUM - 1 "RN", DAY_OF_WEEK, NUM_OF_DAYS
FROM ( Select IDX, REF_DATE, NUM_OF_DAYS,
CASE WHEN Nvl(To_Char(A_DATE), 'N') = 'N' And
Case When SubStr(To_Char(REF_DATE, 'Day'), 1, 3) IN('Sat', 'Sun') Then 'Y' Else 'N' End = 'N'
THEN 1
END "DAYS_PAST",
To_Char(REF_DATE, 'Day') "DAY_OF_WEEK",
A_DATE,
Nvl(To_Char(A_DATE), 'N') "IS_HOLYDAY",
Case When SubStr(To_Char(REF_DATE, 'Day'), 1, 3) IN('Sat', 'Sun') Then 'Y' Else 'N' End "IS_WEEKEND"
From ( Select REF_DATE, NUM_OF_DAYS From test_period ) p
Left Join holydays h ON(h.A_DATE = REF_DATE)
MODEL
Dimension By (0 as IDX)
Measures (REF_DATE, NUM_OF_DAYS, A_DATE, 0 AS DAYS_PAST)
RULES ITERATE(100) -- NUMBER OF ITERATIONS SHOULD BE BIGGER TO COVER SKIPPED DAYS
(
REF_DATE[ITERATION_NUMBER] = REF_DATE[0] - ITERATION_NUMBER,
NUM_OF_DAYS[ITERATION_NUMBER] = NUM_OF_DAYS[0]
)
Order by REF_DATE DESC
)
WHERE DAYS_PAST Is Not Null And ROWNUM <= NUM_OF_DAYS + 1
)
R e s u l t :
IDX REF_DATE RN DAY_OF_WEEK NUM_OF_DAYS
---------- --------- ---------- ----------- -----------
0 01-MAR-23 0 Wednesday 7
1 28-FEB-23 1 Tuesday 7
2 27-FEB-23 2 Monday 7
5 24-FEB-23 3 Friday 7
6 23-FEB-23 4 Thursday 7
7 22-FEB-23 5 Wednesday 7
8 21-FEB-23 6 Tuesday 7
9 20-FEB-23 7 Monday 7
Now just select the date you want - the 7th date backwards counting just working days.
SELECT REF_DATE "TARGET_DATE"
FROM days
WHERE RN = NUM_OF_DAYS
7 days from 2023-03-01
TARGET_DATE
-----------
20-FEB-23

Oracle: Return the specific records based on one column date

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

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

Just the month january not working

In this query I found the weeks of month, for example november begin 1th Wednesday and ends sunday 5.
These are the first week on november.
SELECT * FROM (
WITH days AS
(SELECT to_date('01012017','ddmmyyyy') + level-1 date_in
FROM dual
CONNECT BY level < 32)
SELECT date_in,
TO_CHAR(date_in,'IW') - TO_CHAR(TRUNC(date_in,'MM'),'IW') + 1 week_number
FROM days) where week_number = 1;
The week_number can change depending of the weeks of the month, but in January is not working.
Your requirement is not clear for me but perhaps you are looking for this:
SELECT TRUNC (next_day(TRUNC(TO_DATE('20022017','ddmmyyyy'), 'mm'),'monday') - 1), 'IW') AS first_week
FROM dual;
I'm still a bit unclear as to what you're looking for, but perhaps the following query will help you on your way:
WITH YEAR_START AS (SELECT TO_DATE('01012017', 'DDMMYYYY') AS FIRST_DAY_OF_YEAR
FROM DUAL),
MONTH_START AS (SELECT FIRST_DAY_OF_YEAR AS FIRST_DAY_OF_MONTH
FROM YEAR_START
UNION ALL
SELECT ADD_MONTHS(FIRST_DAY_OF_YEAR, LEVEL) AS FIRST_DAY_OF_MONTH
FROM YEAR_START
CONNECT BY LEVEL <= 11),
MONTH_START_AND_END AS (SELECT FIRST_DAY_OF_MONTH,
ADD_MONTHS(FIRST_DAY_OF_MONTH, 1) - INTERVAL '1' DAY AS LAST_DAY_OF_MONTH
FROM MONTH_START),
ABS_MONTH_WEEKS AS (SELECT FIRST_DAY_OF_MONTH,
LAST_DAY_OF_MONTH,
TO_NUMBER(TO_CHAR(FIRST_DAY_OF_MONTH, 'IW')) AS ABS_FIRST_WEEK_OF_MONTH,
TO_NUMBER(TO_CHAR(LAST_DAY_OF_MONTH, 'IW')) AS ABS_LAST_WEEK_OF_MONTH
FROM MONTH_START_AND_END),
REL_MONTH_WEEKS AS (SELECT a.*,
1 AS REL_FIRST_WEEK_OF_MONTH,
ABS_LAST_WEEK_OF_MONTH - CASE
WHEN ABS_FIRST_WEEK_OF_MONTH > ABS_LAST_WEEK_OF_MONTH THEN 0
ELSE ABS_FIRST_WEEK_OF_MONTH-1
END AS REL_LAST_WEEK_OF_MONTH
FROM ABS_MONTH_WEEKS a)
SELECT *
FROM REL_MONTH_WEEKS;
Best of luck.

update a table using a stored procedure in oracle

I have a table t_time where I have the below attributes
time_key, calendar_dt, cal_year, cal_quarter, cal_month, cal_week, week_in_month, cal_st_dt_of_wk, cal_end_dt_of_wk, rfrsh_dt, cal_yyyymm
select * from t_time where time_key = (select max(time_key) from t_time);
74937 31-12-2015 2015 4 12 5 5 27-12-2015 02-01-2016 17-07-2009 201512
I want to write a stored proc such that when i specify the year,t_time should be inserted with all the keys and other attributes..
like
for 2016
time_key calendar_dt cal_year cal_quarter cal_month cal_week week_in_month cal_st_dt_of_wk cal_end_dt_of_wk rfrsh_dt cal_yyyymm
74938 01-01-2016 2016 1 1 1 1 01-01-2016 02-01-2016 22-04-2015 201601
74939 02-01-2016 2016 1 1 1 1 01-01-2016 02-01-2016 22-04-2015 201601
74940 03-01-2016 2016 1 1 2 2 03-01-2016 09-01-2016 22-04-2015 201601
74941 04-01-2016 2016 1 1 2 2 03-01-2016 09-01-2016 22-04-2015 201601
cal_end_dt_of_wk is saturday of that week
cal_st_dt_of_wk is sunday of that week
can someone give me an idea to start with..
time_key - time_key + 1
calendar_dt - select sysdate from dual;
cal_year - select extract(year from sysdate) from dual;
cal_quarter - select case when extract(month from sysdate) in (1,2,3) then 1
case when extract(month from sysdate) in (4,5,6) then 2
case when extract(month from sysdate) in (7,8,9) then 3
case when extract(month from sysdate) in (10,11,12) then 4 else 0 end as cal_quarter from dual;
cal_month - select extract(month from sysdate) from dual;
cal_week - select to_char(to_date(sysdate),'ww') from dual;
week_in_month - select to_char(to_date(sysdate),'w') from dual;
cal_st_dt_of_wk - select trunc(sysdate,'iw')-1 from dual;
cal_end_dt_of_wk - select trunc(sysdate,'iw')+5 from dual;
rfrsh_dt - select sysdate from dual;
cal_yyyymm - select to_char(sysdate,'yyyymm') from dual;
Ok, it's a lot of stuff ;) but I think this should work now:
-- provide year as YYYY
CREATE OR REPLACE PROCEDURE fill_table (year IN VARCHAR2) IS
date_holder DATE := TO_DATE ('01.01.' || year,'DD.MM.YYYY');
BEGIN
WHILE (TO_CHAR (date_holder,'YYYY') = year) LOOP
INSERT INTO t_time
VALUES (1,
date_holder,
extract(year from date_holder),
case when extract(month from date_holder) in (1,2,3) then 1
when extract(month from date_holder) in (4,5,6) then 2
when extract(month from date_holder) in (7,8,9) then 3
when extract(month from date_holder) in (10,11,12) then 4 else 0 END,
extract(month from date_holder),
to_char(to_date(date_holder),'ww'),
to_char(to_date(date_holder),'w'),
trunc(date_holder,'iw')-1,
trunc(date_holder,'iw')+5,
sysdate ,
to_char(date_holder,'yyyymm'));
date_holder := date_holder +1;
END LOOP;
END;
/
So the basic idea is:
start with the 1.1.<YEAR> date
add one day by the other and insert values as you described
There seems to be an issue on the calendar week, week in month, start and end of week though .... anyway - the approach should be fine. I omitted the correct key calculation as well - best option would be a SEQUENCE.
p.s.: check this demo.

Resources