I seem to be having an issue when using a Table Collection Expression to select from an Associative Array (Note: I am using Oracle 12c, so this is allowed: Oracle Documentation)
Take the following "simple" example:
First, create a package which declares a record and the associative array:
CREATE OR REPLACE PACKAGE table_test
IS
TYPE pt_DateSpan IS RECORD
(
StartDate DATE,
EndDate DATE
);
TYPE pt_DateSpanTable IS TABLE OF pt_DateSpan INDEX BY PLS_INTEGER;
END;
/
Then, I wrote the following anonymous block to test the functionality:
DECLARE
l_tTest table_test.pt_DateSpanTable;
PROCEDURE lp_outputAArray (p_aaInput table_test.pt_DateSpanTable) IS
l_nTableSize INTEGER;
BEGIN
--I know I can use p_aaInput.COUNT, but I want to select using TABLE() to show that the functionality "works"
SELECT COUNT(*)
INTO l_nTableSize
FROM TABLE(p_aaInput);
dbms_output.put_line('Table Size: '||l_nTableSize);
FOR i IN 1..p_aaInput.COUNT LOOP
dbms_output.put_line(i||': '||to_char(p_aaInput(i).StartDate, 'MM/DD/YYYY')||' - '||to_char(p_aaInput(i).EndDate, 'MM/DD/YYYY'));
END LOOP;
END lp_outputAArray;
BEGIN
--ADD RECORD TO ASSOCIATIVE ARRAY
SELECT to_date('01/01/2000', 'MM/DD/YYYY'), to_date('01/01/2010', 'MM/DD/YYYY')
BULK COLLECT INTO l_tTest
FROM DUAL;
lp_outputAArray(l_tTest);
--SELECT THE ASSOCIATIVE ARRAY INTO ITSELF
SELECT t.StartDate, t.EndDate
BULK COLLECT INTO l_tTest
FROM TABLE(l_tTest) t;
lp_outputAArray(l_tTest);
END;
/
This block produces the following output:
Table Size: 1
1: 01/01/2000 - 01/01/2010
Table Size: 0
My question is why is the second output not identical to the first?
Also, I realize that I don't need to use BULK COLLECT in most of this example, it is a simplified version of my actual code which does SELECT from actual tables.
My final goal was to use UNION ALL to allow me to append values to my Associative Array instead of replacing it when performing a series of SELECT statements. Something like this:
SELECT *
BULK COLLECT INTO l_tTest
FROM (SELECT t.StartDate, t.EndDate
FROM TABLE(l_tTest) t
UNION ALL
SELECT to_date('01/01/2011', 'MM/DD/YYYY'), to_date('01/01/2019', 'MM/DD/YYYY')
FROM DUAL);
I would appreciate any help you could provide.
It appears that when you use:
SELECT ...
BULK COLLECT INTO array
FROM ...
Then the first thing that happens is that the array you BULK COLLECT INTO is re-initialised to an empty array.
Therefore, when you want to use it in the table collection expression it is already empty and no rows are generated.
Instead, you could use a non-associative array and use the MULTISET operators in PL/SQL:
CREATE OR REPLACE PACKAGE table_test
IS
TYPE range IS RECORD
(
StartDate DATE,
EndDate DATE
);
TYPE range_table IS TABLE OF range
--INDEX BY PLS_INTEGER
;
END;
/
DECLARE
l_ranges table_test.range_table := table_test.range_table();
l_ranges2 table_test.range_table := table_test.range_table();
PROCEDURE output_ranges(
range_array table_test.range_table
)
IS
idx PLS_INTEGER;
BEGIN
dbms_output.put_line('Table Size: '||range_array.COUNT);
idx := range_array.FIRST;
LOOP
EXIT WHEN idx IS NULL;
dbms_output.put_line(
idx||': '||range_array(idx).StartDate||' - '||range_array(idx).EndDate
);
idx := range_array.NEXT(idx);
END LOOP;
END output_ranges;
BEGIN
l_ranges.EXTEND(2);
l_ranges(1) := table_test.range(DATE '2000-01-01', DATE '2001-01-01');
l_ranges(2) := table_test.range(DATE '2001-01-01', DATE '2002-01-01');
l_ranges2.EXTEND(2);
l_ranges2(1) := table_test.range(DATE '2002-01-01', DATE '2003-01-01');
l_ranges2(2) := table_test.range(DATE '2003-01-01', DATE '2004-01-01');
output_ranges(l_ranges);
output_ranges(l_ranges2);
l_ranges := l_ranges MULTISET UNION ALL l_ranges2;
output_ranges(l_ranges);
END;
/
Which outputs:
Table Size: 2
1: 01-JAN-00 - 01-JAN-01
2: 01-JAN-01 - 01-JAN-02
Table Size: 2
1: 01-JAN-02 - 01-JAN-03
2: 01-JAN-03 - 01-JAN-04
Table Size: 4
1: 01-JAN-00 - 01-JAN-01
2: 01-JAN-01 - 01-JAN-02
3: 01-JAN-02 - 01-JAN-03
4: 01-JAN-03 - 01-JAN-04
Related
I have a record as following and I want to populate this with a for loop.
declare
type ch_type is table of record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab;
begin
for i in (select * from emp) loop
rec_typr.id := i.emp_id;
rec_typr.name := i.first_name;
end loop;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
but I get the error:
PLS:0302 component first must be declared.
Can you help me with this?
Two things are problematic in your code.
1) type ch_type is table of record is syntactically incorrect. You must first declare a record and then define its collection type.
2) Using implicit cursor loop is not an efficient method to load a collection and definitely can't be done the way you're trying to do. Use much simpler BULK COLLECT method instead.
declare
type ch_type is record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab;
begin
select emp_id,first_name bulk collect into
rec_typr from emp;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
/
Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PL/SQL procedure successfully completed.
EDIT
I need to populate the record through a loop not through bulk collect.
Is it any way?
Yes, there is. But, it is less efficient than the method described above.
declare
type ch_type is record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab := ch_type_tab();
i INTEGER := 1;
begin
rec_typr.extend;
for rec in
(
select emp_id,first_name bulk collect into
rec_typr from emp
)
loop
rec_typr(i).id := rec.emp_id;
rec_typr(i).name := rec.first_name;
rec_typr.extend;
i := i + 1;
end loop;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
/
Is it possible in pl/sql to loop through a number of id's that need to go in the WHERE clause of the pl/sql statement. The sql statement itself is pretty simple, but I need to iterate over a number of id's:
SELECT x_name
FROM table_x
WHERE x_id = {array of 90 id's};
How can I insert the 90 id's here so that sql iterates over them? I tried using the cursor For Loop, but I'm stuck. The code below is erroneous, but it might give an indication what Im trying to achieve here
DECLARE
TYPE x_id_array IS VARRAY(3) OF NUMBER;
CURSOR cur_x_id (x_ondz_id NUMBER) IS
SELECT x_name
FROM table_x
WHERE x_id = var_ondz_id;
loop_total integer;
x_id x_id_array;
name VARCHAR;
BEGIN
x_id_new := x_id_array(8779254, 8819930, 8819931); --3 for testing
loop_total := x_id_new.count;
FOR i in 1 .. loop_total LOOP
dbms_output.put_line('x_id: ' || x_id_new(i) || '= Name: ' || x_name );
END LOOP;
END;
/
The expected out put would be
x_id: 8779254= Name: Name_1
x_id: 8819930= Name: Name_2
x_id: 8819931= Name: Name_3
...
... etc for all 90 id's in the array
Any help is appreciated
We can use TABLE function on a collection to get a list of numbers / character.
SELECT *
FROM TABLE ( sys.odcinumberlist(8779254,8819930,8819931) );
8779254
8819930
8819931
Here I'm using Oracle's internal VARRAY with a limit of 32767. You may use your own NESTED TABLE type.
create OR REPLACE TYPE yourtype AS TABLE OF NUMBER;
and then select it.
SELECT *
FROM TABLE ( yourtype(8779254,8819930,8819931) );
So, your query can simply be written as
SELECT x_name
FROM table_x
WHERE x_id IN ( SELECT * FROM
TABLE ( yourtype(8779254,8819930,8819931) ) );
12.2 and above, you won't even need to specify TABLE.
SELECT * FROM yourtype(8779254,8819930,8819931) works.
I'm trying to sort an Oracle PL/SQL collection (an associative array) using the approach presented here. I modified the example from the linked page in order to use an associative array, but I guess I'm having some casting issues.
Here it is my code:
DECLARE
TYPE TABLE_TYPE IS TABLE OF NUMBER INDEX BY VARCHAR2(10);
table_in TABLE_TYPE;
table_out TABLE_TYPE;
BEGIN
-- 1. Populating the collection with random numbers between 1 and 50
FOR i IN 1..9 LOOP
SELECT ROUND(DBMS_RANDOM.VALUE(1,50))
INTO table_in('key-'||i)
FROM DUAL;
END LOOP;
-- 2. Trying to order the collection -> throws ORA-00902: invalid datatype
SELECT CAST (MULTISET(
SELECT * FROM TABLE(table_in)
ORDER BY 2
) AS TABLE_TYPE
)
INTO table_out
FROM DUAL;
END;
What am I doing wrong?
You can't use associative array as a table in select, because that time is working SQL Engine and SQL Engine doesn't know what is associative array, SQL Engine can only works with Table Type, and you don't need first select (select from dual), you can do it via pl/sql engine, change it like this
....................
TYPE NumbersTableType IS TABLE OF NUMBER; -- it must be in global types, not in your local package
....................
DECLARE
table_in NumbersTableType := NumbersTableType();
table_out NumbersTableType;
BEGIN
-- 1. Populating the collection with random numbers between 1 and 50
FOR i IN 1..9 LOOP
table_in.Extend;
table_in(i):=ROUND(DBMS_RANDOM.VALUE(1,50));
END LOOP;
SELECT CAST (MULTISET(
SELECT * FROM TABLE(table_in)
ORDER BY 1
) AS NumbersTableType
)
INTO table_out
FROM DUAL;
END;
For sake of completeness I'll report here the trivial workaround I used to order a collection (index by varchar) in Oracle version before 12c. The solution just replaces the associative array collection with a custom Table type (in order to make it visible to the SQL Engine).
CREATE OR REPLACE TYPE MY_TABLE_TYPE_OBJ AS OBJECT (
THE_KEY VARCHAR2(10),
THE_VALUE NUMBER
);
/
CREATE OR REPLACE TYPE MY_TABLE_TYPE IS TABLE OF MY_TABLE_TYPE_OBJ;
/
DECLARE
my_key VARCHAR2(10);
table_in MY_TABLE_TYPE := MY_TABLE_TYPE();
table_out MY_TABLE_TYPE := MY_TABLE_TYPE();
BEGIN
-- Creating an unsorted collection
FOR i IN 1..9 LOOP
table_in.EXTEND;
table_in(i) := MY_TABLE_TYPE_OBJ(
'key-'||i,
ROUND(DBMS_RANDOM.VALUE(1,50))
);
END LOOP;
-- Sorting the collection
SELECT CAST (MULTISET(
SELECT * FROM TABLE(table_in)
ORDER BY 2
) AS MY_TABLE_TYPE
)
INTO table_out
FROM DUAL;
END;
Hope this helps.
SELECT CAST(MULTISET(SELECT *
FROM TABLE(table_a)
ORDER BY loc_sort_column DESC
) as table_a_type
)
INTO table_b
FROM dual;
I have this statement that tosses 1 collection of data into another nested table after sorting it.
The problem I am having is that it is not sorting. I have a variable loc_sort_column that in this case will be a integer/number between 1 and 11 selecting the column but it is not working.
I have stuck 1 in there and it works fine but it doesn't seem to like the variable that contains 1.
Is this an order of operation or something?
What you are doing is ordering by a constant. Following AskTom:
you cannot dynamically return a column position, you dynamically
returned A CONSTANT, A NUMBER, A CONSTANT VALUE - JUST A VALUE
order by < ordinal position> | < expression>
you are returning an expression - not an ordinal position.
What you can do is to use DECODE function to decode column position to real column. Please check my code sample:
CREATE OR REPLACE TYPE my_type FORCE AS OBJECT
(
col_1 NUMBER
,col_2 NUMBER
);
/
CREATE OR REPLACE TYPE my_nested_table AS TABLE of my_type;
/
DECLARE
l_table_a my_nested_table := my_nested_table();
l_table_b my_nested_table := my_nested_table();
l_sort_column NUMBER := 2;
BEGIN
l_table_a.extend(100);
FOR l_i IN 1..l_table_a.COUNT LOOP
l_table_a(l_i) := my_type(ROUND(DBMS_RANDOM.RANDOM,0), ROUND(DBMS_RANDOM.RANDOM,0));
END LOOP;
SELECT CAST(
MULTISET(
SELECT *
FROM TABLE(l_table_a)
ORDER BY DECODE(l_sort_column, 1, col_1
, 2, col_2) DESC
) AS my_nested_table
)
INTO l_table_b
FROM dual;
DBMS_OUTPUT.PUT_LINE('col_1 col_2');
FOR l_i IN 1..l_table_b.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(l_table_b(l_i).col_1 || ' ' || l_table_b(l_i).col_2);
END LOOP;
END;
/
I am using Toad. I have a declaration of a table in a package as follows:
TYPE MyRecordType IS RECORD
(ID MyTable.ID%TYPE
,FIELD1 MyTable.FIELD1%TYPE
,FIELD2 MyTable.FIELD2%TYPE
,FIELD3 MyTable.FIELD3%TYPE
,ANOTHERFIELD VARCHAR2(80)
);
TYPE MyTableType IS TABLE OF MyRecordType INDEX BY BINARY_INTEGER;
There is a procedure (lets say MyProcedure), that is using an object of this table type as input/output. I want to run the procedure and see the results (how the table is filled). So I am thinking I will select the results from the table:
declare
IO_table MyPackage.MyTableType;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
select * from IO_table;
end;
I get the message:
Table or view does not exist (for IO_table). If I remove the select line, the procedure runs successfully, but I cannot see its results. How can I see the contents of IO_table after I call the procedure?
You cannot see the results for a PL/SQL table by using Select * from IO_table
You will need to loop through the collection in the annonymous block.
do something like, given in pseudo code below...
declare
IO_table MyPackage.MyTableType;
l_index BINARY_INTEGER;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
l_index := IO_table.first;
While l_index is not null
loop
dbms_output.put_line (IO_table(l_index).id);
.
.
.
.
l_index :=IO_table.next(l_index_id);
end loop;
end;
You have to do it like this:
select * from TABLE(IO_table);
and, of course you missed the INTO or BULK COLLECT INTO clause
1) You can not use associated arrays in SELECT statement, Just nested tables or varrays declared globally.
2) You should use TABLE() expression in SELECT statement
3) You can't simply use SELECT in PL/SQL code - cursor FOR LOOP or REF CURSOR or BULK COLLECT INTO or INTO must be used.
4) The last but not least - please study the manual:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjcol.htm#ADOBJ00204
Just an example:
SQL> create type t_obj as object( id int, name varchar2(10));
2 /
SQL> create type t_obj_tab as table of t_obj;
2 /
SQL> var rc refcursor
SQL> declare
2 t_var t_obj_tab := t_obj_tab();
3 begin
4 t_var.extend(2);
5 t_var(1) := t_obj(1,'A');
6 t_var(2) := t_obj(2,'B');
7 open :rc for select * from table(t_var);
8 end;
9 /
SQL> print rc
ID NAME
---------- ----------
1 A
2 B