1. Trang chủ
  2. » Công Nghệ Thông Tin

Oracle PL/SQL Language Pocket Reference- P12

50 392 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Exception Handlers
Trường học O'Reilly & Associates
Chuyên ngành Oracle PL/SQL Programming
Thể loại book
Năm xuất bản 2000
Thành phố Sebastopol
Định dạng
Số trang 50
Dung lượng 207,49 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Previous: 8.10 RAISE Nothing but Exceptions Assigning Values to and from Records Record Types and Record Compatibility Nested Records Records in PL/SQL programs are very similar in conce

Trang 1

3 Using another exception section within the first exception section, trap this exception as the

"else" in this pseudo-IF statement Within the exception handler, try to convert the string with TO_DATE and the third mask, MM/YY If it works, I am done If it doesn't work, an

exception is raised

4 I have only three masks, so if I cannot convert the string after these three TO_DATE calls, the user entry is invalid and I will simply return NULL

The function convert_date that follows illustrates the full PL/SQL version of the preceding

pseudocode description I make liberal use of the WHEN OTHERS exception handler because I have

no way of knowing which exception would have been raised by the conversion attempt:

FUNCTION convert_date (value_in IN VARCHAR2) RETURN DATE

/* IF MM/DD/YY mask works, set return value */

return_value := TO_DATE (value_in, 'MM/DD/YY');

/* OTHERWISE RETURN NULL */

WHEN OTHERS THEN

Trang 2

Previous: 8.8

NO_DATA_FOUND:

Multipurpose Exception

Oracle PL/SQL Programming, 2nd Edition

Next: 8.10 RAISE Nothing but Exceptions

Trang 3

Previous: 8.9 Exception

Handler as IF Statement

Chapter 8Exception Handlers

Next: 9 Records in PL/SQL

8.10 RAISE Nothing but Exceptions

Have you noticed that the RAISE statement acts in many ways like a GOTO statement? The GOTO statement in PL/SQL looks like this:

A very significant and fundamental difference between GOTO and RAISE, however, is that GOTO branches to another execution statement, whereas RAISE branches to the exception section The RAISE statement, in other words, shifts the focus of the program from normal execution to "error handling mode." Both from the standpoint of code readability and also of maintenance, you should never use the RAISE statement as a substitute for a control structure, be it a GOTO or an IF

statement

If you have not tried to use RAISE in this way, you might think that I am building up a straw man in order to knock it down Would that it were so Just in the process of writing this book, I ran across several examples of this abuse of exception handling Check out, for example, the function

description for GET_GROUP_CHAR_CELL in Oracle Corporation's Oracle Forms Reference

Volume 1 It offers a function called Is_Value_In_List, which returns the row number of the value if

it is found in the record group, as an example of a way to use GET_GROUP_CHAR_CELL

The central logic of Is_Value_In_List is shown in the following example The function contains three different RAISE statements all of which raise the exit_function exception:

1 FUNCTION Is_Value_In_List

Trang 4

2 (value VARCHAR2, rg_name VARCHAR2, rg_column

Exception Handling Quick Facts and Tips

Here are some facts and tips to remember about exception handling:

section of that block

section of the enclosing block, if it exists

section of the enclosing block, if it exists

Trang 5

transferred to the exception section You cannot return to that execution section after the exception is raised

The third RAISE on line 17 is also questionable This RAISE is the very last statement of the

function Now, to my mind, the last line of a function should be a RETURN statement The whole point of the function, after all, is to return a value In this case, however, the last line is an exception, because the author has structured the code so that if I got this far, I have not found a match So raise the exception, right? Wrong

"Row-not-found" is not an exception from the standpoint of the function That condition should be considered one of the valid return values of a function that asks "Is value in list?" This function

should be restructured so that the exception is raised only when there is a problem

From the perspective of structured exception handling in PL/SQL, this function suffered from several weaknesses:

Poorly named exceptions

The exception names exit_function and return_value describe actions, rather than error

conditions The name of an exception should describe the error which took place

Exceptions for valid outcomes

By using these "action" names, the developers are actually being very open about how they are manipulating the exception handler They say, "I use exceptions to implement logic

branching." We should say to them, "Don't do it! Use the constructs PL/SQL provides to handle this code in a structured way."

If you encounter either of these conditions in code you are writing or reviewing, take a step back Examine the logical flow of the program and see how you can use the standard control structures (IF, LOOP, and perhaps even GOTO) to accomplish your task The result will be much more readable and maintainable code

Previous: 8.9 Exception

Handler as IF Statement

Oracle PL/SQL Programming, 2nd Edition

Trang 6

The Oracle Library

Navigation

Copyright (c) 2000 O'Reilly & Associates All rights reserved

Trang 7

Previous: 8.10 RAISE

Nothing but Exceptions

Assigning Values to and from Records

Record Types and Record Compatibility

Nested Records

Records in PL/SQL programs are very similar in concept and structure to the rows of a database table A record is a composite data structure, which means that it is composed of more than one

element or component, each with its own value The record as a whole does not have value of its

own; instead, each individual component or field has a value The record gives you a way to store and

access these values as a group

If you are not familiar with using records in your programs, you might initially find them

complicated When used properly, however, records will greatly simplify your life as a programmer You will often need to transfer data from the database into PL/SQL structures and then use the

procedural language to further massage, change, or display that data When you use a cursor to read information from the database, for example, you can pass that table's record directly into a single PL/SQL record When you do this you preserve the relationship between all the attributes from the table

9.1 Record Basics

This section introduces the different types of records and the benefits of using them in your programs

9.1.1 Different Types of Records

PL/SQL supports three different kinds of records: table-based, cursor-based, and

programmer-defined These different types of records are used in different ways and for different purposes, but all

Trang 8

three share the same internal structure: every record is composed of one or more fields However, the way these fields are defined in the record depend on the record type Table 9.1 shows this information about each record type

Table 9.1: PL/SQL Record Types

Record Type Description Fields in Record

Table-based A record based on a table's column

structure

Each field corresponds to and has the same name as a column in a table

Cursor-based A record based on the cursor's

SELECT statement

Each field corresponds to a column

or expression in the cursor SELECT statement

Programmer-defined

A record whose structure you, the programmer, get to define with a declaration statement

Each field is defined explicitly (its name and datatype) in the TYPE statement for that record; a field in a programmer-defined record can even be another record

Figure 9.1 illustrates the way a cursor record adopts the structure of the SELECT statement by using the %ROWTYPE declaration attribute (explained later in this chapter)

Figure 9.1: Mapping of cursor structure to PL/SQL record

9.1.2 Accessing Record-Based Data

Trang 9

You access the fields within a record using dot notation, just as you would identify a column from a database table, in the following format:

<record name>.<field name>

You would reference the first_name column from the employee table as follows:

9.1.3 Benefits of Using Records

The record data structure provides a high-level way of addressing and manipulating program-based data This approach offers the following benefits:

Data abstraction

Instead of working with individual attributes of an entity or object, you think of and

manipulate that entity as a "thing in itself."

Aggregate operations

You can perform operations which apply to all the columns of a record

Leaner, cleaner code

You can write less code and make what you do write more understandable

The following sections describe each of these benefits

9.1.3.1 Data abstraction

When you abstract something, you generalize it You distance yourself from the nitty-gritty details and concentrate on the big picture When you create modules, you abstract the individual actions of the module into a name The name (and program specification) represents those actions

Trang 10

When you create a record, you abstract all the different attributes or fields of the subject of that

record You establish a relationship between all those different attributes and you give that

relationship a name by defining a record

9.1.3.2 Aggregate operations

Once you have stored information in records, you can perform operations on whole blocks of data at

a time, rather than on each individual attribute This kind of aggregate operation reinforces the

abstraction of the record Very often you are not really interested in making changes to individual components of a record, but instead to the object which represents all of those different components

Suppose that in my job I need to work with companies, but I don't really care about whether a

company has two lines of address information or three I want to work at the level of the company itself, making changes to, deleting, or analyzing the status of a company In all these cases I am

talking about a whole row in the database, not any specific column The company record hides all that information from me, yet makes it accessible when and if I need it This orientation brings you closer to viewing your data as a collection of objects with rules applied to those objects

9.1.3.3 Leaner, cleaner code

Using records also helps you to write clearer code and less of it When I use records, I invariably produce programs which have fewer lines of code, are less vulnerable to change, and need fewer comments Records also cut down on variable sprawl; instead of declaring many individual variables,

I declare a single record This lack of clutter creates aesthetically attractive code which requires fewer resources to maintain

9.1.4 Guidelines for Using Records

Use of PL/SQL records can have a dramatic impact on your programs, both in initial development and in ongoing maintenance To ensure that I personally get the most out of record structures, I have set the following guidelines for my development:

create a corresponding record (except in the case of cursor FOR loops) I always FETCH into

a record, rather than into individual variables In those few instances when it might involve a little extra work over simply fetching into a single variable, I marvel at the elegance of this approach and compliment myself on my commitment to principle

create a new (or use a predefined) table-based record to store that data I keep my variable use

to a minimum and dynamically link my program data structures to my RDBMS data structures with the %ROWTYPE attribute

variables as parameters in my procedural interfaces This way, my procedure calls are less likely to change over time, making my code more stable There is a downside to this

technique, however: if a record is passed as an OUT or IN OUT parameter, its field values are

Trang 11

saved by the PL/SQL program in case of the need for a rollback This can use up memory and consume unnecessary CPU cycles

9.1.5 Referencing a Record and its Fields

The rules you must follow for referencing a record in its entirety or a particular field in the record are the same for all types of records: table, cursor, and programmer-defined

A record's structure is similar to that of a database table Where a table has columns, a record has fields You reference a table's column by its name in a SQL statement, as in:

SELECT company_id FROM company;

Of course, the fully qualified name of a column is:

<table_name>.<column_name>

This full name is often required in a SQL statement to avoid ambiguity, as is true in the following statement:

SELECT employee.company_id, COUNT(*) total_employees

FROM company, employee

WHERE company.company_id = employee.company_id;

If I do not preface the name of company_id with the appropriate table name, the SQL compiler will not know to which table that column belongs The same is true for a record's fields You reference a record by its name, and you reference a record's field by its full name using dot notation, as in:

<record_name>.<field_name>

So if I create a record named company_rec, which contains a company_id field, then I would

reference this field as follows:

Trang 12

customer_rec customer_sales_rectype;

BEGIN

display_sales_data (customer_rec);

END;

I didn't make a single dotted reference to any particular field in the customer record Instead I

declared the record type, used it to create the record, used the record type again to define the type for the parameter in the procedure specification, and finally called the procedure, passing it the specific record I declared

9.1.6 Comparing Two Records

While it is possible to stay at the record level in certain situations, you can't avoid direct references to fields in many other cases If you want to compare records, for example, you must always do so through comparison of the records' individual fields

Suppose you want to know if the old company information is the same as the new company

information, both being stored in records of the same structure The following test for equality will not compile:

IF old_company_rec = new_company_rec / Illegal syntax!

PL/SQL will not automatically compare each individual field in the old company record to the

corresponding field in the new company record Instead, you will have to perform that detailed check yourself, as in:

IF old_company_rec.name = new_company_rec.name AND

Trang 13

FETCH company_cur INTO company_rec;

company_rec.name := 'New Name';

Next: 9.2 Table-Based Records

8.10 RAISE Nothing but

Exceptions

Book Index 9.2 Table-Based Records

The Oracle Library

Navigation

Copyright (c) 2000 O'Reilly & Associates All rights reserved

Trang 14

Previous: 9.1 Record Basics Chapter 9

Records in PL/SQL

Next: 9.3 Cursor-Based Records

Suppose we have a table defined as the following:

CREATE TABLE rain_forest_history

rain_forest_rec.country_code

rain_forest_rec.analysis_date

rain_forest_rec.size_in_acres

rain_forest_rec.species_lost

9.2.1 Declaring Records with the %ROWTYPE Attribute

To create a table record, you declare it with the %ROWTYPE attribute The %ROWTYPE attribute

is very similar to the %TYPE attribute discussed in Chapter 4, Variables and Program Data, except that it is used to declare a composite structure rather than the simple, scalar variable produced with %TYPE Sounds perfect for a record, doesn't it?

The general format of the %ROWTYPE declaration for a table record is:

Trang 15

<record_name> <table_name>%ROWTYPE;

where <record_name> is the name of the record, and <table_name> is the name of a table or view whose structure forms the basis for the record Just as the %TYPE attribute automatically provides the column's datatype to the variable, %ROWTYPE provides the datatypes of each of the columns in

a table for the record's fields

In the following example, a %TYPE declaration defines a variable for the company name, while the

%ROWTYPE declaration defines a record for the entire company row A SELECT statement then fills the comp_rec record with a row from the table

Notice that I do not need to specify the names of company's columns in either the record declaration

or the SELECT statement I can keep the code very flexible with the table record If the DBA adds a column to the table, changes the name of a column, or even removes a column, the preceding lines of code will not be affected at all (You would, however, need to recompile your programs in order to pick up the change in data structure.)

Of course, if my program makes an explicit reference to a modified column, that code would

probably have to be changed With a strong reliance on data manipulation through records, however, you can keep such references to a minimum

Previous: 9.1 Record Basics Oracle PL/SQL

Programming, 2nd Edition

Next: 9.3 Cursor-Based Records

The Oracle Library

Navigation

Copyright (c) 2000 O'Reilly & Associates All rights reserved

Trang 16

Previous: 9.2 Table-Based

Records

Chapter 9Records in PL/SQL

Next: 9.4 Defined Records

Programmer-9.3 Cursor-Based Records

A cursor-based record, or cursor record, is a record whose structure is drawn from the SELECT list of

a cursor (See Chapter 6, Database Interaction and Cursors, for more information on cursors.) Each field in the record corresponds to and has the same name as the column or aliased expression in the cursor's query This relationship is illustrated by Figure 9.1

The same %ROWTYPE attribute used to declare table records is also used to declare a record for an explicitly declared cursor, as the following example illustrates:

DECLARE

/* Define the cursor */

CURSOR comp_summary_cur IS

SELECT C.company_id, name, city

FROM company C, sales S

WHERE c.company_id = s.company_id;

/* Create a record based on that cursor */

9.3.1 Choosing Columns for a Cursor Record

You could declare a cursor record with the same syntax as a table record, but you don't have to match

a table's structure A SELECT statement creates a "virtual table" with columns and expressions as the

Trang 17

list of columns A record based on that SELECT statement allows you to represent a row from this virtual table in exactly the same fashion as a true table record The big difference is that I get to determine the fields in the record, as well as the names for those fields Through the cursor you can, therefore, create special-purpose records tailored to a particular program and need

The query for a cursor can contain all or only some of the columns from one or more tables A cursor can also contain expressions or virtual columns in its select list In addition, you can provide aliases for the columns and expressions in the select list of a cursor These aliases effectively rename the fields in the cursor record

In the following example I create a cursor against the rain forest history table for all records showing

a greater than average loss of species in 1994 Then, for each record found, I execute the

publicize_loss procedure to call attention to the problem and execute project_further_damage to come up with an analysis of future losses:

FETCH high_losses_cur INTO high_losses_rec;

EXIT WHEN high_losses_cur%NOTFOUND;

Trang 18

9.3.2 Setting the Record's Column Names

The column aliases change the names of the fields in the record In the above example, the

customized column names are more descriptive of the matter at hand than the standard column

names; the code becomes more readable as a result

A cursor's query can also include calculated values or expressions; in those cases, you must provide

an alias for that calculated value if you want to access it through a record Otherwise, there is no way for PL/SQL to create a named field for that value in the record and that name is your handle to the data Suppose, for example, I have a parameterized cursor and record defined as follows:

CURSOR comp_performance_cur (id_in IN NUMBER) IS

SELECT name, SUM (order_amount)

FROM company

WHERE company_id = id_in;

comp_performance_rec comp_performance_cur%ROWTYPE;

I can refer to the company name with standard dot notation:

IF comp_performance_rec.name = 'ACME' THEN

But how can I refer to the sum of the order_amount values? I need to provide a name for this

calculated column, as shown below:

CURSOR comp_performance_cur (id_in IN NUMBER)

NOTE: Even though the same %ROWTYPE attribute is used in creating both table

and cursor records and the declarations themselves look very similar, the record created

Trang 19

from a table has a different record type from the record created from a cursor Records

of different types are restricted in how they can interact, a topic we will explore in the next section

Previous: 9.2 Table-Based

Records

Oracle PL/SQL Programming, 2nd Edition

Next: 9.4 Defined Records

Trang 20

Previous: 9.3 Cursor-Based

Records

Chapter 9Records in PL/SQL

Next: 9.5 Assigning Values

to and from Records

9.4 Programmer-Defined Records

Now you know how to create a record with the same structure as a table or a cursor These are

certainly very useful constructs in a programming language designed to interface with the Oracle RDBMS Yet do these kinds of records cover all of our needs for composite data structures?

What if I want to create a record that has nothing to do with either a table or a cursor? What if I want

to create a record whose structure is derived from several different tables and views? Should I really have to create a "dummy" cursor just so I can end up with a record of the desired structure? For just these kinds of situations, PL/SQL offers programmer-defined records, declared with the TYPE RECORD statement.[1]

[1] Programmer-defined records are supported but undocumented in PL/SQL

Release 1.1

With the programmer-defined record, you have complete control over the number, names, and

datatypes of fields in the record

To declare a programmer-defined record, you must perform two distinct steps:

1 Declare or define a record TYPE containing the structure you want in your record

2 Use this record TYPE as the basis for declarations of your own actual records having that structure

9.4.1 Declaring Programmer-Defined Record TYPEs

You declare a record type with the record TYPE statement The TYPE statement specifies the name

of the new record structure, and the components or fields which make up that record

The general syntax of the record TYPE definition is:

TYPE <type_name> IS RECORD

(<field_name1> <datatype1>,

Trang 21

You cannot, on the other hand, declare a field to be an exception or a cursor (With PL/SQL Release 2.3, you can declare a field to be a cursor variable.)

Here is an example of a record TYPE statement:

TYPE company_rectype IS RECORD

(comp# company.company_id%TYPE,

name company.name%TYPE);

9.4.2 Declaring the Record

Once you have created your own customized record types, you can use those types in declarations of specific records The actual record declarations have the following format:

Trang 22

customer, as well as a calculated, total amount of sales for the customer I can then use this new

record type to declare records with the same structure as this type:

In addition to specifying the datatype, you can supply default values for individual fields in a record with the DEFAULT or := syntax You can also apply constraints to the declaration of a record's fields You can specify that a field in a record be NOT NULL (in which case you must also assign a default value) Finally, each field name within a record must be unique

9.4.3 Examples of Programmer-Defined Record Declarations

Suppose that I declare the following subtype (an alias for VARCHAR2), a cursor, and a PL/SQL table data structure:

SUBTYPE long_line_type IS VARCHAR2;

CURSOR company_sales_cur IS

SELECT name, SUM (order_amount) total_sales

FROM company c, order o

WHERE c.company_id = o.company_id;

TYPE employee_ids_tabletype IS

TABLE OF employee.employee_id

INDEX BY BINARY_INTEGER;

I can then declare the following programmer-defined records:

employees I use the %TYPE attribute to link the fields in the record directly to the table I then add a third field which is actually a PL/SQL table of employee ID numbers (PL/SQL tables are described in Chapter 10, PL/SQL Tables.)

TYPE company_rectype IS RECORD (company_id company.company_id%TYPE,

Trang 23

company_name company.name%TYPE, new_hires_tab employee_ids_tabletype);

record, including: the NOT NULL constraint, use of a subtype, the %TYPE attribute, default value specification, a PL/SQL table, and a nested record These varieties are shown below

TYPE mishmash_rectype IS RECORD (emp_number NUMBER(10) NOT NULL, paragraph_text long_line_type, company_nm company.name%TYPE, total_sales company_sales_cur.total_sales%TYPE := 0,

new_hires_tab employee_ids_tabletype, prefers_nonsmoking_fl BOOLEAN := FALSE, new_company_rec company_rectype

);

BEGIN

As you can readily see, PL/SQL offers you tremendous flexibility in designing your own record structures They can represent your tables, views, and SELECT statements in a PL/SQL program They can also, however, be arbitrarily complex, with fields that are actually records within records, and with fields that are PL/SQL tables

Previous: 9.3 Cursor-Based

Records

Oracle PL/SQL Programming, 2nd Edition

Next: 9.5 Assigning Values

to and from Records

9.3 Cursor-Based Records Book Index 9.5 Assigning Values to and

Trang 24

Previous: 9.4

Programmer-Defined Records

Chapter 9Records in PL/SQL

Next: 9.6 Record Types and Record Compatibility

9.5 Assigning Values to and from Records

You can modify the values in a record in the following ways:

These assignment methods are described in the sections that follow

9.5.1 Direct Field Assignment

The assignment operator (:=) changes the value of a particular field In the first assignment,

total_sales is zeroed out In the second assignment, a function is called to return a value for the Boolean flag output_generated (it is set to either TRUE or FALSE):

Trang 25

/* Insert a row in the table using the record values */

INSERT INTO rain_forest_history VALUES

9.5.2 SELECT INTO from an Implicit Cursor

Use the implicit cursor SELECT INTO to fill the values in a record You can SELECT INTO either the record as a whole or the individual fields in the record:

/* Move values directly into the record: */

SELECT customer_id, name, SUM (sales)

INTO top_customer_rec

FROM customer

WHERE sold_on BETWEEN < ADD_MONTHS (SYSDATE, -3);

/* or list the individual fields: */

SELECT customer_id, name, SUM (sales)

INTO top_customer_rec.customer_id, top_customer_rec

customer_name,

top_customer_rec.total_sales

FROM customer

WHERE sold_on BETWEEN < ADD_MONTHS (SYSDATE, -3);

If you SELECT INTO a record, you must be sure that the structure of the select list (columns or

expressions) matches that of the record

The INTO clause of an implicit query is the only part of a SQL DML statement in which a PL/SQL record (as an aggregate and not its component fields) can be referenced

9.5.3 FETCH INTO from an Explicit Cursor

Use an explicit cursor to FETCH values INTO a record You can FETCH INTO the record as a whole

Ngày đăng: 07/11/2013, 19:15

TỪ KHÓA LIÊN QUAN