oracle ora-32040, what am i doing wrong? - oracle

I can't compile this procedure, I have a ORA-32040
Description: recursive WITH clause must use a UNION ALL operation. Below procedure I paste a code with example data set and object definitions.
Please let me know how to rewrite that procedure. I used union all operation but it doesn't work.
create or replace procedure ra_hi_proc is
begin
insert into ra_hi_tab (
select *
from(
with
rh (edate, st, c_ra) as (
select *
from ra_hi
where trunc(cast(edate as date)) = to_date(sysdate)
),
r1 (edate, st, c_ra) as (
select *
from ra_hi
where trunc(cast(edate as date)) = to_date(sysdate-1)
),
sto(st_number) as (
select st_number
from stor
)
select *
from (
select a.*, b.st
from rh a
left join r1 b
on a.st = b.st
)
union all
select rh.date, st_number, 0, null
from rh, sto
where st_number not in (
select st
from rh
)
));
commit;
end;
create table ra_hi (edate date , st VARCHAR2(20), c_ra number);
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('22-11-2020','dd-mm-yyyy'),'1875','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('09-12-2020','dd-mm-yyyy'),'1285','1');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('28-12-2020','dd-mm-yyyy'),'1146','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('13-01-2021','dd-mm-yyyy'),'1589','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('12-01-2021','dd-mm-yyyy'),'1021','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('07-01-2021','dd-mm-yyyy'),'1138','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('28-12-2020','dd-mm-yyyy'),'1233','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('19-12-2020','dd-mm-yyyy'),'1492','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('08-12-2020','dd-mm-yyyy'),'1138','2');
Insert into ra_hi (EDATE,ST,C_RA) values (to_date('24-11-2020','dd-mm-yyyy'),'1499','2');
create table stor (st_number varchar2(20));
Insert into STOR (ST_NUMBER) values ('1674');
Insert into STOR (ST_NUMBER) values ('1078');
Insert into STOR (ST_NUMBER) values ('1079');
Insert into STOR (ST_NUMBER) values ('1081');
Insert into STOR (ST_NUMBER) values ('1082');
Insert into STOR (ST_NUMBER) values ('1084');
Insert into STOR (ST_NUMBER) values ('1092');
Insert into STOR (ST_NUMBER) values ('1093');
Insert into STOR (ST_NUMBER) values ('1094');
Insert into STOR (ST_NUMBER) values ('1095');
create table ra_hi_tab (
edate DATE
ST VARCHAR2(20 BYTE)
C_RA NUMBER
ST_S VARCHAR2(20 BYTE)

This compiles without errors:
create or replace procedure ra_hi_proc is
begin
insert into ra_hi_tab (edate, st, c_ra, st_s)
with rh (edate, st, c_ra) as (
select edate, st, c_ra
from ra_hi
where trunc(edate) = trunc(sysdate)
),
r1 (edate, st, c_ra) as (
select *
from ra_hi
where trunc(edate) = trunc(sysdate-1)
),
sto(st_number) as (
select st_number
from stor
)
select a.edate, a.st, a.c_ra, b.st
from rh a
left join r1 b
on a.st = b.st
union all
select rh.edate, st_number, 0, null
from rh
CROSS JOIN sto
where st_number not in (select st from rh);
END;
/
db<>fiddle here
Note:
You question does not tell us what you intend the output to be so I cannot verify the output of the procedure.
You are filtering on today and yesterday and your sample data has neither of those dates so it does not appear to do anything.
Do not use TO_DATE on a value that is already a DATE (such as SYSDATE).
Do not mix the ANSI join syntax and the legacy comma-join syntax.
Do not use COMMIT in a procedure (or function); use it in the block that calls the procedure (or function) as this allows you to chain multiple procedures together and COMMIT or ROLLBACK them as a single transaction. If you COMMIT in each procedure then you are unable to ROLLBACK.
I am not sure you intended to use a CROSS JOIN; however, you did use one so I have just replicated it (see note 1).

Your query has several issue, see my comment. I don't see any reason for recursive CTE. Without having a look at the details, the statement could be much simpler like this:
insert into ra_hi (
WITH rh AS (
SELECT edate, st, c_ra
FROM v_ra_hi
WHERE trunc(edate) = trunc(sysdate)
)
SELECT a.*, b.st
FROM v_ra_hi a
LEFT JOIN rh b ON a.st = b.st
WHERE a.trunc(edate) = trunc(sysdate-1)
UNION ALL
SELECT rh.edate, st_number, 0, NULL
FROM rh
CROSS JOIN stor#scd
WHERE re = 1
and st_number NOT IN (SELECT st FROM rh);

Related

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 delete from tableA where a duplicate row is in tableB

As the title says, I am looking for a way to remove all rows from TableA where there is a matching row in TableB.
the Tables A & B have about 30 columns in them so a WHERE A.col1 = B.col1 etc would be a little problematical. Ideally I was hoping for something like
DELETE FROM tableA WHERE IN TableB
(overly simplified by this type of thing)
IN clause can compare all columns returned from select
DELETE FROM tableA WHERE ( col1,col2,col3,.. ) IN ( select col1,col2,col3... FROM TableB );
The brute force way to establish if two records from each table are the same is to just compare every column:
DELETE
FROM tableA a
WHERE EXISTS (SELECT 1 FROM tableB b WHERE a.col1 = b.col1 AND a.col2 = b.col2 AND ...
a.col30 = b.col30);
You could create function which checks structures of tables and, if they are the same, creates string containing correct conditions to compare.
For example here are two tables:
create table t1 (id, name, age) as (
select 1, 'Tom', 67 from dual union all
select 2, 'Tia', 42 from dual union all
select 3, 'Bob', 16 from dual );
create table t2 (id, name, age) as (
select 1, 'Tom', 51 from dual union all
select 3, 'Bob', 16 from dual );
Now use function:
select generate_condition('T1', 'T2') from dual;
result:
T1.ID = T2.ID and T1.NAME = T2.NAME and T1.AGE = T2.AGE
Copy this, paste and run delete query:
delete from t1 where exists (select 1 from t2 where <<PASTE_HERE>>)
Here is the function, adjust it if needed. I used user_tab_columns so if tables are on different schemas you need all_tab_columns and compare owners too. If you have Oracle 11g you can replace loop with listagg(). Second table has to contain all columns of first table and they have to be same type and length.
create or replace function generate_condition(i_t1 in varchar2, i_t2 in varchar2)
return varchar2 is
v varchar2(1000) := '';
begin
for rec in (select column_name, u2.column_id
from user_tab_cols u1
left join (select * from user_tab_cols where table_name = i_t2) u2
using (column_name, data_type, data_length)
where u1.table_name = i_t1 order by u1.column_id)
loop
if rec.column_id is null then
v := 'ERR: incompatible structures';
goto end_loop;
end if;
v := v||' and '||i_t1||'.'||rec.column_name
||' = '||i_t2||'.'||rec.column_name;
end loop;
<< end_loop >>
return(ltrim(v, ' and '));
end;
If you want to avoid running process manually you need dynamic PL/SQL.
create table tableA (a NUMBER, b VARCHAR2(5), c INTEGER);
create table tableB (a NUMBER, b VARCHAR2(5), c INTEGER);
As you said
WHERE A.col1 = B.col1 etc would be a little problematical
you could intersect the tables and mention all columns from tableA one time, like this:
delete tableA
where (a,b,c) in (select * from tableA
intersect
select * from tableB);

To split delimited value in column input in where clause

I need to split a column which is Pipe delimited and compare with records. Something like this
select 1
from T1 t1
where t1.date_col not between '01-JAN-2005' and '31-JAN-2005';
I need to fill the between clause value from reference table where the data is something like
ref_table
col_1
01-JAN-2005 | 31-JAN-2005
Query I am trying to achieve
REGEXP_SUBSTR ( col_1
, '^[^|]+') from ref_table
Which is resulting into 01-JAN-2005.
Table T1
date_col
01-Jan-05
15-Jan-05
31-Mar-05
Ref_table
col_1
01-JAN-2005 | 31-JAN-2005
You can do as this:
--Sample data
with t1 as (
select '01-Jan-05' col_1 union
select '15-Jan-05' union
select '31-Mar-05'
),
Ref_table as (
select '01-JAN-2005 | 31-JAN-2005' col_1
)
select *
from t1,
Ref_table r
where to_date(t1.col_1, 'DD/MON/YY')
not between to_date(trim(regexp_replace(r.col_1, '(.*)\|.*', '\1')), 'DD/MON/YY')
and to_date(trim(regexp_replace(r.col_1, '.*\|(.*)', '\1')), 'DD/MON/YY')
Although you really should improve your ref_table design. Store values with some char separated always turns out to be a problem.
Join the dates from table t1 with the intervals from the reference table and subtract the result from the original set of dates:
select t11.date_col
from t1 t11
minus
select t12.date_col
from t1 t12
join (
select to_date ( trim ( substr(col_1, 1, instr(col_1, '|') - 1) ), 'DD-Mon-YYYY' ) d_from
, to_date ( trim ( substr(col_1, instr(col_1, '|') + 1) ), 'DD-Mon-YYYY' ) d_to
from ref_table
) rt on (
rt.d_from <= t12.date_col
AND rt.d_to >= t12.date_col
)
;
I assume that col_1 from your reference table contains the excluded intervals in pairs.

Insert into select query with cursor value

I want to write a SQK script with insert query in Oracle where one of the value will be fetched from cursor and rest all will be retrieved from table.
For example, consider Employee table:
Emp_No | Emp_Name
1 | AAA
...........
I am reading the table into a cursor.
Cursor c1 is select emp_no, emp_name from employee;
I am iterating the cursor and adding to a table along with information from another table.
for empCur in c1
loop
insert into employee_info(emp_no, emp_name, address, age, ... ) values (empCur.emp_no, empCur.emp_name, select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no)
end loop;
Is my script valid? If not is there any other way to achieve it? Since few values are in cursor and few are in another table I am not sure how to handle this.
Your script isn't correct because this
select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no
is not a valid expression. You may use a scalar subquery (one-row, one-column subquery) only, like this:
insert into t1(col1, col2, col3) values (1, (select col1 from t2), (select col2 from t2));
Or you may try insert from select:
for empCur in c1 loop
insert into employee_info(emp_no, emp_name, address, age, ... )
select empCur.emp_no, empCur.emp_name, t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no;
end loop;
If you want to use the cursor, why not just join the tables inside the cursor?
for empCur in ( select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no )
) loop
insert into employee_info(...) values ( empCur.emp_no, ...);
end loop;
Or with a sql insert: (if you can choose sql over pl/sql - T Kyte says do it)
insert into employee_info
select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no );

PostgreSQL: One condition for multiple tables

I need to retrieve data from tables by checking a single condition for multiple tables. Here is the sample example as shown below:
create table tb1 (
slno int,
name text,
address text
);
create table tb2 (
slno int,
fname text,
faddress text
);
create table tb3 (
slno int,
mname text,
maddress text
);
create table tb4 (
slno int,
lname text,
laddress text
);
insert into tb1 values(1,'aaa','bbb');
insert into tb1 values(2,'2aaa','2bbb');
insert into tb1 values(3,'3aaa','3bbb');
insert into tb1 values(4,'4aaa','4bbb');
insert into tb1 values(5,'5aaa','5bbb');
insert into tb2 values(1,'faaa','fbbb');
insert into tb2 values(2,'f2aaa','f2bbb');
insert into tb2 values(3,'f3aaa','f3bbb');
insert into tb2 values(4,'f4aaa','f4bbb');
insert into tb2 values(5,'f5aaa','f5bbb');
insert into tb3 values(1,'maaa','mbbb');
insert into tb3 values(2,'m2aaa','m2bbb');
insert into tb3 values(3,'m3aaa','m3bbb');
insert into tb3 values(4,'m4aaa','m4bbb');
insert into tb3 values(5,'m5aaa','m5bbb');
insert into tb4 values(1,'laaa','lbbb');
insert into tb4 values(2,'l2aaa','l2bbb');
insert into tb4 values(3,'l3aaa','l3bbb');
insert into tb4 values(4,'l4aaa','l4bbb');
insert into tb4 values(5,'l5aaa','l5bbb');
The query:
select distinct t2.slno
from
tb1,
tb2 as t2,
tb3 as t3,
tb4 as t4
where
tb1.slno = t2.slno or
t2.slno = t3.slno or
t3.slno = t4.slno;
Note: The above query makes my task complete but taking too more time for execution for huge data.
Questions:
1.How to reduce time complexity?
2. Is there any better way then this?
You can greatly reduce the cost by doing a distinct before the join
select distinct t2.slno
from
(select distinct slno from tb1) t1
cross join
(select distinct slno from tb2) t2
cross join
(select distinct slno from tb3) t3
cross join
(select distinct slno from tb4) t4
where
t1.slno = t2.slno or
t2.slno = t3.slno or
t3.slno = t4.slno
http://sqlfiddle.com/#!15/184dd/3
But if you explain why you have four identical tables you can have a better answer.

Resources