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.
CREATE TABLE test_table
(
col1 NUMBER(10),
col2 NUMBER(10)
);
INSERT INTO test_table
VALUES (1, 2);
I am writing a stored procedure wherein if I give a table name as an input, that should give me the table data and column details.
For example:
SELECT *
FROM <input_table_name>;
But this causes an error that the SQL command has not ended properly even though I have taken care of this.
My attempt:
CREATE OR REPLACE PROCEDURE sp_test(iv_table_name IN VARCHAR2,
p_out_cur OUT SYS_REFCURSOR)
AS
lv_str VARCHAR2(400);
lv_count NUMBER(1);
lv_table_name VARCHAR2(255):=UPPER(iv_table_name);
BEGIN
lv_str := 'SELECT * FROM '||lv_table_name;
SELECT COUNT(1) INTO lv_count FROM all_tables WHERE table_name = lv_table_name;
IF lv_count = 0 THEN
dbms_output.put_line('Table does not exist');
ELSE
OPEN p_out_cur FOR lv_str;
END IF;
END sp_test;
Tool used: SQL developer(18c)
In dynamic SQL, you do NOT terminate statement with a semicolon.
EXECUTE IMMEDIATE 'SELECT * FROM '||lv_table_name||';';
-----
remove this
Anyway, you won't get any result when you run that piece of code. If you really want to see table's contents, you'll have to switch to something else, e.g. create a function that returns ref cursor.
Sample data:
SQL> SELECT * FROM test_table;
COL1 COL2
---------- ----------
1 2
3 4
Procedure you wrote is now correct:
SQL> CREATE OR REPLACE PROCEDURE sp_test (iv_table_name IN VARCHAR2,
2 p_out_cur OUT SYS_REFCURSOR)
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 BEGIN
8 lv_str := 'SELECT * FROM ' || lv_table_name;
9
10 SELECT COUNT (1)
11 INTO lv_count
12 FROM all_tables
13 WHERE table_name = lv_table_name;
14
15 IF lv_count = 0
16 THEN
17 DBMS_OUTPUT.put_line ('Table does not exist');
18 ELSE
19 OPEN p_out_cur FOR lv_str;
20 END IF;
21 END sp_test;
22 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_rc SYS_REFCURSOR;
3 l_col1 NUMBER (10);
4 l_col2 NUMBER (10);
5 BEGIN
6 sp_test ('TEST_TABLE', l_rc);
7
8 LOOP
9 FETCH l_rc INTO l_col1, l_col2;
10
11 EXIT WHEN l_rc%NOTFOUND;
12
13 DBMS_OUTPUT.put_line (l_col1 || ', ' || l_col2);
14 END LOOP;
15 END;
16 /
1, 2 --> contents of the
3, 4 --> TEST_TABLE
PL/SQL procedure successfully completed.
SQL>
A function (instead of a procedure with the OUT parameter):
SQL> CREATE OR REPLACE FUNCTION sf_test (iv_table_name IN VARCHAR2)
2 RETURN SYS_REFCURSOR
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 l_rc SYS_REFCURSOR;
8 BEGIN
9 lv_str := 'SELECT * FROM ' || lv_table_name;
10
11 SELECT COUNT (1)
12 INTO lv_count
13 FROM all_tables
14 WHERE table_name = lv_table_name;
15
16 IF lv_count = 0
17 THEN
18 raise_application_error (-20000, 'Table does not exist');
19 ELSE
20 OPEN l_rc FOR lv_str;
21 END IF;
22
23 RETURN l_rc;
24 END sf_test;
25 /
Function created.
Testing:
SQL> SELECT sf_test ('liksajfla') FROM DUAL;
SELECT sf_test ('liksajfla') FROM DUAL
*
ERROR at line 1:
ORA-20000: Table does not exist
ORA-06512: at "SCOTT.SF_TEST", line 18
SQL> SELECT sf_test ('TEST_TABLE') FROM DUAL;
SF_TEST('TEST_TABLE'
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
COL1 COL2
---------- ----------
1 2
3 4
SQL>
I'm expecting to write a query which takes second column if first column not found in table in oracle DB. In my case 'name' column is not present in table 'employees'
NOTE : I'm using reference cursor
I tried below,
query1:='select id or name,age from employees';
when I execute above statement, getting error
ORA-00904 "name": invalid identifier
ORA-06512 : at "employees", line 21
Explicitly, I don't think you can (as you saw).
Though, you can select * from employees and it'll work:
SQL> declare
2 l_rc sys_refcursor;
3 l_row dept%rowtype;
4 begin
5 open l_rc for select * from dept;
6 fetch l_rc into l_row;
7 end;
8 /
PL/SQL procedure successfully completed.
SQL>
Alternatively, did you consider creating a select statement dynamically, by querying user_tab_columns? Something like this:
SQL> declare
2 l_str varchar2(500);
3 l_rc sys_refcursor;
4 begin
5 for cur_r in (select column_name from user_tab_columns
6 where table_name = 'DEPT'
7 )
8 loop
9 l_str := l_str || ', '|| cur_r.column_name;
10 end loop;
11
12 l_str := 'select ' || ltrim(l_str, ', ') || ' from dept';
13
14 dbms_output.put_line(l_str);
15
16 open l_rc for l_str;
17 end;
18 /
select DEPTNO, DNAME, LOC from dept --> this is the SELECT statement
PL/SQL procedure successfully completed.
SQL>
I'm trying to send variable schema name to cursor via procedure input
Here is my lame try, but you can see what I want to do:
CREATE OR REPLACE PROCEDURE HOUSEKEEPING
(SCHEMANAME in varchar2)
IS
CURSOR data_instances IS select table_name
from SCHEMANAME.table_name where TYPE='PERMANENT' and rownum<200 ;
BEGIN
DBMS_OUTPUT.PUT_LINE(SCHEMANAME);
END;
/
it throws expected
PL/SQL: ORA-00942: table or view does not exist
is there lawful way to make schema name work as variable? thanks
There is a way; you'll need some kind of dynamic SQL because you can't use schema (or object) names like that. For example, you could use refcursor instead.
Sample table:
SQL> create table table_name as
2 select 'EMP' table_name, 'PERMANENT' type from dual union all
3 select 'DEPT' , 'TEMPORARY' from dual union all
4 select 'BONUS' , 'PERMANENT' from dual;
Table created.
Procedure; note the way I composed SELECT statement first (so that I could display it and check whether it is correct), and then used it in OPEN. Loop is here to ... well, loop through the cursor. I'm just displaying table names I found - you'd probably do something smarter.
SQL> create or replace procedure housekeeping (par_schemaname in varchar2)
2 is
3 l_str varchar2(500);
4 l_rc sys_refcursor;
5 l_table_name varchar2(30);
6 begin
7 l_str := 'select table_name from ' ||
8 dbms_assert.schema_name(upper(par_schemaname)) ||
9 '.table_name where type = ''PERMANENT'' and rownum < 200';
10 open l_rc for l_str;
11
12 loop
13 fetch l_rc into l_table_name;
14 exit when l_rc%notfound;
15
16 dbms_output.put_line(l_table_name);
17 end loop;
18 close l_rc;
19 end;
20 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec housekeeping('SCOTT');
EMP
BONUS
PL/SQL procedure successfully completed.
SQL>
I have many functions similiar to this one:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CURSOR myCUR1 IS SELECT myField1,
myField2,
myField3,
myField4,
myField5,
myField6,
myField7,
myField8,
myField9,
myField10,
myField11,
myField12,
myField13,
myField14,
myField15,
myField16,
myField17,
myField18,
myField19,
myField20,
myField21,
myField22,
myField23,
myField24,
myField25,
myField26,
myField27,
myField28,
myField29,
myField30,
myField31
FROM myTable;
BEGIN
-- I wish to move the part below to different procedure
OPEN myCUR1;
FETCH myCUR1 INTO rep_table(myIdx).day1, rep_table(myIdx).day2, rep_table(myIdx).day3, rep_table(myIdx).day4, rep_table(myIdx).day5,
rep_table(myIdx).day6, rep_table(myIdx).day7, rep_table(myIdx).day8, rep_table(myIdx).day9, rep_table(myIdx).day10,
rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
rep_table(myIdx).day31;
CLOSE myCUR1;
END REP_HELPER1;
I wish to do the part from open myCUR; to close myCUR; in a separate univesral procedure. As I have many functions like the above and the cursor is always different. So I would like to have one procedure which would do the open,fetch, close part :
PROCEDURE PB_HELPER_READ_INTO_DAYS(nIndex IN BINARY_INTEGER, myCUR by reference, rep_table IN OUT rep_table_T)
Is it possible to to it in plsql?
EDIT:
Based on yours clues I wrote it like this:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
myCUR1 SYS_REFCURSOR;
BEGIN
OPEN myCUR1 FOR SELECT myField1,
myField2,
myField3,
myField4,
myField5,
myField6,
myField7,
myField8,
myField9,
myField10,
myField11,
myField12,
myField13,
myField14,
myField15,
myField16,
myField17,
myField18,
myField19,
myField20,
myField21,
myField22,
myField23,
myField24,
myField25,
myField26,
myField27,
myField28,
myField29,
myField30,
myField31
FROM myTable;
MY_READ(myIdx , myCUR1, rep_table)
END REP_HELPER1;
PROCEDURE MY_READ(myIdx IN BINARY_INTEGER, cur IN SYS_REFCURSOR, rep_table IN OUT rep_table_T) IS
BEGIN
FETCH cur INTO rep_table(myIdx).day1, rep_table(myIdx).day2, rep_table(myIdx).day3, rep_table(myIdx).day4, rep_table(myIdx).day5,
rep_table(myIdx).day6, rep_table(myIdx).day7, rep_table(myIdx).day8, rep_table(myIdx).day9, rep_table(myIdx).day10,
rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
rep_table(myIdx).day31;
CLOSE cur;
END MY_READ;
Option which would work is to create a package, declare cursor globally and use it in any procedure you want. For example:
SQL> create or replace package pkg_test is
2 procedure p1;
3 end;
4 /
Package created.
SQL> create or replace package body pkg_test is
2 cursor c1 is select * from dept;
3 c1r c1%rowtype;
4
5
6 procedure p1 is
7 begin
8 open c1;
9 fetch c1 into c1r;
10 close c1;
11 end p1;
12 end;
13 /
Package body created.
SQL>
This won't work: declaring a cursor in one procedure and working with it in another:
SQL> create or replace package pkg_test is
2 procedure p1;
3 procedure p2;
4 end;
5 /
Package created.
SQL> create or replace package body pkg_test is
2 procedure p1 is
3 cursor c1 is select * from dept;
4 c1r c1%rowtype;
5 begin
6 null;
7 end p1;
8
9
10 procedure p2 is
11 begin
12 open c1;
13 fetch c1 into p1.c1r;
14 close c1;
15 end p2;
16 end;
17 /
Warning: Package Body created with compilation errors.
SQL> show err
Errors for PACKAGE BODY PKG_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
12/5 PL/SQL: SQL Statement ignored
12/11 PLS-00201: identifier 'C1' must be declared
13/5 PL/SQL: SQL Statement ignored
13/11 PLS-00201: identifier 'C1' must be declared
14/5 PL/SQL: SQL Statement ignored
14/11 PLS-00201: identifier 'C1' must be declared
SQL>
Also, you can't reference it using the "owner" procedure's prefix:
SQL> create or replace package body pkg_test is
2 procedure p1 is
3 cursor c1 is select * from dept;
4 c1r c1%rowtype;
5 begin
6 null;
7 end p1;
8
9
10 procedure p2 is
11 begin
12 open p1.c1;
13 fetch p1.c1 into p1.c1r;
14 close p1.c1;
15 end p2;
16 end;
17 /
Warning: Package Body created with compilation errors.
SQL> show err
Errors for PACKAGE BODY PKG_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
12/5 PL/SQL: SQL Statement ignored
12/14 PLS-00225: subprogram or cursor 'P1' reference is out of scope
13/5 PL/SQL: SQL Statement ignored
13/11 PLS-00225: subprogram or cursor 'P1' reference is out of scope
14/5 PL/SQL: SQL Statement ignored
14/11 PLS-00225: subprogram or cursor 'P1' reference is out of scope
SQL>
You can define the cursor in a package spec outside of any procedure or function. Then use that cursor virtually any where a cursor is valid (except as a reference cursor). Includes any procedure/function within the package or any standalone procedure/function, and even an anonymous block. Just be sure to reference as package_name.cursor_name anywhere outside of the package. See demo)
create or replace package pkg_test is
cursor c_dept is select * from dept;
procedure p1;
procedure p2;
end pkg_test;
/
This makes maintenance of the cursor quite easy since there is only one definition, so only one place of maintenance.
You can put only FETCH and CLOSE in a different procedure. Would be this (when you have only one OUT parameter, then I prefer a FUNCTION):
CREATE OR REPLACE FUNCTION REP_HELPER (myIdx IN BINARY_INTEGER, from_d IN DATE) RETURN SYS_REFCURSOR IS
myCur SYS_REFCURSOR;
BEGIN
OPEN myCur FOR
SELECT myField1, ...
FROM myTable;
RETURN myCur;
END REP_HELPER;
And use it like this:
DECLARE
cur SYS_REFCURSOR;
BEGIN
cur := REP_HELPER(...);
FETCH cur INTO ...
CLOSE cur;
END;
A more advanced solution would be dynamic SQL with DBMS_SQL Package:
CREATE OR REPLACE FUNCTION REP_HELPER(myIdx IN BINARY_INTEGER, from_d IN DATE) RETURN NUMBER IS
curid NUMBER := DBMS_SQL.OPEN_CURSOR;
sql_stmt VARCHAR2(32000);
BEGIN
sql_stmt := 'SELECT myField1, ... FROM myTable';
DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
RETURN curid;
END REP_HELPER;
DECLARE
cur SYS_REFCURSOR;
curid NUMBER;
ret INTEGER;
BEGIN
curid := REP_HELPER(...);
ret := DBMS_SQL.EXECUTE(curid);
-- Switch from DBMS_SQL to native dynamic SQL
cur := DBMS_SQL.TO_REFCURSOR(curid);
FETCH cur INTO ...
CLOSE cur;
END;
or
CREATE OR REPLACE PROCEDURE REP_HELPER(curid IN OUT NUMBER, myIdx IN BINARY_INTEGER, from_d IN DATE) IS
sql_stmt VARCHAR2(32000);
BEGIN
sql_stmt := 'SELECT myField1, ... FROM myTable';
DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
END REP_HELPER;
DECLARE
cur SYS_REFCURSOR;
curid NUMBER;
ret INTEGER;
BEGIN
curid NUMBER := DBMS_SQL.OPEN_CURSOR;
REP_HELPER(curid, ...);
ret := DBMS_SQL.EXECUTE(curid);
-- Switch from DBMS_SQL to native dynamic SQL
cur := DBMS_SQL.TO_REFCURSOR(curid);
FETCH cur INTO ...
CLOSE cur;
END;
But I think this would be an overkill.
Update:
You can compose the SQL string also dynamically, e.g.:
sql_stmt := 'SELECT ';
FOR i IN 1..31 LOOP
sql_stmt := sql_stmt || 'myField'||i||',';
END LOOP;
sql_stmt := REGEXP_REPLACE(sql_stmt, ',$');
sql_stmt := sql_stmt || ' FROM '||table_name;
sql_stmt := sql_stmt || ' WHERE the_date = :d';
OPEN cur FOR sql_stmt USING from_d;