Difference between bulk collect and cursor - oracle

I wonder what is the difference between using a bulk collect and then loop on the result and using a cursor. And subquestion : Are there cases where the only option is a cursor?
Thanks

Oracle implicitly optimizes PL/SQL for-cursor-loop to use fetches by 100 rows, so it's similar to bulk collect limit 100.
Simple example:
SQL> begin
2 for r in (select/*+findme*/ level n from dual connect by level<=100) loop
3 exit;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
SQL> select fetches, rows_processed,sql_text
2 from v$sql
3 where lower(sql_text) like 'select/*+findme*/%';
FETCHES ROWS_PROCESSED SQL_TEXT
---------- -------------- ----------------------------------------------------------
1 100 SELECT/*+findme*/ LEVEL N FROM DUAL CONNECT BY LEVEL<=100
1 row selected.
NB: Such optimisation works only if plsql_optmize_level parameter>=2 (default=2). In case of plsql_optmize_level < 2, for-loop fetches by 1 row. You can try to change it and check the difference.
PS. + You can't use a cursor for-loop with dynamic sql

Related

Oracle Cursor, No Data Found exception

I'm trying to modify a procedure that removes a user from the system, adding a bit that generates a list of any areas over which they are the only admin. I have read that cursors can work when 0 results are found, so I am assuming that my query is the problem. Tables 1 (A) and 2 (B&C) are joined by the primary key of table A. Subquery B is a list of all the areas a user is admin over, and Subquery C is a list of all areas with only one admin. In the specific instance I'm testing, there is no matching area_id between subqueries B and C. Is there a way I can modify this query to work properly? I tried moving the opening of the cursor behind an if statement, but still receive the error, so I'm assuming it's the declaration of the cursor that is resulting in the exception being thrown.
CURSOR AA_CUR IS
SELECT AREA_NAME
FROM FILE_TRANSFER.AREA_LU A,
(SELECT AREA_ID
FROM FILE_TRANSFER.USER_AREA_ACCESS
WHERE AREA_ADMIN='Y'
AND USERNAME IN (SELECT USERNAME
FROM FILE_TRANSFER.USER_INFORMATION
WHERE USER_INFORMATION_ID = v_UIID)) B,
(SELECT AREA_ID, AREA_ADMIN, COUNT(*)
FROM FILE_TRANSFER.USER_AREA_ACCESS
WHERE AREA_ADMIN='Y'
HAVING COUNT(*) = 1
GROUP BY AREA_ID, AREA_ADMIN) C
WHERE A.AREA_ID = B.AREA_ID
AND B.AREA_ID = C.AREA_ID;
Your assumption is wrong.
Cursor's SELECT can't return NO_DATA_FOUND. If it returns nothing, it is silently ignored, nothing happens.
SQL> declare
2 cursor c1 is
3 select 'x' from dual
4 where 1 = 2; -- this query returns "nothing"
5 c1r c1%rowtype;
6 begin
7 open c1;
8 fetch c1 into c1r;
9 close c1;
10 end;
11 /
PL/SQL procedure successfully completed.
See? Nothing happened. Just to show that query will raise NO_DATA_FOUND (if ran separately):
SQL> declare
2 l_val varchar2(1);
3 begin
4 select 'x' into l_val from dual
5 where 1 = 2; -- this will raise NO_DATA_FOUND
6 end;
7 /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 4
SQL>
Therefore, there must be some other SELECT statement that is raising that error. Which one? Can't tell as you didn't post much of your code and - even if you did - without your tables and data it would be difficult to guess.
So, what to do? Debug your code. A simple option is to put DBMS_OUTPUT.PUT_LINE calls between every two statements so that you'd see which part of code actually failed - then you'll be able to try to fix it.

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.

Find out if a collection was populated by bulk collect

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.

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.)

simple way to return 2 or more rows using a SELECT in pl/sql

I am trying to figure out the most simple way to return 2 or more rows using a select statement in pl/sql to the sqlplus output. Here is a basic example.
begin
select sysdate,'12345' xid from dual
union all
select sysdate,'67890' xid from dual;
end;
/
Error is "an INTO clause is expected in this SELECT statement"
You would use a cursor to return the result of a query:
SQL> VARIABLE x REFCURSOR
SQL> BEGIN
2 OPEN :x FOR SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 3;
3 END;
4 /
PL/SQL procedure successfully completed.
SQL> PRINT x
ROWNUM
----------
1
2
3
Yes, you can return a ResultSet from a PL/SQL. Take a look at this URL.
When writing a SQL code in a BEGIN-END block, you cannot display its result-set in the editor. Trim the BEGIN-END from your query, and it would happily display the results.
Basically, code within a BEGIN-END block is not used for displaying results, it is used for data processing instead. So you should simply write:
SELECT SYSDATE,'12345' XID FROM DUAL
UNION ALL
SELECT SYSDATE,'67890' FROM DUAL;
to display the result-set in your SQL editor.
Also notice that I removed alias "XID" in second SELECT statement: you need to give column aliases in only first query in a UNION/UNION ALL statement.

Resources