Oracle Dynamically call cursor - oracle

I've two cursors both have almost similar code just a little difference in the group by condition, I want if the id is 1 or 2 e.t.c then it should open cur1 else cur2, basically one cursor at a time. Is there any possibility of achieving this?
if id in (1,2,3,4) then
cursor_value:= 'cur1';
else
cursor_value := 'cur2';
end if;
for i in cursor_value loop
end loop;

You can use an OPEN-FOR statement. Example:
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- Open cursor & specify bind argument in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
/

One option would be to include both cursor's SELECT statements into cursor FOR loop, along with a condition that chooses which one of them will be used. For example:
SQL> declare
2 id number := &par_id;
3 begin
4 for cur_r in (select * from emp
5 where deptno = 10
6 and id in (1,2,3,4)
7 union all
8 select * from emp
9 where deptno = 20
10 and id not in (1,2,3,4)
11 )
12 loop
13 dbms_output.put_Line(cur_r.deptno ||' '||cur_r.ename);
14 end loop;
15 end;
16 /
Enter value for par_id: 1 --> ID = 1, so use SELECT for DEPTNO = 10
old 2: id number := &par_id;
new 2: id number := 1;
10 CLARK
10 KING
10 MILLER
PL/SQL procedure successfully completed.
SQL> /
Enter value for par_id: 823 --> ID <> 1, so use SELECT for DEPTNO = 20
old 2: id number := &par_id;
new 2: id number := 823;
20 SMITH
20 JONES
20 SCOTT
20 ADAMS
20 FORD
PL/SQL procedure successfully completed.
SQL>

The simple way could be to use if else condition.
DECLARE
type cur REF CURSOR;
c cur;
BEGIN
IF id in (1,2,3,4) THEN
OPEN c FOR 'cursor query 1';
ELSE
OPEN c FOR 'cursor query 2';
END IF ;
END;
Cheers!!

Related

How to return more than one select queries in same procedure

I would like to ask how can i print output in procedure more than one statement.
Assume that you want to show dba_objects and segments row count. But i can not use dbms_sql.return_result my version is 11g.
Something like,
create or replace procedure get_rows_count
(
cursor1 out SYS_REFCURSOR,
cursor2 out SYS_REFCURSOR
)
as
begin
open cursor1 for select count(*) from dba_objects;
open cursor2 for select count(*) from dba_segments;
end get_rows_count;
/
Assume that you want to show dba_objects and segments row count
I assumed it. Conclusion: that's not the way to do it. If you want to get row count from e.g. dba_objects, then you should just
select count(*) from dba_objects;
in any variation you want (pure SQL, function that returns that number, procedure with an OUT parameter (worse option), ...). But, creating a procedure which uses ref cursor for that purpose is ... well, wrong.
If I got you wrong, then: procedure you wrote is OK. You can call it from another PL/SQL procedure (named or anonymous), fetch result into a variable and do something with it (e.g. display it).
Your procedure (selects from Scott's tables; I don't have access to DBA_ views):
SQL> CREATE OR REPLACE PROCEDURE get_rows_count (cursor1 OUT SYS_REFCURSOR,
2 cursor2 OUT SYS_REFCURSOR)
3 AS
4 BEGIN
5 OPEN cursor1 FOR SELECT * FROM emp;
6
7 OPEN cursor2 FOR SELECT * FROM dept;
8 END get_rows_count;
9 /
Procedure created.
How to call it? See line #8:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 rc1 SYS_REFCURSOR;
3 rc2 SYS_REFCURSOR;
4 --
5 rw1 emp%ROWTYPE;
6 rw2 dept%ROWTYPE;
7 BEGIN
8 get_rows_count (rc1, rc2);
9
10 DBMS_OUTPUT.put_line ('Employees -----------');
11
12 LOOP
13 FETCH rc1 INTO rw1;
14
15 EXIT WHEN rc1%NOTFOUND;
16
17 DBMS_OUTPUT.put_line (rw1.ename);
18 END LOOP;
19
20 --
21 DBMS_OUTPUT.put_line ('Departments ---------');
22
23 LOOP
24 FETCH rc2 INTO rw2;
25
26 EXIT WHEN rc2%NOTFOUND;
27
28 DBMS_OUTPUT.put_line (rw2.dname);
29 END LOOP;
30
31 DBMS_OUTPUT.put_line ('First ref cursor: ' || rc1%ROWCOUNT);
32 DBMS_OUTPUT.put_line ('Second ref cursor: ' || rc2%ROWCOUNT);
33 END;
34 /
Result:
Employees -----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
Departments ---------
ACCOUNTING
RESEARCH
SALES
OPERATIONS
First ref cursor: 14
Second ref cursor: 4
PL/SQL procedure successfully completed.
SQL>
You can use famous DBMS_OUTPUT.PUT_LINE() along with %ROWCOUNT suffix for your case such as
SET serveroutput ON
CREATE OR REPLACE PROCEDURE get_rows_count(
cursor1 OUT SYS_REFCURSOR,
cursor2 OUT SYS_REFCURSOR,
count1 OUT INT,
count2 OUT INT
) AS
cur_rec_obj user_objects%ROWTYPE;
cur_rec_seg user_segments%ROWTYPE;
BEGIN
OPEN cursor1 FOR SELECT * FROM user_objects;
LOOP
FETCH cursor1 INTO cur_rec_obj;
EXIT WHEN cursor1%NOTFOUND;
END LOOP;
OPEN cursor2 FOR SELECT * FROM user_segments;
LOOP
FETCH cursor2 INTO cur_rec_seg;
EXIT WHEN cursor2%NOTFOUND;
END LOOP;
count1 := cursor1%ROWCOUNT;
count2 := cursor2%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(count1);
DBMS_OUTPUT.PUT_LINE(count2);
END;
/
and you can call as follows from the SQL Window of PL/SQL Developer :
DECLARE
v_cursor1 SYS_REFCURSOR;
v_cursor2 SYS_REFCURSOR;
v_count1 INT;
v_count2 INT;
BEGIN
get_rows_count(v_cursor1, v_cursor2, v_count1, v_count2 );
END;
/

How can I create an array in PL/SQL that takes values from a SELECT statement?

I'm trying to create a script that can select a list of values, put them into an array, then iterate the array so that the selected values can be deleted. I think I'm ultimately just confusing myself, but my biggest issue seems to be figuring out how to select values into an array. With every other example I've seen on here, people define the array statically, not with a select statement.
I've tried to create a FOR loop that looks like this:
DECLARE
type PERMIDS IS VARRAY(10) OF VARCHAR2(10);
ID PERMIDS;
total integer;
BEGIN
ID := PERMIDS('1','2','3','4','5');
total := ID.count;
FOR i in 1 .. total LOOP
SYS.DBMS_OUTPUT.PUT_LINE('ID: ' || ID(i));
END LOOP;
END;
This runs, but it doesn't allow for dynamic definition of the array.
What I'd like to do is something like this, with the select statement potentially yielding any number of results:
DECLARE
type PERMIDS IS VARRAY(10) OF VARCHAR2(10);
ID PERMIDS;
total integer;
BEGIN
SELECT rg.ID INTO ID
FROM table1 rg
LEFT JOIN table2 a
ON rg.ID = a.ID
WHERE a.ID = '1234';
total := ID.count;
FOR i in 1 .. total LOOP
SYS.DBMS_OUTPUT.PUT_LINE('ID: ' || ID(i));
END LOOP;
END;
It's important that this loop can handle any number of results from the select statement, 0 included, as a user could have any number of records.
One option is to use built-in sys.odcivarchar2list type and bulk collect into it.
Sample table (department names will be put into a collection):
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
PL/SQL procedure:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_id sys.odcivarchar2list;
3 begin
4 select dname
5 bulk collect into l_id
6 from dept;
7
8 for i in 1 .. l_id.count loop
9 dbms_output.put_line('ID = ' || l_id(i));
10 end loop;
11 end;
12 /
ID = ACCOUNTING
ID = RESEARCH
ID = SALES
ID = OPERATIONS
PL/SQL procedure successfully completed.
SQL>

Select Query in cursor FOR LOOP

Pretty new to Oracle, need help on this Procedure, using Oracle 11g
Sample table data (TAB1):
ID
Amount
Currency
10
300
GBP
15
500
GBP
20
100
GBP
Requirement is to select all the ID's from TAB1 based on currency and store it in a variable and later use these ID's in other select Queries within the same Stored Procedure.
CREATE OR REPLACE PROCEDURE myproc (i_id IN VARCHAR, i_curr IN VARCHAR)
AS
CURSOR GET_I IS
SELECT ID, CURRENCY
FROM TAB1
WHERE CURRENCY = 'GBP';
-- This will give me 3 ID's (10, 15 & 20) which I am storing in variable r_1 below.
r_1 VARCHAR (5) : NULL;
BEGIN
OPEN GET_I;
LOOP
FETCH GET_I INTO r_1;
IF GET_I%NOTFOUND
THEN
EXIT;
---In the below ELSE PART can we run a select query using the value stored in r_1??
--ELSE
--Data stored in r_1 to be used in further select queries in later part and output of the below
--be returned as SYS_REFCURSOR;
--BELOW two lines gives error
--FOR I in r_1 (
--SELECT ID FROM TAB2 WHERE TAB2.ID=r_1);
END IF;
END LOOP;
CLOSE GET_ID;
END;
It looks you want to use a nested FOR loop.
I'd suggest you to use cursor FOR loops - they are easier to maintain as you don't have to declare cursor variable (by the way, in your case it wouldn't work anyway as you'd like to store both ID and CURRENCY into a scalar R_1 variable), open the cursor, pay attention about exiting the loop and closing the cursor. In a cursor FOR loop, Oracle does all that for you.
Here's an example:
Sample table:
SQL> select * from tab1;
ID AMOUNT CUR
---------- ---------- ---
10 300 GBP
15 500 GBP
20 100 GBP
Procedure:
SQL> create or replace procedure myproc as
2 begin
3 for cur_id in (select id from tab1) loop
4 dbms_output.put_line('ID = ' || cur_id.id);
5 for cur_other in (select amount, currency
6 from tab1
7 where id = cur_id.id --> use ID fetched in outer loop
8 )
9 loop
10 dbms_output.put_line(cur_other.amount ||' - '|| cur_other.currency);
11 end loop;
12 end loop;
13 end;
14 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec myproc;
ID = 10
300 - GBP
ID = 15
500 - GBP
ID = 20
100 - GBP
PL/SQL procedure successfully completed.
SQL>
How to return a refcursor?
SQL> create or replace procedure myproc (par_id in tab1.id%type,
2 par_rc out sys_refcursor) as
3 begin
4 for cur_id in (select id
5 from tab1
6 where id = par_id
7 ) loop
8
9 open par_rc for select amount, currency
10 from tab1
11 where id = cur_id.id;
12 end loop;
13 end;
14 /
Procedure created.
SQL> var l_rc refcursor
SQL>
SQL> exec myproc(10, :l_rc);
PL/SQL procedure successfully completed.
SQL> print l_rc
AMOUNT CUR
---------- ---
300 GBP
SQL>

How to update ref cursor values in oracle?

I want to fetch limited no. of rows using refcursor. then I need to update same set of records. is it possible?
create or replace PROCEDURE myproc (
P_ROWCOUNT IN NUMBER,
OUT_TXN_IDS OUT OF_CR_TYPE,
P_CD_ERROR OUT NUMBER,
P_DS_ERROR OUT VARCHAR2
)
AS
V_TXNID NUMBER;
BEGIN
P_CD_ERROR := 0;
P_DS_ERROR := 'SUCCESS';
OPEN OUT_TXN_IDS for
SELECT id FROM table1 WHERE status='N' AND ROWNUM<=P_ROWCOUNT;
EXCEPTION
WHEN OTHERS THEN
P_CD_ERROR := sqlcode;
P_DS_ERROR := 'WF-ERROR - myproc - ' || substr(SQLERRM, 1, 200);
RETURN;
END myproc;
I need to update same records to status Y after refcursor returns. can we do this. please suggest
I don't have your tables nor data so I simplified it a little bit, but - it should work nonetheless.
Initial statuses:
SQL> SELECT status, count(*) FROM table1 group by status;
S COUNT(*)
- ----------
Y 7
N 7
Procedure: basically, you'd modify rows represented by ID returned by ref cursor.
SQL> DECLARE
2 out_txn_ids SYS_REFCURSOR;
3 p_rowcount NUMBER := 5;
4 l_id table1.id%TYPE;
5 BEGIN
6 OPEN out_txn_ids FOR SELECT id
7 FROM table1
8 WHERE status = 'N'
9 AND ROWNUM <= p_rowcount;
10
11 LOOP
12 FETCH out_txn_ids INTO l_id;
13
14 EXIT WHEN out_txn_ids%NOTFOUND;
15
16 UPDATE table1
17 SET status = 'Y'
18 WHERE id = l_id;
19 END LOOP;
20
21 CLOSE out_txn_ids;
22 END;
23 /
PL/SQL procedure successfully completed.
Result:
SQL> SELECT status, count(*) FROM table1 group by status;
S COUNT(*)
- ----------
Y 12
N 2
SQL>

Access cursor by column name dynamically

Can I access a cursor's column dynamically? I mean by name? something like this:
declare
v_cursor := select * from emp;
begin
FOR reg IN v_cursor LOOP
dbms_output.put_line(**reg['column_name_as_string']**);
end loop;
end;
I know the bold part is not PL/SQL, but I'm looking for something like that and can't find it anywhere.
You can use the package DBMS_SQL to create and access cursors with dynamic queries.
However it's not really straightforward to access a column by name because the DBMS_SQL package uses positioning and in a dynamic query we may not know the order of the columns before the execution.
Furthermore, in the context of this question, it appears that we may not know which column we want to display at compile time, we will assume that the column we want to display is given as a parameter.
We can use DBMS_SQL.describe_columns to analyze the columns of a SELECT query after it has been parsed to build a dynamic mapping of the columns. We will assume that all columns can be cast into VARCHAR2 since we want to display them with DBMS_OUTPUT.
Here's an example:
SQL> CREATE OR REPLACE PROCEDURE display_query_column(p_query VARCHAR2,
2 p_column VARCHAR2) IS
3 l_cursor INTEGER;
4 l_dummy NUMBER;
5 l_description_table dbms_sql.desc_tab3;
6 TYPE column_map_type IS TABLE OF NUMBER INDEX BY VARCHAR2(32767);
7 l_mapping_table column_map_type;
8 l_column_value VARCHAR2(4000);
9 BEGIN
10 l_cursor := dbms_sql.open_cursor;
11 dbms_sql.parse(l_cursor, p_query, dbms_sql.native);
12 -- we build the column mapping
13 dbms_sql.describe_columns3(l_cursor, l_dummy, l_description_table);
14 FOR i IN 1 .. l_description_table.count LOOP
15 l_mapping_table(l_description_table(i).col_name) := i;
16 dbms_sql.define_column(l_cursor, i, l_column_value, 4000);
17 END LOOP;
18 -- main execution loop
19 l_dummy := dbms_sql.execute(l_cursor);
20 LOOP
21 EXIT WHEN dbms_sql.fetch_rows(l_cursor) <= 0;
22 dbms_sql.column_value(l_cursor, l_mapping_table(p_column), l_column_value);
23 dbms_output.put_line(l_column_value);
24 END LOOP;
25 dbms_sql.close_cursor(l_cursor);
26 END;
27 /
Procedure created
We can call this procedure with a query known only at run-time:
SQL> set serveroutput on
SQL> exec display_query_column('SELECT * FROM scott.emp WHERE rownum < 5', 'ENAME');
SMITH
ALLEN
WARD
JONES
PL/SQL procedure successfully completed
SQL> exec display_query_column('SELECT * FROM scott.emp WHERE rownum < 5', 'EMPNO');
7369
7499
7521
7566
PL/SQL procedure successfully completed
Use caution with dynamic SQL: it has the same privileges as the user and can therefore execute any DML and DDL statement allowed for this schema.
For instance, the above procedure could be used to create or drop a table:
SQL> exec display_query_column('CREATE TABLE foo(id number)', '');
begin display_query_column('CREATE TABLE foo(id number)', ''); end;
ORA-01003: aucune instruction analysée
ORA-06512: à "SYS.DBMS_SQL", ligne 1998
ORA-06512: à "APPS.DISPLAY_QUERY_COLUMN", ligne 13
ORA-06512: à ligne 1
SQL> desc foo
Name Type Nullable Default Comments
---- ------ -------- ------- --------
ID NUMBER Y
It's probably easiest to make the query dynamic if you can.
DECLARE
v_cursor SYS_REFCURSOR;
dynamic_column_name VARCHAR2(30) := 'DUMMY';
column_value VARCHAR2(32767);
BEGIN
OPEN v_cursor FOR 'SELECT ' || dynamic_column_name || ' FROM dual';
LOOP
FETCH v_cursor INTO column_value;
EXIT WHEN v_cursor%NOTFOUND;
dbms_output.put_line( column_value );
END LOOP;
CLOSE v_cursor;
END;
If you really want to have a hardcoded SELECT * and dynamically select a column from that by name, I think you could do that using DBMS_SQL as Vincent suggests, but it will be somewhat more complex.
You mean something like:
declare
cursor sel_cur is
select * from someTable;
begin
for rec in sel_cur
loop
dbms_output.put_line('col1: ' || rec.col1);
end loop;
end;

Resources