between function and year variable in oracle - oracle

[I have a table T1 with 2 columns NAME & DT as shown in figure
CREATE TABLE T1 ( NAME VARCHAR2(1), DT TIMESTAMP(3) );
INSERT INTO T1 VALUES ( ‘A’ , TO_TIMESTAMP(‘2021-02-04 12:00:00.000000000’, ‘YYYY-MM-DD HH24:MI:SS.FF’));
INSERT INTO T1 VALUES ( ‘B’ , TO_TIMESTAMP(‘2021-02-05 12:00:00.000000000’, ‘YYYY-MM-DD HH24:MI:SS.FF’));
INSERT INTO T1 VALUES ( ‘C’ , TO_TIMESTAMP(‘2021-02-15 12:00:00.000000000’, ‘YYYY-MM-DD HH24:MI:SS.FF’));
After executing above query, data gets populated and I want to select the records from DT from 1st jan till today of this year.
I have created a procedure as shown below such that if I run the same procedure next year, it needs to select the records from 1st jan till sysdate of that particular year i.e. here year changes..I have created a variable for the year and assigned current year to it..In next year, year variable will be 2022 etc..
How should I query it in WHERE clause to select the records from 1st Jan till sysdate of particular year (i.e. 2021/2022/2023 so on)..and you can see DT format is Timestamp
CREATE PROCEDURE P1
AS
V_YEAR VARCHAR2(4); -- year variable
V_YEAR := TO_CHAR((SYSDATE), ‘YYYY); -- Assigning current year to this variable
BEGIN
SELECT * FROM T1 WHERE DT BETWEEN TO_DATE(‘01/01/2021’, ‘mm/dd/yyyy’) AND TO_DATE(SYSDATE, ‘mm/dd/yyyy’);
END;
/]
1

You can use trunc in your query as follows:
DT BETWEEN TRUNC(SYSDATE,'YYYY') AND SYSDATE;
TRUNC(SYSDATE,'YYYY') returns the starting day of the year

Yet another option would be
WHERE TRUNC(DT, 'YEAR') = TRUNC(SYSDATE, 'YEAR');
If you wanted to get everything in the current year

Related

How to use complex condition in insert into select query in oracle

I want to insert some records with insert into select query in oracle. My condition should be when month of CREATE_DATE in SITA_HOSTS table is equal with month of sysdate - 1. Now my question is what should I write for the where statement?
This is my code:
DECLARE
p_year VARCHAR2(50);
n_year NUMBER;
n_month NUMBER;
j_year VARCHAR2(4);
j_month VARCHAR2(4);
c_month NUMBER;
BEGIN
SELECT TO_CHAR(sysdate, 'YYYY-MM-DD','nls_calendar=persian') INTO p_year FROM dual; --Change sysdate to jalali date
SELECT regexp_substr(p_year,'[^-]+', 1, 1) INTO j_year
FROM dual; -- Get year of jalalian sysdate
SELECT regexp_substr(p_year,'[^-]+', 1, 2) INTO j_month
FROM dual;--Get month of jalalian sysdate
n_year := TO_NUMBER(j_year);
n_month := TO_NUMBER(j_month);
insert into sita_orders(REL_MODULE,REL_ID,CREATE_DATE,AMOUNT,CUSTOMER_ID,PROJECT_ID,ORDER_STATUS,TITLE,YEAR)
SELECT 1,ID,sysdate,78787878,CUSTOMER_ID,PROJECT_ID,0,HOSTING_TITLE,j_year
FROM SITA_HOSTS
WHERE ????;
END;
At the end I should say that my date is Jalali date
Here is one way:
WHERE TRUNC(create_date,'MM') = ADD_MONTHS(TRUNC(SYSDATE,'MM'), -1)
TRUNC(date, 'MM') truncates to midnight on the first day of the month of the date.
It really depends on the content/meaning and data type of create_date within sita-hosts table. In addition to that is the requirement also unclear. Shall the insert also cover hosts that were created a couple of years ago or only the ones created last month.
solution for the hosts created during the last month. With trying to enforce the usage of indexes if there are some.
select <your select list>
from sita_hosts
where create_date between add_months(trunc(sysdate,'mm')-1) and trunc(sysdate,'mm')
and create_date < trunc(sysdate,'mm')
the second where clause will exclude all times that are on the start of this month just at midnight.
Thanks a lot guys. I have written this code and it works fine:
insert into sita_orders(REL_MODULE,REL_ID,CREATE_DATE,AMOUNT,CUSTOMER_ID,PROJECT_ID,ORDER_STATUS,TITLE,YEAR)
SELECT 1,ID,sysdate,100000,CUSTOMER_ID,PROJECT_ID,0,HOSTING_TITLE,TO_CHAR(sysdate, 'YYYY','nls_calendar=persian')
FROM SITA_HOSTS
WHERE
to_number(TO_CHAR(CREATE_DATE, 'MM','nls_calendar=persian')) <= to_number(TO_CHAR(sysdate, 'MM','nls_calendar=persian') - 1)
and ID not in (
select REL_ID from sita_orders where REL_MODULE=1 and YEAR=TO_CHAR(sysdate, 'YYYY','nls_calendar=persian')
);

Loop for a cursor - PL/SQL

I am working on analyzing huge set of data over a year. The approach is to pick the data one day at a time with the help of a cursor and keep on feeding another table with whole year data :-
declare
i_start_date date := date '2019-04-01';
i_end_date date := date '2019-04-02';
begin
for cur_r in (select a.id, b.status
from table1 a join table2 b on a.msg_id = b.msg_id
where b.t_date between i_start_date and i_end_date
)
loop
insert into test_table (id, status)
values (cur_r.id, cur_r.status);
end loop;
end;
/
Could you please help me run this cursor in a PL/SQL block for the whole year with error handling (e.g:- if data is already there for Apr 01 it should not be inserted again in the table creating no duplicates)
Something like below:-
declare
i_start_date date := date '2019-01-01'; --start date set
i_end_date date := date '2019-12-31'; --end date set
begin
for i_start_date<=i_end_date --condition to fetch data & insert
(for cur_r in (select a.id, b.status
from table1 a join table2 b on a.msg_id = b.msg_id
where b.t_date = i_start_date
)
loop
insert into test_table (id, status)
values (cur_r.id, cur_r.status);
end loop;)
i_start_date+1 -- increment start date
end;
/
Thanks,
Why do you even need pl/sql?
insert into test_table (id,
status
)
values (select a.id,
b.status
from table1 a
join table2 b on a.msg_id = b.msg_id
where b.t_date between date '2019-04-01
and date '2019-04-02'
and b.t_date not in (select t_date
from status)
;
But beware in your comparison of DATEs (which I have simply replicated) that oracle DATE always includes a time component, and the above comparison will truncate your supplied dates to midnight. Thus, a row with b.t_date = to_date('2019-04-02 09:10:11','yyyy-mm-dd') will not be selected.
If you have a Primary Key with the date value you can handle the exception with dup_val_on_index and then use a return.
BEGIN
...
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
...
RETURN;
END;
Or you can use a MERGE to command when to insert or not.
MERGE INTO TEST_TABLE T
USING CUR_R C
ON (C.DATE = T.DATE)
WHEN NOT MATCHED THEN
INSERT (id, status)
values (cur_r.id, cur_r.status);
You can directly use insert into <table> select ... statement as
SQL> insert into test_table
select a.id, b.status
from table1 a
join table2 b
on a.msg_id = b.msg_id
where b.t_date >= trunc(sysdate) - interval '1' year
and not exists ( select 0 from test_table t where t.id = a.id );
SQL> commit;
through use of b.t_date >= trunc(sysdate) - interval '1' year starting from the one year before to the current day.
If you need to start with a certain date such as date'2019-04-01' and scan for upcoming one year period,
then use b.t_date between date'2019-04-01' and date'2019-04-01' + interval '1' year - 1
and exclude the already existing data within the test_table through
not exists ( select 0 from test_table t where t.id = a.id ) considering those id columns are unique or primary keys in their respective tables.

Oracle PL/SQL update based on values calculated from multiple tables

I need to write a PL/SQL that sums up the value from 2 tables and update the value to another table.
I have the following tables: ONLINE_SALES, STORE_SALES, TOTAL_SALES
Assume the tables are structured like this:
ONLINE_SALES: OS_ID, STORE_ID, SEQ, ITEM_NAME, PRICE, PURCHASED_DATE
STORE_SALES: SS_ID, STORE_ID, SEQ, ITEM_NAME, PRICE, PURCHASED_DATE
TOTAL_SALES: STORE_ID, YEAR, TOTAL_INCOME
I want to write a PL/SQL that runs monthly and sums up the income (values in PRICE field) made in the month from both ONLINE_SALES and STORE_SALES of each store (identified by STORE_ID) and add the value to record in TOTAL_SALES with relative year.
My idea is to first filter the record by PURCHASED_DATE from both table with a SELECT, loop through all selected rows and sum to a variable and at last update the result with an UPDATE. But I am stuck in the first step since I found that I cannot use SELECT and only SELECT INTO is available.
Any ideas on how such PL/SQL can be written?
This solution assumes your stored procedure takes a DATE which it uses to identify the month and the year which is being totalled. Perhaps your assignment is expecting a different form of input? No worries. The deriving the values of year and date range is separate from the main process, so it is easy to swap in some different logic.
The loop uses an explicit cursor with the FOR UPDATE syntax. This locks the TOTAL_SALES table, which means you are guaranteed to be able to update all the rows.
create or replace calc_total_sales
( p_month in date )
is
cursor c_tot_sales (p_year number) is
select * from total_sales
where year = p_year
for update of total_income;
c_tot_sales c_tot_sales%rowtype;
l_os_sales number;
l_ss_sales number;
l_year number;
1_first_day date;
1_last_day date;
begin
l_year := to_number( to_char( p_month, 'yyyy') );
l_first_day := trunc( p_month, 'mm');
l_last_day := last_day( p_month);
open c_tot_sales( l_year );
loop
fetch c_tot_sales into r_tot_sales;
exit when c_tot_sales%not found;
select sum(price)
into l_os_sales
from online_sales
where store_id = r_tot_sales.store_id
and purchased_date >= l_first_day
and purchased_date <= l_last_day;
select sum(price)
into l_ss_sales
from store_sales
where store_id = r_tot_sales.store_id
and purchased_date >= l_first_day
and purchased_date <= l_last_day;
update total_sales
set total_income = total_income + nvl(l_ss_sales,0) + nvl(l_os_sales,0)
where current of c_total_sales;
end loop;
close c_tot_sales;
commit;
end;
/

Stored procedure to get one year of data splitted per months

I am relatively new to Oracle so i'd really appreciate some help.
I have a query like:
SELECT * FROM reservations WHERE date between (date1) and (date2).
What I need is to get all the reservations within the interval : today's date and today's date -1 year, so basically 1 year of history.
I want to run the above query with interval of 1 months, and export the query set to excel.
I need some help in the logic of the loop (create a stored procedure or function), as i will think later for the export to excel.
This will give all records from 1 year back to today:
SELECT * FROM reservations
WHERE date >= trunc( sysdate ) - interval '1' year
AND date < trunc( sysdate ) + interval '1' day
I want to run the above query with interval of 1 months,
I understand that you want to run this query 12 times, each time for another monthly period. If yes, then run this query 12 times changing the parameter X (within SELECT 1 As X FROM dual subquery), beginning from 12 to 1 (or 1 to 12):
SELECT * FROM reservations
CROSS JOIN (
SELECT 1 As X FROM dual
) x
WHERE date >= trunc( sysdate ) - x * interval '1' month
AND date < trunc( sysdate ) + interval '1' day - ( x - 1 ) * interval '1' month
Here is the procedure to create the queries month-wise starting from sysdate .
create or replace procedure monthwise_query
as
t_date DATE := TRUNC(SYSDATE) ;
c_date DATE ;
BEGIN
FOR i in 1..12
LOOP
c_date := t_date;
dbms_output.put_line('select * from reservations where rev_date between to_date('''||c_date||''',''DD-MON-YY'') and (select add_months(to_date('''||c_date||''',''DD-MON-YY''),-1) from dual);');
select add_months(c_date,-1)
into t_date
from dual;
END LOOP;
END;
- It can be extended with UTIL_FILE utility inside this procedure to write each query result to single/multiple files.
- SPOOL from SQL*plus for each query will be easy
- UNIX script to loop through each monthwise-query to load to file also possible

Loop Insert Date a daily commit day to day timestamp ORACLE

im executing query in oracle. i need insert data everyday commit in looping like this :
DECLARE
start_date NUMBER;
end_date NUMBER;
business_date VARCHAR2 (8);
BEGIN
start_date := TO_NUMBER (TO_CHAR (TO_DATE ('2017-01-01', 'yyyy-MM-dd')));
end_date := TO_NUMBER (TO_CHAR (TO_DATE ('2018-01-01', 'yyyy-MM-dd')));
FOR cur_r IN start_date .. end_date
LOOP
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE TO_NUMBER (TO_CHAR (TO_DATE (datecreated, 'yyyy-MM-dd')))>=start_date+cur_r
AND TO_NUMBER (TO_CHAR (TO_DATE (datecreated, 'yyyy-MM-dd')))<=end_date;
COMMIT;
END LOOP;
END;
I dunt know error this script .. please help me .. btw i newbie in oracle sorry ..
As #boneist pointed out, your manipulation using numbers isn't going to work. You should keep the data type as it is and compare with values of the same data type.
Assuming you have a legitimate need to do this in a loop you could do something like this:
BEGIN
FOR r IN (
select date '2017-01-01' + level -1 as this_date
from dual
connect by level <= date '2018-01-01' - date '2017-01-01'
)
LOOP
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE datecreated >= r.this_date
AND datecreated < r.this_date + 1;
COMMIT;
END LOOP;
END;
/
Or if the data type is actually a timestamp rather than a date as suggested in a comment, something like:
BEGIN
FOR r IN (
select timestamp '2017-01-01 00:00:00'
+ (level -1) * interval '1' day as this_timestamp
from dual
connect by level <= extract(day from timestamp '2018-01-01 00:00:00'
- timestamp '2017-01-01 00:00:00')
)
LOOP
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE datecreated >= r.this_timestamp
AND datecreated < r.this_timestamp + interval '1' day;
COMMIT;
END LOOP;
END;
/
... though you might want to work on the condition for the connect-by query, e.g.
FOR r IN (
select timestamp '2017-01-01 00:00:00'
+ numtodsinterval(level -1, 'DAY') as this_timestamp
from dual
connect by timestamp '2017-01-01 00:00:00'
+ numtodsinterval(level -1, 'DAY') < timestamp '2018-01-01 00:00:00'
)
LOOP
...
or as #boneist suggested in a comment, with a simpler loop:
BEGIN
FOR num_days in 0..(date '2018-01-01' - date '2017-01-01' - 1)
LOOP
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE datecreated >= timestamp '2017-01-01 00:00:00'
+ numtodsinterval(num_days, 'DAY')
AND datecreated < timestamp '2017-01-01 00:00:00'
+ numtodsinterval(num_days + 1, 'DAY');
COMMIT;
END LOOP;
END;
/
The main problem with this approach is restartability. If there is an error part way through the loop you can't just re-run it, as you'd be inserting duplicates.
Multiple inserts and commits are also less efficient that a single insert, or even multiple inserts and a single commit. If you don't have enough undo space to allow a single transaction to do all the work you need you should be fixing the database configuration to allow that, rather than working around it and potentially compromising data integrity.
i need backup this table . and insert only 2 month in new table
That sounds like you need to partition the table by month and use partition swaps to shift old months from the live to the backup table, perhaps. Partitioning costs more but if you have those data volumes it may be justified.
Failing that you could consider renaming your current table to backup, recreating your original table, and just copying the two months' worth of data you want to keep back to that. But that's a one-off thing, you still have the ongoing problem of ageing records out of the main table and into backup. And it has its own issues with dependencies, constraints, etc.
You don't need any loop and you should skip all these TO_CHAR, TO_NUMBER, TO_DATE conversions. Try this:
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE datecreated BETWEEN DATE '2017-01-01' AND DATE '2018-01-01';
Perhaps datecreated has time values different to 00:00:00, in this case you should run
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE TRUNC(datecreated) BETWEEN DATE '2017-01-01' AND DATE '2018-01-01';
or in case datecreated is a VARCHAR2 data type rather than DATE run
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE TO_DATE(datecreated, 'YYYY-MM-DD') BETWEEN DATE '2017-01-01' AND DATE '2018-01-01';
Assuming your datecreated column is of data type DATE and you only want one copy of the rows of the table then you do not need PL/SQL:
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE datecreated BETWEEN DATE '2017-01-01' AND DATE '2018-01-01';
COMMIT;
You could use a DATE literal to set the start_date and end_date and use a loop like this.
DECLARE
start_date NUMBER;
end_date NUMBER;
BEGIN
start_date := DATE '2017-01-01';
end_date := DATE '2018-01-01';
FOR cur_r IN 0 .. (end_date - start_date)
LOOP
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE TRUNC (datecreated) = start_date + cur_r;
COMMIT;
END LOOP;
END;
Why wouldn't you do it using a simple INSERT, such as
INSERT INTO file_backup
SELECT *
FROM file_core
WHERE datecreated BETWEEN DATE '2017-01-01' AND DATE '2018-01-01';
If you're just practicing PL/SQL and loops, well, remove COMMIT out of the LOOP. Both START and END_DATE should be "converted" to a number using a proper format mask (i.e. yyyymmdd). FOR loop's index should go from 1 to a difference between END and START_DATE.
[EDITED, after reading MT0's comment]
[EDITED #2, after reading some more comments]
Bah, my code is rubbish, should've thought about what I'm doing. Basically, if I meant to write it properly, it would have looked like #Kaushik Nayak's, and there's really no point in doing it twice.

Resources