update a table using a stored procedure in oracle - 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.

Related

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

No hard coding in where clause in oracle

[I have created a table as shown in figure
CREATE TABLE TABLE1 (CAL_YEAR VARCHAR2(4), NAME VARCHAR2(1))
INSERT INTO TABLE1 VALUES(‘2020’, ’A’)
INSERT INTO TABLE1 VALUES(‘2020’, ’B’)
INSERT INTO TABLE1 VALUES(‘2020’, ’C’)
INSERT INTO TABLE1 VALUES(‘2020’, ’D’)
INSERT INTO TABLE1 VALUES(‘2021’, ’E’)
INSERT INTO TABLE1 VALUES(‘2021’, ’F’)
Let us assume I am querying the statement in this year like
SELECT * FROM TABLE1 WHERE CAL_YEAR= TO_CHAR((SYSDATE),'YYYY');
so I get present year values.
What I need is if the present year data not present in the table then I need to use previous year data i.e. 2020 data...How can I do this in the where clause itself]1
You can simply get the maximum year that is less than or equal the year you query for in a subquery.
SELECT *
FROM table1
WHERE cal_year = (SELECT max(cal_year)
FROM table1
WHERE cal_year <= 2020);
You want to show the previous year, if no row for the current year exists? Simply select all years until now, order by year, take the last one.
SELECT *
FROM table1
WHERE cal_year <= EXTRACT(YEAR FROM SYSDATE)
ORDER BY cal_year DESC
FETCH FIRST ROW WITH TIES;
Optionally (if you're on a database version which doesn't support FETCH FIRST ROW WITH TIES Thorsten suggested):
with year 2021 in the table:
SQL> select * from table1
2 where cal_year = case when (select max(1) from table1
3 where exists (select null from table1
4 where cal_year = to_char(sysdate, 'yyyy')
5 )
6 ) = 1 then to_char(sysdate, 'yyyy')
7 else to_char(add_months(sysdate, -12), 'yyyy')
8 end;
CAL_ N
---- -
2021 E
2021 F
Without this year:
SQL> delete from table1 where cal_year = 2021;
2 rows deleted.
SQL>
SQL> select * from table1
2 where cal_year = case when (select max(1) from table1
3 where exists (select null from table1
4 where cal_year = to_char(sysdate, 'yyyy')
5 )
6 ) = 1 then to_char(sysdate, 'yyyy')
7 else to_char(add_months(sysdate, -12), 'yyyy')
8 end;
CAL_ N
---- -
2020 A
2020 B
2020 C
2020 D
SQL>
What does it do?
lines #2 - 5: SELECT* (within CASE) returns 1 if EXISTS says that there's at least one row whose CAL_YEAR = this year
if that's so (i.e. 1 has been returned), then CAL_YEAR is compared to this year (to_char(sysdate, 'yyyy'))
otherwise, if SELECT returns something else (most probably NULL), CAL_YEAR is compared to previous year (that's what to_char(add_months(sysdate, -12), 'yyyy') does - subtracts 12 months from today's date and extracts year from it)

How to get a list of months between 2 given dates using a query?

I have 2 dates, say 28-Mar-2011 and 29-Jun-2011. I need an sql query that will display the months between these 2 dates including the months containing the dates, ie. June, May, April and March.
Something like this
SQL> ed
Wrote file afiedt.buf
select to_char( add_months( start_date, level-1 ), 'fmMonth' )
from (select date '2011-03-30' start_date,
date '2011-06-29' end_date
from dual)
connect by level <= months_between(
trunc(end_date,'MM'),
trunc(start_date,'MM') )
* + 1
SQL> /
TO_CHAR(ADD_MONTHS(START_DATE,LEVEL-
------------------------------------
March
April
May
June
should work.
Gonna add this solution just because I think it's much cleaner than the others:
SELECT ADD_MONTHS(TRUNC(TO_DATE('28-Mar-2011', 'DD-MON-YYYY'), 'MON'), ROWNUM - 1) date_out
FROM DUAL
CONNECT BY ADD_MONTHS(TRUNC(TO_DATE('28-Mar-2011', 'DD-MON-YYYY'), 'MON'), ROWNUM - 1)
<= TRUNC(TO_DATE('29-Jun-2011', 'DD-MON-YYYY'), 'MON')
You can use the function MONTHS_BETWEEN
SELECT MOD( TRUNC( MONTHS_BETWEEN( '2011-07-29', '2011-03-28' ) ), 12 ) as MONTHS
FROM DUAL
Output
MONTHS
----------
4
I needed an answer to this a couple of days ago. I found another solution I liked more:
select to_char(which_month, 'Mon-yyyy') month
from
(
select
add_months(to_date(:start_date,'mm-yyyy'), rownum-1) which_month
from
all_objects
where
rownum <= months_between(to_date(:end_date,'mm-yyyy'), add_months(to_date(:start_date,'mm-yyyy'), -1))
order by
which_month
)
You could of course use any format you want. I 'union'ed and summed over another set so that I'd get the months even when they didn't have results.
SELECT MIN (to_date((TO_CHAR (Actual_Date, 'DD-MM-RRRR')),'dd-mm-rrrr')) F_DATE,
MAX (to_date((TO_CHAR (Actual_Date, 'DD-MM-RRRR')),'dd-mm-rrrr')) T_DATE,
TO_CHAR (Actual_Date, 'MM-RRRR') TRX_MONTH
FROM ( SELECT TRUNC (TO_DATE (:P_FDATE, 'dd-mm-rrrr')) + LEVEL - 1
Actual_Date
FROM (SELECT TRUNC (TO_DATE (:P_FDATE, 'dd-mm-rrrr'), 'MM') - 1
AS dt
FROM DUAL)
CONNECT BY LEVEL <=
( TO_DATE (:P_TDATE, 'dd-mm-rrrr')
- TRUNC (TO_DATE (:P_FDATE, 'dd-mm-rrrr'))
+ 1))
GROUP BY TO_CHAR (Actual_Date, 'MM-RRRR')
ORDER BY 1
declare
v_date_from_first_day date;
v_date_to_last_day date;
v_month_name varchar2(10);
v_month_number number;
v_year_number number;
v_month_diff number;
begin
v_date_to_last_day := to_date('31.12.2018');
v_date_from_first_day := to_date('01.01.2018');
select months_between(v_date_to_last_day,v_date_from_first_day) as diff into v_month_diff from dual;
for i in 1..round(v_month_diff, 2) loop
select
to_char(trunc(add_months(v_date_to_last_day - months_between(v_date_from_first_day, v_date_to_last_day), -i)), 'fmMonth') as month_nm,
to_char(trunc(add_months(v_date_to_last_day - months_between(v_date_from_first_day, v_date_to_last_day), -i)), 'MM') as month_num,
to_char(trunc(add_months(v_date_to_last_day - months_between(v_date_from_first_day, v_date_to_last_day), -i)), 'YYYY') as year_num
into v_month_name, v_month_number, v_year_number
from dual;
dbms_output.put_line(v_month_number || '/' || v_year_number);
dbms_output.put_line(v_month_name || '/' || v_year_number);
end loop;
end;
Output:
12/2018
11/2018
10/2018
9/2018
8/2018
7/2018
6/2018
5/2018
4/2018
3/2018
2/2018
1/2018
Here, month names are in Croatian
Prosinac/2018
Studeni/2018
Listopad/2018
Rujan/2018
Kolovoz/2018
Srpanj/2018
Lipanj/2018
Svibanj/2018
Travanj/2018
Ožujak/2018
Veljača/2018
Siječanj/2018

View in Oracle to display rows from last weekday

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.

Resources