PL/SQL Stored Procedure - return records from a table - oracle

I am very new to doing SQL work. I was wondering how I return a select statement in a stored procedure in PL/SQL.
My understanding so far (that is little) is that I should put the return of the data in a table and assign the data within the table to a reference cursor. With that loaded then LOOP through the REF Cursor and present the data back?
Actually converting that into code for a stored procedure has lost me completely with little examples to see with my use case. Any help is appreciated.
Many thanks in advance :)

Here's one example: procedure has only one - OUT - parameter, which is a refcursor:
SQL> create or replace procedure p_test (par_rc out sys_refcursor)
2 is
3 begin
4 open par_rc for select deptno, dname, loc from dept;
5 end;
6 /
Procedure created.
In order to call such a procedure, you need to store the result into something. In order to do that, I'll declare a variable (in SQL*Plus, which is a tool I use for this example) and call the procedure using begin-end block, providing the variable name as its parameter:
SQL> var l_rc refcursor;
SQL>
SQL> begin
2 p_test (:l_rc);
3 end;
4 /
PL/SQL procedure successfully completed.
Print the result:
SQL> print l_rc
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
There might be other options, which depend on what you really are doing.

Usually a stored procedure is used to perform manipulations of data in the database, and functions are used to return values or data. If you're just trying to use a select statement within a stored procedure, then you would want to use a cursor, which is something you declare like any other variable at the beginning of the procedure, and then open either implicitly or explicitly within the procedure code.
Example of an implicit cursor:
declare
cursor sample_cur is --this can be your select statement
select sysdate as today from dual;
begin
for rec in sample_cur loop
-- step by step for each record you return in your cursor
dbms_output.put_line(rec.today);
end loop;
end;

Related

SQLDeveloper execute procedure with parameters

I'm struggling to find a way to execute a procedure that has a SYS_REFCURSOR
I found different ways and it appears is easier in sqlplus but I wanted to do it through a query in sqldeveloper
The query takes 3 parameters and has a 4th that is a SYS_REFCURSOR
procedure_example(var1 number,var2 varchar2,var3 varchar2,result out sys_refcursor)
How would I execute something like this in SQLDEVELOPER?
As this is a procedure with an OUT parameter, you'll have to use another PL/SQL block which has DECLARE section (so that you'd have "something" to accept what your procedure returns).
declare
l_rc sys_refcursor;
begin
procedure_example(var1 => 1,
var2 => 2,
var3 => 3,
result => l_rc);
end;
/
(You'd pass meaningful values to IN parameters, of course.)
Another option is to declare a variable, use it while executing the procedure and print its contents. For example (based on Scott's sample schema):
The GUI way...using #littlefoot's code:
create or replace procedure p_test(
par_deptno in number,
par_rc out sys_refcursor)
is
begin
open par_rc for
select employee_id,
first_name,
last_name,
job_id,
salary
from employees
where department_id = par_deptno;
end;
Open in the database (or file), and hit the RUN button.
Set the input parameter, then click 'Ok' - the procedure will run, and you can see your refcursor below.

To display data using cursors in pl/sql

I have written this code to 'display the department names from department table using cursors'
declare
v_dname department.department_name%type;
cursor dept_cursor is select department_name from department;
begin
open dept_cursor;
loop
fetch dept_cursor into v_dname;
exit
when dept_cursor%notfound;
dbms_output.put_line('Department names are :' || v_dname);
end loop;
close dept_cursor;
end;
/
This code runs fine and shows shows 'the procedure is created', but the output values are not being displayed. I tried running the 'dbms_output.put_line' statement alone, it worked. I don't know what else to check. Please help, and thanks in advance!
Did you SET SERVEROUTPUT ON? If not literally, then enable it in your GUI. Because, code itself looks (and works) OK. I don't have your table, but Scott's DEPT is OK for testing as well.
Your code, unmodified (apart from DEPARTMENT being changed to DEPT):
SQL> set serveroutput on --> did you do this?
SQL>
SQL> declare
2 v_dname dept.dname%type;
3
4 cursor dept_cursor is select dname from dept;
5 begin
6 open dept_cursor;
7
8 loop
9 fetch dept_cursor into v_dname;
10
11 exit when dept_cursor%notfound;
12 dbms_output.put_line ('Department names are :' || v_dname);
13 end loop;
14
15 close dept_cursor;
16 end;
17 /
Department names are :ACCOUNTING
Department names are :RESEARCH
Department names are :SALES
Department names are :OPERATIONS
PL/SQL procedure successfully completed.
SQL>
If it still doesn't work, check whether table contains any rows.
One more thing: you said
This code runs fine and shows shows 'the procedure is created'
This isn't actually an Oracle message so I'm not sure whether I'm interpreting it correctly, but: if you actually created a stored procedure (what you posted is an anonymous PL/SQL block), message says Procedure created. It, furthermore, means that you have to actually execute it to get some result.
For example, if procedure's name was p_show_dept, you'd
SQL> set serveroutput on
SQL>
SQL> begin --> executing the procedure
2 p_show_dept;
3 end;
4 /
Department names are :ACCOUNTING
Department names are :RESEARCH
Department names are :SALES
Department names are :OPERATIONS
PL/SQL procedure successfully completed.
SQL>

How to call stored procedure with only OUT parameter?

I have created a stored procedure in Oracle 11g:
CREATE OR REPLACE PROCEDURE greetings(cnt OUT VARCHAR2)
AS
BEGIN
SELECT COUNT(*)
INTO cnt
FROM SYS.all_tables;
END greetings;
but I am unable to call it.
I have tried the following code snippets:
EXEC GREETINGS();
EXEC GREETINGS;
CALL GREETINGS;
The procedure requires one parameter, so - provide it.
SQL> CREATE OR REPLACE PROCEDURE greetings(cnt OUT VARCHAR2)
2 AS
3 BEGIN
4 SELECT COUNT(*)
5 INTO cnt
6 FROM SYS.all_tables;
7 END greetings;
8 /
Procedure created.
One option, which works everywhere, is to use an anonymous PL/SQL block:
SQL> set serveroutput on
SQL> declare
2 l_cnt number;
3 begin
4 greetings(l_cnt);
5 dbms_output.put_line(l_cnt);
6 end;
7 /
87
PL/SQL procedure successfully completed.
Another one works in SQL*Plus (or any other tool which is capable of simulating it):
SQL> var l_cnt number;
SQL> exec greetings(:l_cnt);
PL/SQL procedure successfully completed.
SQL> print l_cnt;
L_CNT
----------
87
Regarding the call example, this is explained in EXECUTE recognizes a stored procedure, CALL does not. It's not obvious from the syntax documentation but it does require brackets, so it is (rather unhelpfully) rejecting the whole thing and giving the impression that greetings is the problem, when actually it is not:
SQL> call greetings;
call greetings
*
ERROR at line 1:
ORA-06576: not a valid function or procedure name
while using the mandatory brackets gets you the real issue:
SQL> call greetings();
call greetings()
*
ERROR at line 1:
ORA-06553: PLS-306: wrong number or types of arguments in call to 'GREETINGS'
As others have pointed out, you are missing the parameter.
SQL> var n number
SQL>
SQL> call greetings(:n);
Call completed.
SQL> print :n
N
----------
134
execute is just a handy SQL*Plus shortcut for the PL/SQL block begin xxx; end; which is less fussy about brackets and gives the same error message with or without them.
(variable and print are SQL*Plus commands and may not be supported in other environments.)
There's no problem with the procedure body. You can call like this :
SQL> var nmr number;
SQL> exec greetings(:nmr);
PL/SQL procedure successfully completed
nmr
------------------------------------------
306 -- > <a numeric value returns in string format>
Oracle doesn't care assigning a numeric value to a string. The execution prints the result directly, but whenever you want you can recall that value of variable(nmr) again, and print as
SQL> print nmr
nmr
---------
306

Oracle procedure input is comma delimited, not returning any values

the procedure Im working has an input variable that is comma delimited. As of right now when I go to run a test script, I dont get any values back. Here is what I have so far.
procedure get_patient(
p_statusmnemonic_in in membermedicalreconcilationhdr.reconciliationstatusmnemonic%type,
p_return_cur_out out sys_refcursor,
p_err_code_out out number,
p_err_mesg_out out varchar2)
is
begin
open p_return_cur_out for
select h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h
where h.reconciliationstatusmnemonic in (p_statusmnemonic_in);
p_err_code_out := 0;
exception
when others then
p_err_code_out := -1;
p_err_mesg_out := 'error in get_patient=> ' || sqlerrm;
end get_patient;
Here is the test script:
set serveroutput on
declare
type tempcursor is ref cursor;
v_cur_result tempcursor;
errcode number;
errmesg varchar2(1000);
p_primarymemberplanid_in membermedicalreconcilationhdr.primarymemberplanid%type;
p_assigneduserid_in membermedicalreconcilationhdr.assigneduserid%type;
p_accountorgid_in membermedicalreconcilationhdr.accountorgid%type;
p_reconstatusmnemonic_in membermedicalreconcilationhdr.reconciliationstatusmnemonic%type;
p_estimatedenddt_in membermedicalreconcilationhdr.estimatedenddt%type;
p_actualenddt_in membermedicalreconcilationhdr.actualenddt%type;
p_inserteddate_in membermedicalreconcilationhdr.inserteddt%type;
p_insertedby_in membermedicalreconcilationhdr.insertedby%type;
p_updateddate_in membermedicalreconcilationhdr.updateddt%type;
p_updatedby_in membermedicalreconcilationhdr.updatedby%type;
begin
get_patient
('COMPLETE,SUSPENDED_PRIOR_TO_COMPARE',v_cur_result, errcode, errmesg);
--('COMPLETE',v_cur_result, errcode, errmesg);
loop
fetch v_cur_result into p_primarymemberplanid_in,p_assigneduserid_in,p_accountorgid_in,p_reconstatusmnemonic_in,
p_estimatedenddt_in,p_actualenddt_in,p_inserteddate_in,p_insertedby_in,
p_updateddate_in,p_updatedby_in;
dbms_output.put_line(' planid '||p_primarymemberplanid_in||' userid '||p_assigneduserid_in);
exit when v_cur_result%notfound;
end loop;
dbms_output.put_line(' error code '||errcode||' message '||errmesg);
end;
As of right now I get values back when I just have one input value, but when I try to do two I dont get anything. Ive done research and it looks like my select statement is correct so Im at a loss as to what Im doing wrong. Any help is appreciated, thanks.
If you can change the definition of the procedure, you are better served passing in a proper collection.
CREATE TYPE status_tbl IS TABLE OF VARCHAR2(100);
procedure get_patient(
p_statusmnemonic_in in status_tbl,
p_return_cur_out out sys_refcursor,
p_err_code_out out number,
p_err_mesg_out out varchar2)
is
begin
open p_return_cur_out for
select h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h
where h.reconciliationstatusmnemonic in (SELECT *
FROM TABLE(p_statusmnemonic_in));
...
Otherwise, you would either have to resort to using dynamic SQL (which would have security and performance implications) or you would need to write code to parse the comma-separated string into a collection and then use the TABLE operator to use that collection in the query.
Assuming you modify the signature of the procedure, the call will also have to change so that you are passing in a collection.
get_patient
(status_tbl('COMPLETE','SUSPENDED_PRIOR_TO_COMPARE'),
v_cur_result,
errcode,
errmesg);
And just to point it out, writing procedures that have error code and error message OUT parameters rather than throwing exceptions is generally highly frowned upon. It makes far more sense to eliminate those parameters and to just throw exceptions when you encounter an error. Otherwise, you are relying on every caller to every procedure to correctly check the returned status code and message (which your sample code does not do). And you are losing a ton of valuable information about things like exactly what line an error occurred on, what the error stack was, etc.
Since you don't post your table definitions or your sample data, it is impossible for us to test this code. Here is a quick demonstration, though, of how it would work
SQL> create table patient (
2 patient_id number primary key,
3 status varchar2(10),
4 name varchar2(100)
5 );
Table created.
SQL> insert into patient values( 1, 'COMPLETE', 'Justin' );
1 row created.
SQL> insert into patient values( 2, 'SUSPENDED', 'Bob' );
1 row created.
SQL> insert into patient values( 3, 'NEW', 'Kerry' );
1 row created.
SQL> commit;
Commit complete.
SQL> CREATE TYPE status_tbl IS TABLE OF VARCHAR2(100);
2 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 create or replace procedure get_patients( p_statuses in status_tbl,
2 p_cursor out sys_refcursor )
3 as
4 begin
5 open p_cursor
6 for select *
7 from patient
8 where status in (select *
9 from table( p_statuses ));
10* end;
SQL> /
Procedure created.
SQL> variable rc refcursor;
SQL> exec get_patients( status_tbl('COMPLETE', 'SUSPENDED'), :rc );
PL/SQL procedure successfully completed.
SQL> print rc;
PATIENT_ID STATUS
---------- ----------
NAME
--------------------------------------------------------------------------------
1 COMPLETE
Justin
2 SUSPENDED
Bob

How to return a resultset / cursor from a Oracle PL/SQL anonymous block that executes Dynamic SQL?

I have this table:
ALLITEMS
---------------
ItemId | Areas
---------------
1 | EAST
2 | EAST
3 | SOUTH
4 | WEST
The DDL:
drop table allitems;
Create Table Allitems(ItemId Int,areas Varchar2(20));
Insert Into Allitems(Itemid,Areas) Values(1,'east');
Insert Into Allitems(ItemId,areas) Values(2,'east');
insert into allitems(ItemId,areas) values(3,'south');
insert into allitems(ItemId,areas) values(4,'east');
In MSSQL, to get a cursor from a dynamic SQL I can do:
DECLARE #v_sqlStatement VARCHAR(2000);
SET #v_Sqlstatement = 'SELECT * FROM ALLITEMS';
EXEC (#v_sqlStatement); --returns a resultset/cursor, just like calling SELECT
In Oracle, I need to use a PL/SQL Block:
SET AUTOPRINT ON;
DECLARE
V_Sqlstatement Varchar2(2000);
outputData SYS_REFCURSOR;
BEGIN
V_Sqlstatement := 'SELECT * FROM ALLITEMS';
OPEN outputData for v_Sqlstatement;
End;
--result is : anonymous block completed
**But all I get is
anonymous block completed".
How do I get it to return the cursor?
(I know that if I do AUTOPRINT, it will print out the information in the REFCURSOR (it's not printing in the code above, but thats another problem))
I will be calling this Dynamic SQL from code (ODBC,C++), and I need it to return a cursor. How?
You can write a PL/SQL function to return that cursor (or you could put that function in a package if you have more code related to this):
CREATE OR REPLACE FUNCTION get_allitems
RETURN SYS_REFCURSOR
AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR SELECT * FROM allitems;
RETURN my_cursor;
END get_allitems;
This will return the cursor.
Make sure not to put your SELECT-String into quotes in PL/SQL when possible. Putting it in strings means that it can not be checked at compile time, and that it has to be parsed whenever you use it.
If you really need to use dynamic SQL you can put your query in single quotes:
OPEN my_cursor FOR 'SELECT * FROM allitems';
This string has to be parsed whenever the function is called, which will usually be slower and hides errors in your query until runtime.
Make sure to use bind-variables where possible to avoid hard parses:
OPEN my_cursor FOR 'SELECT * FROM allitems WHERE id = :id' USING my_id;
in SQL*Plus you could also use a REFCURSOR variable:
SQL> VARIABLE x REFCURSOR
SQL> DECLARE
2 V_Sqlstatement Varchar2(2000);
3 BEGIN
4 V_Sqlstatement := 'SELECT * FROM DUAL';
5 OPEN :x for v_Sqlstatement;
6 End;
7 /
ProcÚdure PL/SQL terminÚe avec succÞs.
SQL> print x;
D
-
X
You should be able to declare a cursor to be a bind variable (called parameters in other DBMS')
like Vincent wrote, you can do something like this:
begin
open :yourCursor
for 'SELECT "'|| :someField ||'" from yourTable where x = :y'
using :someFilterValue;
end;
You'd have to bind 3 vars to that script. An input string for "someField", a value for "someFilterValue" and an cursor for "yourCursor" which has to be declared as output var.
Unfortunately, I have no idea how you'd do that from C++. (One could say fortunately for me, though. ;-) )
Depending on which access library you use, it might be a royal pain or straight forward.
This setting needs to be set:
SET SERVEROUTPUT ON

Resources