Find out if a collection was populated by bulk collect - oracle

I created an oracle Object Type like this:
CREATE OR REPLACE TYPE DFBOWNER."RPT_WIRE_IMPORT_ROWTYPE" AS OBJECT
(
REC_VALUE_DATE DATE
)
/
And then a collection based on this type:
CREATE OR REPLACE TYPE DFBOWNER."RPT_WIRE_IMPORT_TABLETYPE" IS TABLE OF RPT_WIRE_IMPORT_RowType;
/
Now I populate the collection using oracle bulk collect into syntax inside a procedure.
So now i want to test if the collection actually got populated, and i am not sure how to do it.
I tried looking it up:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjcol.htm#autoId17 but I am not able to find what I need.
I also have another question. When the procedure bulk collects data into collections, does the data in the collection become permanent as in a table? Or is it semi-permanent...i.e. only lives for the session...as in a temp table.

I suspect you are looking for the COUNT method, i.e.
DECLARE
l_local_collection dbfowner.rpt_wire_import_tabletype;
BEGIN
SELECT sysdate + level
BULK COLLECT INTO l_local_collection
FROM dual
CONNECT BY level <= 10;
dbms_output.put_line( 'l_local_collection contains ' ||
l_local_collection.count ||
' elements.' );
END;
Like any local variable, l_local_collection will have the scope of the block in which it is declared. The data is stored in the PGA for the session. The data in a collection is not permanent.
You can select from the local collection
SQL> create type some_object as object (
2 rec_value_date date
3 );
4 /
Type created.
SQL> create type some_coll
2 as table of some_object;
3 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_local_collection some_coll;
3 begin
4 select some_object( sysdate + numtodsinterval( level, 'day' ) )
5 bulk collect into l_local_collection
6 from dual
7 connect by level <= 10;
8 for x in (select * from table( l_local_collection ))
9 loop
10 dbms_output.put_line( x.rec_value_date );
11 end loop;
12* end;
SQL> /
20-AUG-12
21-AUG-12
22-AUG-12
23-AUG-12
24-AUG-12
25-AUG-12
26-AUG-12
27-AUG-12
28-AUG-12
29-AUG-12
PL/SQL procedure successfully completed.
but it generally doesn't make sense to go through the effort of pulling all the data from the SQL VM into the PL/SQL VM only to then pass all of the data back to the SQL VM in order to issue the SELECT statement. It would generally make more sense to just keep the data in SQL or to define a pipelined table function to return the data.
If you merely want to iterate over the elements in the collection
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_local_collection some_coll;
3 begin
4 select some_object( sysdate + numtodsinterval( level, 'day' ) )
5 bulk collect into l_local_collection
6 from dual
7 connect by level <= 10;
8 for i in 1 .. l_local_collection.count
9 loop
10 dbms_output.put_line( l_local_collection(i).rec_value_date );
11 end loop;
12* end;
SQL> /
20-AUG-12
21-AUG-12
22-AUG-12
23-AUG-12
24-AUG-12
25-AUG-12
26-AUG-12
27-AUG-12
28-AUG-12
29-AUG-12
PL/SQL procedure successfully completed.
It would make much more sense to iterate over the elements in the collection, which keeps everything in PL/SQL, than to SELECT from the collection, which forces all the data back into the SQL VM.

Related

Oracle - How to execute a query with parameters?

I am new to Oracle and i am trying to execute a simple select with some parameters but i cant get it to work.
For
SELECT idl.column_value clientguid
FROM TableName idl
LEFT JOIN :ParamName_Type olt ON olt.clientguid = idl.column_value
WHERE (olt.flag = 0)
But declare does not work. I could not find any help on internet.
Thanks.
Oracle SQL Developer should handle variables the same way SQLPlus does, that is with the &.
For example ( in SQLPlus for simplicity):
SQL> select 1 from &tableName;
Enter value for tablename: dual
old 1: select 1 from &tableName
new 1: select 1 from dual
1
----------
1
What you can not do is use the parameter as a part of a table name, assuming that Developer "knows" which part is the parameter name and which one is the fixed part.
For example:
SQL> select * from &ParamName_Type;
Enter value for paramname_type:
that is, all the string ParamName_Type wil be interpreted as a variable name and substituited with the value you enter.
Also, consider that this is a client-specific behaviour, not an Oracle DB one; so, the same thing will not work in a different client (Toad for Oracle for example).
Consider that you are trying to use a "parameter" that represents a table name, and you only can do this by the means of some client, because plain SQL does not allow it.
If you need to do such a thing in some piece of code that has to work no matter the client, you need dynamic SQL
If you need something more complex, you may need some dynamic SQL; for example:
SQL> declare
2 vTableName varchar2(30) := '&table_name';
3 vSQL varchar2(100):= 'select 1 from ' || vTableName ||
' union all select 2 from ' || vTableName;
4 type tResult is table of number;
5 vResult tResult;
6 begin
7 execute immediate vSQL bulk collect into vResult;
8 --
9 -- do what you need with the result
10 --
11 for i in vResult.first .. vResult.last loop
12 dbms_output.put_line(vResult(i));
13 end loop;
14 end;
15 /
Enter value for table_name: dual
old 2: vTableName varchar2(30) := '&table_name';
new 2: vTableName varchar2(30) := 'dual';
1
2
PL/SQL procedure successfully completed.
SQL>

Oracle PL/SQL - Show results of declared table

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

Using cursor after a delete statement in a stored procedure [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Oracle Accessing updated records during the same transaction
I have an Oracle stored procedure somewhat like this (actual sqls are different)
CREATE OR REPLACE PROCEDURE mysp
IS
v_copied_row table%ROWTYPE ;
CURSOR p_copied_rows IS
select *
from table ;
BEGIN
delete from table
where <some condition>
OPEN p_copied_rows ;
LOOP
FETCH p_copied_rows into v_copied_rows ;
<do something with fetched row>
END LOOP
close p_copied_rows;
END;
Ideally, I am expecting that the deleted rows should not be part of the result set I am fetching from the cursor, but those are.
I am new to oracle, and want to understand if i am doing something wrong here?
P.S. I've to use cursor for some complex things, so replacing cursor with the SQL is not an option.
If your actual code matched the code you posted, the rows you you delete would not be returned by the cursor
If I create a table with 100 rows
SQL> ed
Wrote file afiedt.buf
1 create table foo
2 as
3 select level col1
4 from dual
5* connect by level <= 100
SQL> /
Table created.
and then create a PL/SQL block that replicates what you posted which deletes 98 of the rows, the cursor that is opened will return only 2 rows
SQL> select count(*) from foo;
COUNT(*)
----------
100
SQL> declare
2 cursor non_deleted_rows
3 is select *
4 from foo;
5 l_rec foo%rowtype;
6 begin
7 delete from foo
8 where col1 <= 98;
9
10 open non_deleted_rows;
11 loop
12 fetch non_deleted_rows into l_rec;
13 exit when non_deleted_rows%notfound;
14
15 dbms_output.put_line( l_rec.col1 );
16 end loop;
17 end;
18 /
99
100
PL/SQL procedure successfully completed.
Now, if you open the cursor before you issue the DELETE, the cursor will return the rows that were deleted. Perhaps in your actual code, the OPEN statement is before the DELETE.

How to use parameters in a 'where value in...' clause?

This works when I have only one state code as a parameter.
How can I get code to work when I have more than one state_code in parm_list?
Requirements:
(1)I don't want to hard code the state codes in my cursor definition
(2) I do want to allow for more than one state code in my where clause
For example: I want to run this code for parm_list = ('NY','NJ','NC').
I'm encountering difficulties in reconciling single quotes in parm_list with the single quotes in the 'where state_code in ' query.
set serveroutput on;
DECLARE
parm_list varchar2(40);
cursor get_state_codes(in_state_codes varchar2)
is
select state_name, state_code from states
where state_code in (in_state_codes);
BEGIN
parm_list := 'NY';
for get_record in get_state_codes(parm_list) loop
dbms_output.put_line(get_record.state_name || get_record.state_code);
end loop;
END;
Using dynamic SQL is the simplest approach from a coding standpoint. The problem with dynamic SQL, though, is that you have to hard parse every distinct version of the query which not only has the potential of taxing your CPU but has the potential to flood your shared pool with lots of non-sharable SQL statements, pushing out statements you'd like to cache, causing more hard parses and shared pool fragmentation errors. If you're running this once a day, that's probably not a major concern. If hundreds of people are executing it thousands of times a day, that is likely a major concern.
An example of the dynamic SQL approach
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos varchar2(100) := '10,20';
3 l_rc sys_refcursor;
4 l_dept_rec dept%rowtype;
5 begin
6 open l_rc for 'select * from dept where deptno in (' || l_deptnos || ')';
7 loop
8 fetch l_rc into l_dept_rec;
9 exit when l_rc%notfound;
10 dbms_output.put_line( l_dept_rec.dname );
11 end loop;
12 close l_rc;
13* end;
SQL> /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
Alternately, you can use a collection. This has the advantage of generating a single, sharable cursor so you don't have to worry about hard parsing or flooding the shared pool. But it probably requires a bit more code. The simplest way to deal with collections
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos tbl_deptnos := tbl_deptnos(10,20);
3 begin
4 for i in (select *
5 from dept
6 where deptno in (select column_value
7 from table(l_deptnos)))
8 loop
9 dbms_output.put_line( i.dname );
10 end loop;
11* end;
SQL> /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
If, on the other hand, you really have to start with a comma-separated list of values, then you will have to parse that string into a collection before you can use it. There are various ways to parse a delimited string-- my personal favorite is to use regular expressions in a hierarchical query but you could certainly write a procedural approach as well
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos tbl_deptnos;
3 l_deptno_str varchar2(100) := '10,20';
4 begin
5 select regexp_substr(l_deptno_str, '[^,]+', 1, LEVEL)
6 bulk collect into l_deptnos
7 from dual
8 connect by level <= length(replace (l_deptno_str, ',', NULL));
9 for i in (select *
10 from dept
11 where deptno in (select column_value
12 from table(l_deptnos)))
13 loop
14 dbms_output.put_line( i.dname );
15 end loop;
16* end;
17 /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
One option is to use INSTR instead of IN:
SELECT uo.object_name
,uo.object_type
FROM user_objects uo
WHERE instr(',TABLE,VIEW,', ',' || uo.object_type || ',') > 0;
Although this looks ugly, it works well and as long as no index on the column being tested was going to be used (because this prevents the use of any index) the performance won't suffer much. If the column being tested is a primary key for instance, definitely this should not be used.
Another option is:
SELECT uo.object_name
,uo.object_type
FROM user_objects uo
WHERE uo.object_type IN
(SELECT regexp_substr('TABLE,VIEW', '[^,]+', 1, LEVEL)
FROM dual
CONNECT BY regexp_substr('TABLE,VIEW', '[^,]+', 1, LEVEL) IS NOT NULL);
In this case, the list of values should be concatenated into a single varchar variable, delimited by commas (or anything you like.)

Selecting Values from Oracle Table Variable / Array?

Following on from my last question (Table Variables in Oracle PL/SQL?)...
Once you have values in an array/table, how do you get them back out again? Preferably using a select statement or something of the like?
Here's what I've got so far:
declare
type array is table of number index by binary_integer;
pidms array;
begin
for i in (
select distinct sgbstdn_pidm
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH'
)
loop
pidms(pidms.count+1) := i.sgbstdn_pidm;
end loop;
select *
from pidms; --ORACLE DOESN'T LIKE THIS BIT!!!
end;
I know I can output them using dbms_output.putline(), but I'm hoping to get a result set like I would from selecting from any other table.
Thanks in advance,
Matt
You might need a GLOBAL TEMPORARY TABLE.
In Oracle these are created once and then when invoked the data is private to your session.
Oracle Documentation Link
Try something like this...
CREATE GLOBAL TEMPORARY TABLE temp_number
( number_column NUMBER( 10, 0 )
)
ON COMMIT DELETE ROWS;
BEGIN
INSERT INTO temp_number
( number_column )
( select distinct sgbstdn_pidm
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH'
);
FOR pidms_rec IN ( SELECT number_column FROM temp_number )
LOOP
-- Do something here
NULL;
END LOOP;
END;
/
In Oracle, the PL/SQL and SQL engines maintain some separation. When you execute a SQL statement within PL/SQL, it is handed off to the SQL engine, which has no knowledge of PL/SQL-specific structures like INDEX BY tables.
So, instead of declaring the type in the PL/SQL block, you need to create an equivalent collection type within the database schema:
CREATE OR REPLACE TYPE array is table of number;
/
Then you can use it as in these two examples within PL/SQL:
SQL> l
1 declare
2 p array := array();
3 begin
4 for i in (select level from dual connect by level < 10) loop
5 p.extend;
6 p(p.count) := i.level;
7 end loop;
8 for x in (select column_value from table(cast(p as array))) loop
9 dbms_output.put_line(x.column_value);
10 end loop;
11* end;
SQL> /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
SQL> l
1 declare
2 p array := array();
3 begin
4 select level bulk collect into p from dual connect by level < 10;
5 for x in (select column_value from table(cast(p as array))) loop
6 dbms_output.put_line(x.column_value);
7 end loop;
8* end;
SQL> /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
Additional example based on comments
Based on your comment on my answer and on the question itself, I think this is how I would implement it. Use a package so the records can be fetched from the actual table once and stored in a private package global; and have a function that returns an open ref cursor.
CREATE OR REPLACE PACKAGE p_cache AS
FUNCTION get_p_cursor RETURN sys_refcursor;
END p_cache;
/
CREATE OR REPLACE PACKAGE BODY p_cache AS
cache_array array;
FUNCTION get_p_cursor RETURN sys_refcursor IS
pCursor sys_refcursor;
BEGIN
OPEN pCursor FOR SELECT * from TABLE(CAST(cache_array AS array));
RETURN pCursor;
END get_p_cursor;
-- Package initialization runs once in each session that references the package
BEGIN
SELECT level BULK COLLECT INTO cache_array FROM dual CONNECT BY LEVEL < 10;
END p_cache;
/
The sql array type is not neccessary. Not if the element type is a primitive one. (Varchar, number, date,...)
Very basic sample:
declare
type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
pidms TPidmList;
begin
select distinct sgbstdn_pidm
bulk collect into pidms
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH';
-- do something with pidms
open :someCursor for
select value(t) pidm
from table(pidms) t;
end;
When you want to reuse it, then it might be interesting to know how that would look like.
If you issue several commands than those could be grouped in a package.
The private package variable trick from above has its downsides.
When you add variables to a package, you give it state and now it doesn't act as a stateless bunch of functions but as some weird sort of singleton object instance instead.
e.g. When you recompile the body, it will raise exceptions in sessions that already used it before. (because the variable values got invalided)
However, you could declare the type in a package (or globally in sql), and use it as a paramter in methods that should use it.
create package Abc as
type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
function CreateList(majorCode in Varchar,
program in Varchar) return TPidmList;
function Test1(list in TPidmList) return PLS_Integer;
-- "in" to make it immutable so that PL/SQL can pass a pointer instead of a copy
procedure Test2(list in TPidmList);
end;
create package body Abc as
function CreateList(majorCode in Varchar,
program in Varchar) return TPidmList is
result TPidmList;
begin
select distinct sgbstdn_pidm
bulk collect into result
from sgbstdn
where sgbstdn_majr_code_1 = majorCode
and sgbstdn_program_1 = program;
return result;
end;
function Test1(list in TPidmList) return PLS_Integer is
result PLS_Integer := 0;
begin
if list is null or list.Count = 0 then
return result;
end if;
for i in list.First .. list.Last loop
if ... then
result := result + list(i);
end if;
end loop;
end;
procedure Test2(list in TPidmList) as
begin
...
end;
return result;
end;
How to call it:
declare
pidms constant Abc.TPidmList := Abc.CreateList('HS04', 'HSCOMPH');
xyz PLS_Integer;
begin
Abc.Test2(pidms);
xyz := Abc.Test1(pidms);
...
open :someCursor for
select value(t) as Pidm,
xyz as SomeValue
from table(pidms) t;
end;

Resources