oracle weakly typed record as parameter - oracle

Let's say I have this construct in a PLSQL procedure:
...
for rec in
(
select a, b, c from t;
)
loop
process_record(rec);
end loop;
...
procedure process_record(p_rec in ???)
...
How do I pass rec, which is a record of a weakly typed cursor, to a procedure for processing.
I don't want to define a cursor and a type for the record of this type.
Is this possible?
TIA
Gold

you have to define a cursor, or a type (both not needed) in order to tell oracle the structure of the input (unless you want to go down the route or defining an AnyData/AnyType approach.
eg
declare
cursor my_template
is
select a,b,c from t;
procedure process_record(p_rec in my_template%rowtype)
is
begin
null;
end;
begin
for rec in
(
select a, b, c from t
)
loop
process_record(rec);
end loop;
end;
/

I don't know of a simple way to to that (and would be most surprised to see one) but I have a workaround:
Use XMLType as your type:
declare
begin
for rec in (select xmlelement("p_rec", xmlforest(a, b, c)) r from t) loop
process_record(rec.r);
end loop;
end;
...
create or replace procedure process_record(p_rec in XMLtype) as
BEGIN
dbms_output.put(p_rec.extract('//A/text()').getstringval() || ',');
dbms_output.put(p_rec.extract('//B/text()').getstringval() || ',');
dbms_output.put_line(p_rec.extract('//C/text()').getstringval());
END;
BTW, why do you want to do that?

Related

PL/SQL Function - Bulk Collect into and Pipe Row

I am new to PL/SQL have issue with output value of a function.
I want to execute a SQL statement in a function and return the results. The function will be executed with following command: select * from table(mypkg.execute_query('1'));
I was using following article as refence "Bulk Collect Into" and "Execute Immediate" in Oracle, but without success.
It seems that I am using wrong data type. System returns issue on following line: PIPE Row(results)
create or replace package mypkg
as
type node is table of edges%ROWTYPE;
function execute_query (startNode in varchar2) RETURN node PIPELINED;
end;
create or replace package body mypkg
as
function execute_query(startNode in varchar2) RETURN node PIPELINED
AS
results node;
my_query VARCHAR2(100);
output VARCHAR2(1000);
c sys_refcursor;
BEGIN
my_query := 'SELECT DISTINCT * FROM EDGES WHERE src='|| startNode;
open c for my_query;
loop
fetch c bulk collect into results limit 100;
exit when c%notfound;
PIPE Row(results);
end loop;
close c;
END;
end;
I tried several options with cursor but wasn't able to return the value. If you have idea how to return the data by using something else than PIPELINED, please let me know.
Thanks for your support!
Fixed body:
create or replace package body mypkg
as
function execute_query(startNode in varchar2) RETURN node PIPELINED
AS
results node;
my_query VARCHAR2(100);
output VARCHAR2(1000);
c sys_refcursor;
BEGIN -- don't use concatenation, it leads to sql injections:
my_query := 'SELECT DISTINCT * FROM EDGES WHERE src=:startNode';
-- use bind variables and bind them using cluase "using":
open c for my_query using startNode;
loop
fetch c bulk collect into results limit 100;
-- "results" is a collection, so you need to iterate it to pipe rows:
for i in 1..results.count loop
PIPE Row(results(i));
end loop;
exit when c%notfound;
end loop;
close c;
END;
end;
/

wrong number or types of arguments in call to P_AA

When I try to compile the procedure with collection type as in parameter, I'm getting the error like
wrong number or types of arguments in call to 'P_AA'
-------Procedure created with in parameter as nested table------------
create or replace procedure p_aa(serv in t45)
is
aa serv_item%rowtype;
begin
for i in 1..serv.count
loop
select a.* into aa from serv_item a where a.serv_item_id = serv(i);
dbms_output.put_line('Serv item '||aa.serv_item_id||' '||'status '||aa.status);
end loop;
end;
/
----------Calling the package----------
declare
type t1 is table of number;
cursor c1 is select serv_item_id from serv_item;
n number:=0;
v t1;
begin
open c1;
loop
fetch c1 into v(n);
exit when c1%notfound;
n:=n+1;
end loop;
close c1;
p_aa(v);
end;
/
How can I fix my code?
Your procedure defines the parameter like this:
serv in t45
So t45 is the defined datatype of the parameter.
Now when you call the procedure you pass in a variable v. And how is v defined?
type t1 is table of number;
...
v t1;
t1 is a different type to t45. Even if they have identical structures they are different types. And that's why you get PLS-00306. The solution is quite simple: define v as t45.
i am getting error like 'Reference to uninitialized collection'.
You need to initialise the collection. You do this using the default constructor of the type, either at the start of the program ...
v := t45();
... or when you declare it:
v t45 := t45();
Once you get beyond that you will find your assignment logic is wrong: you're fetching into an element of the collection before you increment the counter or extend the array. So what you need is this:
declare
cursor c1 is select serv_item_id from serv_item;
n number:=0;
v t45 := t45();
x number;
begin
open c1;
loop
fetch c1 into x;
exit when c1%notfound;
n:=n+1;
v.extend();
v(n) := x;
end loop;
close c1;
p_aa(v);
end;
/
Alternatively use the less verbose bulk collect, which handles all the looping and type management implicitly :
declare
v t45;
begin
select serv_item_id
bulk collect into v
from serv_item;
p_aa(v);
end;
/
Here is a db<>fiddle demo showing both approaches working.

Oracle PL/SQL array input into parameter of pipelined function

I am new to PL/SQL. I have created a pipelined function inside a package which takes as its parameter input an array of numbers (nested table).
But I am having trouble trying to run it via an sql query. Please see below
my input array
CREATE OR REPLACE TYPE num_array is TABLE of number;
my function declaration
CREATE OR REPLACE PACKAGE "my_pack" as
TYPE myRecord is RECORD(column_a NUMBER);
TYPE myTable IS TABLE of myRecord;
FUNCTION My_Function(inp_param num_array) return myTable PIPELINED;
end my_pack;
my function definition
CREATE OR REPLACE PACKAGE BODY "my_pack" as
FUNCTION My_Function(inp_param num_array) return myTable PIPELINED as
rec myRecord;
BEGIN
FOR i in 1..inp_param.count LOOP
FOR e IN
(
SELECT column_a FROM table_a where id=inp_param(i)
)
LOOP
rec.column_a := e.column_a;
PIPE ROW (rec);
END LOOP;
END LOOP;
RETURN;
END;
end my_pack;
Here is the latest code I've tried running from toad. But it doesn't work
declare
myarray num_array;
qrySQL varchar2(4000);
begin
myarray := num_array(6341,6468);
qrySQL := 'select * from TABLE(my_pack.My_Function(:myarray))';
execute immediate qrySQL;
end;
So my question is how can I feed an array into this pipelined function from either TOAD or SQL Developer. An example would be really handy.
Thanks
The error is fairly clear, you have a bind variable that you haven't assigned anything to. You need to pass your actual array with:
qrySQL := 'select * from TABLE(my_pack.My_Function(:myarray))';
execute immediate qrySQL using myarray;
It's maybe more useful, if you want to call it from PL/SQL, to use static SQL as a cursor:
set serveroutput on
declare
myarray num_array;
begin
myarray := num_array(6341,6468);
for r in (select * from TABLE(my_pack.My_Function(myarray))) loop
dbms_output.put_line(r.column_a);
end loop;
end;
/
Or just query it statically as a test, for fixed values:
select * from TABLE(my_pack.My_Function(num_array(6341,6468)));
SQL Fiddle with some minor tweaks to the function to remove errors I think came from editing to post.

How to populate nested object table in pl/sql block?

I struggle a problem, which, i think, is rather simple.
I have a type T_OPERATION_TAG in a database which is created as:
CREATE OR REPLACE TYPE t_operation_tag AS OBJECT(
tag_name VARCHAR2(30),
tag_value VARCHAR2(30),
CONSTRUCTOR FUNCTION t_operation_tag RETURN SELF AS RESULT
)
I also have another type T_OPERATION_TAGS, which is defined as follows
CREATE OR REPLACE TYPE t_operation_tags AS TABLE OF t_operation_tag;
Then in my pl/sql block i have the following code
DECLARE
p_op_tags t_operation_tags;
BEGIN
p_op_tags := t_operation_tags();
FOR i IN (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition)
LOOP
--How to append new lines to p_op_tags ?
END LOOP;
END;
So, if the SELECT-query in the FOR LOOP returns,e.g., five lines then how can I populate my P_OP_TAGS object table with these five lines?
Like this:
DECLARE
p_op_tags t_operation_tags;
p_cursor sys_refcursor;
p_limit number := 5;
BEGIN
open p_cursor for
SELECT t_operation_tag(tag_name, tag_value)
FROM op_tags_table
;
fetch p_cursor bulk collect into p_op_tags limit p_limit;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
close p_cursor;
END;
Or if you prefer the loop clause:
DECLARE
p_op_tag t_operation_tag;
p_op_tags t_operation_tags;
p_limit number := 5;
BEGIN
p_op_tags := t_operation_tags();
for i in (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition
and rownum < p_limit + 1)
loop
p_op_tag := t_operation_tag(i.tag_name, i.tag_value);
p_op_tags.extend();
p_op_tags(p_op_tags.COUNT) := p_op_tag;
end loop;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
END;
/
You don't really need a cursor or loop at all, if you're populating the collection entirely from your query; you can bulk collect straight into it:
DECLARE
p_op_tags t_operation_tags;
BEGIN
SELECT t_operation_tag(tag_name, tag_value)
BULK COLLECT INTO p_op_tags
FROM op_tags_table
WHERE some_condition;
...
END;
/

Return N columns from a table function

I need to implement a table function, which I will submit a request with an unknown number of columns. It looks like:
SELECT * from TABLE (function())
where function, for example'SELECT x, y FROM z. I don't know how do this, so I'd like to hear some sort of way to solve, just as an idea.
I think what you are asking is you are getting multiple rows in the o/p when you are using
the function in select statement .
if i create a function as follows:
create or replace function get1job
(id in varchar2)
return varchar2 is
tittle jobs.JOB_TITLE%type;
begin
select job_title into tittle from jobs where job_id=id;
return tittle;
end get1job;
and use it in select statement .
i will write :
select get_job('AD_PRES') from dual;
i will get only one row
if i write :
select get_job('AD_PRES') from jobs;
the number of rows displayed will be equal to the number of rows in the table jobs.
Here is an example for a fully dynamic SQL, you can insert any SELECT statement and it prints out a corresponding HTML:
CREATE OR REPLACE PROCEDURE HtmlTable(sqlStr IN VARCHAR2) IS
cur INTEGER := DBMS_SQL.OPEN_CURSOR;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB;
res INTEGER;
c INTEGER;
aCell VARCHAR2(4000);
BEGIN
DBMS_OUTPUT.PUT_LINE('<table>');
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(cur, columnCount, describeColumns);
DBMS_OUTPUT.PUT_LINE('<thead><tr>');
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE(' <td>'||describeColumns(i).COL_NAME||'</td>');
DBMS_SQL.DEFINE_COLUMN(cur, i, aCell, 4000);
END LOOP;
DBMS_OUTPUT.PUT_LINE('</tr></thead>');
res := DBMS_SQL.EXECUTE(cur);
DBMS_OUTPUT.PUT_LINE('<tbody>');
WHILE (DBMS_SQL.FETCH_ROWS(cur) > 0) LOOP
DBMS_OUTPUT.PUT_LINE('<tr>');
c := 1;
WHILE (c <= columnCount) LOOP
DBMS_SQL.COLUMN_VALUE(cur, c, aCell);
DBMS_OUTPUT.PUT_LINE(' <td>'||aCell||'</td>');
c := c + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('</tr>');
END LOOP;
DBMS_OUTPUT.PUT_LINE('</tbody>');
DBMS_OUTPUT.PUT_LINE('</table>');
DBMS_SQL.CLOSE_CURSOR(cur);
END HtmlTable;
Use this as a base for your application. Then you can execute it like this:
BEGIN
HtmlTable('SELECT x, y FROM z');
END;

Resources