Can an SQL procedure return a table? - sql

Can an Oracle SQL procedure return a table? I'm currently using a dbms_output to print out the outputs of two cursors which are in a loop, although this would look nicer if it was returning two columns instead. Would that be possible within a procedure?

A PL/SQL function can return a nested table. Provided we declare the nested table as a SQL type we can use it as the source of a query, using the the TABLE() function.
Here is a type, and a nested table built from it:
SQL> create or replace type emp_dets as object (
2 empno number,
3 ename varchar2(30),
4 job varchar2(20));
5 /
Type created.
SQL> create or replace type emp_dets_nt as table of emp_dets;
2 /
Type created.
SQL>
Here is a function which returns that nested table ...
create or replace function get_emp_dets (p_dno in emp.deptno%type)
return emp_dets_nt
is
return_value emp_dets_nt;
begin
select emp_dets(empno, ename, job)
bulk collect into return_value
from emp
where deptno = p_dno;
return return_value;
end;
/
... and this is how it works:
SQL> select *
2 from table(get_emp_dets(10))
3 /
EMPNO ENAME JOB
---------- ------------------------------ --------------------
7782 CLARK MANAGER
7839 KING PRESIDENT
7934 MILLER CLERK
SQL>
SQL Types offer us a great deal of functionality, and allow us to build quite sophisticated APIs in PL/SQL. Find out more.

I think that you can use Oracle Cursor for this (if your Oracle version supports it):
PROCEDURE myprocedure(
mycursor OUT SYS_REFCURSOR )
AS
BEGIN
OPEN mycursor FOR SELECT * FROM mytable;
END;
END;

This may also help:
DECLARE
TYPE t_emptbl IS TABLE OF scott.emp%rowtype;
v_emptbl t_emptbl;
ret_val t_emptbl;
--
Function getEmployeeList Return t_emptbl
IS
BEGIN
SELECT * bulk collect INTO v_emptbl FROM scott.emp;
-- Print nested table of records:
FOR i IN 1 .. v_emptbl.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (v_emptbl(i).empno);
END LOOP;
RETURN v_emptbl;
END;
--
BEGIN
ret_val:= getEmployeeList;
END;
/

Related

Store a list of certain numbers globally and use it as the value of parameter in a PL/SQL function

I have certain numbers eg., (1,2,3,7,8) which need to be stored in an object (let's say, l_list) and this l_list will used as the value of the parameter in a function while calling a select query.
Storing data globally:
l_list := (1,2,3,7,8);
Creating a function:
create or replace function f_get_result (p_list number)
return varchar2
as
l_result varchar2(100);
Begin
select l_code
into l_result
from table_1;
return l_result;
end f_get_result;
Calling the function in select query:
select f_get_result(l_list) from dual;
Till now I have tried to define the datatype of the parameter as type table and varray. But, it didn't work.
Here is the code which I used:
-- creating a type ---
CREATE OR REPLACE TYPE num_array AS TABLE OF varchar2(100);
-- the function ---
create or replace function fn_id_exp(p_branch in num_array)
return varchar2
is
txt varchar2(1000);
txt_1 varchar2(1000);
begin
for i in 1..p_branch.count loop
select branch_name into txt_1 from tbl_branch
where branch_code = p_branch(i);
txt := txt ||txt_1||chr(10);
end loop;
return txt;
end;
-- the select query ---
select fn_id_exp(num_array('100','200')) from dual;
expectation: select fn_id_exp(l_list) from dual; , need to use this much, num_array should not be there,
where l_list is an object which is storing certain numbers like (100,200,300).
Please, help me in this, I am bit new in this. Can somebody, give me an an appropriate example regards this?
Note: It doesn't matter where and what datatype these number are getting stored. Requirement is just to call all the numbers(1,2,3,7,8) in this:
select fn_id_exp(l_list) from dual;
Another option, which doesn't require you to use your own type, is to use Oracle's built-in types.
Here's an example; function returns employees that work in departments passed as a collection.
SQL> create or replace function f_test (par_deptno in sys.odcinumberlist)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp
9 where deptno in (select * from table(par_deptno));
10
11 return retval;
12 end;
13 /
Function created.
Testing:
SQL> select * from table(f_test(sys.odcinumberlist(10, 20)));
COLUMN_VALUE
--------------------------------------------------------------------------------
MILLER
KING
CLARK
FORD
ADAMS
SCOTT
JONES
SMITH
8 rows selected.
SQL>
Note that such a result might be more convenient than returning a string which "mimics" rows in a table (whose values are separated by line feed character), as you'd still might need to split them into rows if you wanted to further process the result.
You said:
expectation: select fn_id_exp(l_list) from dual; , need to use this much, num_array should not be there
But - why not? What's wrong with num_array (or whichever type name you'd use)? That's the syntax, you should follow it if you want to get the result. Silly requirements, in my opinion.
Anyway: you could pass a string of e.g. comma-separated numeric values such as in this example; you'll have to split them to rows. See if it helps.
SQL> create or replace function f_test (par_deptno in varchar2)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp
9 where deptno in (select regexp_substr(par_deptno, '[^,]+', 1, level)
10 from dual
11 connect by level <= regexp_count(par_deptno, ',') + 1
12 );
13
14 return retval;
15 end;
16 /
Function created.
Testing:
SQL> select * from table(f_test('10, 20'));
COLUMN_VALUE
--------------------------------------------------------------------------------
SMITH
JONES
CLARK
SCOTT
KING
ADAMS
FORD
MILLER
8 rows selected.
SQL>
Use a collection:
CREATE TYPE number_array AS TABLE OF NUMBER;
Then:
CREATE FUNCTION fn_id_exp(
p_branch in number_array
) return varchar2
IS
txt varchar2(1000);
BEGIN
SELECT LISTAGG(branch_name, CHR(10)) WITHIN GROUP (ORDER BY rn)
INTO txt
FROM (SELECT ROWNUM AS rn,
COLUMN_VALUE AS id
FROM TABLE(p_branch)) i
INNER JOIN tbl_branch b
ON (i.id = b.branch_code);
RETURN txt;
END;
/
Then:
select fn_id_exp(number_array(100,200)) from dual;
Given the sample data:
CREATE TABLE tbl_branch (branch_code, branch_name) AS
SELECT 100, 'Leafy Stem' FROM DUAL UNION ALL
SELECT 200, 'Big Twig' FROM DUAL;
Outputs:
FN_ID_EXP(NUMBER_ARRAY(100,200))
Leafy StemBig Twig
However
It may be simpler to not use a function and just use a query:
SELECT branch_name
FROM tbl_branch
WHERE branch_code IN (100, 200)
ORDER BY branch_code;
or, if you want to use the array::
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF number_array(100, 200)
ORDER BY branch_code;
or, pass in an array as a bind parameter (via JDBC, etc.) then:
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF :your_array
ORDER BY branch_code;
Which output:
BRANCH_NAME
Leafy Stem
Big Twig
fiddle
Update
If you want a static array then you can create a package:
CREATE PACKAGE static_data IS
FUNCTION getList RETURN NUMBER_ARRAY;
END;
/
With the body:
CREATE PACKAGE BODY static_data IS
list NUMBER_ARRAY;
FUNCTION getList RETURN NUMBER_ARRAY
IS
BEGIN
RETURN list;
END;
BEGIN
list := NUMBER_ARRAY(100, 200);
END;
/
then you can use it in your query:
select fn_id_exp(static_data.getlist()) from dual;
or
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF static_data.getlist()
ORDER BY branch_code;
fiddle

returning a value using dbms_output.put_line and out parameter in an Oracle procedure

What is the difference between printing a value using dbms_output.put_line and out parameter in oracle procedure. Will dbms_output.put_line return value to front end?
If not, how to return more than one row to front end using a stored procedure?
DBMS_OUTPUT.PUT_LINE doesn't make much sense in stored procedures or front end applications that can't display it (for example, although it makes a valid call in Oracle Forms or Application Express, you won't see anything on the screen).
You don't really expect users to run your procedures in SQL*Plus or TOAD or SQL Developer, do you? Therefore, use DBMS_OUTPUT.PUT_LINE for debugging purposes.
I guess that this is what you might be using:
SQL> create or replace procedure p_test_2
2 (par_deptno in number)
3 is
4 begin
5 for cur_r in (select ename from emp where deptno = par_deptno) loop
6 dbms_output.put_line(cur_r.ename);
7 end loop;
8 end;
9 /
Procedure created.
SQL> exec p_test_2(10);
CLARK
KING
MILLER
PL/SQL procedure successfully completed.
SQL>
If you want to compare it to a procedure that is supposed to return multiple values, then one option is to use OUT parameter whose datatype is SYS_REFCURSOR. Here's how; you'll notice that it requires some more code - the procedure itself and separate PL/SQL block which will do something with its result.
SQL> create or replace procedure p_test3
2 (par_deptno in number, par_emp out sys_refcursor)
3 is
4 begin
5 open par_emp for select * from emp where deptno = par_deptno;
6 end;
7 /
Procedure created.
SQL>
SQL> declare
2 l_emp sys_refcursor;
3 l_rec emp%rowtype;
4 begin
5 p_test3(10, l_emp);
6
7 loop
8 fetch l_emp into l_rec;
9 exit when l_emp%notfound;
10 -- You'd do something with those values here; I'm just displaying ENAME
11 dbms_output.put_line(l_rec.ename);
12 end loop;
13 end;
14 /
CLARK
KING
MILLER
PL/SQL procedure successfully completed.
SQL>
Alternatively, for display purposes, in SQL*Plus you might use this:
SQL> var l_rec refcursor
SQL> exec p_test3(10, :l_rec);
PL/SQL procedure successfully completed.
SQL> print l_rec
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09.06.1981 00:00:00 2450 10
7839 KING PRESIDENT 17.11.1981 00:00:00 5000 10
7934 MILLER CLERK 7782 23.01.1982 00:00:00 1300 10
SQL>
Your function uses DBMS_OUTPUT.PUT_LINE to display your results column-wise, followed by a new_line character. for example please refer below query.
create or replace procedure TEMP_ARRAy_RECORD
is
--type emp_det_tbl IS TABLE OF temp_employee_det%ROWTYPE index by pls_integer;
type lemp_det IS RECORD
(
--l_emp_id int,
l_emp_id temp_employee_det.emp_id%type,
l_city temp_employee_det.city%type,
l_amount temp_employee_det.amount%type
);
TYPE emp_det IS VARRAY(15) OF lemp_det;
f_emp_det emp_det;
BEGIN
for i in (select emp_id,city,amount BULK COLLECT into f_emp_det from temp_employee_det)
loop
dbms_output.put(i.city||',');--fetch all data in single row
dbms_output.put_line(i.city||',');--fetch all data in column wise
exit when SQL%NOTFOUND;
end loop;
dbms_output.new_line;--
end;
execute TEMP_ARRAy_RECORD
refer below output will be shown.
SQL*Plus statement executed
PL/SQL block executed
CHALISGAON,BHADGAON,PACHORA,JALGAON,NASHIK,
Depends on what you mean by "front end". If you're running in SQL*Plus or SQL Developer, using dbms_output will sent content to a buffer, that's then displayed in a particular window - if serveroutput is on for that session.
A properly instrumented program would use a utility such as Logger to make debugging notes to a custom table, for later perusal.
OUT parameters in procedures just receive that information into the actual parameter, perhaps some local variable. This could be further processed, displayed on screen, or sent to another procedure.
DBMS_OUTPUT does not return output in the sense of an I/O stream or function return value. It is a rather basic debugging tool, that works by placing the specified text into a PL/SQL array that can then be retrieved at the end of the call by the client application calling dbms_output.get_lines(). Tools such as SQL*Plus and PL/SQL Developer do this automatically, subject to preference settings, but a web front end will not.

Performance Tuning In Plsql Function Query

I have plsql function. And this function has query like as below:
select colum_name
from table_name
where filter_coloumn_name in ( select filter_coloumn_name from table_name_2);
My function is used by other query to return value from query. This query which use this function take more time. Even the site (front screen) down. Because table_name_2 has three million record. So that i must use some performance tuning method in my function. I change my function query like as below:
cursor my_cursor IS
select filter_coloumn_name from table_name_2;
TYPE cursor_array_type IS TABLE OF my_cursor%ROWTYPE INDEX BY BINARY_INTEGER;
m cursor_array_type;
TYPE cursor_table_type IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
cursor_table_object cursor_table_type;
fetch_size NUMBER := 5000;
index_var number;
begin
index_var := 1;
open my_cursor;
loop
FETCH my_cursor BULK COLLECT
into m LIMIT fetch_size;
exit when my_cursor %notfound;
for i in 1 .. m.count loop
cursor_table_object (index_var) := m(i).filter_coloumn_name;
index_var := index_var + 1;
end loop;
end loop;
Close my_cursor;
select colum_name
from table_name
where filter_coloumn_name in (cursor_table_object);
select colum_name
from table_name
where filter_coloumn_name in (cursor_table_object);
Namely, i want fetch all values at one time, and then i want use this table object like as above. But i can't use table object with in condition expression in sql query.
I take PLS-00382: expression is of wrong type error.
I want to use this table object like as below:
select colum_name
from table_name
where filter_coloumn_name in ('bla', 'bla bla', 'bla bla bla');
Should i convert table object to the array ?
A suggestion, based on what you've said so far: how about using a table function? It makes it possible to use something like statement you wanted to use, i.e.
select colum_name from table_name where filter_coloumn_name in (cursor_table_object);
but which returns
PLS-00382: expression is of wrong type
Here's an example based on Scott's schema. Have a look:
SQL> create or replace function f_test
2 return sys.odcinumberlist
3 is
4 -- Cursor's SELECT should contain your 3-million-rows table
5 cursor cur_r is select deptno from dept where deptno < 30;
6 l_ret sys.odcinumberlist;
7 begin
8 open cur_r;
9 fetch cur_r bulk collect into l_ret;
10 close cur_r;
11 return l_ret;
12 end;
13 /
Function created.
SQL> -- How to use it?
SQL> select e.ename
2 from emp e join table(f_test) x on x.column_value = e.deptno;
ENAME
----------
MILLER
KING
CLARK
ADAMS
SCOTT
FORD
JONES
SMITH
8 rows selected.
SQL>
You must use exists instead of in, also table_name_2 must have index over filter_coloumn_name field.
Using cursor only what you get is full access to a big table.

can a procedure in oracle have ordinary select * statement inside begin and end block?

create procedure
return(val_id int)
is
begin
select *
from emp
where emp_id = val_id;
end
No, In Oracle you can't run ordinary SELECT statement, it's not supported.
You can do this in many other RDBMS, for example in MySql this works in this way:
A.4.14. Can MySQL 5.5 stored routines return result sets?
Stored procedures can, but stored functions cannot. If you perform an
ordinary SELECT inside a stored procedure, the result set is returned
directly to the client. You need to use the MySQL 4.1 (or higher)
client/server protocol for this to work. This means that, for example,
in PHP, you need to use the mysqli extension rather than the old mysql
extension.
In other words, if you run ordinary SELECT statement within a procedure, then MySql automatically creates a resultset which is then passed to the caller client.
In Oracle it is not done in automatic manner - you must explicitely declare a cursor and pass it as OUT parameter to the caller, for example:
CREATE OR REPLACE PROCEDURE test22( val_id int, cur OUT sys_refcursor )
IS
BEGIN
open cur for select EMPNO, ENAME
from emp
where empno = val_id;
END;
/
And the caller must retrieve the cursor from the parameter and handle it in explicit way, there is no any "implicit automation", for example (in SQL-Plus or SQL-Developer):
var ppp refcursor
exec test22( 7654, :ppp );
print ppp
EMPNO ENAME
---------- ----------
7654 MARTIN
But beginning from Oracle 12.1c there is Implicit Statement Results feature available which make it possible to simulate this behaviour:
Implicit Statement Results
Before Oracle Database 12c, a PL/SQL stored
subprogram returned result sets from SQL queries explicitly, through
OUT REF CURSOR parameters, and the client program that invoked the
subprogram had to bind to those parameters explicitly to receive the
result sets.
As of Oracle Database 12c, a PL/SQL stored subprogram can return query
results to its client implicitly, using the PL/SQL package DBMS_SQL
instead of OUT REF CURSOR parameters. This technique makes it easy to
migrate applications that rely on the implicit return of query results
from stored subprograms from third-party databases to Oracle Database.
For more information, see "DBMS_SQL.RETURN_RESULT Procedure" and
"DBMS_SQL.GET_NEXT_RESULT Procedure".
On Oracle 12c you can just do:
CREATE OR REPLACE PROCEDURE test22( val_id int )
IS
cur sys_refcursor;
BEGIN
open cur for select EMPNO, ENAME
from emp
where empno = val_id;
DBMS_SQL.RETURN_RESULT( cur, true );
END;
/
and in the client (SQL/Plus, SQL-Developer, in Java, in C# etc) you can just call this procedure, and the resultset will be automatically retrieved:
exec test22( 7654 );
PL/SQL procedure successfully completed.
ResultSet #1
EMPNO ENAME
---------- ----------
7654 MARTIN
You can even return multiple resultsets to the client, an example:
CREATE OR REPLACE PROCEDURE test22( val_id int )
IS
cur sys_refcursor;
cur2 sys_refcursor;
cur3 sys_refcursor;
BEGIN
open cur for select EMPNO, ENAME
from emp
where empno = val_id;
DBMS_SQL.RETURN_RESULT( cur, true );
open cur2 for select ENAME, sal, deptno
from emp
where empno = val_id;
DBMS_SQL.RETURN_RESULT( cur2, true );
open cur3 for select ENAME, mgr, sal, deptno, hiredate
from emp
where empno = val_id;
DBMS_SQL.RETURN_RESULT( cur3, true );
END;
/
exec test22( 7654 );
PL/SQL procedure successfully completed.
ResultSet #1
EMPNO ENAME
---------- ----------
7654 MARTIN
ResultSet #2
ENAME SAL DEPTNO
---------- ---------- ----------
MARTIN 1250 30
ResultSet #3
ENAME MGR SAL DEPTNO HIREDATE
---------- ---------- ---------- ---------- ----------------
MARTIN 7698 1250 30 1981/09/28 00:00

Using define in simple sql statement

Is there a way of defining a section of SQL as a constant for repeated use in a simple oracle SQL statement. In a similar way that can be done in a script and in a similar way to the WITH statement?
Something like
Define meat as "foodtype<>'fruit' and foodtype<>'veg'"
select * from meals where meat
In SQL*Plus you could use DEFINE.
For example,
SQL> define l_str = 'ename = ''SCOTT'''
SQL>
SQL> select empno, deptno from emp where &l_str;
old 1: select empno, deptno from emp where &l_str
new 1: select empno, deptno from emp where ename = 'SCOTT'
EMPNO DEPTNO
---------- ----------
7788 20
SQL>
NOTE DEFINE is a SQL*Plus command. Define sets a user variable or displays its value.
However, not possible in plain SQL as the query won't parse and would throw an error. Since, during parse time, Oracle would expect it to be static, you cannot have it dynamic.
You can use Dynamic SQL in PL/SQL, such that you could have it as a string variable and use it repeatedly in the scope of the session.
For example,
SQL> DECLARE
2 var_str VARCHAR2(1000);
3 var_str1 VARCHAR2(1000);
4 v_empno emp.empno%type;
5 BEGIN
6 var_str := 'ename = ''SCOTT''';
7 var_str1 := 'select empno FROM emp WHERE '|| var_str;
8 EXECUTE IMMEDIATE var_str1 INTO v_empno;
9
10 dbms_output.put_line(v_empno);
11
12 END;
13 /
7788
PL/SQL procedure successfully completed.
SQL>
As per your question it seems that the condition should be dynamic. It can be acheived in PLSQL Block like below
DECLARE
v1 varchar2(100) := ' foodtype <> ''fruit'' and foodtype <> ''Veg''';
mealstyp meals%ROWTYPE;
BEGIN
EXECUTE IMMEDIATE 'select * from meals where'||v1 into mealstyp;
LOOP
--your type record
END LOOP;
END