how to use array as bind variable - sql

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

Related

SQL Stored Procedure data type for list of rows

What data type can I use to store all rows found by SELECT query?
CREATE OR REPLACE PROCEDURE handleFailedCalls(xNumber in varchar(10)) AS
result {DATA TYPE I WANT};
BEGIN
select * into result
from CALLS c1
where c1.status = 'fail'
END
/
One way is to use a REFCURSOR variable of OUT type.
CREATE OR REPLACE PROCEDURE handleFailedCalls(xNumber in varchar2,
p_result OUT SYS_REFCURSOR
) AS
BEGIN
OPEN p_result FOR select * from CALLS c1
where c1.status = 'fail'
END
/
Also, use VARCHAR2 instead of VARCHAR. It should be without the size, as procedure arguments with size won't compile.
The procedure can be called to receive the cursor into a local ref cursor variable.
DECLARE
res_cur SYS_REFCURSOR;
BEGIN
handleFailedCalls('Xnumber1', res_cur );
END;
/
Use BULK COLLECT, example:
DECLARE
TYPE emp_typ IS TABLE OF employees%ROWTYPE INDEX BY PLS_INTEGER;
all_employees emp_typ;
BEGIN
SELECT * BULK COLLECT INTO all_employees FROM employees;
A SELECT ... BULK COLLECT INTO statement can return multiple rows. You must set up collection variables to hold the results. You can declare associative arrays or nested tables that grow as needed to hold the entire result set.

Store data of each iteration of a LOOP and return all of them on a ref cursor?

Sorry for the bad question name, i'll give you an example to be more specific.
var1 VARCHAR(20);
var2 VARCHAR(20);
--MYRECORD CONTAINS COLUMNS ELEMENT0, VAL
FOR MYRECORD IN EXPLICITCURSOR LOOP
SELECT COL1, COL2 INTO var1, var2 FROM table1 WHERE table1.COLUMNT=MYRECORD.VAL;
END LOOP;
As you can see i have a LOOP and inside it i have a SELECT. By now, for testing, i'm saving the results into two variables overwritten everytime.
I need to save on each iteration (ELEMENT0, COL1, COL2) and i'd give them on output with a REF CURSOR.
EDIT1: I'm looking in this moment on the possibility to define a RECORD and a TABLE of my record type. Can anyone give me an example for my case? I'm having problems on setting a table as output parameter.
This is what i have prepared at the beginning of the package.
TYPE my_record is RECORD(
ELEMENT0 varchar2(20),
COL1 varchar2(20),
COL2 varchar2(20));
TYPE my_table IS TABLE OF my_record;
and for now i'm using an OUT parameter for my procedure like this:
TABLERESULT OUT my_table
I'm trying to insert my three varchar values inside my OUT param on each iteration of LOOP in this way (values are setted correctly):
INSERT INTO TABLERESULT(ELEMENT0,COL1,COL2) VALUES(ELEMENT0,COL1,COL2);
and it gives me error:
PL/SQL: ORA-00942: table or view does not exist
I'm doing something wrong using this type of OUT param?
Any suggestions? Thank you. (I'm using Oracle 11g)
EDIT2: By the help of #APC i found that naming error and now the compiler doesn't give problems. I'll continue and i'll let you know.
Here this can be achieved simply without any iterations required.
Hope below snippet helps. Please pardon any syntax error as i dont have any workspace to execute this command.
--Wwhat i would suggest is rather going row-by-row this can be achieved by single iteration and can be returnedas ref cursor as output.
DECLARE
lv sys.odcivarchar2list;
lv_ref sys_refcursor;
BEGIN
SELECT val BULK COLLECT
INTO lv
FROM TABLE2;
OPEN lv_ref FOR SELECT * FROM TABLE1 WHERE TABLE1.COL IN
(SELECT COLUMN_VALUE FROM TABLE(lv)
);
END;
/
Create an object type that matches your record structure.
Create a nested table type of those object types.
For each tuplet of strings, add to the array.
Then return a cursor variable as follows:
OPEN cv FOR SELECT FROM TABLE (my_array);
RETURN cv;
The table function converts the array into a result set that can be assigned to the cursor variable.
Here's a link to a LiveSQL script that will run the code you see below:
https://livesql.oracle.com/apex/livesql/file/content_FFTOKNC4AHGPOQE79FF76S7EQ.html
CREATE OR REPLACE TYPE three_ot
AUTHID DEFINER IS OBJECT
(
element0 VARCHAR2 (200),
col1 VARCHAR2 (200),
col2 VARCHAR2 (200)
)
/
CREATE OR REPLACE TYPE three_nt IS TABLE OF three_ot
/
CREATE OR REPLACE FUNCTION data_for_you
RETURN SYS_REFCURSOR
AUTHID DEFINER
IS
l_cursor SYS_REFCURSOR;
l_nt three_nt;
BEGIN
SELECT three_ot (TO_CHAR (employee_id), last_name, first_name)
BULK COLLECT INTO l_nt
FROM hr.employees;
OPEN l_cursor FOR SELECT * FROM TABLE (l_nt);
RETURN l_cursor;
END;
/
DECLARE
l_cursor SYS_REFCURSOR := data_for_you ();
l_three three_ot;
element0 VARCHAR2 (200);
col1 VARCHAR2 (200);
col2 VARCHAR2 (200);
BEGIN
LOOP
FETCH l_cursor INTO element0, col1, col2;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.put_line (col1);
END LOOP;
CLOSE l_cursor;
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.

PL/SQL Dynamic Loop Value

My goal is to keep a table which contains bind values and arguments, which will later be used by dbms_sql. The below pl/sql example is basic, it's purpose is to illustrate the issue I am having with recalling values from prior loop objects.
The table account_table holds acccount information
CREATE TABLE account_table (account number, name varchar2(100)));
INSERT INTO mytest
(account, name)
VALUES
(1 ,'Test');
COMMIT;
The table MYTEST holds bind information
CREATE TABLE mytest (bind_value varchar2(100));
INSERT INTO mytest (bind_value) VALUES ('i.account');
COMMIT;
DECLARE
v_sql VARCHAR2(4000) := NULL;
v_ret VARCHAR2(4000) := NULL;
BEGIN
FOR I IN (
SELECT account
FROM account_table
WHERE ROWNUM = 1
) LOOP
FOR REC IN (
SELECT *
FROM mytest
) LOOP
v_sql := 'SELECT ' || rec.bind_value || ' FROM dual';
EXECUTE IMMEDIATE v_sql INTO v_ret;
dbms_output.put_line ('Account: ' || v_ret);
END LOOP;
END LOOP;
END;
/
I cannot store the name i.account and later use the value that object. My idea was to use NDS; but, while the value of v_sql looks ok (it will read "Select i.account from dual"), an exception of invalid identifier would be raised on i.account. Is there a way to get the value of the object? We are using Oracle 11g2.
Thanks!
Dynamic SQL will not have the context of your PL/SQL block. You cannot use identifiers that are defined in the PL/SQL block in your dynamic SQL statement. So you cannot reference i.account and reference the loop that you have defined outside of the dynamic SQL statement. You could, of course, dynamically generate the entire PL/SQL block but dynamic PL/SQL is generally a pretty terrible approach-- it is very hard to get that sort of thing right.
If you are trying to use the value of i.account, however, you can do something like
v_sql := 'SELECT :1 FROM dual';
EXECUTE IMMEDIATE v_sql
INTO v_ret
USING i.account;
That doesn't appear to help you, though, if you want to get the string i.account from a table.
Are you always trying to access the same table with the same where clause, but just looking for a different column each time? If so, you could do this:
p_what_I_want := 'ACCOUNT';
--
SELECT decode(p_what_I_want
,'ACCOUNT', i.account
, 'OTHER_THING_1', i.other_thing_1
, 'OTHER_THING_2', i.other_thing_2
, 'OTHER_THING_1', i.default_thing) out_thing
INTO l_thing
FROM table;
The above statement is not dynamic but returns a different column on demand...

Passing table parameter using select clause in Oracle

I have 2 types:
CREATE OR REPLACE TYPE id_type AS OBJECT
(
id NUMBER(19, 0)
);
CREATE OR REPLACE TYPE id_table AS TABLE OF id_type;
And I have a function A(param IN id_table).
Now, if I have another function using first one, how am I supposed to pass the param like that A(SELECT 1 FROM DUAL)? Can I only do it manually creating an id_table var, filling it and then passing to A() function?
You can do something like
DECLARE
l_ids id_table;
l_return <<data type>>;
BEGIN
SELECT id_type( 1 )
BULK COLLECT INTO l_ids
FROM dual;
l_return := a( l_ids );
END;
It's not obvious, though, why you have an id_type in this case. It would seem more logical to simply declare id_table
CREATE OR REPLACE TYPE id_table
AS TABLE OF NUMBER(19,0);
It's also not obvious whether you are really intending to populate the collection by selecting from dual. If you really want to have a single-element collection, you can simply initialize it. I'm guessing, however, that you really intend to populate the collection by querying a table other than DUAL in which case you'd prefer the BULK COLLECT
DECLARE
l_ids id_table := new id_table( id_type( 1 ) );
l_return <<data type>>;
BEGIN
l_return := a( l_ids );
END;