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 ?)
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.
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.
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
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.
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
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!
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;
/
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".
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 /
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