Executing a query using a Collection as argument in Oracle - oracle

Is there any way I can execute immediate a query with a collection as its argument.
I want to define a type as type my_type as table of number and then execute a dynamic query (created by concating proper parts) using execute immediate QUERY using COLLECTION.
When I write such a code I get PLS-00457 expressions has to be of SQL types.

From oracle documentation :https://docs.oracle.com/cd/B12037_01/appdev.101/b10807/11_dynam.htm
Example 7-4 Dynamic SQL for Object Types and Collections
The below example illustrates the use of objects and collections. Suppose you define object type Person and VARRAY type Hobbies, as follows:
CREATE TYPE Person AS OBJECT (name VARCHAR2(25), age NUMBER);
CREATE TYPE Hobbies IS VARRAY(10) OF VARCHAR2(25);
Using dynamic SQL, you can write a package that uses these types:
CREATE OR REPLACE PACKAGE teams AS
PROCEDURE create_table (tab_name VARCHAR2);
PROCEDURE insert_row (tab_name VARCHAR2, p Person, h Hobbies);
PROCEDURE print_table (tab_name VARCHAR2);
END;
/
CREATE OR REPLACE PACKAGE BODY teams AS
PROCEDURE create_table (tab_name VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE ' || tab_name ||
' (pers Person, hobbs Hobbies)';
END;
PROCEDURE insert_row (
tab_name VARCHAR2,
p Person,
h Hobbies) IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO ' || tab_name ||
' VALUES (:1, :2)' USING p, h;
END;
PROCEDURE print_table (tab_name VARCHAR2) IS
TYPE RefCurTyp IS REF CURSOR;
cv RefCurTyp;
p Person;
h Hobbies;
BEGIN
OPEN cv FOR 'SELECT pers, hobbs FROM ' || tab_name;
LOOP
FETCH cv INTO p, h;
EXIT WHEN cv%NOTFOUND;
-- print attributes of 'p' and elements of 'h'
END LOOP;
CLOSE cv;
END;
END;
/
From an anonymous block, you might call the procedures in package TEAMS:
DECLARE
team_name VARCHAR2(15);
BEGIN
team_name := 'Notables';
teams.create_table(team_name);
teams.insert_row(team_name, Person('John', 31),
Hobbies('skiing', 'coin collecting', 'tennis'));
teams.insert_row(team_name, Person('Mary', 28),
Hobbies('golf', 'quilting', 'rock climbing'));
teams.print_table(team_name);
END;
/

Yes, you can do that but
1) type should be defined as OBJECT
2) Oracle version should be 11g or higher
3) if your dynamic block has plsql code, you can assign this collection value inside dynamic block variable and then use it
4) if your dynamic block is just some sql, you need to cast the collection to table first using Table(collection) keyword

Related

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

Oracle PL/SQL Developer: Return %RowType from Package Procedure

i'm kind of new to Oracle Pl\SQL. I was just trying to create a simple Package with a procedure that returns a set of object id's; the code is as follows:
--Package Spec
CREATE OR REPLACE PACKAGE TEST IS
--GET OBJECT ID'S FROM CONTROL TABLE
PROCEDURE get_object_id_control(p_obj_id OUT abc_table%ROWTYPE);
END;
--Package Body
PROCEDURE get_object_id_control(p_obj_id OUT abc_table%ROWTYPE) AS
BEGIN
SELECT object_id
INTO p_obj_id
FROM abc_table
WHERE fec_proc IS NULL;
END;
I get Error: PL/SQL: ORA-00913: too many values. Is this the correct way for returning multiple values of same data type, or is there a better approach. Thanks in advance.
You can create a custom table type and set the out parameter of the procedure to that type.
CREATE TABLE ABC_TABLE(ID varchar2(100));
create or replace type abc_tab is table of varchar2(100);
/
CREATE OR REPLACE PACKAGE TEST IS
PROCEDURE get_object_id_control(p_obj_id OUT abc_tab);
END;
/
CREATE OR REPLACE PACKAGE BODY TEST IS
PROCEDURE get_object_id_control(p_obj_id OUT abc_tab) AS
BEGIN
SELECT id
bulk collect INTO p_obj_id
FROM abc_table;
END;
END;
/
Then you can call it like so:
declare
v abc_tab;
begin
TEST.get_object_id_control(p_obj_id => v);
for i in v.first..v.last loop
dbms_output.put_line(v(i));
end loop;
end;
/
Similar to GurV's answer (since he beat me by like 30 seconds...), you can use a PL/SQL object type as well. You do not need the CREATE TYPE statement if you don't need to reference the type in SQL.
--Package Spec
CREATE OR REPLACE PACKAGE TEST AS
TYPE id_table_type IS TABLE OF NUMBER;
--GET OBJECT ID'S FROM CONTROL TABLE
PROCEDURE get_object_id_control(p_obj_id_list OUT id_table_type);
END;
--Package Body
CREATE OR REPLACE PACKAGE BODY TEST AS
PROCEDURE get_object_id_control(p_obj_id_list OUT id_table_type) AS
BEGIN
SELECT object_id
BULK COLLECT INTO p_obj_id_list
FROM abc_table
WHERE fec_proc IS NULL;
END;
END;
To use it:
DECLARE
l_id_list test.id_table_type;
BEGIN
test.get_object_id_control (p_obj_id_list => l_id_list);
FOR i IN l_id_list.FIRST .. l_id_list.LAST LOOP
DBMS_OUTPUT.put_line (l_id_list (i));
END LOOP;
END;

How to transfer cursor to the procedure dynamically and to set rowtype variable dynamically?

I have written the procedure with dynamically set cursor and %rowtype variable:
create or replace procedure process(source_table IN varchar2, my_cursor IN sys_refcursor)
is
c sys_refCURSOR;
rec my_cursor%rowtype;
begin
Dbms_Output.put_line('process starts');
open c for 'select * from '||source_table;
loop
fetch c into rec;
exit when c%notfound;
end loop;
close c;
Dbms_Output.put_line('process is over');
end process;
I am going to transfer cursor to the procedure with the function as follows:
CREATE OR REPLACE FUNCTION ddp_get_allitems (source_table IN Varchar2)
RETURN SYS_REFCURSOR
AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR 'SELECT * FROM '|| source_table;
RETURN my_cursor;
END ddp_get_allitems;
While compiling the procedure "process" I have the error:
PLS-00320 the declaration of the type of the expression is incomplete or malformed.
The compiler has hilighted the row with "rec my_cursor%rowtype;" as the error source. The varibale "source_table" and "my_cursor" are based upon the same table (select * from my_table).
So Why the error has arisen and how to remove it?
Since PL/SQL is statically typed the compiler needs to know the types of all the variables at compile time.
So there is no room for advanced metaprogramming. I'm afraid you can't do that.
There are, however, generic types found at SYS.STANDARD and a few internal functions accepting them.
-- The following data types are generics, used specially within package
-- STANDARD and some other Oracle packages. They are protected against
-- other use; sorry. True generic types are not yet part of the language.
type "<ADT_1>" as object (dummy char(1));
type "<RECORD_1>" is record (dummy char(1));
type "<TUPLE_1>" as object (dummy char(1));
type "<VARRAY_1>" is varray (1) of char(1);
type "<V2_TABLE_1>" is table of char(1) index by binary_integer;
type "<TABLE_1>" is table of char(1);
type "<COLLECTION_1>" is table of char(1);
type "<REF_CURSOR_1>" is ref cursor;
Take "<ADT_1>" for example. There is XMLTYPE constructor or DBMS_AQ ENQUEUE and DEQUEUE functions. You can pass any kind of object there.
For now you cannot use this datatype in custom functions since they are "not yet part of the language", but maybe some day there will be some support for this.
Just a thought to modify soe params which can basically same output
you want to achieve. Basically here for Function i have replaced
RETURN type as TABLE TYPE which can be easilt called in Procedure abd
rest manipulations can be done.Let me know if this helps
--SQL Object creation
CREATE TYPE source_table_obj IS OBJECT
(<TABLE_ATTRIBITES DECLARATION>);
--SQL TABLE type creation
CREATE TYPE source_table_tab IS TABLE OF source_table_obj;
--Function creation with nested table type as RETURN type
CREATE OR REPLACE FUNCTION ddp_get_allitems(
source_table IN VARCHAR2)
RETURN source_table_tab
AS
src_tab source_table_tab;
BEGIN
SELECT * BULK COLLECT INTO src_tab FROM source_table;
RETURN src_tab;
END ddp_get_allitems;
-- Using Function's OUT param as an IN Param for Procedure an do all the requird processing
CREATE OR REPLACE PROCEDURE process(
source_table IN VARCHAR2,
src_tab_in IN source_table_tab)
IS
BEGIN
FOR i IN src_tab_in.FIRST..src_tab_in.LAST
LOOP
dbms_output.put_line('job processing');
END LOOP;
END process;

Oracle table type to nested table cast error

I declared table type and set a value in it with using loop. I am having an error while I was casting this t_table
DECLARE
TYPE t_row IS RECORD
(
id NUMBER,
description VARCHAR2(50)
);
TYPE t_table IS TABLE OF t_row;
l_tab t_table := t_table();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.extend();
l_tab(l_tab.last).id := i;
l_tab(l_tab.last).description := 'Description for ' || i;
END LOOP;
SELECT * from TABLE(CAST(l_tab AS t_table));
END
Best regards
Why do you want to do a select onto the the type? You would use the the TABLE() and the CAST rather if you have a collection in a column stored in a table.
You could just loop through the table in your code. Example:
for i in l_tab.first .. l_tab.last
loop
dbms_output.put_line(l_tab(i).id||' '||l_tab(i).description);
end loop;
Since l_tab is of type t_table, there's no need for the cast. But that's not your problem.
Your problem is that you're trying to reference a PL/SQL type in SQL, which you simply can't do. You can either remove the select as #hol suggested or make the type a database object (which will allow SQL to access it):
CREATE OR REPLACE TYPE t_row AS OBJECT
(
id NUMBER,
description VARCHAR2 (50)
);
CREATE OR REPLACE TYPE t_table AS TABLE OF t_row;
DECLARE
l_tab t_table := t_table ();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.EXTEND ();
l_tab (l_tab.LAST) := t_row (i, 'Description for ' || i);
END LOOP;
FOR r IN (SELECT * FROM TABLE (l_tab)) LOOP
DBMS_OUTPUT.put_line (r.id);
END LOOP;
END;
There is a second problem with the initial code, in that you are running a select without telling the code what to do with it. Unlike some other procedural SQL extensions, PL/SQL does not allow you to implicitly return a handle to a resultset (prior to 12c). You must either handle it directly or explicitly return a ref_cursor that points to it. The code above has been update to primitively handle the result of the query.

pass pl/sql record as arguement to procedure

How to pass pl/sql record type to a procedure :
CREATE OR REPLACE PACKAGE BODY PKGDeleteNumber
AS
PROCEDURE deleteNumber (
list_of_numbers IN List_Numbers
)
IS
i_write VARCHAR2(5);
BEGIN
--do something
END deleteNumber;
END PKGDeleteNumber;
/
In this procedure deleteNumber I have used List_Numbers, which is a record type. The package declaration for the same is :
CREATE OR REPLACE PACKAGE PKGDeleteNumber
AS
TYPE List_Numbers IS RECORD (
IID NUMBER
);
TYPE list_of_numbers IS TABLE OF List_Numbers;
PROCEDURE deleteNumber (
list_of_numbers IN List_Numbers
);
END PKGDeleteNumber;
I have to execute the procedure deleteNumber passing a list of values. I inserted numbers in temp_test table, then using a cursor U fetched the data from it :
SELECT *
BULK COLLECT INTO test1
FROM temp_test;
Now, to call the procedure I am using
execute immediate 'begin PKGDELETENUMBER.DELETENUMBER(:1); end;'
using test1;
I have tried many other things as well(for loop, dbms_binding, etc). How do I pass a pl/sql record type as argument to the procedure?
EDIT:
Basically, I want to pass a list of numbers, using native dynamic sql only...
adding the table temp_test defn (no index or constraint):
create table test_temp (
IID number
);
and then inserted 1,2,3,4,5 using normal insert statements.
For this solution,
In a package testproc
CREATE TYPE num_tab_t IS TABLE OF NUMBER;
CREATE OR REPLACE PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t) AS
BEGIN
dbms_output.put_line(p_num_array.COUNT);
END;
/
this is called from sql prompt/toad
DECLARE
v_tab testproc.num_tab_t := testproc.num_tab_t(1, 10);
BEGIN
EXECUTE IMMEDIATE 'BEGIN testproc.my_dyn_proc_test(:1); END;' USING v_tab;
END;
this will not work.This shows error.I am not at my workstation so am not able to reproduce the issue now.
You can't use RECORD types in USING clause of EXECUTE IMMEDIATE statement. If you just want to pass a list of numbers, why don't you just use a variable of TABLE OF NUMBER type? Check below example:
CREATE TYPE num_tab_t IS TABLE OF NUMBER;
CREATE OR REPLACE PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t) AS
BEGIN
dbms_output.put_line(p_num_array.COUNT);
END;
/
DECLARE
v_tab num_tab_t := num_tab_t(1, 10);
BEGIN
EXECUTE IMMEDIATE 'BEGIN my_dyn_proc_test(:1); END;' USING v_tab;
END;
Output:
2
Edit
Try this:
CREATE TYPE num_tab_t IS TABLE OF NUMBER;
CREATE OR REPLACE PACKAGE testproc AS
PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t);
END;
/
CREATE OR REPLACE PACKAGE BODY testproc AS
PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t) AS
BEGIN
dbms_output.put_line(p_num_array.COUNT);
END;
END;
/
DECLARE
v_tab num_tab_t := num_tab_t(1, 10);
BEGIN
EXECUTE IMMEDIATE 'BEGIN testproc.my_dyn_proc_test(:1); END;' USING v_tab;
END;
Use an object type. object types are visible to all packages

Resources