Вы находитесь на странице: 1из 5

COMP9311 Labs - Using PL/SQL (III)

http://www.cse.unsw.edu.au/~cs3311/10s1/lab/lab6.html

Using PL/SQL, Lab Exercise (III)


Aim
This laboratory class aims to show you how to handle exceptional conditions that might occur while PL/SQL programs are executing.

Exercises
Recall from the previous PL/SQL laboratories that you can execute PL/SQL programs from within the SQL*Plus system. Recall also that you need to set the serveroutput switch in SQL*Plus before any PL/SQL output appears. As we did previously, we'll conduct the exercises for this laboratory in SQL*Plus, so login and set the switch:

% sql SQL*Plus: Release 10.1.0.3.0 - Production on Tue Feb 26 00:00:00 2007 Copyright (c) 1982, 2004, Oracle. All rights reserved.

Enter user-name: YourOracleUsername@cse Enter password: YourOraclePassword Connected to: Oracle Database 10g Enterprise Edition Release 10.1.0.3.0 - Production With the Partitioning, OLAP and Data Mining options SQL> set serveroutput on
Check that you've set everything up ok by running the command:

SQL> exec dbms_output.put_line('Hello, '||user||'!') Hello, YourOracleUserName! PL/SQL procedure successfully completed.
We'll be working with the same bank database as we used in the first PL/SQL laboratory, so you should reload it now. Even if you kept it after the last lab, it's a good idea to reload it to make sure that you get the same output as shown in the exercises below.

SQL> @/home/cs9311/public_html/10s1/lab/script/makeBankDb.sql Building small bank database. Please wait ... SQL>
Exceptions Exceptions are unusual or erroneous conditions that occur when a program is executing. One way to deal with such conditions is to fill your code with tests to make sure that every operation can be safely carried out with any data that's given to it. As you might imagine, to do this completely would require extensive modification of your program code and would severely obscure the original program structure. An alternative way to handle such conditions is to allow errors to occur during program program execution and deal with them then. This latter approach is the exception handling approach. When an error occurs, an exception is raised and normal program execution is halted. The system then looks for some code that has been set up to deal with the error condition. If it can't find any, it has its own (crude) approach of simply terminating the program and printing some information about where the problem occurred. This simple approach to error handling is generally not acceptable in the context of PL/SQL programs, where it is no use telling a user of your program which line is wrong, since the user has no option to examine and repair the code. More likely, you need the program to report the problem and then continue running as best it can, or prompt the user to take some action to repair the problem. Consider the following extremely simple PL/SQL function that computes the inverse of a given number:

create or replace function invert(x int) return real is begin return 1.0 / x; end;
Load this definition into your SQL*Plus session. Don't forget that you need to terminate this definition by typing the slash character (/). This also ensures that the function is created and inserted into the user_objects table so that you can make use of it. If you execute code to use this function you observe the following expected results for most inputs:

1 of 5

23-01-2012 14:35

COMP9311 Labs - Using PL/SQL (III)

http://www.cse.unsw.edu.au/~cs3311/10s1/lab/lab6.html

SQL> exec dbms_output.put_line(invert(2)) .5 PL/SQL procedure successfully completed. SQL> exec dbms_output.put_line(invert(6)) .1666666666666666666666666666666666666667 PL/SQL procedure successfully completed. SQL> exec dbms_output.put_line(invert(-8)) -.125 PL/SQL procedure successfully completed.
However, if you give an argument of zero to the invert function, PL/SQL attempts to compute the result of division by zero, and produces the following:

SQL> exec dbms_output.put_line(invert(0)) begin dbms_output.put_line(invert(0)); end; * ERROR at line 1: ORA-01476: divisor is equal to zero ORA-06512: at "YourUserName.INVERT", line 3 ORA-06512: at line 1
From the error messages, it is reasonably clear what has gone wrong. We can handle the division-by-zero exception ourselves by modifying the invert function as follows:

create or replace function invert(x int) return real is begin return 1.0 / x; exception when zero_divide then dbms_output.put_line('You tried to invert zero!'); return 0.0; end;
The exception handling code is introduced by the keyword exception and contains a sequence of when clauses (in this case only one). Each when clause handles a particular kind of exception. There are a number of system-defined exceptions, including the zero_divide one that we handle here. The attempt to invert zero now produces:

SQL> exec dbms_output.put_line(invert(0)) You tried to invert zero! 0 PL/SQL procedure successfully completed. SQL>
In other words, the program has retained control of the situation rather than simply allowing the system to terminate it. Note, however, that the exception handler still needs to return a value for the function. Cursors and Exceptions There are a number of exceptions related specifically to the use of cursors. We will examine these via a sequence of examples. A very common activity in PL/SQL programming is to copy a value from a database table into a PL/SQL variable via a select...into statement. Consider the following simple procedure to print the balance for a specified customer from our simple banking database:

create or replace procedure balanceFor(name varchar2) is amount int; begin select balance into amount from Accounts where holder = name;

2 of 5

23-01-2012 14:35

COMP9311 Labs - Using PL/SQL (III)

http://www.cse.unsw.edu.au/~cs3311/10s1/lab/lab6.html

dbms_output.put_line(name||' has '||amount); end;


This function might be used as follows:

SQL> exec balanceFor('John') John has 5000 PL/SQL procedure successfully completed. SQL> exec balanceFor('Wayne') Wayne has 250 PL/SQL procedure successfully completed. SQL>
This procedure works ok, as long as we remember everyone's name. If, however, we forget that Steven is actually called "Steve" in the database, we observe the following:

SQL> exec balanceFor('Steven') begin balanceFor('Steven'); end; * ERROR at line ORA-01403: no ORA-06512: at ORA-06512: at

1: data found "YourUserName.BALANCEFOR", line 4 line 1

The error looks serious and the cause isn't immediately obvious. The actual problem is that select...into expects to receive exactly one answer to store in the PL/SQL variable. In this case, the problem is that the query has failed to find any answers (i.e. we asked for the balance for a customer who does not appear in the database). This generates a no_data_found exception, which is then passed to SQL*Plus to deal with. It would clearly be much better if the program itself printed a message that was more directly related to the problem e.g.

SQL> exec balanceFor('Steven') There is no customer called Steven


This can be acheived by defining an exception handler for no_data_found, as follows:

create or replace procedure balanceFor(name varchar2) is amount int; begin select balance into amount from Accounts where holder = name; dbms_output.put_line(name||' has '||amount); exception when no_data_found then dbms_output.put_line('There is no customer called '||name); end;
Another problem emerges if John decides to open a second account, at the Clovelly branch:

SQL> insert into Accounts values ('John', 'Clovelly', 1500); 1 row created. SQL>
Now if we ask about John's balance, we observe:

SQL> exec balanceFor('John') begin balanceFor('John'); end; * ERROR at line 1: ORA-01422: exact fetch returns more than requested number of rows ORA-06512: at "YourUserName.BALANCEFOR", line 4 ORA-06512: at line 1

3 of 5

23-01-2012 14:35

COMP9311 Labs - Using PL/SQL (III)

http://www.cse.unsw.edu.au/~cs3311/10s1/lab/lab6.html

SQL>
This has caused the too_many_rows exception because the select..into query returned more than one solution to store in the PL/SQL variable. Once again, the system has "handled" the error, but we could do better (more informative) error handling ourselves, via an explicit exception handler:

create or replace procedure balanceFor(name varchar2) is amount int; begin select balance into amount from Accounts where holder = name; dbms_output.put_line(name||' has '||amount); exception when no_data_found then dbms_output.put_line('There is no customer called '||name); when too_many_rows then dbms_output.put_line(name||' has more than one account'); end;
When both of these exceptions are handled, the entire range of possibilities for select..into has been covered. Exercise: Handle the too_many_rows case by printing the balances for all of the accounts in the form:

SQL> exec balanceFor('John') John has (UNSW,500), (Clovelly,1500)


End exercise. Consider that the bank adopts a policy that nobody can hold more than one account. Under these conditions, the holder becomes a primary key (assuming no duplicate names), so let's modify the Accounts relation to relfect this. Note that we have to first remove the new account for John, otherwise we'd violate the primary key constraint.

SQL> delete from Accounts where holder='John' and branch='Clovelly'; 1 row deleted. SQL> alter table Accounts add primary key (holder); Table altered. SQL>
Now consider a procedure that inserts a new customer and assigns them the bank standard introductory balance of $500.

create or replace procedure newAccount(name varchar2, branch varchar2) is begin insert into Accounts values (name, branch, 500); dbms_output.put_line(name||' has $500 in a new account at '||branch); end;
This procedure is executed as follows:

SQL> exec newAccount('Norman','UNSW') Norman has $500 in a new account at UNSW PL/SQL procedure successfully
But if we attempt to give any customer a second account, we find:

SQL> exec newAccount('John','Clovelly') begin newAccount('John','Clovelly'); end; * ERROR at line 1: ORA-00001: unique constraint (YourUserName.SYS_C001135) violated ORA-06512: at "YourUserName.NEWACCOUNT", line 3 ORA-06512: at line 1
In this case, the dup_val_on_index exception has been triggered by attempting to insert a duplicate primary key value. (Note: you

4 of 5

23-01-2012 14:35

COMP9311 Labs - Using PL/SQL (III)

http://www.cse.unsw.edu.au/~cs3311/10s1/lab/lab6.html

might not see exactly the same constraint number as above, since these are allocated internally by Oracle). We could explicitly handle this constraint to improve the error-reporting behaviour as follows:

create or replace procedure newAccount(name varchar2, branch varchar2) is begin insert into Accounts values (name, branch, 500); dbms_output.put_line(name||' has $500 in a new account at '||branch); exception when dup_val_on_index then dbms_output.put_line(name||' already has one account'); end;
User-defined Exceptions As well as system-defined exceptions, it is possible for PL/SQL programs to define their own exceptions for unusual conditions unique to that application. Consider the withdraw procedure form the first PL/SQL lab, re-written to use an exception to handle the case of attmepting to overdraw an account:

create or replace procedure withdraw(person varchar2, amount real) is curr real; final real; OverDrawn exception; begin select balance into curr from Accounts where holder = person; if (amount > curr) then raise OverDrawn; else final := curr - amount; update Accounts set balance = final where holder = person and balance > amount; commit; dbms_output.put_line('Final balance: ' || final); end if; exception when OverDrawn then dbms_output.put_line('Insufficient Funds'); end;
Exercise: Improve the error-handling of this procedure by adding exception handlers for the case where person refers to an unknown person. Is there any point adding an exception handler for the case where the same person has two accounts?

5 of 5

23-01-2012 14:35

Вам также может понравиться