PL/SQL - How to use an array in an IN Clause - oracle

I'm trying to use an array of input values to my procedure in an IN Clause as part of the where clause of a cursor. I know that this has been asked before, but I haven't seen how to make my syntax compile correctly.
In the package specification, the type is
TYPE t_brth_dt IS TABLE OF sourceTable.stdt_brth_dt%TYPE INDEX BY PLS_INTEGER;
sourceTable.std_brth_dt is a date column in the table.
Simplified version of my cursor is in the package body is -
cursor DataCursor_Sort( p_brth_dt in t_brth_dt) is
SELECT *
FROM sourceTable
WHERE a.brth_dt IN (select column_value
from table(p_brth_dt))
When I try to compile this, I'm getting the following errors.
[1]:(Error): PLS-00382: expression is of wrong type
[2]:(Error): PL/SQL: ORA-22905: cannot access rows from a non-nested table item
I know this looks similar to other questions, but I don't understand what the syntax error is.

In order to use collection defined as a nested table or an associative array in the from clause of a query you either should, as #Alex Poole correctly pointed out, create a schema level (SQL) type or use one, that is available to you trough ODCIConst package - odcidatelist as you intend to use a list of dates. For example, your cursor definition might look like this:
cursor DataCursor_Sort(p_brth_dt in sys.odcidatelist) is
select *
from sourceTable
where a.brth_dt IN (select column_value
from table(p_brth_dt))
OR
cursor DataCursor_Sort(p_brth_dt in sys.odcidatelist) is
select s.*
from sourceTable s
join table(p_brth_dt) t
on (s.brth_dt = t.column_value)
Note: You should take into consideration the time part of a date when performing a date comparison. If you want to compare date part only it probably would be useful to get rid of time part by using trunc() function.

It is possible to use a PL/SQL-defined nested table type (as opposed to a SQL-defined nested table type) indirectly in an IN clause of a SELECT statement in a PL/SQL package. You must use a PIPELINED function as an intermediary. It felt kind of clever to write, but I don't believe in its fundamental usefulness.
CREATE OR REPLACE PACKAGE so18989249 IS
TYPE date_plsql_nested_table_type IS TABLE OF DATE;
dates date_plsql_nested_table_type;
FUNCTION dates_pipelined RETURN date_plsql_nested_table_type PIPELINED;
PROCEDURE use_plsql_nested_table_type;
END so18989249;
/
CREATE OR REPLACE PACKAGE BODY so18989249 IS
FUNCTION dates_pipelined RETURN date_plsql_nested_table_type
PIPELINED IS
BEGIN
IF (dates.count > 0)
THEN
FOR i IN dates.first .. dates.last
LOOP
IF (dates.exists(i))
THEN
PIPE ROW(dates(i));
END IF;
END LOOP;
END IF;
END;
PROCEDURE use_plsql_nested_table_type IS
BEGIN
dates := NEW date_plsql_nested_table_type();
-- tweak these values as you see fit to produce the dbms_output results you want
dates.extend(5);
dates(1) := DATE '2013-12-25';
dates(2) := DATE '2013-01-01';
dates(3) := DATE '2013-07-01';
dates(4) := DATE '2013-09-03';
dates(5) := DATE '2008-11-18';
FOR i IN (SELECT o.owner,
o.object_name,
o.object_type,
to_char(o.last_ddl_time, 'YYYY-MM-DD') AS last_ddl
FROM all_objects o
WHERE trunc(o.last_ddl_time) IN
(SELECT column_value FROM TABLE(dates_pipelined))
--uses pipeline function which uses pl/sql-defined nested table
)
LOOP
dbms_output.put_line('"' || i.owner || '"."' || i.object_name || '" ("' || i.object_type || ') on ' || i.last_ddl);
END LOOP;
END;
END so18989249;
/
begin so18989249.use_plsql_nested_table_type; end;
/

The type has to be created at SQL level, not in a package. An SQL query doesn't know how to use any types defined in PL/SQL. So you'd have to do:
CREATE OR REPLACE TYPE t_brth_dt IS TABLE OF date;
/
... and remove the type from your package specification. (Or give them different names, at least, and they won't be interchangeable in use). Because it's at SQL level, you also can't use sourceTable.stdt_brth_dt%TYPE in the declaration, unfortunately.

Related

Bulk collect sql

i made a data_object by myself :
CREATE OR REPLACE TYPE my_object AS OBJECT(
number_type NUMBER,
varchar_type VARCHAR2(20)
)
and then i create type
CREATE OR REPLACE TYPE my_nt IS TABLE OF my_object;
And I want with nested table and this object make a procedure, that will be return number of employyes of some departments. I ve got Two tables: employees and department a this is my code:
DECLARE
enum_dname my_nt := my_nt();
PROCEDURE print_l IS
BEGIN
DBMS_OUTPUT.put_line('---------------------------------------------------------');
FOR i IN 1..enum_dname.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(enum_dname(i));
END LOOP;
END;
BEGIN
SELECT COUNT(emp_id) as number_of, department_name
BULK COLLECT INTO enum_dname
FROM employees e, department d
WHERE e.department_id = d.department_id
GROUP BY department_name;
print_l;
END;
And it show me errors : PLS - 00306: Wrong numbers of argument in call type: PUT_LINE
and PL\SQL : ORA - 00947:not enough values
THANK YOU!
You have two errors. As #SudiptaMondal pointed out (and as here) you can't pass an object to put_line(), you have to pass a single string value - or something which evaluates to a string, which can be concatenated or implicit converted or whatever. So here you might do:
DBMS_OUTPUT.PUT_LINE(enum_dname(i).varchar_type || ': ' || enum_dname(i).number_type);
or however you want to format that output. Using dbms_output for anything except debugging generally not a good idea as you have no control over whether someone using your code has output enabled. But this may be enough for this exercise.
The second problem, causing the ORA-00947, is because your query is trying to bulk collect two scalar variables into a collection of objects. You need to include the object constructor:
SELECT my_object(COUNT(emp_id), department_name)
BULK COLLECT INTO enum_dname
...

Insert into nested table plsql

I'm a newbie with pl sql and I'm facing some problems with inserting into nested tables (I'm using these just to test a procedure).
So my code is:
insert into t_prenotazioni
(nro_cliente, data_disponibilita)
values
(righe.nro_cliente, v_data_disponibilita);
where t_prenotazioni is a table of a type defined by me, righe.nro_cliente is a value that I get from a cursor and v_data_disponibilita is a variable.
The error that I get is:
PLS-00330 invalid use of type name or subtype
You are probably trying to do something like:
declare
type type_prenotazioni is record(nro_cliente number, data_disponibilita date);
type prenotazioni is table of type_prenotazioni;
vPrenotazioni prenotazioni;
begin
vPrenotazioni := new prenotazioni();
vPrenotazioni.extend(1);
vPrenotazioni(1).nro_cliente := 10;
vPrenotazioni(1).data_disponibilita := sysdate;
--
for i in vPrenotazioni.first .. vPrenotazioni.last loop
dbms_output.put_line(vPrenotazioni(i).nro_cliente || ' - ' ||
to_char(vPrenotazioni(i).data_disponibilita, 'dd/mm/yyyy')
);
end loop;
end;
I would stronlgy recommend having a look at the Oracle documentation to improve your knowledge; this is only a simple, small example, but there are many many different things you may want to do.

ROWID in casted pl-sql collection

Is there any rowid like facility in a pl-sql collection? In my case, while I am using this collection in an sql query, I also need the sequence number as they are put in. I know modification is data struecture is a way, but I want to use the index of the collection. so what I am looking for is something like this:
TYPE t_List IS TABLE OF VARCHAR2(200);
and
declare
v_Data t_List := t_List('data 1'
,'data_2'
,'data3');
......
FOR Rec IN (SELECT Column_Value v
,ROWID r
FROM TABLE(CAST(v_data t_list)))
LOOP
Dbms_Output.Put_Line('at ' || Rec.r || ':' || Rec.v);
-- .... and other codes here
END LOOP;
The loop is not expected to be executed in sequence, but I want something built-in like ROWID that is like the index of the collection.
Only schema-level types can be used in SQl statement, even within a PL/SQL block. As you seem to suggest you already know, you can create your own object type that includes the 'sequence' ID:
CREATE TYPE t_object AS OBJECT (
id NUMBER,
data VARCHAR2(200)
)
/
And a collection of that type:
CREATE TYPE t_List IS TABLE OF t_object;
/
And then populate the ID as you build the list:
DECLARE
l_List t_List := t_List(t_object(1, 'data 1')
,t_object(2, 'data_2')
,t_object(3, 'data3'));
BEGIN
FOR Rec IN (SELECT id, data
FROM TABLE(l_list))
LOOP
Dbms_Output.Put_Line('at ' || Rec.id || ':' || Rec.data);
-- .... and other codes here
END LOOP;
END;
/
Without an object type you can use the ROWNUM pseudocolumn:
CREATE TYPE t_List IS TABLE OF VARCHAR2(200);
/
DECLARE
v_Data t_List := t_List('data 1'
,'data_2'
,'data3');
BEGIN
FOR Rec IN (SELECT Column_Value v
,ROWNUM r
FROM TABLE(v_data))
LOOP
Dbms_Output.Put_Line('at ' || Rec.r || ':' || Rec.v);
-- .... and other codes here
END LOOP;
END;
/
anonymous block completed
at 1:data 1
at 2:data_2
at 3:data3
As far as I'm aware that isn't guaranteed to preserve the original creation sequence. I think it almost certainly will at the moment, but perhaps isn't something you should rely on as always being true. (There is no order without an order by, but here you don't have anything you can order by without destroying your initial order...).
If you query a subset of the table - I'm not sure what you mean by "the loop is not expected to be executed in sequence" - you'd need to generate the ROWNUM in a subquery before filtering or it won't be consistent. You'd also need to generate the ROWNUM in a subquery if you're joining this to other, real, tables - I imagine you are, otherwise you could use a PL/SQL collection.
If indexing is important, use Associative Arrays(Also known as Index-by tables)
Refer his.

Listagg function with PLSQL collection

I have a PL/SQL collection of following type
type p_typ_str_tab is table of varchar2(4000) index by pls_integer;
I would like to aggregate the values into a single string with a simple inline function like LISTAGG without writing any custom functions or for loops. All examples of LISTAGG don't show how to use PL/SQL collections. I'm using Oracle 11g R2. Is this possible?
To be able to use LISTAGG function with a collection, the collection must be declared as nested table not as an associative array and must be created as sql type (schema object) because it's not possible to use pl/sql type in a select statement. To that end you might do the following:
--- create a nested table type
SQL> create or replace type t_tb_type is table of number;
2 /
Type created
--- and use it as follows
SQL> select listagg(column_value, ',') within group(order by column_value) res
2 from table(t_tb_type(1,2,3)) -- or call the function that returns data of
3 / -- t_tb_type type
RES
-------
1,2,3
Otherwise the loop is your only choice.
LISTAGG is an analytic SQL function, and those don't operate against PL/SQL collections, but cursors/rowsets.
So, in short, no, it's not possible.
That said, iterating over a PL/SQL table to build a concatenated string is trivial:
l_new_string := null;
for i in str_tab.first .. str_tab.last
loop
if str_tab(i) is not null then
l_new_string := str_tab(i) || ', ';
end if;
end loop;
-- trim off the trailing comma and space
l_new_string := substr(l_new_string, 1, length(l_new_string) - 2);

TO_CHAR of an Oracle PL/SQL TABLE type

For debugging purposes, I'd like to be able to "TO_CHAR" an Oracle PL/SQL in-memory table. Here's a simplified example, of what I'd like to do:
DECLARE
TYPE T IS TABLE OF MY_TABLE%ROWTYPE INDEX BY PLS_INTEGER;
V T;
BEGIN
-- ..
-- Here, I'd like to dbms_output V's contents, which of course doesn't compile
FOR i IN V.FIRST .. V.LAST LOOP
dbms_output.put_line(V(i));
END LOOP;
-- I want to omit doing this:
FOR i IN V.FIRST .. V.LAST LOOP
dbms_output.put_line(V(i).ID || ',' || V(i).AMOUNT ...);
END LOOP;
END;
Can this be achieved, simply? The reason I ask is because I'm too lazy to write this debugging code again and again, and I'd like to use it with any table type.
ok, sorry this isn't complete, but to followup with #Lukas, here's what I have so far:
First, instead of trying to create anydata/anytype types, I tried using XML extracted from a cursor...weird, but its generic:
CREATE OR REPLACE procedure printCur(in_cursor IN sys_refcursor) IS
begin
FOR c IN (SELECT ROWNUM rn,
t2.COLUMN_VALUE.getrootelement () NAME,
EXTRACTVALUE (t2.COLUMN_VALUE, 'node()') VALUE
FROM TABLE (XMLSEQUENCE (in_cursor)) t,
TABLE (XMLSEQUENCE (EXTRACT (COLUMN_VALUE, '/ROW/node()'))) t2
order by 1)
LOOP
DBMS_OUTPUT.put_line (c.NAME || ': ' || c.VALUE);
END LOOP;
exception
when others then raise;
end;
/
Now, to call it, you need a cursor, so I tried casting to cursor in pl/sql, something like:
open v_cur for select * from table(cast(v_tab as tab_type));
But depending on how v_tab is defined, this may or may not cause issues in pl/sql cast (using %rowtype in nested table def seems to give issues).
Anyway, you can build on this or refine it as you like. (and possibly use xmltable...)
Hope that helps

Resources