Cursor with multiple queries in oracle not compiling - oracle

I have created a cursor that has two queries joined with inner join, but query is not compiling their is error at the end of first query but the same query is getting executed without cursor.
cursor data is
select * from
select rid,id, order from table1
inner join
select pid, name, order from table2
on table1.order = table2.order
original query is much bigger and complicated but end result would be this.
Their are compilation errors at the end of first query and those are generic nature, I guess may be syntax for creating a two joined queries is wrong (this is a wild guess though)
Error:
SQL statement ignored //at select word of first query
Missing right parenthesis //at the last word of first query

Example based on Scott's schema:
SELECT should contain column aliases if columns returned by those inline views share the same name; otherwise, you won't know which one you're using
inline views should have their own aliases; basically, that's always a good idea - prefix columns with table aliases, otherwise you'll soon forget which column belongs to which table
SQL> declare
2 cursor data is
3 select a.empno a_empno, b.ename b_ename
4 from (select empno, ename, deptno from emp) a
5 inner join
6 (select empno, ename, deptno from emp) b
7 on a.deptno = b.deptno
8 where rownum < 5;
9 begin
10 for data_r in data loop
11 dbms_output.put_line(data_r.b_ename);
12 end loop;
13 end;
14 /
SMITH
JONES
SCOTT
ADAMS
PL/SQL procedure successfully completed.
SQL>

You have to put your subqueries in parenthesis and add aliases for the subqueries:
cursor data is
select * from
(select rid,id, order from table1) table1
inner join
(select pid, name, order from table2) table2
on table1.order = table2.order

Here is another answer for you, with just small differences and with an example:
CREATE OR REPLACE PROCEDURE p_test(n_test in number)
AS
CURSOR data
IS
SELECT *
FROM
(SELECT rid
, id
, "order" or1
FROM table1) tab1
INNER JOIN
(SELECT pid
, name
, "order" or1
FROM table2 ) tab2
ON tab1.or1 = tab2.or1;
BEGIN
FOR data_i IN data LOOP
DBMS_OUTPUT.PUT_LINE(data_i.rid);
END LOOP;
END p_test;
Here is the DEMO

Related

List all tables,numrows,numcol and primary key for table if any in oracle

I want to QUERY to generate a table like below:
Tablename||noofrows||noofcolumns||PRIMARKEYCOL(IF ANY for the table)
xyz. 590. 11. xyz_id
bcd. 934 15 null
...
...
So far I was able to do this until now in 2 query:
Query 1:
select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name;
note:Count_rows() is function to calculate rows as stats are not up to date
query 2:
select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%');
Problem
I need to merge this table to one query (as shown in example above) so that I can represent the data in one table. My issue in clubbing the table is, with joins and how to make sure table without any primary keys are represent because if I just directly give constraint type ='p' in the where clause of the join I see that it only shows table with Primarykeys I am not able to figure this out.
Primary key can have more than just a single column, so - if you'd want to return them all, you'd either have to "aggregate" them, somehow (listagg looks like a natural choice, but - in 12c - you can't fetch distinct list of columns which you might need because of joining those tables duplicates appear).
As you already use a function to return number of rows (you'd probably rather regularly collect statistics, though, but yes - if tables are frequently modified (inserts and deletes), counting on-the-fly is the way to go.
So, alternative approach with 2 functions and 1 simple query. Yes, I know - context switching and stuff, but - see if it does any good.
This is what you already have (see if your and my code look similar):
SQL> create or replace function count_rows (par_table_name in varchar2)
2 return number
3 is
4 -- return number of rows in PAR_TABLE_NAME
5 retval number;
6 begin
7 execute immediate 'select count(*) from ' ||
8 dbms_assert.sql_object_name(par_table_name) into retval;
9 return retval;
10 end;
11 /
Function created.
This is new (to simplify fetching distinct list of primary key columns):
SQL> create or replace function pk_cols (par_table_name in varchar2)
2 return varchar2
3 is
4 -- return list of primary key columns for PAR_TABLE_NAME, sorted by column's
5 -- position within the primary key constraint
6 retval varchar2(100);
7 begin
8 select listagg(b.column_name, ', ') within group (order by b.position)
9 into retval
10 from user_constraints a join user_cons_columns b on b.constraint_name = a.constraint_name
11 where upper(a.table_name) = dbms_assert.sql_object_name(upper(par_table_name))
12 and a.constraint_type = 'P';
13 return retval;
14 end;
15 /
Function created.
Finally, that simple query I mentioned:
SQL> select a.table_name,
2 count_rows(a.table_name) total_rows,
3 max(a.column_id) total_cols,
4 pk_cols(a.table_name) primkey_cols
5 from user_tab_columns a
6 group by a.table_name;
TABLE_NAME TOTAL_ROWS TOTAL_COLS PRIMKEY_COLS
--------------- ---------- ---------- -------------------------
DEPT 4 3 DEPTNO
SO_TEST 0 1
TEST 7 3 ORD, TERMREGIONAL
EMP 14 8 EMPNO
TRAINING 0 5 TRAINING_PLACE_ID
TEMP_SE 10 3
<snip>
Just join the two queries:
select tab_cols.*, tab_keys.* from
(select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name) tab_cols,
(select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%')) tab_keys
where tab_cols.table_name = tab_keys.table_name(+);

Is there any faster way to perform merge on 120k records weekly in oracle pl/sql?

There are around 120k records in the database, and based on a few functions I calculate scores for all the records, weekly I have to update the table with new records and respective scores.
Below is a procedure that I am using to merge data into the table:
create or replace procedure scorecalc
AS
score1 number;
score2 number;
score3 number;
CURSOR cur IS
SELECT Id_number from tableA;
r_num cur%ROWTYPE;
BEGIN
--OPEN cur;
FOR r_num IN cur
LOOP
select functionA(r_num.id_number),functionb(r_num.id_number),functionc(r_num.id_number) into score1, score2,score3 from dual;
Merge into scores A USING
(Select
r_num.id_number as ID, score1 as scorea, score2 as scoreb, score3 as scorec, TO_DATE(sysdate, 'DD/MM/YYYY') as scoredate
FROM DUAL) B
ON ( A.ID = B.ID and A.scoredate = B.scoredate)
WHEN NOT MATCHED THEN
INSERT (
ID, scorea, scoreb, scorec, scoredate)
VALUES (
B.ID, B.scorea, B.scoreb, B.scorec,B.scoredate)
WHEN MATCHED THEN
UPDATE SET
A.scorea = B.scorea,
A.scoreb = B.scoreb,
A.scorec = B.scorec;
COMMIT;
END LOOP;
END;
whereas functionA/ B/ C has complex queries, joins in it to calculate the score.
Please suggest me any way to improve the performance because currently with this snippet of code I am only able to insert some 2k records in 1 hour? Can I use parallel DML here?
Thank you!
Why are you doing this in a procedure? This could all be done via DML:
MERGE INTO scores a USING
(SELECT ta.id_number AS ID,
functionA(ta.id) AS scoreA,
functionB(ta.id) AS scoreB,
functionC(ta.id) AS scoreC,
TO_DATE(sysdate, 'DD/MM/YYYY') as scoredate
FROM tableA ta) b
ON (a.id = b.id AND a.scoredate = b.scoredate)
WHEN MATCHED THEN UPDATE SET
a.scorea = b.scorea,
a.scoreb = b.scoreb,
a.scorec = b.scorec
WHEN NOT MATCHED THEN INSERT (ID, scorea, scoreb, scorec, scoredate)
VALUES (B.ID, B.scorea, B.scoreb, B.scorec,B.scoredate);
If you want to try using PARALLEL hint after that, feel free. But you should definitely get rid of that cursor and stop doing "Slow-by-slow" processing.
Something I've had some success with has been inserting from a select statement. It is pretty performant as it doesn't involve row by row inserting.
In your case, I'm thinking it would be something like:
INSERT INTO table (ID, scorea, scoreb, scorec, scoredate)
SELECT functionA(id_number), functionB(id_number), functionC(id_number)
FROM tableA
An example of this can be found at the link below:
https://docs.oracle.com/cd/B12037_01/appdev.101/b10807/13_elems025.htm
To schedule it just put the statement #Del in a procedure block;
create or replace procedure Saturday_Night_Merge is
begin
<Put the merge statement here>
end Saturday_Night_Merge;

For update of this query expression is not allowed (Cursor)

I am running below query
Select location, sum(units) as Units
from
( Select a.from_loc as location, sum(units) as units
from emp a, dept b
where a.id=b.id
Union all
Select a.to_loc as location, sum(units) as units
feom emp a, dept b
where a.id=b.id)
group by location;
Above query is giving me data in below format.
Location | sum(Units)
--------------------
100 | 350
200 | 450
Now i need to update another table Class with units given by above query.
Class is having Location as primary key column and units column also.
I tried to create a cursor but its throwing error, for update cannot be used
Here is snippet of cursor code
Declare
V_location number(20);
V_units (20),
Cursor c1 is
Select location, sum(units) as Units
from
( Select a.from_loc as location, sum(units) as units
from emp a, dept b
where a.id=b.id
Union all
Select a.to_loc as location, sum(units) as units
from emp a, dept b
where a.id=b.id)
group by location -----above select query
for update;
Begin
Open c1;
Loop
Fetch c1 into v_location, v_units;
Exit when c1%notfound;
Update class set units=v_units
where location=v_location;
End loop;
Close c1;
End;
Its throwing For update of this query expression is not allowed
Could someone please let me know what i am doing wrong in here. Or some other approach to update the class table
Would this do any good?
I presume that the FOR UPDATE clause causes problems
I removed the whole DECLARE section and used your SELECT statement in a cursor FOR loop as it is easier to maintain (no opening, closing, exiting, ...)
BEGIN
FOR cur_r IN ( SELECT location, SUM (units) AS units
FROM (SELECT a.from_loc AS location, SUM (units) AS units
FROM emp a, dept b
WHERE a.id = b.id
UNION ALL
SELECT a.to_loc AS location, SUM (units) AS units
FROM emp a, dept b
WHERE a.id = b.id)
GROUP BY location)
LOOP
UPDATE class
SET units = cur_r.units
WHERE location = cur_r.location;
END LOOP;
END;
[EDIT, after reading a comment]
IF-THEN-ELSE is to be done using CASE (or DECODE); for example:
update class set
units = case when location between 1 and 100 then cur_r.units / 10
else cur_r.units / 20
end
where location = cur_r.location

Query taking long when i use user defined function with order by in oracle select

I have a function, which will get greatest of three dates from the table.
create or replace FUNCTION fn_max_date_val(
pi_user_id IN number)
RETURN DATE
IS
l_modified_dt DATE;
l_mod1_dt DATE;
l_mod2_dt DATE;
ret_user_id DATE;
BEGIN
SELECT MAX(last_modified_dt)
INTO l_modified_dt
FROM table1
WHERE id = pi_user_id;
-- this table contains a million records
SELECT nvl(MAX(last_modified_ts),sysdate-90)
INTO l_mod1_dt
FROM table2
WHERE table2_id=pi_user_id;
-- this table contains clob data, 800 000 records, the table 3 does not have user_id and has to fetched from table 2, as shown below
SELECT nvl(MAX(last_modified_dt),sysdate-90)
INTO l_mod2_dt
FROM table3
WHERE table2_id IN
(SELECT id FROM table2 WHERE table2_id=pi_user_id
);
execute immediate 'select greatest('''||l_modified_dt||''','''||l_mod1_dt||''','''||l_mod2_dt||''') from dual' into ret_user_id;
RETURN ret_user_id;
EXCEPTION
WHEN OTHERS THEN
return SYSDATE;
END;
this function works perfectly fine and executes within a second.
-- random user_id , just to test the functionality
SELECT fn_max_date_val(100) as max_date FROM DUAL
MAX_DATE
--------
27-02-14
For reference purpose i have used the table name as table1,table2 and table3 but my business case is similar to what i stated below.
I need to get the details of the table1 along with the highest modified date among the three tables.
I did something like this.
SELECT a.id,a.name,a.value,fn_max_date_val(id) as max_date
FROM table1 a where status_id ='Active';
The above query execute perfectly fine and got result in millisecods. But the problem came when i tried to use order by.
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt,fn_max_date_val(id) as max_date
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc ;
-- It took almost 300 seconds to complete
I tried using index also all the values of the status_id and last_modified, but no luck. Can this be done in a right way?
How about if your query is like this?
select a.*, fn_max_date_val(id) as max_date
from
(SELECT a.id,a.name,a.value,a.status_id,last_modified_dt
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc) a;
What if you don't use the function and do something like this:
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt x.max_date
FROM table1 a
(
select max(max_date) as max_date
from (
SELECT MAX(last_modified_dt) as max_date
FROM table1 t1
WHERE t1.id = a.id
union
SELECT nvl(MAX(last_modified_ts),sysdate-90) as max_date
FROM table2 t2
WHERE t2.table2_id=a.id
...
) y
) x
where a.status_id ='Active'
order by status_id desc,last_modified_dt desc;
Syntax might contain errors, but something like that + the third table in the derived table too.

subquery inside INSERT statement

I just recently found out that subqueries are not allowed in INSERT statements that are inside stored procedures. This is my script:
begin
execute immediate 'truncate table itcustadm.GL_DTPJ_TEST2';
insert into GL_DTPJ_TEST2
(rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select
tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
(select ent.bank_desc from crmuser.end ent where ent.bank_id = gam.bank_id);
But since the (select ent.bank_desc from crmuser.end ent where ent.bank_id = gam.bank_id) at the bottom of the SELECT statement is not allowed by Oracle, what's the best way to accomplish this?
I actually have this code right before the INSERT statement, but I don't know how to exactly use it:
get_bank_desc := '(select ent.bank_desc from crmuser.end ent ' ||
'where ent.bank_id = gam.bank_id)';
I am not sure what you are exactly trying for, but below code may be useful for you, you can achieve inserting a SubQuery output into a table using below query sample, but make sure output of the SubQuery is a single row o/p, so that you can escape from "ORA-01427: single-row SubQuery returns more than one row" ERROR.
insert into test_ins1
values(1,(SELECT COL2 FROM TEST_INS WHERE COL1=1 ));
Even then you can use rownum in where condition and take the single value.
Please let me know in case of any doubts
declare
bank_desc_temp bank_desk_type; /* the type defined in crmuser.ent for bank_desc*/
begin
select ent.bank_desc into bank_desc_temp from crmuser.end ent where ent.bank_id = gam.bank_id;
execute immediate 'truncate table itcustadm.GL_DTPJ_TEST2';
insert into GL_DTPJ_TEST2
(rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select
tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
bank_desc_temp;
end;
When you say "not allowed" what do you mean? Did you get an error?
I ask, because subqueries are definitely allowed inside an insert as select statement, providing you have the syntax correct (and the subquery returns at most one row), e.g.:
create table test_tab (col1 number, col2 varchar2(10));
begin
insert into test_tab
select 1,
(select 'Yes' from dual d2 where d.dummy = d2.dummy)
from dual d;
commit;
end;
/
select * from test_tab;
COL1 COL2
---------- ----------
1 Yes
There are some syntax issues with the code you provided - where is the from clause, and where are the tq and gam aliases defined?
There are two syntax you can use in your insert statement:
(I)
INSERT INTO table_name( column1, column2....columnN)
VALUES ( value1, value2....valueN);
(II)
INSERT INTO table (column1, column2, ... )
SELECT expression1, expression2, ...
FROM source_table(s)
WHERE conditions;
In your example, you should choose the second approach:
insert into GL_DTPJ_TEST2 (rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
ent.bank_desc
from crmuser.gam
join crmuser.end ent
on ent.bank_id = gam.bank_id
;
basically, if you want to add records using an insert statement, you should use a full select statement first. Here is how I would do it:
(1)
select *
from table1;
(2)
select column1
,column2
,column3
from table1;
(3)
select t1.column1
,t1.column2
,t1.column3
,t2.column4
,t2.column5
from table1 t1
join table2 t2
on t2.id = t1.id
;
(4)
insert into table3 (col1
,col2
,col3
,col4
,col5)
select t1.column1
,t1.column2
,t1.column3
,t2.column4
,t2.column5
from table1 t1
join table2 t2
on t2.id = t1.id
;

Resources