Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Jim.

Asked: November 13, 2001 - 2:08 pm UTC

Last updated: January 06, 2007 - 8:29 am UTC

Version: 8i

Viewed 10K+ times! This question is

You Asked

Admitted newby to plsql and this may be a classic example of where I should use the select instead of the explicit cursor. Read the thread on that discussion but still don't understand the problem I see in my logic. Here is close to what it looks like:

SELECT A.EID FROM GEO_STREET_NAMES A
2 WHERE A.EID = 51;

EID
----------
51

SQL> declare
2 STR_ID NUMBER(9);
3 CURSOR ACursor IS
4 SELECT A.EID FROM GEO_STREET_NAMES A
5 WHERE A.EID = 51;
6 BEGIN
7 OPEN ACursor;
8 LOOP
9 FETCH ACursor INTO str_id;
10 DBMS_OUTPUT.PUT_LINE('STREET # :' || STR_ID);
11 EXIT WHEN ACursor%NOTFOUND;
12 DBMS_OUTPUT.PUT_LINE('LAST STREET # :' || STR_ID);
13 END LOOP;
14 END;
15 /
STREET # :51
LAST STREET # :51
STREET # :51

PL/SQL procedure successfully completed.

SQL>


As you can see from the first select, there is only one row. Yet from the displays, it appears to loop twice. This is why I am experiencing a perplexing end-of-file problem. I need/use several variables from the fetch so I was told I couldn't use the implied cursor. I apologize if this is too basic a question but would appreciate the assistance.

and Tom said...

It does loop two times.

Once to fetch the record.
The second one hits the no data found.

You have to find no data in order to get no data found!

You always will code:

open c;
loop
fetch c into ...;
EXIT WHEN C%NOTFOUND;

process fetched record
end;
close c;


If you want to code performant, easy to read code that will run faster and be easier to maintain then the above, you will simply code:


begin
for x in ( select a.eid from geo_street_names a where a.eid = 51 )
loop
dbms_output.put_line( .... );
end loop;
end;
/

thats it -- no declares, no cursors, no open, no close - just you and your code, short, sweet, simple, fast.



Rating

  (15 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Jim Powell, November 13, 2001 - 2:41 pm UTC


Question on explicit vs. implicit

Chaim Moshe Tessler, November 14, 2001 - 9:41 am UTC

Based on your answer would there be an example
when you would recommend an explicit cursor
and not implicit cursor ?
(When for example to implicit cursor(s) close ?)

Tom Kyte
November 14, 2001 - 11:11 am UTC

I use explicit cursors ONLY when I *have* to.

That is -- when using a REF CURSOR.

Otherwise I never ever use them. Takes more keystrokes, runs slower. Don't see the benefit in them.


Less key storess , Yes , but ....

Nag, November 14, 2001 - 12:04 pm UTC

Tom

From your indepth discussions on explicit and implicit cursors, I infer that

When we explicitly open and close a cursor then it is explicit.

When the cursor is opened and closed implicitly then it is an implicit cursor.

I accept that implicit cursors lead to less key strokes. But the amount of time taken to open and close a cursor should be very little. Is that such a huge benefit, that you always ( and very adamantly) write off explicit cursors.


Tom Kyte
November 14, 2001 - 1:10 pm UTC

Well, why don't we try it and see?  

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3  
  4      cursor c1 is select * from t;
  5      n      number;
  6  begin
  7      l_start := dbms_utility.get_time;
  8      for i in 1 .. 1000
  9      loop
 10          open c1;
 11          loop
 12              fetch c1 into n;
 13              exit when c1%notfound;
 14          end loop;
 15          close c1;
 16  
 17          -- now lets simulate a SELECT into as well
 18  
 19          open c1;
 20          fetch c1 into n;
 21          if ( c1%notfound ) then raise NO_DATA_FOUND; end if;
 22          fetch c1 into n;
 23          if ( c1%found ) then raise TOO_MANY_ROWS; end if;
 24          close c1;
 25  
 26      end loop;
 27      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 28  
 29      l_start := dbms_utility.get_time;
 30      for i in 1 .. 1000
 31      loop
 32          for x in ( select * from t )
 33          loop
 34              null;
 35          end loop;
 36  
 37          select * into n from t;
 38      end loop;
 39      commit;
 40      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 41  end;
 42  /
54 hsecs
42 hsecs

PL/SQL procedure successfully completed.


Ok, so what we have is:

o it takes more code to write explicit cursors
o it is more error prone, you must remember to open them, close them
o you have to declare all of the variables all of the time (nothing 
  like a cursor for loop for ease of programming if you ask me)
o they are measurably slower


Well, I haven't found anything good to say about them.  I'm a polarized sort of guy -- far too many people believe the total opposite, that EXPLICIT cursors are the only way to program.  I'm on a crusade to put that myth to rest, along with the myths that

o more then a single digit number of extents is bad (false)

o the most discriminating fields should be on the leading edge of an index (depends)

o you don't need to understand the database, its just a black box (totally false)

o explicit cursors are faster then implicit (provably false)

and so on.
I figure people are going to pick one or the other here -- implicit or explicit.  They should pick implicit, period, plain and simple.

 

Implicit Vs. Explicit cursors.

Scott Watson, November 14, 2001 - 4:07 pm UTC

Very interesting Tom, but I needed to prove this to myself and therefore called on dbms_profiler to see what it had to say.

Explicit
47.853066 cursor c1 is select * from t;
n number;
begin
1.1920017 for i in 1 .. 1000
loop
.48333280 open c1;
loop
84.425063 fetch c1 into n;
1.3781577 exit when c1%notfound;
end loop;
1.1353816 close c1;
-- now lets simulate a SELECT into as well
.52310376 open c1;
37.182641 fetch c1 into n;
.47884312 if ( c1%notfound ) then raise NO_DATA_FOUND; end if;
27.758576 fetch c1 into n;
.72809931 if ( c1%found ) then raise TOO_MANY_ROWS; end if;
1.1030756 close c1;


Implicit

78.293107 for x in ( select * from t )
loop
1.4356460 null;
end loop;

62.485708 select * into n from t;

Yup, implicit cursors are faster, however, if we remove the time to declare the cursor the times are pretty close. I wonder why this overhead exists on the cursor c1 definition but not on the for loop? Any insights?

Thanks for the information.



Tom Kyte
November 14, 2001 - 4:59 pm UTC

Well, the cursor declare is not really "executed" -- its the act of explicitly opening, closing and fetching that adds time indirectly to it.

The cursor definition takes really 0 seconds -- nothing (its just a definition after all). Its the code that opens/closes and touches it that indirectly appear to add time to that line.

Note that you cannot remove the time to "define" the cursor as then you wouldn't have an explicit cursor test!

Implicit always better ??

jeroen, November 15, 2001 - 3:43 am UTC

Hi Tom,

About explicit cursors, you say it is just "you and your code".
Suppose that "in the real world" you are working with a team of developers, where some are better than others in optimizing SQL.
Than you could have one package containing all the explicit cursors (whose functionality is strictly defined and cannot change on account of a single programmers needs), and any number of procedures using them.
I think that would be wonderful for your parse ratios, and you would also be saving a lot of duplicate work, and making the code very maintainable in case of changes in table structures.
It is obvious this scenario is dependent on good coordination of team efforts, but I would say there are benefits to this approach.
Would you agree about this, or is this also a myth ?

Kind regards,

Jeroen


Tom Kyte
November 15, 2001 - 6:44 am UTC

Great -- you can still use them implicitly (some people might call those 'views' might they?)

scott@ORA817DEV.US.ORACLE.COM> create or replace package tuned_queries
2 as
3 cursor c1 (p_ename in varchar2 ) is
4 select ename, empno from emp where ename like p_ename;
5 end;
6 /

Package created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> create or replace procedure p
2 as
3 begin
4 for x in tuned_queries.c1( '%A%' )
5 loop
6 dbms_output.put_line( x.ename || ',' || x.empno );
7 end loop;
8 end;
9 /

Procedure created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> exec p
A,7369
ADAMS,7876
ALLEN,7499
BLAKE,7698
CLARK,7782
JAMES,7900
MARTIN,7654
WARD,7521

PL/SQL procedure successfully completed.

Not nearly as readable -- since the query isn't there (must cross reference) and you do lose the performance edge (they are more or less the same now), but the coding ease is still there.

Perhaps I would consider getting some views in place or training people on how to write SQL better. I find most people, when given the tools (eg: access to tkprof and a lesson on how to use the results) are more then willing to make their sql go faster...


So you have the option of

o using packaged cursors
o using views

short of training...

(this is not a myth, however I think it can be greatly impacted by giving people access to the tools they need. I would say probably 80% or more of sites have NEVER HEARD of tkprof (lack of training), and of those that have heard of it the DBA's have draconian rules the prohibit access to the tool!! (amazes me each and every time))



control no data found

A reader, March 11, 2005 - 4:48 am UTC

Hi

from your code

open c;
loop
fetch c into ...;
EXIT WHEN C%NOTFOUND;

process fetched record
end;
close c;


It works fine but what if the cursor returns no rows? How do you distinguish between no rows and last fetch (no_data_found)? For example we probably need to write to a log something like

table X has no data to be processed (when cursor returns 0 rows), if we use the code above we will always have that message in our log files.

Tom Kyte
March 11, 2005 - 6:19 am UTC

what is the difference between

a) no rows
b) last fetch

for a cursor like that will NEVER throw no_data_found, only a select into does that.

why is a cursor that returns 0 rows "an error", to me -- no way, it is just a result set of zero rows, no problem.

to distinguish

A reader, March 11, 2005 - 6:45 am UTC

Hi

I dont mean a cursor that returns 0 rows means "an error". What I am trying to do is to distinguish in the logfile/process that if there were no data to be processed.

I guess I will add a variable to count the number of loops, if it´s first loop and I get a no_Data_found then that means there are not data to be processed otherwise it´s the last fetch that caused no data found

Cheers

Tom Kyte
March 11, 2005 - 7:24 am UTC

c%rowcount at the bottom.

you will NOT get a no_data_found from an explicit cursor, that only happens with select into.

by the way this is what i mean

A reader, March 11, 2005 - 6:49 am UTC

declare
cursor cEmp is select * from emp;
recEmp emp%rowtype;
begin
open cEmp;
loop
fetch cEmp into recEmp;
if (cEmp%NOTFOUND) then
dbms_output.put_line('NO_DATA');
end if;
exit when cEmp%NOTFOUND;
dbms_output.put_line(recEmp.ename);
end loop;
end;
/

ALLEN
WARD1
MARTIN
BLAKE
CLARK
TURNER
NO_DATA

here for example NO_DATA should not appear but since it´s the last fetch that causes no data found I can see that message. What it should does is print

NO_DATA when the cursor return no rows

and

ALLEN
WARD1
MARTIN
BLAKE
CLARK
TURNER

when there are rows

thx!



Tom Kyte
March 11, 2005 - 7:28 am UTC

ops$tkyte@ORA9IR2> declare
  2          cursor c is select * from dual;
  3          l_dummy dual.dummy%type;
  4  begin
  5          open c;
  6          loop
  7                  fetch c into l_dummy;
  8                  exit when c%notfound;
  9          end loop;
 10          dbms_output.put_line( c%rowcount || ' rows processed' );
 11          close c;
 12  end;
 13  /
1 rows processed

PL/SQL procedure successfully completed.


use c%rowcount after the loop to see how many rows processed.  If zero, do whatever. 

Cursor For loop

Anne, October 18, 2005 - 11:26 am UTC

Hi Tom, is there a way to determine if there are no rows in the following cursor without explicitly declaring cursor and fetching?

begin
for x in ( select a.eid from geo_street_names a where a.eid = 51 )
loop
......
end loop;
end;
/


Tom Kyte
October 18, 2005 - 2:18 pm UTC

begin
l_found := false;
for x in ( select ... )
loop
l_found := true;
....


would work of course..

Prior to 10G

Quadro, October 18, 2005 - 10:03 pm UTC

In Oracle releases prior to 10G explicit cursors will do one fetch at a time, so

for cur in (select * from 100_rows_table)
loop
null;
end loop;

will results in 100 LIO's. With explicit cursors you can "bulk collect" them.

So, i disagree with "use explicit cursor whenever you can do that".

Tom Kyte
October 19, 2005 - 6:55 am UTC

fine, so... you would use method 2 or 3 - or would you use.... well, a static cursor with bulk collect?

explicit means implicit

Quadro, October 18, 2005 - 10:04 pm UTC

Sorry, for my previous post explicit should be read as "implicit" :-)

BULK COLLECT

Martin, October 19, 2005 - 3:08 am UTC

There's no argument there either, since you can bulk collect an implicit cursor :

SQL> DECLARE
  2    TYPE x IS TABLE OF VARCHAR2(10);
  3    tab x;
  4  BEGIN
  5    SELECT 'x'
  6      BULK COLLECT INTO tab
  7      FROM all_objects
  8     WHERE rownum <= 100;
  9  END;
 10  /

PL/SQL procedure successfully completed.

The only possible reason would be when using the LIMIT clause, since I don't think there's syntax for doing that when dealing with implicit cursors?

Regards 

2 Martin

Quadro, October 19, 2005 - 4:38 am UTC

Processing million rows one-by-one doesn't make sense.
Bulk collecting million rows without limit doesn't make sense too.

So, yes - it's all about bulk fecthing with limit.

slow-by-slow

Martin, October 19, 2005 - 5:18 am UTC

"
Processing million rows one-by-one doesn't make sense.
Bulk collecting million rows without limit doesn't make sense too.
"

You wouldn't believe it with some of the code that i've seen. ;-)

how to convert %notfound to mysql procedures

godha, January 05, 2007 - 11:19 am UTC

SQL> declare
2 STR_ID NUMBER(9);
3 CURSOR ACursor IS
4 SELECT A.EID FROM GEO_STREET_NAMES A
5 WHERE A.EID = 51;
6 BEGIN
7 OPEN ACursor;
8 LOOP
9 FETCH ACursor INTO str_id;
10 DBMS_OUTPUT.PUT_LINE('STREET # :' || STR_ID);
11 EXIT WHEN ACursor%NOTFOUND;
12 DBMS_OUTPUT.PUT_LINE('LAST STREET # :' || STR_ID);
13 END LOOP;
14 END;
15 /

Tom Kyte
January 06, 2007 - 8:29 am UTC

step a) find a mysql forum

step b) explain to them in detail what this code does (when you do that, you'll have to explain that line 10 is actually a "bug", line 11 should be line 10 and line 10 should be line 11 else the code is buggy)

step c) ask them how to do that same thing in mysql


More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library