How include array (or similar) into an in-clause SQL statement? - sql

I am new to oracle and PL SQL. Currently I struggle with the handling of arrays and "similar things" like i.e. collection. I am trying to build a procedure like:
procedure insert_by_array( my_array some_array_type)
begin
insert into table1 (some_column)
select some_column
from table2
where column2 in my_array
;
end;
However I could not make I did try some array types but I did not find the right one. The entries of the type must be varchar2 - a part of this criterium I am open to any array type. I.e when my array_type is
type array_of_strings is varray(100) of varchar2(40);
My error would be: "local collection types are not alowed in sql statements"
I am using Oracle Database 12c Enterprise Edition Release 12.1.0.2.0.
So at the end, this worked:
create type table_of_strings IS TABLE OF VARCHAR2(64); --define it global;
declare
my_table table_of_strings;
begin
my_table := table_of_strings('aaa', 'bb','c');
insert into table1 (some_column)
select some_column
from table2
where column2 in (select * from table(my_table))
;
end;

First of all, you have to define sql level collection. (varray is not an option)
create type array_of_strings as table of varchar2(40);
Now you can use table or "member of" approach
declare
c array_of_strings := new array_of_strings();
begin
c.extend();
c(c.count) := 'A';
c.extend();
c(c.count) := 'B';
for rec in (select * from dual where 'A' member of c ) loop
dbms_output.put_line('Option with memeber of ');
end loop;
for rec in (select * from dual where 'A' in (select * from table(c))) loop
dbms_output.put_line('Option with table');
end loop;
end;

As error says, local collections, defined in procedure, function, code block, cannot be used in queries, at least in Oracle 11 which I use.
This does not work (PLS-00642):
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings;
begin
select * bulk collect into v_o from dual where dummy in (select * from table(v_i));
end;
So... either use type defined at schema level (your own or predefined sys.odcivarchar2list):
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings;
v_so sys.odcivarchar2list := sys.odcivarchar2list();
begin
v_so.extend(v_i.count);
for i in 1..v_i.count loop
v_so(i) := v_i(i);
end loop;
select * bulk collect into v_o from dual where dummy in (select * from table(v_so));
end;
... either use dynamic sql:
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings;
v_str varchar2(4000);
begin
for i in 1..v_i.count loop
v_str := v_str || case when i > 1 then ', ' end || ''''||v_i(i)||'''';
end loop;
execute immediate 'select * from dual where dummy in ('||v_str||')' bulk collect into v_o ;
end;
... either use loop (probably slowest):
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings := strings();
begin
for rec in (select * from dual) loop
if rec.dummy member of v_i then
v_o.extend();
v_o(v_o.count) := rec.dummy;
end if;
end loop;
end;

Related

Iterate through row's columns

I have table with 100 columns with not correlated names (ABC1, DA23, EE123 - there is no common pattern there).
I want to iterate through every row and every column in this table.
My current script:
BEGIN
FOR single_row IN (
SELECT *
FROM MY_TABLE)
LOOP
--iterate through columns of 'single_row'
--for each nullable column do insert with real current column name and column value)
--I assume each column is nullable except of ID
INSERT INTO ANOTHER_TABLE VALUES (single_row.id, column_name, column_value);
END LOOP;
END;
So for example, if MY_TABLE contains 2 rows:
ID|ABC1|DA23|EE123|...
1|123|456|789|...
2|321|654|987|...
After running my script, my ANOTHER_TABLE will contain:
MY_TABLE_ID|COLUMN_NAME|COLUMN_VALUE
1|ABC1|123
1|DA23|456
1|EE123|789
... other columns from row 1
2|ABC1|321
2|DA23|654
2|EE123|987
... other columns from row 2
How I can do this?
I'm using Oracle 11g
EDIT
#vkp provided great solution, but there is one more thing to solve. I don't want to specify all columns in in clause. I would love to use some kind of query there or * or anything else, just to not be forced to list all of them.
I have tried something like this:
select *
from MY_TABLE t
unpivot (
column_value for column_name in (select column_name
from user_tab_columns
where table_name = 'MY_TABLE'
and nullable = 'Y')
) u
but it returns error:
ORA-00904: : invalid identifier
00904. 00000 - "%s: invalid identifier"
This is an application of unpivot.
select *
from my_table m
unpivot (column_value for column_name in (ABC1,DA23,EE123)) u
null values for any of the columns for an id won't be shown in the result.
If you have to include null values in the output, use the option INCLUDE NULLS.
select *
from my_table m
unpivot include nulls (column_value for column_name in (ABC1,DA23,EE123)) u
Edit: To include column names dynamically, use
DECLARE
sql_stmt VARCHAR2(4000);
var_columns VARCHAR2(4000); --use clob datatype if the column names can't fit in with this datatype
BEGIN
SELECT LISTAGG(column_name,',') WITHIN GROUP(ORDER BY column_name)
INTO var_columns
FROM user_tab_columns
WHERE table_name='MY_TABLE' AND column_name<>'ID';
sql_stmt:='select * from my_table m
unpivot
(column_value for column_name in (' || var_columns || ')) u';
EXECUTE IMMEDIATE sql_stmt;
END;
/
First option. With dynamic sql.
declare
v_ctx number;
v_query varchar2(500);
v_total NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_column_cnt NUMBER;
v_value varchar2(32767);
v_result clob := '';
v_rownum number := 0;
begin
v_ctx := dbms_sql.open_cursor;
v_query := 'select * from user_objects where rownum < 100';
dbms_sql.parse(v_ctx,v_query,dbms_sql.v7);
v_total := dbms_sql.execute(v_ctx);
DBMS_SQL.DESCRIBE_COLUMNS(v_ctx, v_column_cnt, v_desctab);
for i in 1 .. v_column_cnt loop
dbms_sql.define_column(v_ctx, i, v_value /* data_type varchar2*/, 32767 /* max_length*/);
end loop;
loop
exit when dbms_sql.fetch_rows(v_ctx) = 0;
v_rownum := v_rownum +1;
for i in 1 .. v_column_cnt loop
dbms_sql.column_value(v_ctx, i, v_value);
dbms_output.put_line(v_rownum||' - '||v_desctab(i).col_name||' - '||v_value);
end loop;
end loop;
dbms_sql.close_cursor(v_ctx);
exception
when others then
dbms_sql.close_cursor(v_ctx);
raise;
end;
/
2nd option with xquery.
select t1.id,t2.* from xmltable('for $i in ora:view("<you_table_here>")/ROW
return $i'
columns id FOR ORDINALITY
, row_value xmltype path'.'
) t1
,xmltable('for $i in $row_value/ROW/* return $i'
passing t1.row_value as "row_value"
columns col_index for ORDINALITY ,
column_name varchar2(100) path 'name()',
column_value varchar2(100) path 'text()'
) t2
Here is a simple solution using REF CURSOR.
I've tried this code and it's working at my end.
DECLARE
query_2 VARCHAR2(1000);
TYPE icur IS REF CURSOR;
ic icur;
col_val VARCHAR2(100);
BEGIN
FOR j IN
(SELECT * FROM user_tab_cols WHERE table_name = UPPER('MY_TABLE'))
LOOP
dbms_output.put_line(j.column_name);
query_2 := 'SELECT ' || j.column_name|| ' FROM MY_TABLE';
OPEN ic FOR query_2;
LOOP
FETCH ic INTO col_val;
EXIT WHEN ic%NOTFOUND;
INSERT INTO ANOTHER_TABLE VALUES( j.column_name, col_val);
END LOOP;
END LOOP;
END;
/

oracle cursor parameter as collection

it works like:
create or replace procedure back_end_proc(p_rc OUT SYS_REFCURSOR) is
v_script VARCHAR2(4000); begin
v_script := 'select sysdate data from dual where ''a'' = :a union all select sysdate-1 data from dual where ''b'' = :b';
open p_rc for v_script using 'a','b'; end;
when calling the procedure:
DECLARE
rc SYS_REFCURSOR;
BEGIN
victom.back_end_proc2(:rc );
END;
will have an output rows:
DATA
27.06.2017 11:25:02
26.06.2017 11:25:02
THE QUESTION IS:
how to modify the using 'a','b' params into one array or some collection data that will contain 'a','b' as one string or smth like one single value?
If your target query looked like this ...
select * from whatever
where id in ( .... )
it would be quite simple to pass a collection.
create or replace type tt1 as table of varchar2(1);
/
create or replace procedure back_end_proc(
p_ids in tt1
, p_rc OUT SYS_REFCURSOR) is
begin
open p_rc for
'select * from whatever
where id member of :1'
using p_ids;
end;
However, your posted case is slightly more complicated because you want to assign specific values from the collection in each sub-query. This means you need to use a VARRAY because that is the only collection type which guarantees ordering. So:
create or replace type va1 as varray(10) of varchar2(1);
/
create or replace procedure back_end_proc(
p_ids in va1
, p_rc OUT SYS_REFCURSOR) is
begin
open p_rc for
'select sysdate from dual where ''a'' = :1
union all
select sysdate-1 from dual where ''b'' = :2'
using p_ids(1), p_ids(2);
end;

Dynamic SQL - ORACLE

I have the following procedure, which does not compile correctly, because it refers to non existing objects (table does not exist)
Here is only a section of the code (i used generic names for tables and columns):
DECLARE
C INTEGER := 0;
BEGIN
SELECT COUNT(1) INTO C FROM USER_TABLES WHERE TABLE_NAME = 'MY_TABLE';
IF C > 0 THEN
DECLARE
CURSOR c_maps IS SELECT COLUM_NAME1, COLUM_NAME2 FROM MY_TABLE WHERE ACTIVE = 1;
BEGIN
FOR prec IN c_maps LOOP
some code...;
END LOOP;
EXECUTE IMMEDIATE 'some code..';
END;
END IF;
END;
/
I don't know how to write this statement dynamically, since the table "MY_TABLE" does not exist:
CURSOR c_maps IS SELECT COLUM_NAME1, COLUM_NAME2 FROM MY_TABLE WHERE ACTIVE =1;
I also tried to write it like:
CURSOR c_maps IS SELECT COLUM_NAME1, COLUM_NAME2 FROM (Select 'MY_TABLE' from dual) WHERE ACTIVE = 1;
However, than it refers to the column "ACTIVE" which also does not exist at compile time...It is possible to write the whole procedure inside "execute immediate" - block? I have tried different variants, however without success
You may need to open the cursor in a different way, so that the non existing table is only referred in dynamic SQL; for example:
declare
c integer := 0;
curs sys_refcursor;
v1 number;
v2 number;
begin
select count(1)
into c
from user_tables
where table_name = 'MY_TABLE';
if c > 0
then
open curs for 'select column_name1, column_name2 from my_table where active = 1';
loop
fetch curs into v1, v2;
exit when curs%NOTFOUND;
dbms_output.put_line(v1 || ' - ' || v2);
end loop;
else
dbms_output.put_line('The table does not exist');
end if;
end;
/

Alias for a table of type?

I have the following definition for a table type
create table number_table as table of number;
And I'd like to use it like in the following example.
declare
l_myTable number_table := number_table(1, 2, 3);
begin
for i in (select * from l_myTable) loop
dbms_output.put_line(i.???); -- how do I reference the numbers here?
end loop;
end;
Forgive my code being somewhat pointless, how would I reference that i.??? though to get the number out of the iterator?
Just slightly change your query to
select rownum, column_value from l_myTable
and then use dbms_output.put_line(i.rownum); as an index and dbms_output.put_line(i.column_value ); as a value
create type number_table as table of number;
/
You do not need to use a Cursor FOR LOOP as you can just iterate over the collection:
declare
l_myTable number_table := number_table(3, 2, 1);
begin
for i in 1 .. l_myTable.COUNT loop
dbms_output.put_line( l_myTable(i) );
end loop;
end;
/
However, if there is some reason why you need to use a cursor then you can use the ROWNUM pseudocolumn to get index within the collection and the COLUMN_VALUE pseudocolumn to get the value at that index in the collection using the query:
SELECT ROWNUM, COLUMN_VALUE FROM TABLE( l_myTable )
Like this:
declare
l_myTable number_table := number_table(3, 2, 1);
begin
for i in ( SELECT ROWNUM, COLUMN_VALUE FROM TABLE( l_myTable ) ) loop
dbms_output.put_line( i.rownum || ': ' || i.column_value ); -- how do I reference the numbers here?
end loop;
end;
/

I need to use variable with TABLE type in IN operator

I need help with PL/SQL. How can i use variable with TABLE data type (in my case this variable involve 3 VARCHAR2 elements) with IN operator, without use access by index?
Example
select field1
from dual
where field1 in (myTableVariable);
myTableVariable must returning from function.
Not finished function:
declare
v_string varchar2(100);
v_string2 varchar2(100);
TYPE V_ARRAY IS TABLE OF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
result V_ARRAY;
n number;
begin
n := 1;
v_string := '13,15,02';
while v_string != ' ' loop
select regexp_substr(v_string, '^[a-z0-9]*', 1),
regexp_replace(v_string, '^[a-z0-9]*(,|$)', '')
into v_string2, v_string
from dual;
result(n) := v_string2;
n := n + 1;
dbms_output.put_line(v_string2);
end loop;
return result;
end;
First:
Declare your table data type on the schema level (i.e. not in a package).
Then:
select field1
from dual
where feld1 in (select column_value from table(myTableVariable));
Enjoy!