Hello I'm a beginner at PL/SQL and some help would be appreciated.
So I have this procedure here and my goal is to have it so that when this procedure is executed that I can enter a 5 digit integer (a zipcode) and it will just select those values from the table and display just as if I've done a query like
SELECT * FROM customers WHERE customer_zipcode = "input zipcode".
create or replace PROCEDURE LIST_CUSTOMER_ZIPCODE(
p_zipcode IN customers.customer_zipcode%TYPE,
p_disp OUT SYS_REFCURSOR)
-- User input Variable, Display Variable
IS
BEGIN
OPEN p_disp for SELECT customer_first_name, customer_zipcode FROM customers
WHERE customer_zipcode=p_zipcode;
EXCEPTION
-- Input Sanitization
WHEN no_data_found THEN
dbms_output.put_line('-1');
END;
EXEC LIST_CUSTOMER_ZIPCODE(07080);
When I execute this command I just keep getting this error.
https://i.stack.imgur.com/nCI8T.png
If you are using SQL*Plus or SQL Developer then you can declare a bind variable and then call the procedure passing the variable and then print it:
SELECT * FROM customers WHERE customer_zipcode = "input zipcode".
create or replace PROCEDURE LIST_CUSTOMER_ZIPCODE(
p_zipcode IN customers.customer_zipcode%TYPE,
p_disp OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_disp FOR
SELECT customer_first_name, customer_zipcode
FROM customers
WHERE customer_zipcode = p_zipcode;
EXCEPTION
-- Input Sanitization
WHEN no_data_found THEN
dbms_output.put_line('-1');
END;
/
VARIABLE cur SYS_REFCURSOR;
EXEC LIST_CUSTOMER_ZIPCODE('07080', :cur);
PRINT cur;
However, your exception handling block is never going to be called as the cursor can return zero rows without raising that exception so the procedure could be simplified to:
create or replace PROCEDURE LIST_CUSTOMER_ZIPCODE(
p_zipcode IN customers.customer_zipcode%TYPE,
p_disp OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_disp FOR
SELECT customer_first_name, customer_zipcode
FROM customers
WHERE customer_zipcode = p_zipcode;
END;
/
You can't just execute such a procedure as it expects 2 parameters; one is IN, while another is OUT (ref cursor which contains result set).
I don't have your tables so I'll demonstrate it using Scott's sample schema by passing department number and returning list of employees working in that department.
Procedure:
SQL> set serveroutput on
SQL> create or replace procedure p_list
2 (p_deptno in dept.deptno%type,
3 p_disp out sys_refcursor
4 )
5 is
6 begin
7 open p_disp for select ename, job from emp
8 where deptno = p_deptno;
9 end;
10 /
Procedure created.
This is how you use it:
SQL> declare
2 l_list sys_refcursor;
3 l_ename emp.ename%type;
4 l_job emp.job%type;
5 begin
6 p_list(10, l_list); --> calling the procedure; use 2 parameters
7
8 loop
9 fetch l_list into l_ename, l_job;
10 exit when l_list%notfound;
11 dbms_output.put_line(l_ename ||' - '|| l_job);
12 end loop;
13 end;
14 /
CLARK - MANAGER
KING - PRESIDENT
MILLER - CLERK
PL/SQL procedure successfully completed.
SQL>
TRY EXEC LIST_CUSTOMER_ZIPCODE(:p_Zipcode);
If you put ':' before any string, It will become substitution string and you can type your input.
Related
I have the following procedure executing some business logic (looping through a cursor):
PROCEDURE myproc() AS
CURSOR mycur IS
SELECT * FROM mytable;
BEGIN
FOR c IN mycur LOOP
...business logic here...
...many lines of code...
END LOOP;
END myproc;
I'd like to have different procedures and execute the same business logic for different cursors (sets of data). For example I'd like to have one procedure myproc_adopters() for:
SELECT * FROM mytable WHERE cutomer_type='Adopters'
and another procedure myproc_others() for others:
SELECT * FROM mytable WHERE customer_type!='Adopters'
So I'd like to have one main procedure mainproc() containing cursor loop and business logic and other procedures calling this main procedure and sending different cursors as parameters. The problem is that it seems that cursor FOR loop does not accept cursor as variable that I can send as procedure call parameter:
PROCEDURE myproc_adopters() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type='Adopters';
BEGIN
mainproc(mycur);
END myproc_adopters;
PROCEDURE myproc_others() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type!='Adopters';
BEGIN
mainproc(mycur);
END myproc_others;
PROCEDURE mainproc(mycur IN SYS_REFCURSOR) AS
BEGIN
FOR c IN mycur LOOP <-- does not accept cursor as variable
...
END LOOP;
END mainproc;
How to send different cursor to the same cursor FOR LOOP?
Your idea is OK (at least, to me), but - you then have to pass refcursor (as you declared it).
For example:
mainproc:
SQL> create or replace procedure mainproc (mycur in sys_refcursor)
2 is
3 l_row emp%rowtype;
4 begin
5 loop
6 fetch mycur into l_row;
7 exit when mycur%notfound;
8
9 dbms_output.put_line(l_row.ename ||' - '|| l_row.job ||' - '|| l_row.sal);
10 end loop;
11 end;
12 /
Procedure created.
This procedure accepts a parameter and - depending on it - opens refcursor with some criteria and then calls mainproc, passing that refcursor:
SQL> create or replace procedure myproc_adopters (par_deptno in emp.deptno%type) is
2 l_rc sys_refcursor;
3 begin
4 if par_deptno = 10 then
5 open l_rc for select * from emp where job = 'CLERK';
6 elsif par_deptno = 20 then
7 open l_rc for select * from emp where sal > 2000;
8 end if;
9
10 mainproc (l_rc);
11 close l_rc;
12 end;
13 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec myproc_adopters(10);
SMITH - CLERK - 840
ADAMS - CLERK - 1100
JAMES - CLERK - 950
MILLER - CLERK - 1300
PL/SQL procedure successfully completed.
SQL>
SQL> exec myproc_adopters(20);
JONES - MANAGER - 2975
BLAKE - MANAGER - 2850
CLARK - MANAGER - 2450
SCOTT - ANALYST - 3000
KING - PRESIDENT - 5000
FORD - ANALYST - 3000
PL/SQL procedure successfully completed.
SQL>
There are two other ways you could do this that don't require a refcursor:
Add a parameter to your myproc procedure, and update the cursor to retrieve the rows based on the parameter, e.g.:
PROCEDURE myproc(p_query_type IN VARCHAR2) AS
CURSOR mycur IS
SELECT *
FROM mytable
WHERE (p_query_type IS NULL -- or whatever value you want to indicate all rows, e.g. p_query_type = 'ALL'
OR (UPPER(p_query_type) = 'ADOPTERS' AND customer_type = 'Adopters')
OR (UPPER(p_query_type) = 'OTHERS' AND customer_type != 'Adopters'));
BEGIN
FOR c IN mycur LOOP
...business logic here...
...many lines of code...
END LOOP;
END myproc;
Or you could extract the business logic into its own procedure (complete with the requisite number of parameters), and the wrapper procedures call that within the cursor-for-loop, e.g.:
PROCEDURE mainproc (p_param1 mytable.col1%TYPE,
p_param2 mytable.col2%TYPE,
....)
BEGIN
...business logic here...
...many lines of code...
END mainproc;
PROCEDURE myproc_adopters() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type='Adopters';
BEGIN
FOR c IN mycur LOOP
mainproc (p_param1 => c.col1,
p_param2 => c.col2,
...)
END LOOP;
END myproc_adopters;
PROCEDURE myproc_others() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type!='Adopters';
BEGIN
FOR c IN mycur LOOP
mainproc (p_param1 => c.col1,
p_param2 => c.col2,
...)
END LOOP;
END myproc_others;
I hope that these procedures are in a package! If you're going to go down the route of having wrapper procedures but your current procedure isn't in a package, I highly recommend you move it into a package.
My personal preference would be to go with a combination of both options; have the cursor decide what rows to select based on the input parameter, but also encapsulate the business logic in its own procedure, or better yet, break it down into separate procedures to make it more manageable, as one long, spaghetti-like procedure isn't good programming practice (IMO).
I have an Oracle stored procedure spFinTest that I use in an SSRS report.
I wish to create another stored procedure spFinTestInsert that takes the output of spFinTest and inserts it into a table sbceybudget_financial_year.
I can create an all in one stored procedure that inserts the data into the destination table, but what I am looking to achieve is to just have the one stored procedure that can have code updates for data extracts and not to have to update a second separate insert stored procedure that has the same code.
So, update in one place only and reuse the same stored procedure.
The following is a simplified version of the main stored procedure:
create or replace procedure spFINTEST
(s1 OUT SYS_REFCURSOR)
AS
BEGIN
OPEN s1 FOR
SELECT
FIN_YR + 1 AS FIN_YR,
SCHOOL_YEAR_WEEKS
FROM
sbceybudget_financial_year
WHERE
fin_yr = 2021
;
END spFINTEST;
This stored procedure only has an "out" variable.
The final intention is once I can do this, then I will call spFinTestInsert from an "Execute SQL Task" in an SSIS package.
I'm a bit stumped as to how I create this second stored procedure that calls the first and inserts the results into a named table, so if anyone can help I would be most grateful.
I would do this in a package: it allows you to declare cursor type easily to make it more clear.
Test table:
create table sbceybudget_financial_year(
fin_yr int,
SCHOOL_YEAR_WEEKS int
)
/
Package specification:
create or replace package pkg_spFIN as
type spFIN_RowType is record(
FIN_YR sbceybudget_financial_year.FIN_YR%type,
SCHOOL_YEAR_WEEKS sbceybudget_financial_year.SCHOOL_YEAR_WEEKS%type
);
type spFIN_CurType IS REF CURSOR RETURN spFIN_RowType;
type spFIN_tab is table of spFIN_RowType;
procedure spFINTEST (s1 OUT SYS_REFCURSOR);
procedure spFinTestInsert;
end pkg_spFIN;
/
Package body:
create or replace package body pkg_spFIN as
function get_cursor(n int) return spFIN_CurType is
c spFIN_CurType;
begin
open c for
SELECT
t.FIN_YR + 1 AS FIN_YR,
t.SCHOOL_YEAR_WEEKS
FROM
sbceybudget_financial_year t
WHERE
t.fin_yr = n;
return c;
end;
procedure spFINTEST (s1 OUT SYS_REFCURSOR)
is
begin
s1:=get_cursor(2021);
end spFINTEST;
procedure spFinTestInsert
is
cur spFIN_CurType;
tab spFIN_tab;
begin
pkg_spFIN.spFINTEST(cur);
loop
fetch cur bulk collect into tab limit 100;
exit when tab.count()=0;
for i in 1..tab.count loop
dbms_output.put_line(tab(i).FIN_YR);
dbms_output.put_line(tab(i).SCHOOL_YEAR_WEEKS);
-- or insert:
-- insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS)
-- values(tab(i).FIN_YR, SCHOOL_YEAR_WEEKS)
-- you can change it to FORALL insert
end loop;
end loop;
end spFinTestInsert;
end pkg_spFIN;
/
Test data:
begin
insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS) values(2019,19);
insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS) values(2020,20);
insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS) values(2021,21);
commit;
end;
/
And finally test call:
call pkg_spFIN.spFinTestInsert();
Full example on DBFiddle: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c31a5714e5db74eaa3a83fae03964349
I don't have your tables so I'll use Scott's DEPT for illustration.
This is the "target" table; values fetched by ref cursor will be inserted into it:
SQL> create table test_dept as select deptno, dname from dept where 1 = 2;
Table created.
This is data I expect:
SQL> select deptno, dname from dept where deptno <= 20;
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
This is your current procedure:
SQL> create or replace procedure spfintest (s1 out sys_refcursor)
2 as
3 begin
4 open s1 for select deptno, dname from dept where deptno <= 20;
5 end spfintest;
6 /
Procedure created.
This is a procedure which calls spfintest and inserts values into test_dept:
SQL> create or replace procedure spfitestinsert as
2 rc sys_refcursor;
3 --
4 l_deptno dept.deptno%type;
5 l_dname dept.dname%type;
6 begin
7 spfintest(rc);
8 loop
9 fetch rc into l_deptno, l_dname;
10 exit when rc%notfound;
11
12 insert into test_dept (deptno, dname)
13 values (l_deptno, l_dname);
14 end loop;
15 end;
16 /
Procedure created.
Testing:
SQL> exec spfitestinsert;
PL/SQL procedure successfully completed.
SQL> select * from test_dept;
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
SQL>
Everything is here, so I guess it works.
I have a cursor inside package specification like below
Create package pkgname
is
Cursor C1(report_date date) is select * from some_table where some_date=report_date;
procedure proc(repdate date)
end;
I want a functionality that assign this cursor C1 to other cursor in procedure of the same package body.
Create package body pkgname is
Procedure proc(repdate date)
Is
Cursor C2 is C1(repdate);
Begin
for j in C2 loop
--do some task;
end loop;
End;
End;
Why would you do that? If you declared the cursor in package specification, simply use it in package body. For example:
SQL> create or replace package pkg_test as
2 cursor c1 (par_job varchar2) is
3 select empno, ename from emp
4 where job = par_job;
5 procedure proc (par_job in varchar2);
6 end;
7 /
Package created.
SQL> create or replace package body pkg_test as
2 procedure proc (par_job in varchar2) is
3 begin
4 for cur_r in pkg_test.c1(par_job) loop --> here it is!
5 dbms_output.put_line(cur_r.empno ||' '|| cur_r.ename);
6 end loop;
7 end;
8 end;
9 /
Package body created.
SQL> set serveroutput on
SQL> exec pkg_test.proc('CLERK');
7369 SMITH
7876 ADAMS
7900 JAMES
PL/SQL procedure successfully completed.
SQL>
i have 2 stored procedures 1 is called A with the following impl
PROCEDURE A(p_id IN NUMBER, lic_cat_2 OUT varchar2,lic_cat_1 OUT varchar2,traffic_code OUT varchar2,lic_type OUT varchar2,emp_num OUT varchar2)
// Some LOGIC
end A ;
and PROCEDURE B which is a wrapper to proc A but i need to get other value with a query
PROCEDURE B(ph_id IN NUMBER, lic_cat_2 OUT varchar2,lic_cat_1 OUT varchar2,traffic_code OUT varchar2,lic_type OUT varchar2,emp_num OUT varchar2)
declare number phone_id
begin
select into phone_id parent_id from per_phones where phone_id= p_id
exec A(phone_id,lic_cat_2 OUT varchar2,lic_cat_1 OUT varchar2,traffic_code OUT varchar2,lic_type OUT varchar2,emp_num OUT varchar2);
END B;
but it gives me PLS-00103: Encountered the symbol “CREATE”
You're calling the A procedure in a wrong manner:
omit EXEC, it is a SQL*Plus command
omit parameters' description (IN/OUT, datatype) - pass only values
omit DECLARE; you need it in triggers or anonymous PL/SQL blocks, but not in stored procedures
by the way, variable name comes first, datatype next (for phone_id)
I'd suggest you to prefix parameters and variables with p_ (or par_) and l_ respectively (or any other prefix you want) to distinguish them from column names. Otherwise, it is easy to get confused.
also, use table aliases in your queries for the same reason
So:
CREATE OR REPLACE PROCEDURE B (p_ph_id IN NUMBER,
p_lic_cat_2 OUT VARCHAR2,
p_lic_cat_1 OUT VARCHAR2,
p_traffic_code OUT VARCHAR2,
p_lic_type OUT VARCHAR2,
p_emp_num OUT VARCHAR2)
IS
l_phone_id NUMBER;
BEGIN
SELECT p.parent_id
INTO l_phone_id
FROM per_phones p
WHERE p.phone_id = p_ph_id;
A (l_phone_id,
p_lic_cat_2,
p_lic_cat_1,
p_traffic_code,
p_lic_type,
p_emp_num);
END B;
As I don't have your tables, for example (to show how to do it) I used Scott's sample schema:
SQL> create or replace procedure a (par_deptno in number, par_dname out varchar2)
2 is
3 begin
4 select dname into par_dname from dept where deptno = par_deptno;
5 end;
6 /
Procedure created.
SQL>
SQL> create or replace procedure b (par_empno in number, par_dname out varchar2) is
2 l_deptno emp.deptno%type;
3 begin
4 select deptno into l_deptno from emp where empno = par_empno;
5
6 a(l_deptno, par_dname);
7 end;
8 /
Procedure created.
SQL>
SQL> set serveroutput on
SQL> declare
2 l_dname dept.dname%type;
3 begin
4 b (7654, l_dname);
5 dbms_output.put_line('Dname = ' || l_dname);
6 end;
7 /
Dname = SALES
PL/SQL procedure successfully completed.
SQL>
I am trying to do a stored procedure in SQL Developer, which returns multiple records from a single table. But when I call the procedure, it returns the empty variables (taking into account that the table has records).
CREATE OR REPLACE PROCEDURE PURE_ENC_SELECCIONAR_INTERACCIONES(
startDate IN varchar2,
endDate IN varchar2,
o_interactionId OUT PURE_ENC_INTERACTION.INTERACTIONID%TYPE,
o_interactionDate OUT PURE_ENC_INTERACTION.INTERACTIONDATE%TYPE,
o_queueId OUT PURE_ENC_INTERACTION.QUEUEID%TYPE,
o_personId OUT PURE_ENC_INTERACTION.PERSONID%TYPE,
o_numSolicitud OUT PURE_ENC_INTERACTION.NUMSOLICITUD%TYPE,
o_customerDni OUT PURE_ENC_INTERACTION.CUSTOMERDNI%TYPE,
o_customerName OUT PURE_ENC_INTERACTION.CUSTOMERNAME%TYPE,
o_ani OUT PURE_ENC_INTERACTION.ANI%TYPE,
o_dnis OUT PURE_ENC_INTERACTION.DNIS%TYPE,
o_custom1 OUT PURE_ENC_INTERACTION.CUSTOM1%TYPE,
o_custom2 OUT PURE_ENC_INTERACTION.CUSTOM2%TYPE,
o_custom3 OUT PURE_ENC_INTERACTION.CUSTOM3%TYPE,
o_custom4 OUT PURE_ENC_INTERACTION.CUSTOM4%TYPE,
o_custom5 OUT PURE_ENC_INTERACTION.CUSTOM5%TYPE)
IS
BEGIN
FOR loop_int IN (
SELECT INTERACTIONID, INTERACTIONDATE, QUEUEID, PERSONID, NUMSOLICITUD, CUSTOMERDNI,
CUSTOMERNAME, ANI, DNIS, CUSTOM1, CUSTOM2, CUSTOM3, CUSTOM4, CUSTOM5
INTO o_interactionId, o_interactionDate, o_queueId, o_personId, o_numSolicitud,
o_customerDni, o_customerName, o_ani, o_dnis, o_custom1, o_custom2, o_custom3,
o_custom4, o_custom5
FROM PURE_ENC_INTERACTION )
--WHERE INTERACTIONDATE >= startDate AND INTERACTIONDATE < endDate )
LOOP
DBMS_OUTPUT.PUT_LINE('InteractionID: '|| o_interactionId);
END LOOP loop_int;
END;
I execute the procedure:
SET serveroutput ON
DECLARE
o_interactionId VARCHAR2(200);
o_interactionDate VARCHAR2(200);
o_queueId VARCHAR2(200);
o_personId VARCHAR2(200);
o_numSolicitud VARCHAR2(200);
o_customerDni VARCHAR2(200);
o_customerName VARCHAR2(200);
o_ani VARCHAR2(200);
o_dnis VARCHAR2(200);
o_custom1 VARCHAR2(200);
o_custom2 VARCHAR2(200);
o_custom3 VARCHAR2(200);
o_custom4 VARCHAR2(200);
o_custom5 VARCHAR2(200);
BEGIN
PURE_ENC_SELECCIONAR_INTERACCIONES(
'2019-10-20T12:30:03',
'2019-10-29T03:30:03',
o_interactionId,
o_interactionDate,
o_queueId,
o_personId,
o_numSolicitud,
o_customerDni,
o_customerName,
o_ani,
o_dnis,
o_custom1,
o_custom2,
o_custom3,
o_custom4,
o_custom5);
END;
When I run it, returns:
Procedimiento PL/SQL terminado correctamente.
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
InteractionID:
When I run this sample:
SELECT INTERACTIONID, INTERACTIONDATE, QUEUEID , PERSONID , NUMSOLICITUD , CUSTOMERDNI , CUSTOMERNAME , ANI , DNIS , CUSTOM1,CUSTOM2 ,CUSTOM3 ,CUSTOM4 ,CUSTOM5
FROM PURE_ENC_INTERACTION;
It returns all the registers fine.
What could be the problem?
Well, you didn't post what you exactly have. How do I know? Because the procedure has syntax errors. You can't use cursor FOR loop like that; INTO is required for SELECT statements in PL/SQL, but not where you used it.
Simplified, on Scott's schema, this is what you did:
SQL> create or replace procedure p_test (par_deptno in number, par_ename out varchar2) is
2 begin
3 for cur_r in (select ename from emp into par_ename
4 where deptno = par_deptno)
5 loop
6 dbms_output.put_line('Ename: ' || par_ename);
7 end loop;
8 end;
9 /
Warning: Procedure created with compilation errors.
SQL> show err
Errors for PROCEDURE P_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
3/17 PL/SQL: SQL Statement ignored
3/39 PL/SQL: ORA-00933: SQL command not properly ended
SQL>
As I said: your code is invalid.
This, on the other hand, is valid:
SQL> create or replace procedure p_test (par_deptno in number, par_ename out varchar2) is
2 begin
3 for cur_r in (select ename from emp
4 where deptno = par_deptno)
5 loop
6 par_ename := cur_r.ename;
7 dbms_output.put_line('Ename from P_TEST: ' || par_ename);
8 end loop;
9 end;
10 /
Procedure created.
SQL> declare
2 l_ename varchar2(20);
3 begin
4 p_test (10, l_ename);
5 dbms_output.put_line('Ename from anonymous PL/SQL block: ' || l_ename);
6 end;
7 /
Ename from P_TEST: CLARK
Ename from P_TEST: KING
Ename from P_TEST: MILLER
Ename from anonymous PL/SQL block: MILLER
PL/SQL procedure successfully completed.
SQL>
But, it is wrong (logically). The procedure fetched all employees (Clark, King, Miller), but the caller (i.e. the anonymous PL/SQL block) got only one value: the last one fetched by the cursor.
I doubt that this is what you want; doesn't make sense.
I don't know what you really want; perhaps it is to return all rows that satisfy certain condition. If that's the case, you can't return scalar values, that should be *something
else*. Ref cursor is one option, but - you don't need a procedure with bunch of parameters, then - maybe a function is a better option. For example:
SQL> create or replace function f_test (par_deptno in number)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for select ename from emp where deptno = par_deptno;
7 return l_rc;
8 end;
9 /
Function created.
SQL> select f_test(10) from dual;
F_TEST(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
ENAME
----------
CLARK
KING
MILLER
SQL>