Select from PLSQL Associative array? - plsql

Is it possible to use SELECT FROM when using an associative array? I'm passing an array to a stored procedure through a .NET application, and I wanna be able to use that array as a condition when selecting from another table. Lets say I'm passing an array of IDs to the procedure, I wanna be able to do this:
select * from table1 where userID in (select column_value from array)
The type for the array is defined in the package:
type id_array is type of number index by pls_integer

Yes, it is possible, by wrapping the array with a pipelined function. Here's a good primer on pipelined functions:
http://www.oracle-developer.net/display.php?id=429
UPDATE: Oracle 12c now supports querying associative arrays using the TABLE operator, as long as the type is declared in a package spec: https://galobalda.wordpress.com/2014/08/02/new-in-oracle-12c-querying-an-associative-array-in-plsql-programs/
e.g.
select * from table1
where userID in (select column_value from table(array));

No, you can't select from PL/SQL arrays, since you use SQL in select from statements, though you can use DB defined Nested Tables types in SQL. This short article can help you get started.
Take a look a this simple synthetic exmple:
> create type temp_t as table of int;/
Type created.
> select 'test' from dual where 1 in (select * from table(temp_t(1,2,3)));
'TES
----
test

An example using PLSQL (to select from a nested table):
create type temp_r as OBJECT(
temp_varchar2 varchar2(100),
temp_number number(20)
);
/
create type temp_t as TABLE of temp_r;
/
set serveroutput on size 1000000
/
-- PLSQL starts here
declare
temp_rec temp_r := temp_r(null, null); -- empty constructor to initialize object
temp_table temp_t := temp_t(); -- empty constructor to initialize object
lv_ref_cursor SYS_REFCURSOR;
lv_temp_varchar2 varchar(100);
lv_temp_number number(20);
begin
temp_rec.temp_varchar2 := 'first';
temp_rec.temp_number := 1;
temp_table.extend;
temp_table(1) := temp_rec;
temp_table.extend;
temp_table(2) := temp_r('second', 2);
OPEN lv_ref_cursor FOR
SELECT temp_varchar2, temp_number
FROM table(temp_table)
where temp_number = 1;
fetch lv_ref_cursor into lv_temp_varchar2, lv_temp_number;
close lv_ref_cursor;
dbms_output.put_line('returns: ' || lv_temp_varchar2 || ', ' || lv_temp_number);
end;
/

Related

PL SQL: How to populate a cursor in loop

I have a scenario to implement where in we will have an SP with two parameters
1) In parameter of array of Varchar2
2) Cursor -- Out parameter
We have populate the cursor only for each Value passed in array and this opened/populated cursor will be used by Java. But what i have found till now is, if we have to process an array then it should have to be done using loop.
For ex:
Open Cursor_Name
For
For index In Arrar_Parameter.FIRST .. Arrar_Parameter.LAST
LOOP
SELECT * FROM EMP WHERE EmpId = Arrar_Parameter[i] -------> This needs to be looped and not sure if this will work
END LOOP
Can we have some thing like this
Open Cursor_Name
For
SELECT * FROM EMP WHERE EmpId IN (Arrar_Parameter values) ------> To fetch/put all the array values at once without loop.
Kindly suggest hot to populate cursor in this scenario
Assuming your array parameter is a schema-level varray or nested table, you can use a table collection expression:
The table_collection_expression lets you inform Oracle that the value of collection_expression should be treated as a table for purposes of query and DML operations. The collection_expression can be a subquery, a column, a function, or a collection constructor. Regardless of its form, it must return a collection value—that is, a value whose type is nested table or varray. This process of extracting the elements of a collection is called collection unnesting.
This uses a built-in varray type but you can substitute your own:
create procedure procedure_name (
array_parameter sys.odcivarchar2list, cursor_name out sys_refcursor
) as
begin
open cursor_name for
select e.*
from table (array_parameter) a
join emp e on e.empid = a.column_value;
end;
/
You can also use in if you prefer:
create procedure procedure_name (
array_parameter sys.odcivarchar2list, cursor_name out sys_refcursor
) as
begin
open cursor_name for
select *
from emp
where empid in (select column_value from table (array_parameter));
end;
/
If it is a nested table you can also use the member of syntax:
create type my_varchar2_table as table of varchar2(30);
/
create procedure procedure_name (
array_parameter my_varchar2_table, cursor_name out sys_refcursor
) as
begin
open cursor_name for
select *
from emp
where empid member of array_parameter;
end;
/

multiset union distinct gives "wrong number of types or arguments passed" error

I am using a for loop to pass different values to a cursor, bulk collect the data and append it to the same nested table using MULTISET UNION operator. However, to avoid duplicate data, i tried to use MULTISET UNION DISTINCT and it throws the error PLS-00306: wrong number or types of arguments in call to 'MULTISET_UNION_DISTINCT' The codes works well without DISTINCT. Please let me know if i'm missing anything here.
I'm using Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
My code is as follows:
DECLARE
TYPE t_search_rec
IS
RECORD
(
search_id VARCHAR2(200),
search_name VARCHAR2(240),
description VARCHAR2(240) );
TYPE t_search_data
IS
TABLE OF t_search_rec;
in_user_id NUMBER;
in_user_role VARCHAR2(20);
in_period VARCHAR2(20) := 'Sep-15';
in_search_string VARCHAR2(20) := 'v';
l_search_tt t_search_data;
x_search_tt t_search_data;
v_entity_gl VA_LOGIN_API.t_entity_list ;
X_RETURN_CODE VARCHAR2(20);
X_RETURN_MSG VARCHAR2(2000);
CURSOR c_vendors_gl(v_entity VARCHAR2)
IS
SELECT UNIQUE vendor_id,
vendor,
NULL description
FROM XXPOADASH.XX_VA_PO_LINES
WHERE period = in_period
AND entity = v_entity
AND upper(vendor) LIKE upper(in_search_string)
||'%';
BEGIN
in_user_role := 'ROLE';
in_user_id := 4359;
VA_LOGIN_API.DATA_ACCESS_PROC( X_RETURN_CODE => X_RETURN_CODE, X_RETURN_MSG => X_RETURN_MSG, IN_PERSON_ID => in_user_id, IN_PERSON_ROLE => in_user_role, X_ACCESSED_ENTITY_LIST => v_entity_gl );
IF( v_entity_gl.COUNT >0) THEN
x_search_tt := t_search_data();
FOR I IN v_entity_gl.FIRST..v_entity_gl.COUNT
LOOP
OPEN c_vendors_gl(v_entity_gl(i));
FETCH c_vendors_gl BULK COLLECT INTO l_search_tt;
CLOSE c_vendors_gl;
x_search_tt := x_search_tt MULTISET
UNION DISTINCT l_search_tt;
l_search_tt.delete;
END LOOP;
IF x_search_tt.count = 0 THEN
x_return_msg := 'No lines found';
END IF;
END IF;
DBMS_OUTPUT.PUT_LINE(x_return_msg);
DBMS_OUTPUT.PUT_LINE(x_search_tt.count);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(x_return_msg);
DBMS_OUTPUT.PUT_LINE(x_search_tt.count);
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
multiset union distinct requires the elements of the collection to be comparable. In your case the elements are PL/SQL records that are unfortunately not comparable data structures (i.e. PL/SQL provides no build-in mechanism to compare PL/SQL records).
multiset union works because it doesn't need to compare the elements.
One possible workaround is to use Oracle object type instead of PL/SQL record. Object type allows you to implement a comparison method required by multiset union distinct.

Assigning object type in plsql

I need your help to know how to assign the object type through a string in PLSQL
Below is the problem description:
I first created the object types as below:
create or replace type picu_obj is object(Customer_ID varchar2(32767),Customer_Name varchar2(32767),Server_Name varchar2(32767),Time_stamp varchar2(32767));
create or replace type picu_obj_tab is table of picu_obj;
and I have a PLSQL block as below:
declare
l_str1 varchar2(1000);
l_str2 varchar2(10000);
l_newstr1_1 varchar2(10000);
picu_var picu_obj_tab;
cursor c1cudetails
is
select item,current_value
from
(select rownum,
last_value(category ignore nulls) over (order by rownum) category ,
last_value(item ignore nulls) over (order by rownum) item,
current_value
from pi_perfdata_new
order by rownum
)
where upper(category) like '%CUSTOMER%DETAILS%' ;
type cudet is table of c1cudetails%rowtype index by pls_integer;
l_cudet cudet;
begin
/* create dynamic string for items */
open c1cudetails;
fetch c1cudetails bulk collect into l_cudet limit 50;
for i in l_cudet.first..l_cudet.last loop
l_str1:=l_str1||','||''''||l_cudet(i).current_value||'''';
l_str2:=trim(leading ',' from l_str1);
l_newstr1_1:='picu_obj_tab(picu_obj('||l_str2||'))';
end loop;
-- dbms_output.put_line(''||l_newstr1_1||'');
-- picu_var := l_newstr1_1;
close c1cudetails;
end;
For the string "l_newstr1_1" following value is retruned from above PLSQL block
picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
Now I want to assign the above result to variable "picu_var" which I have declared.
Basically I need to convert to the following during runtime.
picu_var := picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
How to achieve the same?
Please suggest how to initialize the object type variable to the string values.
Use dynamic PL/SQL like this:
execute immediate 'begin :x := ' || l_newstr1_1|| '; end;'
using out picu_var;

Mixing arrays and queries

I have a set of product that I want to tag to a certain value so what I did this:
declare
type array_produit_auto is varray(3) of varchar(50);
array array_produit_auto := array_produit_auto('code_product1', 'code_product2', 'code_product3');
begin
for i in 1..array.count loop
update F_PRODUITASS pas
set PAS_NONGES_IDA = 0
WHERE PAS.PAS_CODE_PRODUIT = array(i;
end loop;
end;
commit;
however, the list of these products is too long. Instead I'd like to do this:
declare
type array_produit_auto is varray(3) of varchar(50);
array array_produit_auto := array_produit_auto('code_product4', 'code_product5', 'code_product6');
begin
update F_PRODUITASS pas
set PAS_NONGES_IDA = 1
WHERE PAS.PAS_CODE_PRODUIT NOT IN array;
end;
commit;
except this doesn't work since apparently I can't mix a query and an array this way.
Any idea of how I could make this work?
If you used a nested table then you could query from the nested table, something like this:
DECLARE
v_exclude_list t_array_produit_auto :=
t_array_produit_auto('code_product4', 'code_product5', 'code_product6');
BEGIN
UPDATE F_PRODUITASS pas
SET PAS_NONGES_IDA = 1
WHERE PAS.PAS_CODE_PRODUIT NOT IN ( SELECT *
FROM TABLE(v_exclude_list) )
;
END;
/
Also, you meant varchar2, right?
Update regarding the Opaque error: The type declaration would need to be an object type (create with the CREATE OR REPLACE TYPE syntax rather than a local plsql type as in the DDL below.
CREATE TABLE F_PRODUITASS(PAS_NONGES_IDA number, PAS_CODE_PRODUIT VARCHAR2(50));
INSERT INTO F_PRODUITASS VALUES(3, 'code_product3');
INSERT INTO F_PRODUITASS VALUES(4, 'code_product4');
CREATE OR REPLACE TYPE t_array_produit_auto IS TABLE OF VARCHAR2(50);
If you did not wish to create your own object type, you could use pre-existing varchar2 or number types such as sys.odcivarchar2list as described here:
Anonymous TABLE or VARRAY type in Oracle

Convert stored proc to table to be able to use in select statement

I have to use existing stored procedure which returns REF CURSOR. I need to insert that resultset into a temporary table.
Spec of procedure is:
TYPE cur IS REF CURSOR;
PROCEDURE get(p_one NUMBER ,p_two OUT cur);
How can I insert the resultset of this procedure into a table.
I just re-read the title of your question. Do you actually need to be able to select from a procedure?
If so, this can be achieved by using pipelined functions.
The process for this is:
Create an object type to represent the record-type you require.
Create a nested table type of the object.
Create a pipelined function which returns the nest table.
You can then select from this function.
This example should get you on your way:
create or replace type to_test as object (
val1 varchar2(32),
val2 varchar2(32)
);
create or replace type tt_test as table of to_test;
create or replace function demo_pipe return tt_test pipelined
is
v_test to_test;
begin
for rec in (select * from user_tables) loop
v_test := to_test(rec.table_name, rec.tablespace_name);
pipe row (v_test);
end loop;
end;
/
select * from table(demo_pipe);

Resources