Returning Multiple Columns in stored procedure - ORACLE 11.2 Up - oracle

Just wondering how I go about returning multiple columns from the database with this stored proc, Thanks.
is
cursor sample_cur is --this can be your select statement
select name as today from names;
begin
for rec in sample_cur loop
-- step by step for each record you return in your cursor
dbms_output.put_line(rec.name);
end loop;
end;

Cursor can return multiple columns, for example:
procedure list_something(p_result out sys_refcursor) as
begin
open p_result for
select t.column1,
t.column2
from MY_TABLE t
where t.column3 is not null;
end;
Next you can iterate thought this cursor from Java/.Net, etc.

Apart from Manushin's answer, If you strictly wants answer in your format, You may try below -
is
cursor sample_cur is --this can be your select statement
select name, other_column1, other_column2 as today from names;
begin
for rec in sample_cur loop
-- step by step for each record you return in your cursor
dbms_output.put_line(rec.name || rec.other_column1 || rec.other_column2);
end loop;
end;

Related

Stored Procedure output result set

Apologies for the newbie question, I am writing an Oracle stored procedure that opens a cursor for a specific SQL, calculates some variables for each row returned by the cursor but the stored procedure should return as a result set these variables that have been calculated for each row returned by the cursor. I am a bit confused on how to do this - can anyone help?!
I did read some of it so far I have something like this (just a trimmed down example and not exact code) but just need to return v_calc and v_calc_res in a result set:-
CREATE OR REPLACE procedure sp_test
(
in_input in number,
out_return out sys_refcursor
)
as
v_calc number;
v_calc_res number;
CURSOR C_test IS
select blah from test where blah = in_input;
begin
open c_test
loop
fetch c_test into v_calc;
v_calc_res := v_calc*5;
end loop;
end;
If you want a procedure to return a reference cursor for the calling routine to consume the procedure itself cannot then consume it. Cursors, including reference cursors are 1 way, 1 time consumables. As for as the desired calculations, they can be added to select defined for the cursor. So:
-- setup
create table test (blah integer, blah_stuff varchar2(50) );
-- build sp
create or replace procedure sp_blah_text(
in_input in number
, out_cur out sys_refcursor
)
is
begin
open out_cur for
select blah, blah_stuff, blah*5 as blah_x_5
from test
where blah = in_input;
end sp_blah_text;
-- test data
insert into test(blah, blah_stuff)
select 1,'a' from dual union all
select 2,'b' from dual union all
select 2,'x' from dual union all
select 2,'z' from dual union all
select 3,'c' from dual;
-- test
declare
ref_cur sys_refcursor;
l_blah test.blah%type;
l_stuff test.blah_stuff%type;
l_blah_5 test.blah%type;
begin
dbms_output.enable(null);
sp_blah_text(2,ref_cur);
loop
fetch ref_cur
into l_blah
, l_stuff
, l_blah_5;
exit when ref_cur%notfound;
dbms_output.put_line('blah=' || l_blah || ',stuff=' || l_stuff || ',blah*5=' || l_blah_5);
end loop;
end;
This works a treat thank you very much. I now have a performance issue that maybe you could help with. When I open the cursor, I then run several other SELECT statements to retrieve values using the variables from the cursor (see below). I assume this is because the switch between PL/SQL and SQL engine. Would using table collections help? But as I see since I need different columns from different tables I would need to have several different collections, how could I output everything in one record?
CREATE OR REPLACE procedure sp_test
(
in_input in number
)
as
v_calc number;
v_calc_res number;
v_blah_blah number;
v_blah_blah_blah number;
v_blah_blah_blah number;
CURSOR C_test IS
select blah from test where blah = in_input;
begin
open c_test
loop
fetch c_test into v_calc;
select blah_blah into v_blah_blah from t_blah_blah;
select blah_blah_blah into v_blah_blah_blah from t_blah_blah_blah;
select blah_blah_blah_blah into v_blah_blah_blah_blah from t_blah_blah_blah_blah;
v_calc_res := v_calc*5*v_blah_blah*v_blah_blah_blah*v_blah_blah_blah_blah
end loop;
end;

Validation of result before executing the query

When running a stored procedure to fetch some rows, First I want to validate if the query will return a row before sending the result, and second if it is possible to validate without running the same query twice.
I am using a cursor to store the yielded result, So I tried the cursor attribute %ROWCOUNT & %NOTFOUND. But the doesnt quite work. Plus I want to do this without running a loop on the cursor.
procedure MODULE_LIST_GK(p_module_Id IN MODULE_LIST.MODULE_ID% TYPE,
p_Error_Code out nvarchar2,
p_Error_Msg out nvarchar2,
p_Cursor out sys_refcursor) IS
BEGIN
OPEN p_Cursor FOR
SELECT A.MODULE_ID,
A.MODULE_NM,
A.AUTH_STATUS_ID
FROM MODULE_LIST A
WHERE A.MODULE_ID=p_module_Id;
SELECT COUNT(MODULE_ID)
INTO v_row_num
FROM MODULE_LIST A
WHERE A.MODULE_ID=p_module_Id;
IF v_row_num=0 THEN
p_Error_Code := SQLCODE;
p_Error_Msg := 'Does not Exists';
Return;
end IF;
EXCEPTION
WHEN OTHERS THEN
p_error_code:= SQLCODE;
p_error_msg := SQLERRM;
END MODULE_LIST_GK;
Your implementation have several points that could be improved.
First if you expect that for lot of parameters the returned cursor will be empty,
than first check the empty cursor and only after this check open the cursor. You do it vice versa.
How to check if the cursor is empty? Unfortunatelly you must fetch the first row to be able to verify it.
open l_cur for
select id, status from tab where id = p_id;
fetch l_cur into l_id, l_status;
if l_cur%NOTFOUND then
p_Error_Msg := 'Does not Exists';
Return;
end if;
This check is far more effective that the often used count(*) as it is considering only the first (few) rows and not counting all rows in the cursor.
If the check fails you are ready, othervise simple open the cursor and return it.
open l_cur for
select id, status from tab where id = p_id;
p_Cursor := l_cur;
Two additional thinks come to mind.
You should rething the generall approach if the database is very dynamic. How would you handle the case when other session deletes some row between the check and the second open of the cursor?
Finally consider returning an exception instead of the return code.
In order to know whether a cursor contains rows, you must open it and fetch the first row. Once you've done this, it makes no sense anymore to return that cursor, for the recipient will not be able to fetch that first row, because the cursor already points beyond it.
So, you must select twice. What you'd do is to use ROWNUM or an EXISTS clause here to show the DBMS that you are not interested in any more rows. This can speed up the query extremely.
PROCEDURE module_list_gk(p_module_id IN MODULE_LIST.MODULE_ID%TYPE,
p_error_code OUT NVARCHAR2,
p_error_msg OUT NVARCHAR2,
p_cursor OUT SYS_REFCURSOR) IS
v_count INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM module_list
WHERE module_id = p_module_Id
AND ROWNUM = 1;
IF v_count = 0 THEN
p_error_code := 0; -- Or -1403 for NO DATA FOUND if you like
p_error_msg := 'Does not Exists';
RETURN;
END IF;
OPEN p_Cursor FOR
SELECT module_id, module_nm, auth_status_id
FROM module_list
WHERE module_id = p_module_id;
EXCEPTION WHEN OTHERS THEN
p_error_code:= SQLCODE;
p_error_msg := SQLERRM;
END module_list_gk;
SQLCODE for the first query will be 0 by the way (SELECT COUNT(*) returns one row with the number of records found - no error hence). This is why you should decide to either return zero explicitly or some error code, such as -1403.
Here is the same with EXISTS:
BEGIN
SELECT CASE WHEN EXISTS
(
SELECT NULL
FROM module_list
WHERE module_id = p_module_Id
) THEN 1 ELSE 0 END
INTO v_count
FROM DUAL;
IF v_count = 0 THEN

Oracle, how to open cursor and select one column of many into a variable

I have a Oracle stored procedure returning a reference cursor. I want to open the cursor before i return it to check a count and throw an exception if need be, but im having trouble with syntax and how im supposed to do this.
V_ASN_COUNT NUMBER;
OPEN O_CURSOR FOR
SELECT column1, -- a bunch of columns
column2,
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
OPEN O_CURSOR;
LOOP
FETCH ASN_COUNT INTO V_ASN_COUNT;
END LOOP;
CLOSE O_CURSOR;
IF(V_ASN_COUNT > 1) THEN
RAISE MULTIPLE_ASNS;
END IF;
I think you can do this one:
curid NUMBER;
desctab DBMS_SQL.DESC_TAB;
colcnt NUMBER; -- total number of columns
res NUMBER;
V_ASN_COUNT NUMBER;
BEGIN
OPEN O_CURSOR FOR
SELECT
column1, -- a bunch of columns
column2,
...
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
curid := DBMS_SQL.TO_CURSOR_NUMBER (O_CURSOR);
DBMS_SQL.DESCRIBE_COLUMNS(curid, colcnt, desctab);
-- "ASN_COUNT" is the last column, i. e. "colcnt" refers to column number of "ASN_COUNT"
-- or set colcnt directly, e.g. colcnt := 12;
FOR i IN 1..colcnt LOOP
IF desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, V_ASN_COUNT);
ELSIF desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
.......
ELSE
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar, 25);
END IF;
END LOOP;
-- I do not know if this loop is needed, perhaps you can simply do
-- DBMS_SQL.DEFINE_COLUMN(curid, colcnt, V_ASN_COUNT);
-- for a single column
res := DBMS_SQL.FETCH_ROWS(curid); -- Fetch only the first row, no loop required
DBMS_SQL.COLUMN_VALUE(curid, colcnt, V_ASN_COUNT); -- Loop over all column not required, you just like to get the last column
IF V_ASN_COUNT > 1 THEN
RAISE MULTIPLE_ASNS;
END IF;
DBMS_SQL.CLOSE_CURSOR(curid);
For further details, check Oracle documentation: DBMS_SQL.TO_CURSOR_NUMBER Function.
However, the problem with open/rewinded cursor remains!
Following on from the previous question, if you wanted to open the same cursor multiple times to count over it, you could do something like this:
CREATE OR REPLACE PROCEDURE YOUR_PROC(O_CURSOR OUT SYS_REFCURSOR) is
ASN_NO NUMBER; -- have to define all columns the cursor returns
V_CHECK_ASN_NO NUMBER;
-- local function to generate the cursor, to avoid repeating the text
-- or using dynamic SQL
FUNCTION GET_CURSOR RETURN SYS_REFCURSOR IS
V_CURSOR SYS_REFCURSOR;
BEGIN
OPEN V_CURSOR FOR
SELECT *
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
-- where bunch of stuff
RETURN V_CURSOR;
END;
BEGIN
-- open the cursor for your check; might be better to have a local
-- variable for this rather than touching the OUT parameter this early
O_CURSOR := GET_CURSOR;
LOOP
FETCH O_CURSOR INTO ASN_NO; -- and all other columns!
EXIT WHEN O_CURSOR%NOTFOUND;
IF V_CHECK_ASN_NO IS NOT NULL AND V_CHECK_ASN_NO != ASN_NO THEN
-- means we have two distinct values
CLOSE O_CURSOR;
RAISE MULTIPLE_ASNS;
END IF;
V_CHECK_ASN_NO := ASN_NO;
END LOOP;
-- close the check version of the cursor
CLOSE O_CURSOR;
-- re-open the cursor for the caller
O_CURSOR := GET_CURSOR;
END YOUR_PROC;
You could open the cursor twice with the same SQL string using dynamic SQL, but this version uses a local function to make the cursor SQL static (and thus parsed at compile-time).
The cursor is executed twice, and at least some rows are fetched from the first execution (all rows if there are no duplicates; if there are duplicates then not all may be fetched). The caller gets a fresh result set containing all the rows.
How about making sure the first row is expendable for the validation?
This code will only open cursor once - no concurrency issue. The two first rows of the cursor both represent the first row of the intended result set - fetch first copy for validation and return the rest if validation succeeds.
You still have to fetch all the columns though.
V_ASN_COUNT NUMBER;
OPEN O_CURSOR FOR
WITH qry AS ( SELECT column1, -- a bunch of columns
column2,
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
)
SELECT *
FROM qry
WHERE rownum = 1
UNION ALL
SELECT *
FROM qry;
-- Consume the expendable first row.
FETCH O_CURSOR INTO V_ASN_COUNT; -- and all the other columns!
IF(V_ASN_COUNT > 1) THEN
CLOSE O_CURSOR;
RAISE MULTIPLE_ASNS;
END IF;

PL/SQL Printing Cursor Elements

I tried several ways and looked lots of codes, but I couldn't do it. I have 2 tables
Declare
v_ay varchar2(32);
cursor c_clone_time is
select beko_user_ref
from user_role;
begin
open c_clone_time;
fetch c_clone_time into v_ay
WHILE c_clone_time%FOUND LOOP
dbms_output.put_line (v_ay);
end while;
end;
I'm just trying to print the cursor values, but it is always failing. Can anyone help me ?
There are several spots(syntactical, semantical, and logical errors) in your code needed attention:
Minor one. The fetch c_clone_time into v_ay statement not terminated by semicolon ;.
You end while as any other loop statement with end loop; clause, not end while or end for as you might think.
To be able to print the contents of the cursor and successfully get out of the loop, you need to fetch from that cursor inside the loop as well, otherwise you are stuck with a never-ending loop:
Having said that your code might look look this:
declare
v_ay varchar2(32);
cursor c_clone_time is
select beko_user_ref
from user_role;
begin
open c_clone_time;
fetch c_clone_time into v_ay;
while c_clone_time%found loop
dbms_output.put_line (v_ay);
fetch c_clone_time into v_ay;
end loop;
end;
Test case:
create table user_role(
beko_user_ref varchar2(100)
);
insert into user_role(beko_user_ref)
select dbms_random.string('l', 7)
from dual
connect by level <= 7;
commit;
Print the cursor:
set serveroutput on;
clear screen;
declare
v_ay varchar2(32);
cursor c_clone_time is
select beko_user_ref
from user_role;
begin
open c_clone_time;
fetch c_clone_time into v_ay;
while c_clone_time%found loop
dbms_output.put_line (v_ay);
fetch c_clone_time into v_ay;
end loop;
end;
Result:
anonymous block completed
kcjhygy
cgunlmt
ofxaspd
qwqvnxx
nxjdrli
luevaqk
xvdocpr

Use Cursor variable to insert with a loop in a store procedure

I have a store procedure and I need to take all id´s from a table and insert new rows in other table with those id´s but i dont´t understand well the function cursor
PROCEDURE INSERTMDCGENERAL AS
idCat NUMERIC;
CURSOR cur IS
SELECT ID_CAT_FILTROS_TALENTO into idCat FROM MDC_CAT_FILTROS_TALENTO;
BEGIN
FOR v_reg IN cur LOOP
INSERT INTO MDC_FILTROS_TALENTO(ID_FILTRO,ID_CAT_FILTROS_TALENTO)
VALUES(SEC_MDC_FILTROS_TALENTO.NextVal,idCat);
END LOOP;
COMMIT;
END INSERTMDCGENERAL;
There is rarely any point in doing anything more complex than:
PROCEDURE INSERTMDCGENERAL AS
BEGIN
INSERT INTO MDC_FILTROS_TALENTO
(ID_FILTRO,ID_CAT_FILTROS_TALENTO)
SELECT SEC_MDC_FILTROS_TALENTO.NextVal
, ID_CAT_FILTROS_TALENTO
FROM MDC_CAT_FILTROS_TALENTO;
COMMIT;
END INSERTMDCGENERAL;
This should work in most cases. Only if you are dealing with millions of rows is it likely that you will need to embellish this. Even then you should not use a cursor loop and Row-By-Row processing: that is vastly more inefficient.
This might be what you are expecting...
PROCEDURE INSERTMDCGENERAL AS
idCat NUMERIC;
CURSOR cur IS SELECT ID_CAT_FILTROS_TALENTO FROM MDC_CAT_FILTROS_TALENTO;
BEGIN
open cur;
loop
fetch cur into idCat;
exit when cur%notfound;
INSERT INTO MDC_FILTROS_TALENTO(ID_FILTRO,ID_CAT_FILTROS_TALENTO)
VALUES(SEC_MDC_FILTROS_TALENTO.NextVal,idCat);
END LOOP;
close cur;
COMMIT;
END INSERTMDCGENERAL;

Resources