How to use an Oracle Associative Array in a SQL query - sql

ODP.Net exposes the ability to pass Associative Arrays as params into an Oracle stored procedure from C#. Its a nice feature unless you are trying to use the data contained within that associative array in a sql query.
The reason for this is that it requires a context switch - SQL statements require SQL types and an associative array passed into PL/SQL like this is actually defined as a PL/SQL type. I believe any types defined within a PL/SQL package/procedure/function are PL/SQL types while a type created outside these objects is a SQL type (if you can provide more clarity on that, please do but its not the goal of this question).
So, the question is, what are the methods you would use to convert the PL/SQL associative array param into something that within the procedure can be used in a sql statement like this:
OPEN refCursor FOR
SELECT T.*
FROM SOME_TABLE T,
( SELECT COLUMN_VALUE V
FROM TABLE( associativeArray )
) T2
WHERE T.NAME = T2.V;
For the purposes of this example, the "associativeArray" is a simple table of varchar2(200) indexed by PLS_INTEGER. In C#, the associativeArry param is populated with a string[].
Feel free to discuss other ways of doing this besides using an associative array but know ahead of time those solutions will not be accepted. Still, I'm interested in seeing other options.

I would create a database type like this:
create type v2t as table of varchar2(30);
/
And then in the procedure:
FOR i IN 1..associativeArray.COUNT LOOP
databaseArray.extend(1);
databaseArray(i) := associativeArray(i);
END LOOP;
OPEN refCursor FOR
SELECT T.*
FROM SOME_TABLE T,
( SELECT COLUMN_VALUE V
FROM TABLE( databaseArray )
) T2
WHERE T.NAME = T2.V;
(where databaseArray is declared to be of type v2t.)

You cannot use associative arrays in the SQL scope - they are only usable in the PL/SQL scope.
One method is to map the associative array to a collection (which can be used in the SQL scope if the collection type has been defined in the SQL scope and not the PL/SQL scope).
SQL:
CREATE TYPE VARCHAR2_200_Array_Type AS TABLE OF VARCHAR2(200);
/
PL/SQL
DECLARE
TYPE associativeArrayType IS TABLE OF VARCHAR2(200) INDEX BY PLS_INTEGER;
i PLS_INTEGER;
associativeArray associativeArrayType;
array VARCHAR2_200_Array_Type;
cur SYS_REFCURSOR;
BEGIN
-- Sample data in the (sparse) associative array
associativeArray(-2) := 'Test 1';
associativeArray(0) := 'Test 2';
associativeArray(7) := 'Test 3';
-- Initialise the collection
array := VARCHAR2_200_Array_Type();
-- Loop through the associative array
i := associativeArray.FIRST;
WHILE i IS NOT NULL LOOP
array.EXTEND(1);
array(array.COUNT) := associativeArray(i);
i := associativeArray.NEXT(i);
END LOOP;
-- Use the collection in a query
OPEN cur FOR
SELECT *
FROM your_table
WHERE your_column MEMBER OF array;
END;
/

Related

PL/SQL equivalent of SELECT statement

What would be the PL/SQL equivalent of this SQL query:
SELECT * FROM table(OWNER.PACKAGE.get_exam('123456789'));
This is the Function that I am trying to call:
FUNCTION get_exam(id IN VARCHAR2)
RETURN ab_assign_v1
IS
CURSOR c_exams(cid VARCHAR2) IS
SELECT t_api_exam_v1(
sei.person_id, --unique id
l.description --loc description
)
FROM my_view sei
JOIN loc l
ON sei.loc_code = l.loc_code
v_collection ab_assign_v1;
BEGIN
OPEN c_exams(id);
FETCH c_exams BULK COLLECT INTO v_collection;
CLOSE c_exams;
RETURN v_collection;
EXCEPTION
WHEN OTHERS THEN
error_a1.raise_error(SQLCODE, SQLERRM);
END get_exam;
Hope this helps.
DECLARE
lv <COLLECTION_NAME>;
BEGIN
lv:= OWNER.PACKAGE.get_exam('123456789');
dbms_output.put_line(lv.COUNT);
END;
/
Assuming that you want to return the result of a function :
select owner.package.get_exam('123456789') from table
Your function returns a nested table type. You simply need to declare a variable of that type, and assign to it as you would if it were a scalar:
declare
l_coll your_collection_type;
begin
l_coll := OWNER.PACKAGE.get_exam('123456789');
end;
/
In this example your_collection_type is a placeholder for whatever object your function actually returns.
" I am getting this error: PLS-00201: identifier 'ab_assign_v1' must be declared "
ab_assign_v1 is the type used by your function. From the code posted in your revised question it seems that type is in the same schema which owns the package with the function. However your original pseudo-code prefixes the call with the schema name. So, putting two and two together, you need to revise the variable declaration to include the schema too. (You may need to grant EXECUTE on it too, if you haven't done this already).
declare
l_coll OWNER.ab_assign_v1;
begin
l_coll := OWNER.PACKAGE.get_exam('123456789');
end;
/

What is the difference between nested array and associative array?

There are two links
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/composites.htm#LNPLS99981 and
Purpose of using different types of PL/SQL collections in Oracle
by referring above two links i have two doubt
1.Which one is correct nested table?
2.If the oracle doc is correct what is the difference between nested table and associative array?
Here is another difference which is not that commonly known. You can compare two nested tables with = or <> but associative array you cannot.
DECLARE
TYPE associative_array IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
a_var_associative_array associative_array;
b_var_associative_array associative_array;
TYPE nested_table IS TABLE OF INTEGER;
a_var_nested_table nested_table := nested_table(1, 2, 3, 4, 5);
b_var_nested_table nested_table := nested_table(5, 4, 3, 2, 1);
BEGIN
IF a_var_nested_table = b_var_nested_table THEN
-- Note, the different order of values!
DBMS_OUTPUT.PUT_LINE ( 'TRUE' );
ELSE
DBMS_OUTPUT.PUT_LINE ( 'FALSE' );
END IF;
-- IF a_var_associative_array = b_var_associative_array THEN -> gives you an error!
END;
When you work with nested tables you can also use Multiset Operators, Multiset Conditions and SET which are not available for associative arrays.
A nested table is just an array of n elements.
declare
type nested_table_of_integer is table of integer;
v_my_nested_table nested_table_of_integer;
begin
v_my_nested_table := nested_table_of_integer(); -- initialize
v_my_nested_table.extend(10); -- add 10 elements
v_my_nested_table(1) := 100;
v_my_nested_table(11) := 1000; -- ORA-06533: Subscript beyond count
end;
A nested table must be initialized as shown. It has zero elements at first. To add elements we use EXTEND. This nested table has 10 elements. They are indexed 1 to 10. Element 1 has the value 100. The others have value null. An access to a non-existent element, say the 11th element, raises an error.
An associative array on the other hand is an array of name/value pairs. Let's use numbers (pls_integer typically) for the naming:
declare
type associative_array_of_integer is table of integer index by pls_integer;
v_my_associative_array associative_array_of_integer;
begin
v_my_associative_array(1) := 100;
v_my_associative_array(11) := 1000;
v_my_associative_array(12) := v_my_associative_array(2); -- ORA-01403: no data found
end;
An associative array needs no initialization. It is empty and gets populated. Here we associate the element called 1 with the value 100 and the element with the name 11 with the value 1000. So there are two elements in the array. We get a no data found exception when we try to access a name that is not in the array.
We can also use strings for the names:
declare
type associative_array_of_integer is table of integer index by varchar2(100);
v_my_associative_array associative_array_of_integer;
begin
v_my_associative_array('age father') := 39;
v_my_associative_array('age mother') := 32;
v_my_associative_array('age daughter') := 11;
end;
You can use both collections to get table data, but you use them differently. The nested table has a count and you can just loop from 1 to count to access its elements:
declare
type nested_table_of_integer is table of integer;
v_my_nested_table nested_table_of_integer;
begin
v_my_nested_table := nested_table_of_integer(); -- initialize
select table_name bulk collect into v_my_nested_table from user_tables;
for i in 1 .. v_my_nested_table.count loop
dbms_output.put_line(v_my_nested_table(i));
end loop;
end;
The associative array however must be read from whatever happens to be the first index to the next and next and next using FIRST and NEXT.
declare
type associative_array_of_integer is table of integer index by pls_integer;
v_my_associative_array associative_array_of_integer;
i integer;
begin
select table_name bulk collect into v_my_associative_array from user_tables;
i := v_my_associative_array.first;
while i is not null loop
dbms_output.put_line(v_my_associative_array(i));
i := v_my_associative_array.next(i);
end loop;
end;
The "names" happen to be 1, 2, 3, etc. here (given thus by the bulk collection) and you could access v_my_associative_array(1) for instance. Later in your program, however, after some possible delete operations in the array, there may be gaps, so you don't know whether an element named 1 exists and whether the element before element 4 happens to be element 3. As with bulk collect the "names" for the elements have no meaning you would not really use them, but go instead through the chain as shown.

how to use array as bind variable

I have following array, which will be populated based on some external criteria.
TYPE t_column IS TABLE OF TABLE_1.COLUMN_1%TYPE INDEX BY PLS_INTEGER;
ar_column t_column;
Now, I want to use this ar_column into another cursor, how can i bind it ?
I am looking at something like select * from table1 where column in (ar_colum[0],ar_colum[1] ...); (its a pseudo code)
Using PL/SQL collections in SQL statements requires a few extra steps. The data type must be created, not simply declared as part of a PL/SQL block. Associative arrays do no exist in SQL and must be converted into a nested table or varray at some point. And the TABLE operator must be used to convert the data.
create table table1(column_1 number);
insert into table1 values (1);
commit;
create or replace type number_nt is table of number;
declare
ar_column number_nt := number_nt();
v_count number;
begin
ar_column.extend; ar_column(ar_column.last) := 1;
ar_column.extend; ar_column(ar_column.last) := 2;
select count(*)
into v_count
from table1
where column_1 in (select * from table(ar_column));
dbms_output.put_line('Count: '||v_count);
end;
/
Count: 1

ORACLE PL/SQL: Dynamic SQL Select using a Collection

Is it possible to create a dynamic SQL statement that pulls from an existing collection?
l_collection := pack.get_items(
i_code => get_items_list.i_code ,
i_name => get_items_list.i_name );
Now, let's say I want to select a COUNT from that collection using dynamic SQL. Is that possible? Furthermore, I want to do a sub select from that collection as well.
If the collection type is declared at the schema level, it can be used in SQL statements, including dynamic ones. You need to explicitly cast it to the proper collection type, or the SQL engine has no idea what type it is.
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM TABLE(CAST(:collection AS collection_type))'
INTO l_count
USING l_collection
;
I'm not sure if there's some other reason you want to use dynamic SQL, or if you're just assuming that it's necessary in this case. It shouldn't be necessary if all you want to do is select the count. This inline SQL should work fine:
SELECT COUNT(*) INTO l_count FROM TABLE(CAST(l_collection AS collection_type));
Of course, if that's all you want you don't need SQL at all, just l_count := l_collection.COUNT.
Edit -- adding fully worked out example
CREATE OR REPLACE TYPE testtype AS OBJECT( x NUMBER, y NUMBER);
/
CREATE OR REPLACE TYPE testtypetab AS TABLE OF testtype;
/
DECLARE
t testtypetab := testtypetab();
l_count integer;
BEGIN
-- Populate the collection with some data
SELECT testtype(LEVEL, LEVEL) BULK COLLECT INTO t FROM dual CONNECT BY LEVEL<21;
-- Show that we can query it using inline SQL
SELECT count(*) INTO l_count FROM TABLE(CAST(t AS testtypetab));
dbms_output.put_line( l_count );
-- Clear the collection
t.DELETE;
-- Show that we can query it using dynamic SQL
EXECUTE IMMEDIATE 'select count(*) from table(cast(:collection as testtypetab))'
into l_count using t;
dbms_output.put_line( l_count );
END;
/

How to return rows from a declare/begin/end block in Oracle?

I want to return rows from a select statement within a declare/begin/end block. I can do this in T-SQL but I would like to know how to do it in PL/SQL.
The code looks a bit like the following:
declare
blah number := 42;
begin
select *
from x
where x.value = blah;
end;
An anonymous PL/SQL block, like the one you've shown, can't "return" anything. It can interact with the caller by means of bind variables, however.
So the method I would use in this case would be to declare a cursor reference, open it in the PL/SQL block for the desired query, and let the calling application fetch rows from it. In SQLPlus this would look like:
variable rc refcursor
declare
blah number := 42;
begin
open :rc for
select *
from x
where x.value = blah;
end;
/
print x
If you recast your PL/SQL as a stored function then it could return values. In this case what you might want to do is create a collection type, fetch all the rows into a variable of that type, and return it:
CREATE TYPE number_table AS TABLE OF NUMBER;
CREATE FUNCTION get_blah_from_x (blah INTEGER)
RETURN number_table
IS
values number_table;
BEGIN
SELECT id
BULK COLLECT INTO values
FROM x
WHERE x.value = blah;
RETURN values;
END;
/
Well, this depends heavily on your data access library.
You can return any SQL-compatible type as a parameter. This includes complex SQL types and collection types.
But most libraries are simply not capable of handling Oracle's object types.
Either way, my examples will use these object types:
create type SomeType as object(Field1 VarChar(50));
create type SomeTypeList as table of SomeType;
When your access library can handle object types, you could simply return a list of PL/SQL objects:
begin
:list := SomeTypeList(SomeType('a'),SomeType('b'),SomeType('c'));
end;
If not, you could hack around it by forcing this list into a select and return its result as a cursor:
declare
list SomeTypeList;
begin
list := SomeTypeList(SomeType('a'),SomeType('b'),SomeType('c'));
open :yourCursor for
SELECT A
FROM table(list);
end;