Tải bản đầy đủ (.pdf) (44 trang)

Oracle PL/SQL for dummies phần 4 pot

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (866.42 KB, 44 trang )

Adding User-Defined Exceptions
In addition to predefined exceptions, which were discussed in the preceding
section, you can add user-defined exceptions. The exception usually corre-
sponds to the breaking of some rule and works as a red flag to notify you
about the infraction. With user-defined exceptions, you can use PL/SQL to
clearly identify exception conditions in your business logic.
Before raising a user-defined exception, you must first declare the exception
in the declaration section of the program. The syntax is
<exception_name> exception;
When you raise the exception, you do it by using the RAISE command. The
syntax is:
raise <exception_name>;
Handle your exception just as if it were a named predefined exception. The
syntax is:
when <exception_name> then
For example, a business might have a rule that “A salary increase may not
exceed 300 percent.” If someone tries to implement an increase larger than
300 percent, the whole application module should be halted for a security
investigation. Of course, you could use IF THEN logic to support this
requirement, but the code is clearer when using an exception handler, as
shown in Listing 5-6.
Listing 5-6: A User-Defined Exception
function f_ValidateSalary
(i_empNo_nr NUMBER, i_new_Sal_nr NUMBER)
return VARCHAR2
is
v_current_Sal_nr NUMBER;
e_increaseTooLarge exception;

6
begin


select sal into v_current_Sal_nr
from emp
where empNo=i_empNo_nr;
if (i_newSal_nr/v_current_Sal_nr)*100>300
then
raise e_increaseTooLarge;

13
end if;
maybe lots of other tests here
114
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 114
return ‘Y’;

18
exception
when e_increaseTooLarge then

20
insert into t_LogError
return ‘N’;

22
end;
The following list explains some of the lines in Listing 5-6:

5 The exception declaration.

13 The salary is too large, so the exception is raised. If the exception

is raised, the program jumps to the exception handler.

18 If no exceptions are raised, the function returns ‘Y’ (salary modi-
fication is valid).

20 Detects the e_increaseTooLarge exception after the exception
has been raised.

22 Because an exception was raised, the function returns ‘N’ (salary
modification is invalid).
Assigning a code to a user-defined
exception
User-defined exceptions don’t have associated codes. (See “Understanding
Different Exception Types” earlier in this chapter for an introduction to
codes.) Therefore SQLCODE will return NULL if a user-defined exception is
raised. However, there is a way to associate user-defined exceptions with a
specific code number, using a pragma exception_init statement.
For consistency, and to keep your exceptions organized, it is helpful to assign
a code to each user-defined exception. You can insert this code into your log
table, as shown in Listing 5-7.
Listing 5-7: Code Assigned to a User-Defined Exception
procedure p_validateSalary
(i_empNo_nr NUMBER, i_new_sal_nr NUMBER)
is
v_current_sal NUMBER;
v_error_nr NUMBER;
e_increaseTooLarge exception;
pragma exception_init(e_increaseTooLarge,-20999);

6

begin

(continued)
115
Chapter 5: Handling Exceptions
10_599577 ch05.qxp 5/1/06 12:12 PM Page 115
Listing 5-7
(continued)
exception
when increase_too_much then
v_error_nr := sqlcode;
insert into t_LogError (error_tx)
values(i_empNo_nr||’:’||v_error_nr);
raise;
end;

6 This line associates the previously defined exception with a
number: -20999.
The EXCEPTION_INIT statement is placed in the declaration section of the
block. It is a good practice to always place the EXCEPTION_INIT right next
to the exception declaration.
Also, when assigning a code to a user-defined exception, choose a code
between –20999 and –20000 only. Codes in this range distinguish user-defined
exceptions from predefined exceptions. Oracle has promised that it will never
use the numbers between –20999 and –20000 for any Oracle exceptions, so
you can safely use them for your applications. Although you could conceiv-
ably use any other number, we don’t recommend doing so, just in case Oracle
decides to use that number in the future.
You can still run into trouble by using these numbers for your exceptions if
you’re writing an extension to packaged software. The packaged software

vendor might have already used some of those exceptions. You have to be
very careful if you’re using packaged software to avoid using the same num-
bers that the software uses.
If a user-defined exception is raised and not handled, Oracle will return the
error code you have assigned. If no code number was assigned to the user-
defined exception and that exception was not handled, Oracle uses the
exception ORA-06510 (PL/SQL: unhandled user-defined exception) to notify
the program about the error.
Including error messages in
user-defined exceptions
As mentioned earlier in this chapter, Oracle usually not only provides an
error code or name, but also an explanation of what happened. That explana-
tion is called an error message.
In your user-defined exceptions, you can specify error messages. The only
limitation is that you can only specify error messages for exceptions that
116
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 116
have already been assigned a code. Using the example of not allowing any
salary increase of over 300 percent, you want to add a user-friendly error
message to the user-defined exception, as shown in Listing 5-8.
Listing 5-8: Assigning an Error Message for a User-Defined Exception
procedure p_validateSalary
(i_empNo_nr NUMBER, i_new_sal_tx NUMBER)
is
v_current_sal NUMBER;
e_increaseTooLarge EXCEPTION;

5
pragma exception_init (e_increaseTooLarge,-20999)


6
begin
select salary into v_current_sal
from emp
where empNo=i_empNo_nr;
if (i_newsal_nr/v_current_sal)*100>300
then
raise_application_error (-20999, ‘Cannot triple

14
salary for employee #’||i_empNo);
end if;
< some validation >
exception
when e_increaseTooLarge then
insert into t_logError
raise;
end;
Here are explanations for the called-out lines in the code:

5 The exception is declared.

6 The exception is associated with a numbered code.

14 The built-in procedure RAISE_APPLICATION_ERROR is used
instead of RAISE, because it allows passing not just the exception
itself, but the whole error message. The syntax of that procedure
is very simple, as shown here:
raise_application_error

(<exception code>,<error message>);
This procedure can be extremely helpful, especially for user-defined excep-
tions because now you can explain the problem in greater detail.
The error message must be specified each time the exception is raised. It isn’t
attached directly to the user-defined exception. If that same exception is raised
again by using the RAISE command (rather than RAISE_APPLICATION_
ERROR), SQLERRM will return NULL.
117
Chapter 5: Handling Exceptions
10_599577 ch05.qxp 5/1/06 12:12 PM Page 117
Propagation of Exceptions
The preceding sections give you enough knowledge to work with exceptions
in real life. In complex programs, some procedures within packages might call
functions in different packages that, in turn, call other functions, and so on. It
is important to understand how exceptions propagate between calling pro-
gram units. If an exception is raised in a function called by a procedure, how
does it affect the calling procedure?
How you handle (or choose not to handle) an exception can cause odd
behavior in your program if you don’t understand how exceptions propagate.
If an error occurs in some function being called by your program, your pro-
gram might have to handle that exception.
For example, when loading large amounts of data into a data warehouse,
there are typically very complex rules about how to handle different kinds of
errors. Simple errors (like a missing State code value) are perhaps passed
through and logged for later manual cleanup. Other errors (like an invalid
State code) might cause a referential integrity failure so the record is not
loaded at all. If too many errors exist in a small number of records, this might
indicate that the file being loaded is corrupted and processing should stop. In
each case, the exception is being raised in one program unit and probably
being assessed in an entirely different program unit.

Seeing propagation of exceptions in action
Trying to use a real-world data-migration code example would be a little hard
to follow, so, we have made a simple (though less realistic) example to illus-
trate the principles.
Assume that you have two program units, f_makeAddress_tx and
p_validateZip. The function f_makeAddress_tx takes several text
strings (address, city, state, and zip) and groups them into a single string.
The procedure p_validateZip makes sure that the ZIP code is valid. The
function f_makeAddress_tx calls p_validateZip, as shown in Listing 5-9.
Listing 5-9: Propagating Exceptions between Program Units
create or replace function f_makeAddress_tx (
i_address_tx VARCHAR2,
i_city_tx VARCHAR2,
i_state_tx VARCHAR2,
i_zip_tx VARCHAR2)
return VARCHAR2
is
118
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 118
e_badZip EXCEPTION;

8
pragma EXCEPTION_init(e_badZip,-20998);

9
v_out_tx VARCHAR2(256);
begin
p_validateZip (i_zip_tx);


12
v_out_tx:= i_address_tx||’, ‘||

13
i_city_tx ||’, ‘||
i_state_tx ||’, ‘||
i_zip_tx;
return v_out_tx;

17
exception
when e_badZip then

19
return i_zip_tx || ‘: Invalid zip code.’;
end;
/
create or replace
procedure p_validateZip (i_zipCode_tx VARCHAR2)
is
e_tooShort EXCEPTION;

26
e_tooLong EXCEPTION;

27
e_badZip EXCEPTION;

28
pragma exception_init(e_badZip,-20998);


29
v_tempZip_nr NUMBER;
Begin
if length(i_zipCode_tx)< 5 then
Raise e_tooShort;

33
elsif length(i_zipCode_tx)> 6 then
Raise e_tooLong;

35
end if;
v_tempZip_nr := to_number(i_zipCode_tx);

38
exception
when e_tooLong then

41
insert into t_LogError (error_tx)
values(‘long zip’);
raise e_badZip;
when e_tooShort then

45
insert into t_logError (error_tx)
values(‘short zip’);
raise e_badZip SHOULD be here
when value_error then


48
insert into t_LogError (error_tx)
values(‘non-numeric zip’);
raise; re-raising the same exception
end;
The following list explains particular lines from Listing 5-9:

8 The e_badZip exception is never raised in the function
f_makeAddress_tx. It will be passed from the procedure
p_validateZip.
119
Chapter 5: Handling Exceptions
10_599577 ch05.qxp 5/1/06 12:12 PM Page 119

9 The e_badZip exception should be associated with the code
throughout all routines using it; otherwise, there is no way to
indicate that it is exactly the same exception.

12 Here is where the program calls p_validateZip.

13–17 This is the standard return statement. It will be skipped if an
exception is raised by p_validateZip.

19 In the exception handler, if the e_badZip exception is raised by
p_validateZip, the error string address is returned.

26-28 Various exceptions are declared within p_validateZip.

29 This line associates e_badZip exception with its code.


33, 35 These lines raise exceptions in response to the rule violations.

38 This line raises a predefined VALUE_ERROR exception if there are
any non-numeric characters in i_ZipCode_tx.

41 Logs the error and raises an e_badZip exception that will prop-
agate back to the calling routine.

45 Logs the error but forgets to raise e_badZip. If this exception is
raised, the calling program will never know about it.

48 Intercepts a predefined exception and re-raises the same excep-
tion after logging the problem.
It is helpful to examine how this program behaves with various inputs. The
following scenarios do just that.
Scenario 1: No rule violations
SQL> declare
2 v_out_tx VARCHAR2(2000);
3 begin
4 v_out_tx:=f_makeAddress_tx(‘123 Main Str’,
5 ‘Redwood City’,’California’,’94061’);
6 DBMS_OUTPUT.put_line(v_out_tx);
7 end;
8 /
123 Main Str, Redwood City, California, 94061

9
PL/SQL procedure successfully completed.


9 The function returned the full address string as expected.
No exceptions are raised. Everything follows the normal execution path.
120
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 120
Scenario 2: Short ZIP code
SQL> declare
2 v_out_tx VARCHAR2(2000);
3 begin
4 v_out_tx:=f_makeAddress_tx(‘123 Main Str’,
5’Redwood City’, ‘California’,’940’);
6 DBMS_OUTPUT.put_line(v_out_tx);
7 end;
8 /
123 Main Str, Redwood City, California, 940

9
PL/SQL procedure successfully completed.
SQL>

9 The function returned the full address even though the ZIP code is
invalid.
The exception e_tooShort is raised in the p_validateZip procedure.
However, in the exception handler for e_tooShort, you are just adding a
record in the log without raising any other exception (e_badZip is com-
mented out). Therefore, f_MakeAddress_tx treats the ZIP code as valid.
Scenario 3: Non-numeric ZIP code
SQL> declare
2 v_out_tx VARCHAR2(2000);
3 begin

4 v_out_tx:=f_makeAddress_tx(‘123 Main Str’,
5 ‘Redwood City’ , ‘California’,’9406A’);
6 DBMS_OUTPUT.put_line(v_out_tx);
7 end;
8 /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error:

12
character to number conversion error

13
ORA-06512: at “SCOTT.P_VALIDATEZIP”, line 36

14
ORA-06512: at “SCOTT.F_MAKEADDRES”, line 11

15
ORA-06512: at line 12

16
SQL>
The predefined exception value_error is raised in p_validateZip, which
in turn raises itself after logging an error. The error is propagated back to f_
makeAddress_tx. But there is no exception handler for the value_error
exception in f_makeAddress_tx. In this case, execution is halted.
121
Chapter 5: Handling Exceptions

10_599577 ch05.qxp 5/1/06 12:12 PM Page 121
What shows in the SQL*Plus window (lines 12–16) is an error stack. Oracle
remembers the whole chain of exception calls. This means that if any excep-
tion is raised, you can see all the exceptions raised and the program units
where they were raised.
The stack tells a story that is easily read:

12–14 On line 38 (v_tempZip_nr := to_number(i_zipCode_tx);)
of p_validateZip, a numeric or value error was encountered.
(Oracle uses only uppercase for code elements in exceptions,
that’s why you see P_VALIDATEZIP.)

15–16 Either that exception was not handled or it was re-raised in
the exception handler. In this case, it was re-raised on line 12
(p_validateZip (i_zip_tx);) of f_makeAddress_tx.
Scenario 4: Long ZIP code
SQL> declare
2 v_out_tx VARCHAR2(2000);
3 begin
4 v_out_tx:=f_makeAddress_tx(‘123 Main Str’,
5 ‘Redwood City’,’California’,’940612345’);
6 DBMS_OUTPUT.put_line(v_out_tx);
7 end;
8 /
940612345: Invalid zip code.

9
PL/SQL procedure successfully completed.

9 The function f_makeAddress_tx returned the invalid message

showing that the e_badZip exception was raised in f_make
Address_tx.
In Scenario 3, you see that exceptions are shown in the error stack in the
reverse order of calls. This means that exceptions are handled from the lowest
to the highest level. The exception e_tooLong was raised in p_validate
Zip, which in turn raised e_badZip, which is propagated back to f_make
Address_tx.
Because the exception e_badZip in both program units is associated with
the same code (–20998), the exception handler of the parent routine is able to
detect that e_badZip refers to the same exception in both cases.
Handling exceptions without
halting the program
At times you want to immediately detect and handle the exception and then
continue in your code. You might not want to stop execution of your program
122
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 122
unit. Of course, you can always make the “exception-risky” part of code into
its own program unit to isolate the exception, but sometimes it is convenient
just to make the area of the program an anonymous PL/SQL block (as we dis-
cuss in Chapter 3) and handle the exception right in that block.
Assume you are validating a ZIP code as part of a much larger routine. You
want to detect that there was a bad ZIP code and log the problem but you
don’t want to stop the whole execution of the program. Listing 5-10 is a
rewrite of Listing 5-3 crafted to use this technique.
Listing 5-10: Raising an Exception Local PL/SQL Block
function f_get_speed_nr
(i_distance_nr NUMBER, i_timeSec_nr NUMBER)
return NUMBER
is

v_out_nr NUMBER;
begin

6
could be lots of code here
begin

9
v_out_nr:= i_distance_nr/i_timeSec_nr;
exception
when zero_divide then
insert into t_logError (error_tx)
values (‘Divide by zero in the F_GET_SPEED_NR’);
end;

15
could be lots of more code here
return v_out_nr;

18
end;
The following list gives more details about Listing 5-10:

6 This is the beginning of the main routine. There can be any
amount of code here prior to the anonymous PL/SQL block.

9–15 This is the anonymous PL/SQL block with its own exception
handler.

18 This is the RETURN statement. Notice how you do not need a

RETURN in the anonymous PL/SQL block. After the exception is
handled, processing continues after the block and the RETURN will
be encountered as long as the exception raised in the anonymous
PL/SQL block is handled within its exception handler. If any excep-
tions other than ZERO_DIVIDE were raised in the anonymous
PL/SQL block, the main routine would detect it and the RETURN
statement would not be executed.
123
Chapter 5: Handling Exceptions
10_599577 ch05.qxp 5/1/06 12:12 PM Page 123
Avoiding exceptions raised in declaration
part and exception handler
Exceptions can be handled only if they’re raised in the body of the program
unit. Exceptions raised in the declaration section or in the exception handler
cannot be handled within the same program unit. You should avoid placing
any code declaration section or exception handler that can raise an excep-
tion anywhere other than in the body of your program where it can be explic-
itly handled.
We discuss exceptions raised in the declaration part first. Assume that you
decided to simplify your code by moving the assignment of the variable
v_tempZip_nr in the procedure p_validateZip from the body to the dec-
laration, as shown in Listing 5-11. This means that you might raise an excep-
tion in the declaration section of the program.
Listing 5-11: Raising an Exception in the Declaration Section
procedure p_validatezip (i_zipCode_tx VARCHAR2)
is
e_tooShort EXCEPTION;
e_tooLong EXCEPTION;
e_badZip EXCEPTION;
pragma exception_init(e_badZip,-20998);

v_tempZip_nr number:=to_number(i_zipCode_tx);

7
begin
if length(i_zipCode_TX)< 5 then
raise e_tooShort;
elsif length(i_zipCode_TX)> 6 then
raise e_tooLong;
end if;
exception
when e_tooLong then
insert into t_LogError (error_tx)
values(‘long zip’);
raise e_badZip;
when e_tooShort then
insert into t_logError (error_tx)
values(‘short zip’);
raise e_badZip SHOULD be here
when VALUE_ERROR then
insert into t_logError (error_tx)
values(‘non-numeric zip’);
raise e_badZip;
end;

7 This line of code moved the assignment statement from the
body of the program to the declaration section and a variable
initialization.
124
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 124

Note that the exceptions raised in the declaration section are not handled by
the exception handler.
SQL> declare
2 v_out_tx VARCHAR2(2000);
3 begin
4 v_out_tx:=f_makeAddress_tx
5 (‘123 Main’,’Redwood City’,’California’,’9406A’);
6 DBMS_OUTPUT.put_line(v_out_tx);
7 end;
8 /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to
number conversion error
ORA-06512: at “SCOTT.P_VALIDATEZIP”, line 7
ORA-06512: at “SCOTT.F_MAKEADDRES”, line 12
ORA-06512: at line 4
SQL>
The exception handler for value_error in p_validateZip (remember
that Oracle displays object names in uppercase which is why you see P_
VALIDATEZIP) was never executed. When the exception was raised, control
did not pass to the exception handler. The exception handler is used only
when the exception is raised in the body of the program unit. Exceptions
raised in the declaration section cannot be handled by the exception handler
in that unit.
Exactly the same problem is applicable to exceptions raised in the exception
handlers. The example shown in Listing 5-12 proves that case.
Listing 5-12: Exceptions Raised in the Exception Handler
procedure p_validatezip (i_zipCode_tx VARCHAR2)

is
e_tooShort EXCEPTION;
e_tooLong EXCEPTION;
e_badZip EXCEPTION;
pragma exception_init(e_badZip,-20998);
v_tempZip_nr NUMBER;
begin
if length(i_zipCode_tx)< 5 then
Raise e_tooShort;
elsif length(i_zipCode_tx)> 6 then
Raise e_tooLong;
end if;
v_tempZip_nr :=to_number(i_zipCode_tx);
exception
when e_tooLong then
(continued)
125
Chapter 5: Handling Exceptions
10_599577 ch05.qxp 5/1/06 12:12 PM Page 125
Listing 5-12
(continued)
raise e_badZip;
when e_tooShort then
raise e_badZip;
when VALUE_ERROR then
raise e_badZip;

23
when e_badZip then


24
insert into t_LogError (error_tx)
values(‘problem with Zip’);
raise;
end;
Here’s what’s going on at the end of Listing 5-12:

23 Raises the e_badZip exception.

24 Should handle any e_badZip exceptions, but it does not handle
the e_badZip exception raised in 23.
Writing Exceptional Exceptions
Any PL/SQL block can contain an exception handler. Keep the following rules
in mind to help you write exception handlers. The exception handler:
ߜ Is the last part of the program unit between the last statement of the
main body and the END; statement.
ߜ Always starts with the word EXCEPTION.
ߜ Handles one or more exceptions with the following structure:
when <exceptionA> then
statement1A;
statement2A;
when <exceptionB> then
statement1B;
statement2B;
ߜ Can have any number of statements in each exception block.
ߜ May conclude with a catchall exception to intercept all exceptions not
otherwise handled, using the following structure:
when others then
statement1;
statement2;

But you should be very careful to never use WHEN OTHERS THEN NULL;.
ߜ May include a special statement RAISE that raises the same exception
that was intercepted.
126
Part II: Getting Started with PL/SQL
10_599577 ch05.qxp 5/1/06 12:12 PM Page 126
Chapter 6
PL/SQL and SQL Working Together
In This Chapter
ᮣ Finding out how cursors work
ᮣ Declaring cursors: when and where
ᮣ Looking at the pros and cons of using implicit cursors
ᮣ Making use of cursor variables
ᮣ Structuring cursors for updates and shortcuts
ᮣ Using PL/SQL functions in SQL
T
he main reason to use PL/SQL as a programming language is that it works
really well with SQL. PL/SQL works better with SQL than any other pro-
gramming language does. This cooperation works both ways; you can embed
SQL in PL/SQL code, and you can call PL/SQL functions within SQL struc-
tures. This chapter shows you how to use both languages together more
effectively. For example, you find out
ߜ How to integrate SQL into PL/SQL with cursors: Cursors are one of the
most efficient portions of the PL/SQL language. The ability to use SQL to
define a set of information and then create a cursor to loop through this
information is one of the main reasons for using PL/SQL.
ߜ How cursors allow PL/SQL to retrieve information from an Oracle
database: PL/SQL’s ability to easily and efficiently handle this task is one
of its core strengths as a programming language. A PL/SQL program with
effective cursor handling can execute many times faster than a Java pro-

gram written to perform the same task running on an application server.
ߜ How to call PL/SQL functions in SQL: Calling these functions gives you
the power to have queries return almost any information you can imag-
ine. Any column in a SQL query can be calculated from a PL/SQL func-
tion stored in the database.
11_599577 ch06.qxp 5/1/06 12:12 PM Page 127
Cursors: What They Are
and How to Use Them
Cursors are special PL/SQL objects that you define in the declaration section
of a program. But declaring a cursor is just the beginning. The code in a PL/
SQL block opens the cursor, fetches data from the cursor, and then closes the
cursor. A simple program demonstrating these cursor operations is shown in
Listing 6-1.
Listing 6-1: Declaring a Cursor
declare

1
cursor c_countEmps is
select count(*)
from emp;
v_out_nr NUMBER;

5
begin
open c_countEmps;

7
fetch c_countEmps into v_out_nr;

8

close c_countEmps;

9
DBMS_OUTPUT.put_line(‘number of emps is:’||v_out_nr);
end;
Listing 6-1 declares a cursor that will return a single record. This cursor is
called an explicit cursor, meaning that you explicitly declare it in a declara-
tion section of the program and manipulate the cursor elsewhere in the pro-
gram. We discuss another kind of cursor (called implicit) later in this chapter.

1-5 The DECLARE section defines the cursor and the variable where
you will store the returned result.

7 First, you need to open the cursor by using the OPEN command.

8 When the cursor is open, the FETCH command fetches the
cursor’s contents into an output variable.

9 Finally, clean up after yourself and close the cursor by using the
CLOSE command
This sequence of operations represents the basic cursor-routine theme, but
variations on this theme allow you great flexibility in where you declare cur-
sors and how you manipulate and use them. In addition to a single piece of
information (the count of employees), you can use cursors to
ߜ Retrieve many rows of data by setting up cursors that return the infor-
mation from multiple columns in a SQL query. This technique lets you
use any SQL query in your program no matter how many tables or
columns it references.
128
Part II: Getting Started with PL/SQL

11_599577 ch06.qxp 5/1/06 12:12 PM Page 128
ߜ Loop through, examine, and manipulate the database records returned
by a SQL query. For example, you might want to loop through all your
customers and generate an invoice for each one.
ߜ Loop through cursor records within other cursors by using the pro-
gramming technique known as nesting. For example, you would use
one cursor to loop through departments and a nested cursor to find the
employees in each department.
ߜ Change cursor behavior based on passed parameters. This allows you
to better encapsulate the logic of the cursor without having to reference
global variables.
The sections that follow explain how you use cursors in these four different
ways, so read on for details.
Returning more than one
piece of information
A cursor can return one or more pieces of information. SQL statements may
have lots of columns in the SELECT portion of the query, and cursors cer-
tainly support this.
In Listing 6-1 for counting employees, only one value was returned by the
cursor. Specifying where the information was returned was simple because
only one variable was defined and it was passed to an output variable in the
INTO clause. But what if your cursor returns a whole list of values? In this
case, you have two options:
ߜ Explicitly declare as many variables as you need for all the values that
the cursor returns and list those variables after the INTO in the FETCH
command.
ߜ Explicitly define a record variable consisting of all the variables you need
and then just list the name of the record variable in the INTO clause of
the FETCH command. If you use a record variable, you can use Oracle’s
%ROWTYPE declaration to get Oracle to automatically define a record

variable with the right number of variables in the right order.
In the following sections, you find out how these two options work.
Option 1: Listing the variables separately
Listing variables separately is the quick and dirty option. You can explicitly
declare where you want the values of the cursor returned by using a comma-
delimited list after the INTO keyword in the FETCH statement.
129
Chapter 6: PL/SQL and SQL Working Together
11_599577 ch06.qxp 5/1/06 12:12 PM Page 129
Be sure that the number of variables you return to is exactly the same as the
number retrieved and that they’re listed in the same order as the elements in
the cursor. Also make sure that the variables you’re fetching into are the cor-
rect datatype, as shown in Listing 6-2.
Listing 6-2: Returning Cursor Variables the Quick and Easy Way
declare
cursor c_countemps is
select count(*), sum(sal)
from emp;
v_count_nr NUMBER;
v_sum_nr NUMBER;

6
begin
open c_countEmps;
fetch c_countEmps into v_count_nr, v_sum_nr;
close c_countEmps;
DBMS_OUTPUT.put_line
(‘number of emps is:’||v_count_nr);
DBMS_OUTPUT.put_line
(‘sum of emp salaries is:’||v_sum_nr);


14
end;

14 Shows the number retrieved.
Option 2: Defining a record type
You can declare a record variable that consists of one or more elements and
fetch the cursor into that variable. You can find out more about record variables
in Chapter 11. For now, you need to know that record variables in PL/SQL are
a way of representing a single row from the table, where you define attributes
in the same way that you would define attributes in the table definition.
When you declare a record, the list of elements is in the declaration section
and not in the FETCH command in the middle of the executable code. This
has two advantages:
ߜ Your code is easier to read.
ߜ If you want to use the same cursor in two different places, you don’t have
to repeat the whole list of elements, only the name of one record variable.
Listing 6-3 shows an example of this option.
Listing 6-3: Retrieving Cursor Variables with a Record Variable
declare
cursor c_countEmps is
select count(*) , sum(sal)
from emp;
130
Part II: Getting Started with PL/SQL
11_599577 ch06.qxp 5/1/06 12:12 PM Page 130
type rt_testRecType is record

4
(v_count_nr NUMBER,

v_sum_nr NUMBER);
r_testRec rt_testRecType;

7
begin
open c_countEmps;
fetch c_countEmps into r_testRec;

10
close c_countEmps;
DBMS_OUTPUT.put_line(‘number of emps is:’||
r_testRec.v_count_nr);
DBMS_OUTPUT.put_line(‘sum of emp salaries is:’||
r_testRec.v_sum_nr);
end;
Check out these details about the code:

4 This code declares the RECORD datatype, indicating that you need
a place to store a row of data consisting of two numbers.

7 Here you declare a record variable of the newly created datatype.

10 This line fetches the cursor into the record variable.
Keep in mind that the record and the cursor must have the same variables,
with the same datatype, listed in the same order. Also note how the compo-
nents of the record are referred to in the DBMS_OUTPUT statements. Because
each variable is now part of a record, you need to refer to it by using dot
notation. For example, r_testRec.v_sum_nr refers to the number field
v_sum_nr, which is declared to be part of the record r_testRec.
In both previous options (declaring independent variables and declaring a

special RECORD type) you still have to laboriously list all the elements to
which the cursor data was being returned. Oracle provides a shortcut that
eliminates this tedious work. You can allow the cursor to specify the record
for you by using %ROWTYPE. Instead of having to list all the elements in a
record, you simply declare it to be the same structure as a cursor that you’ve
previously declared or the same type as a table in the database, provided
that you are retrieving all the columns in the table into the cursor. This has
the following advantages:
ߜ You have less code to write, read, and correct.
ߜ If you need to change the data that the cursor retrieves, you have to
make only one change to your code in the SELECT clause of the cursor
declaration. Any record referencing the cursor via %ROWTYPE automati-
cally changes so that the record always matches the cursor.
Listing 6-3 written using a %ROWTYPE declaration would look like Listing 6-4.
131
Chapter 6: PL/SQL and SQL Working Together
11_599577 ch06.qxp 5/1/06 12:12 PM Page 131
Listing 6-4: Defining a Record Type for a Cursor by Using %ROWTYPE
declare
cursor c_countEmps is
select count(*) count_nr, sum(sal) sum_nr

3
from emp;
r_testRec c_countEmps%ROWTYPE;

5
begin
open c_countEmps;
fetch c_countEmps into r_testRec;

close c_countEmps;
DBMS_OUTPUT.put_line(‘number of emps is:’||
r_testRec.count_nr);

11
DBMS_OUTPUT.put_line(‘sum of emp salaries is:’||
r_testRec.sum_nr);
end;
Here’s what’s happening in the listing:

3 Because you’re planning to use a cursor as a reference for
datatype, you must assign aliases to columns in the resulting list
that don’t have real names. (For example, all function results
require aliases, but EMPNO is valid by itself.) These aliases will be
used as column names in the resulting record.

5 Because the record r_testrec takes its structure from the
cursor c_counttemps, you can be sure that r_testrec has
exactly the right structure for the cursor. If you change the cursor,
you don’t need to modify the record structure of r_testrec. It
will adjust itself automatically!

11 The field name in the record variable is the same as the alias you
assigned in the cursor.
Looping through multiple records
In Listing 6-4, only a single row of data is retrieved. However, in the real world,
most situations require you to loop through many records and process them.
For example, a payroll system must loop through all employees and write
checks for all of them.
These real-world systems might have to deal with thousands or even millions

of records retrieved by a single query. The process must not only read those
thousands or millions of rows, but also, in most cases, modify the information
in a record or use the information in the record to do something else entirely,
such as generate an invoice or statement. You can manage such a process by
132
Part II: Getting Started with PL/SQL
11_599577 ch06.qxp 5/1/06 12:12 PM Page 132
integrating the use of a cursor with any of the looping mechanisms we describe
in Chapter 4.
Listing 6-5 shows the basic syntax for looping through the records in a cursor.
Listing 6-5: Looping through Records in a Cursor
declare
cursor c_emp is
select *
from emp;
r_emp c_emp%ROWTYPE;
begin
open c_emp;
loop
fetch c_emp into r_emp;
exit when c_emp%NOTFOUND;

10
DBMS_OUTPUT.put_line(r_emp.eName);
end loop;
close c_emp;

12
end;
Whenever you have a loop, it must have a beginning, a middle, an end, and,

most important, a way to get out of the loop. The program needs to know
when to get out of the loop. With cursors, the time to exit the loop is usually
when there are no more records to process.

10 Detects that there are no more records to process and ends the
looping. %NOTFOUND is a special cursor variable that returns TRUE
when the last fetch to that cursor does not return any records. In
Listing 6-5, the program prints out the name of each employee.
When there are no more employees to process, the FETCH com-
mand won’t return any data, and c_emp%NOTFOUND will return
TRUE. This ends the loop and immediately jumps to the first line
of code after the END LOOP statement.

12 This code line will execute when the loop terminates.
Placing cursors in nested loops
You can loop through cursor records within other cursors. For example, sup-
pose you want to print a roster of all employees in your company, listed by
department. To do this, you would loop through records for each department
in your company and, within each department, loop through the employees.
You can set up two cursors and loop through all department and employee
records in a very efficient way, as shown in Listing 6-6.
133
Chapter 6: PL/SQL and SQL Working Together
11_599577 ch06.qxp 5/1/06 12:12 PM Page 133
Listing 6-6: Cursors in Nested Loops
declare
cursor c_dept is

2
select *

from dept;
r_dept c_dept%ROWTYPE;
cursor c_empInDept (cin_deptNo NUMBER) is

6
select *
from emp
where deptNo = cin_deptNo;

9
r_emp c_empInDept%ROWTYPE;
begin
open c_dept;
loop
fetch c_dept into r_dept;
exit when c_dept%NOTFOUND;
< do something with each department
< such as initialize total salary
open c_empInDept (r_dept.deptNo);
loop
fetch c_empInDept into r_emp;
exit when c_empInDept%NOTFOUND;
< do something with each employee
< such as change their salary
end loop;
close c_empInDept;
end loop;
close c_dept;
end;
Here are some more details about Listing 6-6:


2-5 This line declares the department cursor and record.

6-9 These lines declare the employee cursor and record.

9 How are these cursors different? The employee cursor specifies
the parameter in cin_deptNo (department number to be passed
in). Each time the cursor c_empInDept is called, it returns only
the employees in the department specified by the parameter.
Passing parameters to cursors
Cursors are very useful constructs. They’re the primary method of retrieving
information from the database. One of the things you need to be able to do is
dynamically control the cursor when the program is running.
134
Part II: Getting Started with PL/SQL
11_599577 ch06.qxp 5/1/06 12:12 PM Page 134
For example, if you want to run your payroll for only a single department, it
would require a lot of work to create separate cursors for each department.
Instead, you can use a single cursor that will return the employee records for
any department you specify. The way you tell the cursor which department to
return records for is by passing the department ID to the cursor as a parameter.
Usually, parameters are used in the WHERE clause of the query to filter what
data are returned.
To illustrate the basic syntax of passing parameters in the WHERE clause,
Listing 6-7 counts the number of employees in a specified department.
Listing 6-7: Basic Syntax for Passing Parameters in a Cursor
declare
cursor c_emp (cin_deptNo NUMBER) is

2

select count(*)
from emp
where deptNo = cin_deptNo;
v_deptNo dept.deptNo%type:=10;
v_countEmp NUMBER;
begin
open c_emp (v_deptNo);

9
fetch c_emp into v_countEmp;

10
close c_emp;

11
end;
When passing a parameter to a cursor, the syntax is different from your basic
cursor in the following ways:

2 You must declare the parameter as part of the cursor definition.

9 When you open the cursor, you need to pass a parameter (of the
correct type) to the cursor.

10, 11 When fetching and closing the cursor, you don’t specify the
parameter.
As illustrated in Listing 6-7, the most common use of a parameter is as a vari-
able referenced in the WHERE clause. You can pass a value to the parameter in
various ways.
You can pass a literal value, as in

open c_emp (10);
or a variable, whether it is a simple variable like
open c_emp (v_deptNo)
135
Chapter 6: PL/SQL and SQL Working Together
11_599577 ch06.qxp 5/1/06 12:12 PM Page 135
or part of a record, like this:
open c_emp (r_emp.deptNo);
Use parameters to avoid references to variables outside the cursor. Good
structured programming style involves trying to make your code modular,
meaning that it is self-contained and has no references to anything outside
the module. By using cursor parameters, not only is your code more flexible,
it is easier to read and maintain.
There are various places in the SELECT statement where you reference a
parameter. A few examples follow in Table 6-1. All parameters are prefixed
with cin (cursor in).
Table 6-1 SELECT Statement Examples
Part of SQL Statement Example
Where select
from emp
where deptNo = cin_deptNo
Group by . . . having select count(*)
from emp
group by deptNo
having deptNo > cin_deptNo
Connect by . . . start with select
from emp
start with empNo = cin_empNo
connect by prior empNo =
prior manager

Other than the WHERE clause, the HAVING clause, and the START WITH
clause, a parameter cannot change the query. For example, you can’t change
what is returned in the SELECT clause by using a parameter. Neither can you
change the columns in the ORDER BY clause by using a parameter. For exam-
ple, if you wanted the records returned to be in order by employee last name
instead of by Social Security number, that would require you to change the
name of a column listed in the ORDER BY clause:
order by lname
instead of
order by ssn
136
Part II: Getting Started with PL/SQL
11_599577 ch06.qxp 5/1/06 12:12 PM Page 136
Because parameters can change only values and not names of columns or
tables in the query, you can’t use them to change the FROM, ORDER BY, or
GROUP BY clauses. If you want to modify these parts of the query dynami-
cally, you need to use a REF cursor or dynamic PL/SQL (which are described
in Chapter 13).
Knowing Where to Declare Cursors
You can declare cursors in many different places. Where you place the cursor
definition depends upon where and how you’re going to use the cursor. If
you’re going to use a cursor only once, you can declare it right next to where
it is called. (But are you very sure you are only going to use it once? Famous
last words. . . .) If you plan to reuse the same query many times in the appli-
cation, you shouldn’t have to declare the same cursor every time you want to
run the query. You should declare the cursor in a place where it can easily be
called by all parts of the application.
The discussion of functions and procedures in Chapter 3 covers program
scope. The point where you declare the function or procedure determines
where you are able to call that function or procedure. The same principle

applies to cursor declaration. Table 6-2 offers an overview of what we mean.
In the following sections, you can find out how to place a cursor declaration
in these various locations. For more information about packages, please see
Chapter 3.
Table 6-2 Where to Define the Cursor?
If . . . . . . Then Define the Cursor Here
You use the cursor only The header of the program unit
once in program unit
The program unit is large The local (anonymous) PL/SQL block (for more
and you need the cursor information about anonymous PL/SQL blocks,
in a limited scope see Chapter 3)
You use a cursor throughout a The package body (for more information about
package, but not elsewhere packages, see Chapter 7)
You need to access the The package specification
cursor anywhere
Always define your cursor in as restrictive a scope as possible, but as gener-
ally as needed. Cursors should be accessible enough that you can avoid
having to define the same cursors over and over again.
137
Chapter 6: PL/SQL and SQL Working Together
11_599577 ch06.qxp 5/1/06 12:12 PM Page 137
Defining cursors in the header
of the program unit
The header of the program unit (a function or procedure) is the most common
place to declare a cursor. If you declare the cursor in the header of the pro-
gram unit, you can call it only within that program unit.
Even if a cursor is used only once in a program unit, it is common to define
all the cursors used in a program unit in the declaration header of that unit.
Declaring a cursor in an anonymous PL/SQL block is quite rare. You should
probably avoid doing so because it adds lines to your code body and can

make it hard to find the cursor declaration. (Most programmers immediately
look in the declaration section of a program for cursor declarations.)
If you have so many cursors that you start to lose track of them, the program
unit is probably too big and should be broken up into smaller units.
Listing 6-8 is an example showing a cursor declared within a PL/SQL procedure.
Listing 6-8: Declaring a Cursor within a Procedure
create or replace procedure p_printEmps is

1
cursor c_emp is

2
select *
from emp;
r_emp c_emp%ROWTYPE;
begin
open c_emp;
loop
fetch c_emp into r_emp;
exit when c_emp%NOTFOUND;
DBMS_OUTPUT.put_line(r_emp.eName);
end loop;
close c_emp;
end;
Note that there is no DECLARE used in a procedure. This clause is only
needed for anonymous blocks. The declaration (line 2) begins right after the
CREATE OR REPLACE PROCEDURE statement (line 1).
Defining cursors in the local PL/SQL block
If your program unit is very large and you need the cursor only in a very lim-
ited scope, you can define a small local PL/SQL block and define the cursor to

exist only within that scope.
138
Part II: Getting Started with PL/SQL
11_599577 ch06.qxp 5/1/06 12:12 PM Page 138

×