Oracle Stored procedure - execute for all the select result - oracle

Say I have a stored procedure which accepts 2 varchars, does some processing and updates my business tables. Is there a way that I can run the stored procedure for the results from a select query?
Like,
execute my_stored_proc select varchar_1,varchar_2 from an_ip_table;

You can iterate over results by loop
BEGIN
FOR RECS IN (SELECT varchar_1, varchar_2 FROM an_ip_table)
LOOP
my_stored_proc (RECS.varchar_1, RECS.varchar_2);
END LOOP;
END

This could be a simple way:
begin
for i in (
select varchar_1, varchar_2
from an_ip_table
)
loop
my_stored_proc(i.varchar_1, i.varchar_2);
end loop;
end;

Initially, I thought of just to put a comment, but this needs some explanation, so I'm writing an answer. You are actually doing it the wrong way. Ideally, you should be passing a cursor to your my_stored_proc and fetching the cursor inside the procedure. Your method actually causes multiple calls to procedure for every row from the query result. The processing will be very slow if you have huge volume of data. It is a bad idea even if there are few rows.
Here is a sample procedure that does a dml operation using FORALL.It is just a sample, but you should be able to convert your select query such that you should be able to do dml this way.
CREATE OR REPLACE PROCEDURE my_stored_proc (
p_iptab_cur SYS_REFCURSOR
) AS
TYPE iprec IS RECORD ( col1 an_ip_table.col1%TYPE,
col2 an_ip_table.col1%TYPE );
TYPE iptype IS
TABLE OF iprec;
ips iptype;
BEGIN
FETCH p_iptab_cur BULK COLLECT INTO ips;
FORALL i IN ips.FIRST..ips.LAST
--Your DML-- using the collection of records.
END;
/
--Calling the procedure by passing the `CURSOR`
DECLARE
x SYS_REFCURSOR;
BEGIN
OPEN x FOR select col1, col2
from an_ip_table;
my_stored_proc(x);
END;
/

Related

how to get better performance stored procedure oracle

I wrote a stored procedure, but I don't think it is performing at all. How can I make it work better? Thanks.
Table A has 800k records. Table B has 36k records. Is test data, there will be more records in production environment.
Table B has customer_no column index defined.
I ran it once and it took 22 minutes.
create or replace procedure SP_KVKK
is
TYPE json_data_type IS RECORD
(
del_data CLOB
);
customer_no number(10);
r_del_data C%ROWTYPE;
l_deleted_cur SYS_REFCURSOR;
l_deleted_rec json_data_type;
l_sel_sql VARCHAR2 (500);
cursor mbb_list is
select customer_no from A;
begin
open mbb_list;
loop
fetch mbb_list into customer_no;
exit when mbb_list%notfound;
l_sel_sql := 'SELECT JSON_OBJECT(* RETURNING CLOB) AS DEL_DATA
FROM B where customer_no=' || customer_no;
open l_deleted_cur for l_sel_sql;
loop
fetch l_deleted_cur into l_deleted_rec;
exit when l_deleted_cur%notfound;
r_del_data.DELETED_DOCUMENT_JSON := l_deleted_rec.del_data;
r_del_data.DELETE_DATE := SYSTIMESTAMP;
Insert into C
values r_del_data;
end loop;
close l_deleted_cur;
end loop;
close mbb_list;
end;
The best thing you can do is deal with this construct as a single SQL statement. Stop thinking in terms of row-by-row nested loops which will always be slow, and look to SQL batch operations to handle this:
begin
-- insert your two columns into table C using the data
-- from table B returned as a json_object and systimestamp,
-- where only records from table B that have a customer_no
-- from table A will be selected
insert into C (deleted_document_json, delete_date)
select json_object(B.* returning clob), systimestamp
from B
inner join A using (customer_no);
commit;
end;

How to insert records into variables from cte in oracle?

I have a procedure in which I want to fetch all records from cte into Names variable. But this code is not writing into names from CTE. How can I fetch records into names so that I can later loop through names and get content of field_name?
CREATE OR REPLACE PROCEDURE sp_market
IS
Names VARCHAR2(32767);
BEGIN
WITH CTE(sqql) As
(
SELECT field_name sqql FROM pld_medicare_config
)
SELECT sqql into Names from CTE;
END sp_market;
SELECT sqql into Names from CTE;
You are assigning multiple rows returned from table to a variable, which will fail.
You could simply use a CURSOR FOR LOOP which will create an implicit cursor and you can loop through the names:
CREATE OR REPLACE PROCEDURE sp_market IS
BEGIN
FOR i IN (
SELECT field_name
FROM pld_medicare_config
)
LOOP
-- Your logic goes here
dbms_output.put_line(i.field_name);
END LOOP;
END;
/
I think your best bet is to create a associative array and use BULK COLLECT to populate the table. In its simplest form, the code would look like this:
CREATE OR REPLACE PROCEDURE sp_market IS
TYPE lt_names IS TABLE OF VARCHAR2(32767) INDEX BY PLS_INTEGER;
l_tNames lt_names;
BEGIN
SELECT field_name
BULK COLLECT INTO l_tNames
FROM pld_medicare_config
IF l_tNames.COUNT() > 0 THEN
FOR i IN l_tNames.FIRST..l_tNames.LAST LOOP
NULL; --REPLACE WITH YOUR LOGIC
END LOOP;
END IF;
END;
/
A few notes:
I'm assuming that you've set MAX_STRING_SIZE to EXTENDED. Otherwise, you'll have an issue with VARCHAR2 that big.
As I said, that is the simplest way to do this. If you're expecting a huge result set, you'll want to look into chunking it up. This Oracle blog post is very helpful in giving you multiple options for how to perform bulk processing. (Blog Post)

Run query against another procedure that returns a cursor

I have a procedure that that has an OUT sys_refcursor:
CREATE OR REPLACE PROCEDURE get_detail_data(RC1 IN OUT sys_REFCURSOR)
IS ...
Now in another a procedure, I'm trying to calculate some statistics against the output of that procedure so I can insert them into a table:
CREATE OR REPLACE PROCEDURE load_stats
AS
cur_detail SYS_REFcursor;
BEGIN
-- Load the data from the "get_detail_data" procedure
get_detail_data(cur_detail);
-- Now lets calculate some stats against that detail data and insert
insert into stats_table (
select
id_number, sum(amount)
from table(cur_detail) -- obviously this is not valid
)
END;
How can I run a query against a sys_refcursor dataset and insert those results into another table?
You can start fetching from an open cursor like this:
LOOP -- Fetches 2 columns into variables
FETCH cur_detail INTO v_id_number, v_amount;
EXIT WHEN cv%NOTFOUND;
[... insert etc ...]
END LOOP;
I'd suggest to do aggregation within the cursor, otherwise you need to order by id_number and aggregate within the loop, breaking by id_number.

How to Calculate Procedures peformance

Hi to everyone please suggest me how to check in the following among three method which is the best method and which method gives best performance.
Thanks in adavance.
CREATE OR REPLACE PACKAGE PKG_P
AS
G_SAL NUMBER(7,2):=&G;
END;
1.)NORMAL CURSOR FOR LOOP METHOD :
CREATE OR REPLACE PROCEDURE P(
v_deptno NUMBER,
v_dname VARCHAR2
)
AS
CURSOR c_emp(c_deptno NUMBER,c_dname VARCHAR2)
IS
SELECT E.Ename,D.Dname,E.Sal
FROM Emp E,Dept D
WHERE E.Deptno=D.Deptno
AND
E.Deptno=c_deptno
AND
D.Dname=c_dname
AND
E.Sal=PKG_P.G_SAL ;
BEGIN
FOR i IN c_emp(v_deptno,v_dname)
LOOP
DBMS_OUTPUT.PUT_LINE(i.Ename||' '||i.Dname||' '||i.Sal);
END LOOP;
END;
2)NORMAL CURSOR FOR LOOP WITH IF CONDITION :
CREATE OR REPLACE PROCEDURE P(
v_deptno NUMBER,
v_dname VARCHAR2
)
AS
CURSOR c_emp(c_deptno NUMBER,c_dname VARCHAR2)
IS
SELECT E.Ename,D.Dname,E.Sal
FROM Emp E,Dept D
WHERE E.deptno=D.deptno
AND
E.Deptno=c_deptno
AND
D.Dname=c_dname;
BEGIN
FOR i IN c_emp(v_deptno,v_dname)
LOOP
IF i.sal=PKG_P.G_SAL THEN
DBMS_OUTPUT.PUT_LINE(i.Ename||' '||i.Dname||' '||i.Sal);
END IF;
END LOOP;
END;
3)USING ASSCOCIATE ARRAY:
CREATE OR REPLACE PROCEDURE P(
v_deptno NUMBER,
v_dname VARCHAR2
)
AS
CURSOR c_emp(c_deptno NUMBER,c_dname VARCHAR2)
IS
SELECT E.Ename,D.Dname,E.Sal
FROM Emp E,Dept D
WHERE E.deptno=D.deptno
AND
E.Deptno=c_deptno
AND
D.Dname=c_dname;
TYPE t is RECORD
(
v_ename VARCHAR2(30),
v_dname VARCHAR2(30),
v_sal NUMBER(7,2)
);
TYPE t1 IS TABLE OF t;
t2 t1;
BEGIN
OPEN c_emp(v_deptno,v_dname);
FETCH c_emp BULK COLLECT INTO t2;
FOR i in t2.FIRST..t2.LAST
LOOP
IF t2(i).V_sal=PKG_P.G_SAL THEN
DBMS_OUTPUT.PUT_LINE(t2(i).v_ename||' '||t2(i).V_dname||' '||t2(i).V_sal);
END IF;
END LOOP;
END;
Easy answer: IT DEPENDS
With some time I could probably create some tables for you, where each of the three versions could be faster, because skewed data can confuse the optimizer...
But as general guidelines with not to strange data:
With newer Oracle Versions the first will be the fastest, easiest to read and overall best, With older versions you may have to incorporate BULK COLLECT into the first:
You should always give the optimizer as much information as you can - so all conditions should be in WHERE clause, so it can find the best plan. Also you don't want to read data from disk just to throw it away afterwards (loop with IF)
The second performance killer are PL/SQL context-switches. If you process one row at a time you will get an overhead for fetching each row individually - so you should use BULK COLLECT to read a bunch of data into memory and process them at once. But Oracle will automatically bulk collect cursor FOR-LOOPS since 10g. See this Link: CURSORS IN PLSQL
You should RARELY use BULK COLLECT without limit. What if the Cursor returns 10M rows? Without limit your whole memory will be flooded and your server will choke. So you would fetch in batches of about 100-500 rows (which newer oracle versions will do automatically with the FOR IN... Loop)

storing rows from table using pl/sql

I have to call a procedure multiple times that then populates a table. Problem is the procedure truncates the table after each call. I have to store the results of the table for each run. I looked up the documentation and couldnt get an idea how to do this in pl/sql. Any ideas is much appreciated.
thanks a lot!
After each call of the procedure, copy the resulting data into another table with the same columns:
INSERT INTO TABLE_B
SELECT *
FROM TABLE_A;
When you're finished calling the procedure, all your data is in TABLE_B.
In PL/SQL, it looks like this:
BEGIN
FOR I IN 1..10 LOOP
PROC(I);
INSERT INTO TABLE_B
SELECT *
FROM TABLE_A;
END LOOP;
PROCESS_ALL_DATA();
END;
Update:
If you don't have permissions to create tables, then you could store the partial results in a PL/SQL table (in memory):
DECLARE
TYPE T_T_A IS TABLE OF A%TYPE;
L_IMED_TABLE T_T_A;
BEGIN
FOR I IN 1..10 LOOP
PROC(I);
SELECT * BULK COLLECT INTO L_IMED_TABLE
FROM A;
END LOOP;
FOR I IN L_IMED_TABLE.FIRST .. L_IMED_TABLE.LAST LOOP
PROCESS_RESULT_ROW( L_IMED_TABLE(I) );
END LOOP;
END;
You can have trigger that gets triggered on insert to the main table and populates the same data to a archive table, where you can add more columns like timestamp and call counter and more.

Resources