Output results from Oracle Ref cursor - oracle

How can i rewrite below pl/sql block in order to avoid hard code column names one by one ? Below data are from OE schema. For orders table there are 8 columns on this table. Is it possible to output the results without hard code column names ? Any help is appreciated.
create or replace PACKAGE show_details AS
TYPE rt_order IS REF CURSOR RETURN orders%ROWTYPE;
TYPE typ_cust_rec IS RECORD
(cust_id NUMBER(6), cust_name VARCHAR2(20),
custphone customers.phone_numbers%TYPE,
credit NUMBER(9,2), cust_email VARCHAR2(30));
TYPE rt_cust IS REF CURSOR RETURN typ_cust_rec;
--Get order detail
PROCEDURE get_order(p_orderid IN NUMBER, p_cv_order IN OUT rt_order);
--Get customer detail
PROCEDURE get_cust(p_custid IN NUMBER, p_cv_cust IN OUT rt_cust);
END show_details;
create or replace PACKAGE BODY show_details
AS
PROCEDURE get_order (p_orderid IN NUMBER, p_cv_order IN OUT rt_order)
IS
BEGIN
OPEN p_cv_order FOR
SELECT * FROM orders
WHERE order_id = p_orderid;
-- CLOSE p_cv_order
END get_order;
PROCEDURE get_cust (p_custid IN NUMBER, p_cv_cust IN OUT rt_cust)
IS
BEGIN
OPEN p_cv_cust FOR
SELECT customer_id, cust_first_name,phone_numbers,
credit_limit,cust_email FROM customers WHERE customer_id = p_custid;
-- CLOSE p_cv_cust
END get_cust;
END;
SET SERVEROUTPUT ON SIZE UNLIMITED;
declare
cur_orders show_details.rt_order;
v_ordertab cur_orders%ROWTYPE;
begin
show_details.get_order(p_orderid =>2397, p_cv_order =>cur_orders);
LOOP
FETCH cur_orders INTO v_ordertab;
EXIT WHEN cur_orders%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('ORDER_ID: ' || v_ordertab.ORDER_ID || ' ORDER_DATE: ' || v_ordertab.ORDER_DATE || ' ORDER_MODE: ' || v_ordertab.ORDER_MODE || ' CUSTOMER_ID: ' || v_ordertab.CUSTOMER_ID);
END LOOP;
exception
when others then
DBMS_OUTPUT.put_line ('Error Code : ' || SQLCODE);
end;
/

Since you're executing this from SQL Developer, you can use the variable and print commands, which are carried over from SQL*Plus:
variable cur_orders refcursor;
exec show_details.get_order(p_orderid => 2397, p_cv_order => :cur_orders);
print cur_orders
CUR_ORDERS
-------------------------------------------------------------------------------------------------------------------------------------------------
ORDER_ID CUSTOMER_ID ORDER_MODE ORDER_DATE
--------------------------------------- --------------------------------------- --------------------------------------- -------------------------
2397 42 0 03-JAN-15
Notice that you're passing the ref cursor variable as a bind variable, so there is a colon before the name in the call (:cur_order). And you need to run script rather than run statement.
exec is just a shorthand for an anonymous block, so you could do it explicitly if you prefer, but the effect is the same:
var cur_orders refcursor;
begin
show_details.get_order(p_orderid => 2397, p_cv_order => :cur_orders);
end;
/
print cur_orders
You can also get the output in the result grid if you prefer, as shown here, but print is a bit closer to your dbms_output version. Or you can have a wrapper function so you could call query the procedure from a plain SQL call; depends what your end goal is, and if you're just manually executing the procedure to check the output then print may also be good enough.

Related

PL/SQL - pass query in for loop stored in variable

i have a scenario where i need to create a dynamic query with some if/else statements, i have prepared the query but unable to use this inside loop below is the snippet of what i am trying to acheive.
Query :
select user_name into username from table1 where user ='user1';
for(query)
Loop
Dbms_output.put_line('user name ' || user_name);
END Loop;
is this possible to use the vaiable in the for loop ?
Not really. Notice in the 19c PL/SQL Reference it says:
cursor
Name of an explicit cursor (not a cursor variable) that is not open when the cursor FOR LOOP is entered.
You have to code this the long way, by explicitly fetching the cursor into a record until you hit cursorname%notfound, e.g.
create or replace procedure cheese_report
( cheese_cur in sys_refcursor )
as
type cheese_detail is record
( name varchar2(15)
, region varchar2(30) );
cheese cheese_detail;
begin
loop
fetch cheese_cur into cheese;
dbms_output.put_line(cheese.name || ', ' || cheese.region);
exit when cheese_cur%notfound;
end loop;
close cheese_cur;
end;
Test:
declare
cheese sys_refcursor;
begin
open cheese for
select 'Cheddar', 'UK' from dual union all
select 'Gruyere', 'France' from dual union all
select 'Ossau Iraty', 'Spain' from dual union all
select 'Yarg', 'UK' from dual;
cheese_report(cheese);
end;
/
Cheddar, UK
Gruyere, France
Ossau Iraty, Spain
Yarg, UK
In Oracle 21c you can simplify this somewhat, though you still have to know the structure of the result set:
create or replace procedure cheese_report
( cheese_cur in sys_refcursor )
as
type cheese_detail is record
( cheese varchar2(15)
, region varchar2(30) );
begin
for r cheese_detail in values of cheese_cur
loop
dbms_output.put_line(r.cheese || ', ' || r.region);
end loop;
end;
You could parse an unknown ref cursor using dbms_sql to find the column names and types, but it's not straightforward as you have to do every processing step yourself. For an example of something similar, see www.williamrobertson.net/documents/refcursor-to-csv.shtml.

PLS-00103: Encountered the symbol "PROCEDURE" when expecting one of the following:

Im a learning plsql and im trying to create procedure and calling procedure inside another procedure and i can get desired output. But when i tried to create procedure inside another procedure instead of calling another procedure, im getting the below error
"23/1 PLS-00103: Encountered the symbol "PROCEDURE" when expecting one of the following: ( begin case declare end exception exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge The symbol "declare" was substituted for "PROCEDURE" to continue.
23/18 PLS-00103: Encountered the symbol "." when expecting one of the following: in out ... long double ref char time timestamp interval date binary national character nchar
Errors: check compiler log
"
CREATE OR REPLACE PROCEDURE pro (
empn NUMBER,
emp OUT emp5%rowtype
) IS
salar number;
BEGIN
SELECT
a.*
INTO emp
FROM
emp5 a
WHERE
a.empno = empn;
dbms_output.put_line('The hire date is'
|| ' '
|| emp.hiredate);
dbms_output.put_line('Name is'
|| ' '
|| emp.ename);
procedure p44(emp.hiredate in date,emp.ename varchar,sal out number) IS
salar NUMBER;
BEGIN
SELECT
e.sal
INTO salar
FROM
emp5 e
WHERE
e.hiredate = hire
AND e.ename = enamee;
dbms_output.put_line('salary of the employee'
|| ' '
|| enamee
|| 'is '
|| salar);
END p43;
/
If i give empno number as input to procedure pro, i need output with employees hiredate,employee's name and employee's salary like the below one
The hire date is 20-FEB-81
Name is ALLEN
salary of the employee ALLEN is 1600
A procedure is a single program unit with the structure of:
create or replace procedure p43 () is
...
begin
…
end;
/
Your code has two instances of PROCEDURE and that's what the compiler is complaining about.
If you want two separate procedures you need two separate CREATE statements:
create or replace procedure p43 () is
...
begin
…
end p43;
/
create or replace procedure p44 () is
...
begin
…
end p44;
/
But if what you want is P44 as a private procedure only accessible within the context of P43 you can do that, by defining the procedure in the declaration section after any variable declarations:
create or replace procedure p43 () is
…
procedure p44 () is
...
begin
…
end p44;
begin
…
p44(…);
end p43;
/
Also this is not how we declare parameters.
procedure p44(emp.hiredate in date,emp.ename varchar,sal out number) IS
Give them unique names, say by prefixing them with p_, and if you like use the table column datatype. Something like this:
procedure p44(p_hiredate in emp.hiredate%type
,p_ename in emp.ename%type
,p_sal out emp.sal%type) IS
Giving parameters a distinct name prevents scope confusion when using parameters in SQL statements:
You can not create procedure within procedure.
Procedure is a single object and must be created alone. You can call one procedure from another.
You can use package to create multiple procedures inside single package but in that case also, procedure must be created standalone.
-- procedure must be created standalone
Create or replace procedure p44
As
Begin
-- code
End p44;
/
Create or replace procedure pro
As
Begin
P44; -- call to existing procedure
-- code
End pro;
/
So your case will go like this:
procedure p44(hiredate in date,ename varchar,sal out number) IS
salar NUMBER;
BEGIN
SELECT
e.sal
INTO salar
FROM
emp5 e
WHERE
e.hiredate = hire
AND e.ename = ename;
dbms_output.put_line('salary of the employee'
|| ' '
|| ename
|| 'is '
|| salar);
END p44;
/
CREATE OR REPLACE PROCEDURE pro (
empn NUMBER,
emp OUT emp5%rowtype
) IS
salar number;
BEGIN
SELECT
a.*
INTO emp
FROM
emp5 a
WHERE
a.empno = empn;
dbms_output.put_line('The hire date is'
|| ' '
|| emp.hiredate);
dbms_output.put_line('Name is'
|| ' '
|| emp.ename);
P44(emp.hiredate, emp.ename, salr):
END pro;
/
Cheers!!

How to access and query objects passed as parameter to a procedure while converting from Oracle to postgresql

I have a procedure in Oracle that I need to convert to Postgresql and need help on it. It paases a collection of objects in a procedure.The procedure then checks if each object is present in a database table or not and if present it gives a message that , that specific element is found/present. if some element that is paassed to the procedure is not present in the table, the procedure just doesnt do anything. I have to write equivalent of that in postgresql. I think the heart of the issue is this statement:
SELECT COUNT (*)
INTO v_cnt
FROM **TABLE (p_cust_tab_type_i)** pt
WHERE pt.ssn = cc.ssn;
In Oracle a collection can be treated as a table and one can query it but I dont know how to do that in postgresql. The code to create the table, add data, create the procedure, call the procedure by passing the collection (3 objects) and output of that is posted below. Can someone suggest how this can be done in postgresql?
Following the oracle related code and details:
--create table
create table temp_n_tab1
(ssn number,
fname varchar2(20),
lname varchar2(20),
items varchar2(100));
/
--add data
insert into temp_n_tab1 values (1,'f1','l1','i1');
--SKIP no. ssn no. 2 intentionally..
insert into temp_n_tab1 values (3,'f3','l3','i3');
insert into temp_n_tab1 values (4,'f4','l4','i4');
insert into temp_n_tab1 values (5,'f5','l5','i5');
insert into temp_n_tab1 values (6,'f6','l6','i6');
commit;
--create procedure
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE temp_n_proc (
p_cust_tab_type_i IN temp_n_customer_tab_type)
IS
t_cust_tab_type_i temp_n_customer_tab_type;
v_cnt NUMBER;
v_ssn temp_n_tab1.ssn%TYPE;
CURSOR c
IS
SELECT ssn
FROM temp_n_tab1
ORDER BY 1;
BEGIN
--t_cust_tab_type_i := p_cust_tab_type_i();
FOR cc IN c
LOOP
SELECT COUNT (*)
INTO v_cnt
FROM TABLE (p_cust_tab_type_i) pt
WHERE pt.ssn = cc.ssn;
IF (v_cnt > 0)
THEN
DBMS_OUTPUT.put_line (
'The array element '
|| TO_CHAR (cc.ssn)
|| ' exists in the table.');
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;
/
--caller proc
SET SERVEROUTPUT ON
declare
array temp_n_customer_tab_type := temp_n_customer_tab_type();
begin
for i in 1 .. 3
loop
array.extend;
array(i) := temp_n_cust_header_type( i, 'name ' || i, 'lname ' || i,i*i*i*i );
end loop;
temp_n_proc( array );
end;
/
caller proc output:
The array element 1 exists in the table.
The array element 3 exists in the table.
When you create a table in Postgres, a type with the same name is also created. So you can simply pass an array of the table's type as a parameter to the function.
Inside the function you can then use unnest() to treat the array like a table.
The following is the closest match to your original Oracle code:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
l_count integer;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
loop
select count(*)
into l_count
from unnest(p_cust_tab_type_i) as t
where t.ssn = l_rec.ssn;
if l_count > 0 then
raise notice 'The array element % exist in the table', l_rec.ssn;
end if;
end loop;
END;
$$
language plpgsql;
The row-by-row processing is not a good idea to begin with (neither in Postgres, nor in Oracle). It would be a lot more efficient to get the existing elements in a single query:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
loop
raise notice 'The array element % exist in the table', l_rec.ssn;
end loop;
return;
END;
$$
language plpgsql;
You can call the function like this:
select temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);
However a more "Postgres" like and much more efficient way would be to not use PL/pgSQL for this, but create a simple SQL function that returns the messages as a result:
create or replace function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns table(message text)
as
$$
select format('The array element %s exist in the table', t1.ssn)
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
$$
language sql;
This returns the output of the function as a result rather than using the clumsy raise notice.
You can use it like this:
select *
from temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);

Trying to use a FORALL to insert data dynamically to a table specified to the procedure

I have the need to dynamic know the name of the table that has the same data structure as many others and I can pass in a generic associative array that is of the same structure. Here is the proc
PROCEDURE INSRT_INTER_TBL(P_TABLE_NAME IN VARCHAR2, P_DATA IN tt_type)
IS
BEGIN
FORALL i IN P_DATA.FIRST .. P_DATA.LAST
EXECUTE IMMEDIATE
'INSERT INTO ' || P_TABLE_NAME ||
' VALUES :1'
USING P_DATA(i);
END INSRT_INTER_TBL;
I am getting the following error
ORA-01006: bind variable does not exist
What am I missing here?
So I had to specify all the columns necessary to insert to the table out in the insert statement like:
PROCEDURE INSRT_INTER_TBL(P_TABLE_NAME IN VARCHAR2, P_DATA IN inter_invc_ln_item_type)
IS
BEGIN
FORALL i IN P_DATA.FIRST .. P_DATA.LAST
EXECUTE IMMEDIATE
'INSERT INTO ' || P_TABLE_NAME || ' (ITEM_PK, pk, units, amt) ' ||
' VALUES (:P_INVC_LN_ITEM_PK, :PK, :UNITS, :AMT)'
USING IN P_DATA(i).item_pk, P_DATA(i).pk, P_DATA(i).units, P_DATA(i).amt;
END INSRT_INTER_TBL;
The TABLE operator works better than a FORALL here. It uses less code and probably skips some SQL-to-PL/SQL context switches.
--Simple record and table type.
create or replace type tt_rec is object
(
a number,
b number
);
create or replace type tt_type is table of tt_rec;
--Sample schema that will hold results.
create table test1(a number, b number);
--PL/SQL block that inserts TT_TYPE into a table.
declare
p_table_name varchar2(100) := 'test1';
p_data tt_type := tt_type(tt_rec(1,1), tt_rec(2,2));
begin
execute immediate
'
insert into '||p_table_name||'
select * from table(:p_data)
'
using p_data;
commit;
end;
/
You can run the above code in this SQL Fiddle.
Try VALUES (:1) i.e. have brackets around :1

Display a table inside an Oracle procedure

I'm on Oracle DB 12c and using SQL Developer.
How can I display every rows of a table inside a procedure, if all arguments are "null".
I'm guessing that my IF is correct and I might have seen two or three posts where they used cursors but I'm not very familiar with the utilization of those.
I would like it to basically be a SELECT * FROM Salle but with a condition.
Am I on the right track here ?
CREATE OR REPLACE PROCEDURE Foo1 (noSalle in varchar2, Cat in varchar2, Nb in number)
IS
cursor SYS_REFCURSOR; -- Not sure at all about that
BEGIN
DBMS_OUTPUT.PUT_LINE('Salle : ' || noSalle || 'Cat : ' || Cat);
IF (noSalle = null AND Cat = null AND Nb = null) THEN
OPEN cursor FOR
SELECT * from Salle;
-- Some sort of FOR row IN cursor LOOP ?
-- Display ALL rows of "Salle"
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Erreur Oracle : '||sqlcode||' ; Message Oracle : '||sqlerrm);
END;
SYS_REFCURSOR is used to hold tabular data usually used when you want to return such data from a function.
Null comparison is incorrect. Use colname is null instead.
If you just want to print all the rows using dbms_output.put_line or something similar, you can do this
for row in (select * from salle) loop
dbms_output.put_line(row.col1||' '||row.col2);
end loop;

Resources