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

MARCH 2011: TECHNOLOGY: PL/SQL Building with Blocks By Steven Feuerstein

As Published In

March/April 2011

Part 1 in a series of articles on understanding and using PL/SQL Oracle PL/SQL celebrates its 22nd birthday in 2011. I know this because I am looking at the first Oracle PL/SQL user guide ever published; it is for PL/SQL Release 1.0, and its date of publication is September 1989. I was working for Oracle at that time, building the first sales automation tools ever used by the Oracle USA. sales force. I had already worked with PL/SQL inside SQL Forms 3.0, but with the release of Oracle 6 Database, PL/SQL was available as a free-standing application development language. Three years later, I wrote my first book on PL/SQL and since then have done little professionally but study PL/SQL, write lots and lots of PL/SQL code, and write about this best-of-breed database programming language. Of course, I wasnt the only one. Thousands of developers around the world have been building a multitude of applications based on Oracle PL/SQL in the decades since it was released. Best of all, there continues to be a steady influx of new PL/SQL developers. In fact, with the relatively recent emergence of India, China, and other nations as technology powerhouses, I have seen a whole new generation of developers discover and work to master PL/SQL. To help newcomers to PL/SQL make the most of this language, Oracle Magazine has asked me to write a series of articles for PL/SQL beginners, of which this is the first. If you are an experienced PL/SQL developer, you may also find these articles a handy refresher on PL/SQL fundamentals. I will assume for this series that although my readers are new to PL/SQL, they have had some programming experience and are familiar with SQL. My approach throughout, in addition, will be on getting developers productive in PL/SQL as quickly as possible. What Is PL/SQL? To answer this question, it is important to remember that every Website you visit, every application you run is constructed from a stack of software technologies. At the top of the stack is the presentation layer, the screens or interactive devices with which the user directly interacts. (These days the most popular languages for implementing presentation layers are Java and .NET.) At the very bottom of the stack is the machine code that communicates with the hardware. Somewhere in the middle of the technology stack you will find the database, software that enables us to store and manipulate large volumes of complex data. Relational database technology, built around SQL, is the dominant database technology in the world today.

SQL is a very powerful, set-oriented language whose sole purpose is to manipulate the contents of relational databases. If you write applications built on Oracle Database, you (or someone writing code at a lower level in the technology stack) must be executing SQL statements to retrieve data from or change data in that database. Yet SQL cannot be used to implement all business logic and end-user functionality needed in our applications. That brings us to PL/SQL. PL/SQL stands for Procedural Language/Structured Query Language. PL/SQL offers a set of procedural commands (IF statements, loops, assignments), organized within blocks (explained below), that complement and extend the reach of SQL. It is certainly possible to build applications on top of SQL and Oracle Database without using PL/SQL. Utilizing PL/SQL to perform database-specific operations, most notably SQL statement execution, offers several advantages, though, including tight integration with SQL, improved performance through reduced network traffic, and portability (PL/SQL programs can run on any Oracle Database instance). Thus, the front-end code of many applications executes both SQL statements and PL/SQL blocks, to maximize performance while improving the maintainability of those applications. Building Blocks of PL/SQL Programs PL/SQL is a block-structured language. A PL/SQL block is defined by the keywords DECLARE, BEGIN, EXCEPTION, and END, which break up the block into three sections: 1. Declarative: statements that declare variables, constants, and other code elements, which can then be used within that block 2. Executable: statements that are run when the block is executed 3. Exception handling: a specially structured section you can use to catch, or trap, any exceptions that are raised when the executable section runs

Only the executable section is required. You dont have to declare anything in a block, and you dont have to trap exceptions raised in that block. A block itself is an executable statement, so you can nest blocks within other blocks. Here are some examples:

The classic Hello World! block contains an executable section that calls the DBMS_OUTPUT.PUT_LINE procedure to display text on the screen:

BEGIN DBMS_OUTPUT.put_line ('Hello World!'); END;

Functions and procedurestypes of named blocksare discussed later in this article in more detail, as are packages. Briefly, however, a package is a container for multiple functions and procedures. Oracle extends PL/SQL with many supplied or built-in packages.

This next example block declares a variable of type VARCHAR2 (string) with a maximum length of 100 bytes to hold the string Hello World!. DBMS_OUTPUT.PUT_LINE then accepts the variable, rather than the literal string, for display:

DECLARE l_message VARCHAR2 (100) := 'Hello World!'; BEGIN DBMS_OUTPUT.put_line (l_message); END; Note that I named the variable l_message. I generally use the l_ prefix for local variablesvariables defined within a block of codeand the g_ prefix for global variables defined in a package.

This next example block adds an exception section that traps any exception (WHEN OTHERS) that might be raised and displays the error message, which is returned by the SQLERRM function (provided by Oracle).

DECLARE l_message VARCHAR2 (100) := 'Hello World!'; BEGIN DBMS_OUTPUT.put_line (l_message); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line (SQLERRM); END;

The following example block demonstrates the PL/SQL ability to nest blocks within blocks as well as the use of the concatenation operator (||) to join together multiple strings.

DECLARE

l_message VARCHAR2 (100) := 'Hello'; BEGIN DECLARE l_message2 VARCHAR2 (100) := l_message || ' World!'; BEGIN DBMS_OUTPUT.put_line (l_message2); END; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack); END; Running PL/SQL Blocks Once you have written a block of PL/SQL code, you can execute (run) it. There are many different tools for executing PL/SQL code. The most basic is SQL*Plus, a command-line interface for executing SQL statements as well as PL/SQL blocks. Figure 1 shows an example of executing the simplest of my Hello World! example blocks in SQL*Plus.

Figure 1: Executing Hello World! in SQL*Plus The first thing I do after connecting to the database through SQL*Plus is turn on server output, so that calls to DBMS_OUTPUT.PUT_LINE will result in the display of text on my screen. I then type in the code that constitutes my block. Finally I enter a slash (/) to tell SQL*Plus to execute this block. SQL*Plus then runs the block and displays Hello World! on the screen. SQL*Plus is provided by Oracle as a sort of lowest-common-denominator environment in which to execute SQL statements and PL/SQL blocks. Whereas some developers continue to rely solely on SQL*Plus, most use an integrated development environment (IDE).

Among the most popular of these IDEs (based on informal surveys I take in my training sessions) are

Oracle SQL Developer, from Oracle Toad and SQL Navigator, from Quest Software PL/SQL Developer, from Allround Automations

Each tool offers slightly different windows and steps for creating, saving, and running PL/SQL blocks as well as enabling and disabling server output. In this article series, I will assume only that you have access to SQL*Plus and that you will run all my statements in a SQL*Plus command window. Name Those Blocks! All the blocks I have shown you so far are anonymousthey have no names. If using anonymous blocks were the only way you could organize your statements, it would be very hard to use PL/SQL to build a large, complex application. Instead, PL/SQL supports the definition of named blocks of code, also known as subprograms. Subprograms can be procedures or functions. Generally, a procedure is used to perform an action and a function is used to calculate and return a value. I will explore subprograms in much greater detail in an upcoming article in this series. For now, lets make sure you are comfortable with the basic concepts behind subprogram creation. Suppose I need to display Hello World! from multiple places in my application. I very much want to avoid repeating the same logic in all those places. For example, what happens when I need to change the message, perhaps to Hello Universe!? I will have to find all the locations in my code where this logic appears. Instead, I will create a procedure named hello_world by executing the following data definition language (DDL) command: CREATE OR REPLACE PROCEDURE hello_world IS l_message VARCHAR2 (100) := 'Hello World!'; BEGIN DBMS_OUTPUT.put_line (l_message); END hello_world; I have now, in effect, extended PL/SQL. In addition to calling programs created by Oracle and installed in the database (such as DBMS_OUTPUT.PUT_LINE), I can call my own subprogram inside a PL/SQL block:

BEGIN hello_world; END; I have hidden all the details of how I say hello to the world inside the body, or implementation, of my procedure. I can now call this hello_world procedure and have it display the desired message without having to write the call to DBMS_OUTPUT .PUT_LINE or figure out the correct way to format the string. I can call this procedure from any location in my application. So if I ever need to change that string, I will do so in one place, the single point of definition of that string. The hello_world procedure is very simple. Your procedures will have lots more code inside them, and they will almost always also have parameters. Parameters pass information into subprograms when they are called, and they enable you to create subprograms that are more flexible and generic. They can be used in many different contexts. I mentioned earlier that someday I might want to display Hello Universe! instead of Hello World! I could make a copy of my hello_world procedure and change the string it displays: CREATE OR REPLACE PROCEDURE hello_universe IS l_message VARCHAR2 (100) := 'Hello Universe!'; BEGIN DBMS_OUTPUT.put_line (l_message); END hello_universe; I could, however, end up with dozens of variations of the same hello procedure, which will make it very difficult to maintain my application. A much better approach is to analyze the procedure and identify which parts stay the same (are static) when the message needs to change and which parts change. I can then pass the changing parts as parameters and have a single procedure that can be used under different circumstances. So I will change hello_world (and hello_universe) to a new procedure, hello_place: CREATE OR REPLACE PROCEDURE hello_place (place_in IN VARCHAR2) IS l_message VARCHAR2 (100);

BEGIN l_message := 'Hello ' || place_in; DBMS_OUTPUT.put_line (l_message); END hello_place; Right after the name of the procedure, I add open and close parentheses, and inside them I provide a single parameter. I can have multiple parameters, but each parameter follows the same basic form: parameter_name parameter_mode datatype You must, in other words, provide a name for the parameter, the mode or way it will be used (IN = read only), and the type of data that will be passed to the subprogram through this parameter. In this case, I am going to pass a string for read-only use to the hello_place procedure. And I can now say hello to my world and my universe as follows: BEGIN hello_place ('World'); hello_place ('Universe'); END; Later in this series, we will be exploring the concept of reuse and how to avoid repetition, but you should be able to see from this simple example the power of hiding logic behind a named block. Now suppose I dont just want to display my Hello messages. Sometimes I need to save those messages in a database table; at other times, I must pass the string back to the host environment for display in a Web browser. In other words, I need to separate the way the Hello message is constructed from the way it is used (displayed, saved, sent to another program, and so on). I can achieve this desired level of flexibility by moving the code that constructs the message into its own function: CREATE OR REPLACE FUNCTION hello_message (place_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN 'Hello ' || place_in; END hello_message;

This subprogram differs from the original procedure as follows:

The type of program is now FUNCTION, not PROCEDURE. The subprogram name now describes the data being returned, not the action being taken. The body or implementation of the subprogram now contains a RETURN clause that constructs the message and passes it back to the calling block. The RETURN clause after the parameter list sets the type of data returned by the function.

With the code needed to construct the message inside the hello_message function, I can use this message in multiple ways. I can, for example, call the function to retrieve the message and assign it to a variable: DECLARE l_message VARCHAR2 (100); BEGIN l_message := hello_message ('Universe'); END; Note that I call the hello_message function as part of a PL/SQL statement (in this case, an assignment of a string to a variable). The hello_message function returns a string, so it can be used in place of a string in any executable statement. I can also return to my hello_place procedure and replace the code used to build the string with a call to the function: CREATE OR REPLACE PROCEDURE hello_place (place_in IN VARCHAR2) IS BEGIN DBMS_OUTPUT.put_line (hello_message (place_in)); END hello_place; I can also call the function from within a SQL statement. In the following block, I insert the message into a database table: BEGIN

INSERT INTO message_table (message_date, MESSAGE_TEXT) VALUES (SYSDATE, hello_message('Chicago')); END; Although the hello place logic is very simple, it demonstrates the power of assigning names to one or more executable statements (an algorithm) and then referencing that algorithm simply by specifying the name and any required parameters. Named PL/SQL blocks make it possible to construct complex applications that can be understood and maintained with relative ease. About Names in Oracle Database Now that you can see the importance of assigning names to logic, it is time to talk about the rules for names (or, to be more precise, identifiers) in both PL/SQL and, more generally, Oracle Database. Here are the rules for constructing valid identifiers in Oracle Database:

The maximum length is 30 characters. The first character must be a letter, but each character after the first can be either a letter, a numeral (0 through 9), a dollar sign ($), an underscore (_), or a number sign (#). All of the following are valid identifiers:

hello_world hello$world hello#world but these are invalid: 1hello_world hello%world

PL/SQL is case-insensitive with regard to identifiers. PL/SQL treats all of the following as the same identifier:

hello_world Hello_World HELLO_WORLD

To offer you increased flexibility, Oracle Database lets you bypass the restrictions of the second and third rules by enclosing your identifier within double quotes. A quoted identifier can contain any sequence of printable characters excluding double quotes; differences in case will also be preserved. So all of the following strings are valid and distinct identifiers: "Abc" "ABC" "a b c" You will rarely encounter quoted identifiers in PL/SQL code; some development groups use them to conform to their naming conventions or because they find the mixed-case strings easier to read. These same rules apply to the names of database objects such as tables, views, and procedures, with one additional rule: unless you put double quotation marks around the names of those database objects, Oracle Database will store them as uppercase. So when I create a procedure as follows: CREATE OR REPLACE PROCEDURE hello_world IS BEGIN DBMS_OUTPUT.put_line ('Hello World!'); END hello_world; Oracle Database stores this procedure under the name HELLO_WORLD. In the following block, I call this procedure three times, and although the name looks different in all the calls, they all execute the same procedure: BEGIN hello_world; HELLO_WORLD; "HELLO_WORLD"; END; Oracle Database will not, on the other hand, be able to run my procedure if I call it as follows: BEGIN "hello_world";

END; It will look inside the database for a procedure named hello_world rather than HELLO_WORLD. If you dont want your subprogram names to be stored as uppercase, precede and follow that name with double quotation marks when you create the subprogram: CREATE OR REPLACE PROCEDURE "Hello_World" IS BEGIN DBMS_OUTPUT.put_line ('Hello World!'); END "Hello_World"; Running SQL Inside PL/SQL Blocks PL/SQL is a database programming language. Almost all the programs you will ever write in PL/SQL will read from or write toor read from and write toOracle Database by using SQL. Although this series assumes a working knowledge of SQL, you should be aware of the way you call SQL statements from within a PL/SQL block. And heres some very good news: Oracle Database makes it very easy to write and run SQL statements in PL/SQL. For the most part, you simply write the SQL statement directly in your PL/SQL block and then add the code needed to interface between the SQL statement and the PL/SQL code. Suppose, for example, that I have a table named employees, with a primary key column, employee_id, and a last_name column. I can then see the last name of the employee with ID 138, as follows: SELECT last_name FROM employees WHERE employee_id = 138 Now I would like to run this same query inside my PL/SQL block and display the name. To do this, I need to copy the name from the table into a local variable, which I can do with the INTO clause: DECLARE l_name employees.last_name%TYPE; BEGIN SELECT last_name

INTO l_name FROM employees WHERE employee_id = 138; DBMS_OUTPUT.put_line (l_name); END; First I declare a local variable and in doing so introduce another elegant feature of PL/SQL: the ability to anchor the datatype of my variable back to a tables column. (Anchoring is covered in more detail later in this series.) I then execute a query against the database, retrieving the last name for the employee and depositing it directly into the l_name variable. Of course, you will want to do more than run SELECT statements in PL/SQLyou will want to be able to insert into and update tables and delete from them in PL/SQL as well. Here are examples of each type of data manipulation language (DML) statement:

Delete all employees in department 10 and show how many rows were deleted:

DECLARE l_dept_id employees.department_id%TYPE := 10; BEGIN DELETE FROM employees WHERE department_id = l_dept_id; DBMS_OUTPUT.put_line (SQL%ROWCOUNT); END; Directly inside the DELETE statement, I reference the PL/SQL variable. When the block is executed, the variable name is replaced with the actual value, 10, and the DELETE is run by the SQL engine. SQL%ROWCOUNT is a special cursor attribute that returns the number of rows modified by the most recently executed DML statement in my session.

Update all employees in department 10 with a 20 percent salary increase.

DECLARE l_dept_id employees.department_id%TYPE := 10; BEGIN UPDATE employees

SET salary = salary * 1.2 WHERE department_id = l_dept_id; DBMS_OUTPUT.put_line (SQL%ROWCOUNT); END; Insert a new employee into the table. BEGIN INSERT INTO employees (employee_id , last_name , department_id , salary) VALUES (100 , 'Feuerstein' , 10 , 200000); DBMS_OUTPUT.put_line (SQL%ROWCOUNT); END; In this block, I supply all the column values as literals, rather than variables, directly inside the SQL statement. Resources for PL/SQL Developers If you are just getting started with PL/SQL, you should be aware of and take advantage of a multitude of free online resources. Here are some of the most popular and useful ones:

Oracle Technology Networks PL/SQL page, at oracle.com/technetwork/database/features/plsql: an excellent starting point for exploring PL/SQL resources on the popular Oracle Technology Network Website PL/SQL Obsession, at ToadWorld.com/SF: a collection of the authors resources for PL/SQL development, including training materials, presentations, sample code, standards, best-practice videos, and more PL/SQL Challenge, at plsqlchallenge.com: a daily PL/SQL quiz that will help you test and improve your knowledge of PL/SQL

Next: Controlling Block Execution In this article, you learned about how PL/SQL fits into the wider world of Oracle Database. You also learned how to define blocks of code that will execute PL/SQL

Next Steps statements and to name those blocks so that READ PL/SQL column archive your application code can be more easily used and maintained. Finally, you were DISCUSS PL/SQL introduced to the execution of SQL statements inside PL/SQL. Why Nest Blocks? DOWNLOAD Oracle Database 11g You can put BEGIN before any set of one or TEST your PL/SQL knowledge more executable statements and follow it with END, creating a nested block for those statements. There are two key advantages of doing this: (1) defer allocation of memory for variables needed only within that nested block, and (2) constrain the propagation of an exception raised by one of the statements in the nested block. Consider the following block: DECLARE l_message VARCHAR2 (100) := 'Hello'; l_message2 VARCHAR2 (100) := ' World!'; BEGIN IF SYSDATE >= TO_DATE ('01-JAN-2011') THEN l_message2 := l_message || l_message2; DBMS_OUTPUT.put_line (l_message2); ELSE DBMS_OUTPUT.put_line (l_message); END IF; END; It displays Hello World! when todays date (returned by SYSDATE) is at least the first day of 2011; otherwise, it displays only the Hello message. Yet even when this block is run in 2010, it allocates memory for the l_message2 variable. If I restructure this block, the memory for l_message2 will be allocated only after 2010: DECLARE l_message VARCHAR2 (100) := 'Hello'; BEGIN IF SYSDATE > TO_DATE ('01-JAN-2011') THEN DECLARE l_message2 VARCHAR2 (100) := ' World!'; BEGIN l_message2 := l_message || l_message2;

DBMS_OUTPUT.put_line (l_message2); END; ELSE DBMS_OUTPUT.put_line (l_message); END IF; END; Similarly, I can add an exception section to that nested block, trapping errors and enabling the outer block to continue executing: DECLARE l_message VARCHAR2 (100) := 'Hello'; BEGIN DECLARE l_message2 VARCHAR2 (5); BEGIN l_message2 := 'World!'; DBMS_OUTPUT.put_line ( l_message || l_message2); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line ( DBMS_UTILITY.format_error_stack); END; DBMS_OUTPUT.put_line (l_message); END; In this case, the nested block will raise a VALUE_ERROR exception, because l_message2 is too small (maximum of 5 bytes) for the World! string. The exception section of the nested block will trap and display the error. The outer block will then continue executing. A future article in this series will focus on how exception handling works in PL/SQL. Next in this article series, I will show you how to control the flow of execution in your block: conditional logic with IF and CASE; iterative logic with FOR, WHILE, and simple loops; and raising and handling exceptions.

JULY 2011: TECHNOLOGY: PL/SQL

As Published In

July/August 2011

Controlling the Flow of Execution By Steven Feuerstein Part 2 in a series of articles on understanding and using PL/SQL To help newcomers to PL/SQL make the most of this language, Oracle Magazine has asked me to write a series of articles for PL/SQL beginners, of which this is the second. If you are an experienced PL/SQL developer, you may find these articles a handy refresher on PL/SQL fundamentals. I will assume for this series that although the readers are new to PL/SQL, they have had some programming experience and are familiar with SQL. My approach throughout, in addition, will be to get you productive in PL/SQL as quickly as possible. PL/SQL: At Your Command There is one way PL/SQL is just like every other programming language you will ever use: it (the PL/SQL runtime engine) does only exactly what you tell it to do. Each block of PL/SQL code you write contains one or more statements implementing complex business rules or processes. When you run that block of code, as either an anonymous block or a script or by calling a stored program unit that contains all the logic, Oracle Database follows the directions you specify in that block. It is therefore critical to know how to specify which statements should be run, under what circumstances, and with what frequency. To do this, Oracle Database offers conditional and iterative constructs. This article introduces you to the IF statement, the CASE statement and expression, and the various types of loops PL/SQL supports. Conditional Branching in Code Almost every piece of code you write will require conditional control, the ability to direct the flow of execution through your program, based on a condition. You do this with IFTHEN-ELSE and CASE statements. There are also CASE expressions; although not the same as CASE statements, they can sometimes be used to eliminate the need for an IF or CASE statement altogether. IF. The IF statement enables you to implement conditional branching logic in your programs. With it, youll be able to implement requirements such as the following:

If the salary is between $10,000 and $20,000, apply a bonus of $1,500. If the collection contains more than 100 elements, truncate it.

The IF statement comes in three flavors, as shown in Table 1. Lets take a look at some examples of IF statements. IF Type Characteristics IF THEN This is the simplest form of the IF statement. The condition END IF; between IF and THEN determines whether the set of statements between THEN and END IF should be executed. If

the condition evaluates to FALSE or NULL, the code will not be executed. This combination implements either/or logic: based on the IF THEN condition between the IF and THEN keywords, execute the ELSE code either between THEN and ELSE or between ELSE and END IF; END IF. One of these two sections of statements is executed. This last and most complex form of the IF statement selects a condition that is TRUE from a series of mutually exclusive IF THEN conditions and then executes the set of statements associated ELSIF with that condition. If youre writing IF statements like this in ELSE any Oracle Database release from Oracle9I Database Release END IF; 1 onward, you should consider using searched CASE statements instead. Table 1: IF statement flavors IF-THEN. The following statement compares two numeric values. Note that if one of these two values is NULL, the entire expression will return NULL. In the following example, the bonus is not given when salary is NULL: IF l_salary > 40000 THEN give_bonus (l_employee_id,500); END IF; There are exceptions to the rule that a NULL in a Boolean expression leads to a NULL result. Some operators and functions are specifically designed to deal with NULLs in a way that leads to TRUE and FALSE (and not NULL) results. For example, you can use IS NULL to test for the presence of a NULL: IF l_salary > 40000 OR l_salary IS NULL THEN give_bonus (l_employee_id,500); END IF; In this example, salary IS NULL evaluates to TRUE in the event that salary has no value and otherwise to FALSE. Employees whose salaries are missing will now get bonuses too. (As indeed they probably should, considering that their employer was so inconsiderate as to lose track of their pay in the first place.) IF-THEN-ELSE. Here is an example of the IF-THEN-ELSE construct (which builds upon the IF-THEN construct): IF l_salary <= 40000 THEN

give_bonus (l_employee_id, 0); ELSE give_bonus (l_employee_id, 500); END IF; In this example, employees with a salary greater than $40,000 will get a bonus of $500, whereas all other employees will get no bonus at all. Or will they? What happens if the salary, for whatever reason, happens to be NULL for a given employee? In that case, the statements following the ELSE will be executed and the employee in question will get the bonus, which is supposed to go only to highly paid employees. If the salary could be NULL, you can protect yourself against this problem by using the NVL function: IF NVL(l_salary,0) <= 40000 THEN give_bonus (l_employee_id, 0); ELSE give_bonus (l_employee_id, 500); END IF; The NVL function will return 0 whenever salary is NULL, ensuring that any employees with a NULL salary will also get no bonus (those poor employees). The important thing to remember is that one of the two sequences of statements will always execute, because IF-THEN-ELSE is an either/or construct. Once the appropriate set of statements has been executed, control passes to the statement immediately following END IF. IF-ELSIF. Now lets take a look at the use of ELSIF. This last form of the IF statement comes in handy when you have to implement logic that has many alternatives; it is not an either/or situation. The IF-ELSIF formulation provides a way to handle multiple conditions within a single IF statement. In general, you should use ELSIF with mutually exclusive alternatives (that is, only one condition can be TRUE for any execution of the IF statement). Each ELSIF clause must have a THEN after its condition. (Only the ELSE keyword does not need the THEN keyword.) The ELSE clause in the IF-ELSIF is the otherwise of the statement. If none of the conditions evaluates to TRUE, the statements in the ELSE clause will be executed. But the ELSE clause is optional. You can code an IF-ELSIF that has only IF and ELSIF clauses. In such a case, if none of the conditions is TRUE, no statements inside the IF block will be executed. Heres an example of an IF-ELSIF statement that checks for three different conditions and contains an ELSE clause in case none of those conditions evaluates to TRUE. IF l_salary BETWEEN 10000 AND 20000 THEN give_bonus(l_employee_id, 1000);

ELSIF l_salary > 20000 THEN give_bonus(l_employee_id, 500); ELSE give_bonus(l_employee_id, 0); END IF; CASE: A Useful Alternative to IF The CASE statement and expression offer another way to implement conditional branching. By using CASE instead of IF, you can often express conditions more clearly and even simplify your code. There are two forms of the CASE statement:

Simple CASE statement. This one associates each of one or more sequences of PL/SQL statements with a value. The simple CASE statement chooses which sequence of statements to execute, based on an expression that returns one of those values. Searched CASE statement. This one chooses which of one or more sequences of PL/SQL statements to execute by evaluating a list of Boolean conditions. The sequence of statements associated with the first condition that evaluates to TRUE is executed.

In addition to CASE statements, PL/SQL also supports CASE expressions. A CASE expression is similar in form to a CASE statement and enables you to choose which of one or more expressions to evaluate. The result of a CASE expression is a single value, whereas the result of a CASE statement is the execution of a sequence of PL/SQL statements. Simple CASE statements. A simple CASE statement enables you to choose which of several sequences of PL/SQL statements to execute, based on the results of a single expression. Simple CASE statements take the following form: CASE expression WHEN result1 THEN statements1 WHEN result2 THEN statements2 ... ELSE statements_else END CASE; Here is an example of a simple CASE statement that uses the employee type as a basis for selecting the proper bonus algorithm: CASE l_employee_type WHEN 'S' THEN

award_bonus (l_employee_id); WHEN 'H' THEN award_bonus (l_employee_id); WHEN 'C' THEN award_commissioned_bonus ( l_employee_id); ELSE RAISE invalid_employee_type; END CASE; This CASE statement has an explicit ELSE clause, but the ELSE is optional. When you do not explicitly specify an ELSE clause of your own, PL/SQL implicitly uses the following: ELSE RAISE CASE_NOT_FOUND; In other words, if you dont specify an ELSE clause and none of the results in the WHEN clauses matches the result of the CASE expression, PL/SQL will raise a CASE_NOT_FOUND error. This behavior is different from that of IF statements. When an IF statement lacks an ELSE clause, nothing happens when the condition is not met. With CASE, the analogous situation leads to an error. Searched CASE Statements. A searched CASE statement evaluates a list of Boolean expressions and, when it finds an expression that evaluates to TRUE, executes a sequence of statements associated with that expression. Searched CASE statements have the following form: CASE WHEN expression1 THEN statements1 WHEN expression2 THEN statements2 ... ELSE statements_else END CASE; A searched CASE statement is a perfect fit for the problem of implementing my bonus logic, as shown below: CASE WHEN l_salary BETWEEN 10000 AND 20000 THEN

give_bonus(l_employee_id, 1500); WHEN salary > 20000 THEN give_bonus(l_employee_id, 1000); ELSE give_bonus(l_employee_id, 0); END CASE; Tip: Because WHEN clauses are evaluated in order, you may be able to squeeze some extra efficiency out of your code by listing the most likely WHEN clauses first. In addition, if you have WHEN clauses with expensive expressions (that is, ones requiring lots of CPU cycles and memory), you may want to list those last to minimize the chances that they will be evaluated. Use searched CASE statements when you want to use Boolean expressions as a basis for identifying a set of statements to execute. Use simple CASE statements when you can base that decision on the result of a single expression. Using CASE expressions. A CASE expression returns a single value, the result of whichever result_expression is chosen. Each WHEN clause must be associated with exactly one expression (not statement). Do not use semicolons or END CASE to mark the end of the CASE expression. CASE expressions are terminated by a simple END. A searched CASE expression can be used to simplify the code for applying bonuses. Rather than writing an IF or CASE statement with three different calls to give_bonus, I can call give_bonus just once and use a CASE expression in place of the second argument: give_bonus(l_employee_id, CASE WHEN l_salary BETWEEN 10000 AND 20000 THEN 1500 WHEN l_salary > 40000 THEN 500 ELSE 0 END); Unlike with the CASE statement, no error is raised in the event that no WHEN clause is selected in a CASE expression. Instead, if no WHEN condition is met, a CASE expression will simply return NULL. Iterative Processing with Loops Loops in code give you a way to execute the same body of code more than once. Loops are a very common element of programming, because so much of the real-world activity modeled by our programs involves repetitive processing. We might need, for example, to perform an operation for each month of the previous year. To cite a common example

from Oracles famous employees table, we might want to update information for all the employees in a given department. PL/SQL provides three kinds of loop constructs:

The FOR loop (numeric and cursor) The simple (or infinite) loop The WHILE loop

Each type of loop is designed for a specific purpose with its own nuances, rules for use, and guidelines for high-quality construction. To give you a feel for how the different loops solve their problems in different ways, consider the following three loop examples. In each case, the procedure makes a call to display_total_sales for a particular year, for each year between the start and end argument values. The FOR loop. Oracle Database offers both a numeric and a cursor FOR loop. With the numeric FOR loop, you specify the start and end integer values, and PL/SQL does the rest of the work for you, iterating through each integer value between the start and the end and then terminating the loop: PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER ,end_year_in IN PLS_INTEGER ) IS BEGIN FOR l_current_year IN start_year_in .. end_year_in LOOP display_total_sales (l_current_year); END LOOP; END display_multiple_years; The cursor FOR loop has the same basic structure, but with it you supply an explicit cursor or SELECT statement in place of the low/high integer range: PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER ,end_year_in IN PLS_INTEGER ) IS BEGIN FOR l_current_year IN ( SELECT * FROM sales_data WHERE year

BETWEEN start_year_in AND end_year_in) LOOP display_total_sales (l_current_year); END LOOP; END display_multiple_years; In both the numeric and the cursor FOR loop, Oracle Database implicitly declares the iterator (in the examples above, it is l_current_year) for you, as either an integer or a record. You do not have to (and should not) declare a variable with that same name, as in PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER ,end_year_in IN PLS_INTEGER ) IS l_current_year INTEGER; /* NOT NEEDED */ BEGIN FOR l_current_year IN start_year_in .. end_year_in In fact, if you do declare such a variable, it will not be used by the FOR loop. It is, more specifically, a different integer variable than that declared implicitly by Oracle Database and used within the body of the loop. The simple loop. Its called simple for a reason: it starts simply with the LOOP keyword and ends with the END LOOP statement. The loop will terminate if you execute an EXIT, EXIT WHEN, or RETURN within the body of the loop (or if an exception is raised). Listing 1 presents a simple loop. Listing 2 presents a simple loop that iterates through the rows of a cursor (logically equivalent to the cursor FOR loop of the previous section). Code Listing 1: A simple loop PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER ,end_year_in IN PLS_INTEGER ) IS l_current_year PLS_INTEGER := start_year_in; BEGIN LOOP EXIT WHEN l_current_year > end_year_in;

display_total_sales (l_current_year); l_current_year := l_current_year + 1; END LOOP; END display_multiple_years; Compare the cursor-based simple loop in Listing 2 with the cursor FOR loop. Note that I must explicitly open the cursor, fetch the next record, determine by using the %NOTFOUND cursor attribute whether or not I am done fetching, and then close the cursor after the loop terminates. Code Listing 2: A simple loop that iterates through the rows of a cursor PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER , end_year_in IN PLS_INTEGER) IS CURSOR years_cur IS SELECT * FROM sales_data WHERE year BETWEEN start_year_in AND end_year_in; l_year sales_data%ROWTYPE; BEGIN OPEN years_cur; LOOP FETCH years_cur INTO l_year; EXIT WHEN years_cur%NOTFOUND; display_total_sales (l_year); END LOOP; CLOSE years_cur; END display_multiple_years; The cursor FOR loop requires none of these steps; Oracle Database performs all steps (open, fetch, terminate, close) implicitly. Although a simple loop should not be used to fetch row by row through a cursors dataset, it should be used when (1) you may need to conditionally exit from the loop (with an EXIT statement) and (2) you want the body of the loop to execute at least once. The WHILE loop. The WHILE loop is very similar to the simple loop; a critical difference is that it checks the termination condition up front. That is, a WHILE loop may not even execute its body a single time. Listing 3 demonstrates the WHILE loop.

Code Listing 3: A WHILE loop PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER ,end_year_in IN PLS_INTEGER ) IS l_current_year PLS_INTEGER := start_year_in; BEGIN WHILE (l_current_year <= end_year_in) LOOP display_total_sales (l_current_year); l_current_year := l_current_year + 1; END LOOP; END display_multiple_years; The WHILE loop consists of a condition (a Boolean expression) and a loop body. Before each iteration of the body, Oracle Database evaluates the condition. If it evaluates to TRUE, the loop body will execute. If it evaluates to FALSE or NULL, the loop will terminate. You must then make sure to include code in the body of the loop that will affect the evaluation of the conditionand, at some point, cause the loop to stop. In the procedure in Listing 3, that code is l_current_year := l_current_year + 1 In other words, move to the next year until the total sales for all specified years are displayed. If your WHILE loop does not somehow change the way the condition is evaluated, that loop will never terminate. One Way In, One Way Out In all the examples in the previous section, the FOR loop clearly requires the smallest amount of code. Generally you want to find the simplest, most readable implementation for your requirements. Does that mean that you should always use a FOR loop? Not at all. Using the FOR loop is the best solution for the scenario described because I needed to run the body of the loop a fixed number of times. In many other situations, the number of times a loop must execute will vary, depending on the state of the data in the application. You may also need to terminate the loop when a certain condition has occurred; in this case, a FOR loop is not a good fit. One important and fundamental principle in structured programming is one way in, one way outthat is, a program should have a single point of entry and a single point of

exit. A single point of entry is not an issue with PL/SQLno matter what kind of loop you are using, there is always only one entry point into the loop: the first executable statement following the LOOP keyword. It is quite possible, however, to construct loops that have multiple exit paths. Avoid this practice. Having multiple ways of terminating a loop results in code that is much harder to debug and maintain than it would be otherwise. In particular, you should follow these two guidelines for loop termination: 1. Do not use EXIT or EXIT WHEN statements within FOR and WHILE loops. You should use a FOR loop only when you want to iterate through all the values (integer or record) specified in the range. An EXIT inside a FOR loop disrupts this process and subverts the intent of that structure. A WHILE loop, on the other hand, specifies its termination condition in the WHILE statement itself. Listing 4 presents an example of a FOR loop with a conditional exit. I want to display the total sales for each year within the specified range. If I ever encounter a year with zero sales, however, I should stop the loop. Code Listing 4: A FOR loop with a conditional exit PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER , end_year_in IN PLS_INTEGER) IS BEGIN FOR l_current_year IN start_year_in .. end_year_in LOOP display_total_sales (l_current_year); EXIT WHEN total_sales (l_current_year) = 0; END LOOP; END display_multiple_years; There are now two ways out of the loop. In this situation, I will rewrite the FOR loop as a WHILE loop, which means I must now also declare the iterator and add to it within the loop, as shown in Listing 5. 2. Do not use RETURN or GOTO statements within a loopthese cause the premature, unstructured termination of the loop.

Code Listing 5: A WHILE loop with one exit PROCEDURE display_multiple_years ( start_year_in IN PLS_INTEGER , end_year_in IN PLS_INTEGER) IS

l_current_year PLS_INTEGER := start_year_in; BEGIN WHILE ( l_current_year <= end_year_in AND total_sales (l_current_year) > 0) LOOP Next Steps display_total_sales (l_current_year); l_current_year := l_current_year + 1; READ END LOOP; PL/SQL 101, Part 1 END display_multiple_years; PL/SQL columns Listing 6 presents an example of a FOR loop with a RETURN in it. The total_sales function returns the total sales across the specified years, but if any year has $0 in sales, the function should terminate the loop and return the current total sales. Code Listing 6: A FOR loop with two instances of RETURN DISCUSS PL/SQL TEST your PL/SQL knowledge DOWNLOAD Oracle Database 11g

FUNCTION total_sales ( start_year_in IN PLS_INTEGER , end_year_in IN PLS_INTEGER) RETURN PLS_INTEGER IS l_return PLS_INTEGER := 0; BEGIN FOR l_current_year IN start_year_in .. end_year_in LOOP IF total_sales (l_current_year) = 0 THEN RETURN l_return; ELSE l_return := l_return + total_sales (l_current_year); END IF; END LOOP; RETURN l_return; END total_sales; Note that the loop terminates in one of two ways: either by iterating through all integers between the start and end years or by executing the RETURN inside the loop. In addition and related to that, this function now has two instances of RETURN, which means that there are two ways out of the function. This is also not a recommended way to design your functions. You should have just a single RETURN in your executable section, the last line of the function.

I can restructure this function so that both the loop and the function have just one way out, as shown in Listing 7. Code Listing 7: A loop revision with one way out FUNCTION total_sales ( start_year_in IN PLS_INTEGER , end_year_in IN PLS_INTEGER) RETURN PLS_INTEGER IS l_current_year PLS_INTEGER := start_year_in; l_return PLS_INTEGER := 0; BEGIN WHILE (l_current_year <= end_year_in AND total_sales (l_current_year) > 0) LOOP l_return := l_return + total_sales (l_current_year); l_current_year := l_current_year + 1; END LOOP; RETURN l_return; END total_sales; All the logic required to terminate the loop is now in the WHILE condition, and after the loop is finished, the function executes a single RETURN to send back the total sales value. This second implementation is now simpler and easier to understandand that is for a program that itself is already quite simple. When you work with much more complex algorithms, following the one way in, one way out guidelines will have an even greater impact on the readability of your code. In this article, you learned about how to tell the PL/SQL compiler to conditionally and iteratively execute the statements in your block. This control will enable you to write stored program units that mirror the business process flow defined by your users. Spoiler Alert: The next article in this series, Part 3, will discuss working with strings in PL/SQL programs.

SEPTEMBER 2011: TECHNOLOGY: PL/SQL Working with Strings By Steven Feuerstein

As Published In

September/October 2011

Part 3 in a series of articles on understanding and using PL/SQL

Take the Challenge! Each of my PL/SQL 101 articles offers a quiz to test your knowledge of the information provided in the article. The quiz questions are shown below and also Every application needs data. That seems rather obvious, doesnt it? An application is at PL/SQL Challenge (plsqlchallenge.com), a Website that almost always built on top of database tables. Those tables are full of different kinds offers online quizzes for the PL/SQL language. You can read and answer the of data. And the programs you write quiz here, and then check your answers in whether they are in PL/SQL or another the next issue. If, however, you take the languagemanipulate that data. It is, therefore, extremely important for you to be quiz at PL/SQL Challenge, you will be aware of the different datatypes supported entered into a raffle to win your choice of by PL/SQL and how you can work with those an e-book from OReilly Media (oreilly.com). datatypes. As you might expect, there is an awful lot to learn about datatypes, and not all of that knowledge can fit into a single article. So I will start with one of the most common types of data: strings. Very few database tables and programs do not contain strings strings such as a company name, address information, descriptive text, and so on. As a result, you quite often need to do the following:

Question 1 What will be displayed after executing this block? BEGIN sys.DBMS_OUTPUT.put_line ( INSTR ('steven feuerstein' , 'e' , -1 , 2)); END; Question 2 True or false: When assigning a literal value to a string, that value may not contain within it any single quotes. Question 3

Declare string variables and constants Manipulate the contents of a string (remove characters, join together multiple strings, and so on) Move string data between PL/SQL programs and database tables

This article gives you the information you need to begin working with strings in your PL/SQL programs. What Is a String? A string, also referred to as character data, is a sequence of selected symbols from a particular set of characters. In other words, the symbols in a string might consist of English letters, such as A or B. They might also consist of Chinese characters, such as . There are three kinds of strings in PL/SQL:

What will be displayed after executing this block? BEGIN DBMS_OUTPUT.put_line ( 'REPLACE=' || REPLACE ('steven feuerstein' , 'e' , NULL)); DBMS_OUTPUT.put_line ( 'TRANSLATE=' || TRANSLATE ('steven feuerstein' , 'e' , NULL)); END;

Fixed-length strings. The string is right-padded with spaces to the length specified in the declaration. (See Declaring String Variables, to see padding in action.) Variable-length strings. A maximum length for the string is specified (and it must be no greater than 32,767), but no padding takes place. Character large objects (CLOBs). CLOBs are variable-length strings that can be up to 128 terabytes. Strings can be literals or variables. A string literal begins and ends with a single quotation mark: 'This is a string literal' If you need to embed a single quote inside a string literal, you can type in two single quotes right next to one another, as in: 'This isn''t a date' You can also use the q character to indicate an alternative terminating character for the literal: q'[This isn't a date]' A string variable is an identifier declared with a string datatype and then assigned a value (which could be a literal or an expression). Declaring String Variables To work with strings in your PL/SQL programs, you declare variables to hold the string values. To declare a string variable, you must select from one of the many string datatypes Oracle Database offers, including CHAR, NCHAR, VARCHAR2, NVARCHAR2, CLOB, and NCLOB. The datatypes that are prefixed with an N are national character set datatypes, which means they are used to store Unicode character data. (Unicode is a universal encoded character set that can store information in any language using a single character set.) To declare a variable-length string, you must provide the maximum length of that string. The following code declares a variable, using the VARCHAR2 datatype, that will hold a company name, which cannot (in this declaration) have more than 100 characters: DECLARE l_company_name VARCHAR2(100); You must provide the maximum length; if you leave it out, Oracle Database raises a compile error, as shown below: SQL> DECLARE 2 l_company_name VARCHAR2; 3 BEGIN 4 l_company_name := 'Oracle Corporation';

5 END; 6 / l_company_name VARCHAR2; * ERROR at line 2: ORA-06550: line 2, column 21: PLS-00215: String length constraints must be in range (1 .. 32767) To declare a fixed-length string, use the CHAR datatype: DECLARE l_yes_or_no CHAR(1) := 'Y'; With CHAR (unlike with VARCHAR2) you do not have to specify a maximum length for a fixed-length variable. If you leave off the length constraint, Oracle Database automatically uses a maximum length of 1. In other words, the two declarations below are identical: DECLARE l_yes_or_no1 CHAR(1) := 'Y'; l_yes_or_no2 CHAR := 'Y'; If you declare a CHAR variable with a length greater than 1, Oracle Database automatically pads whatever value you assign to that variable with spaces to the maximum length specified. Finally, to declare a character large object, use the CLOB datatype. You do not specify a maximum length; the length is determined automatically by Oracle Database and is based on the database block size. Here is an example: DECLARE l_lots_of_text CLOB; So, how do you determine which datatype to use in your programs? Here are some guidelines:

If your string might contain more than 32,767 characters, use the CLOB (or NCLOB) datatype. If the value assigned to a string always has a fixed length (such as a U.S. Social Security number, which always has the same format and length, NNN-NNNNNN), use CHAR (or NCHAR). Otherwise (and, therefore, most of the time), use the VARCHAR2 datatype (or NVACHAR2, when working with Unicode data).

Using the CHAR datatype for anything but strings that always have a fixed number of characters can lead to unexpected and undesirable results. Consider the following

block, which mixes variable and fixed-length strings: DECLARE l_variable VARCHAR2 (10) := 'Logic'; l_fixed CHAR (10) := 'Logic'; BEGIN IF l_variable = l_fixed THEN DBMS_OUTPUT.put_line ('Equal'); ELSE DBMS_OUTPUT.put_line ('Not Equal'); END IF; END; At first glance, you would expect that the word Equal would be displayed after execution. That is not the case. Instead, Not Equal is displayed, because the value of l_fixed has been padded to a length of 10 with spaces. Consider the padding demonstrated in the following block; you would expect the block to display Not Equal: BEGIN IF 'Logic' = 'Logic ' THEN DBMS_OUTPUT.put_line ('Equal'); ELSE DBMS_OUTPUT.put_line ('Not Equal'); END IF; END; You should, as a result, be very careful about the use of the CHAR datatype, whether as the type of a variable, database column, or parameter. Once you have declared a variable, you can assign it a value, change its value, and perform operations on the string contained in that variable using string functions and operators. For the rest of this article, I focus on the VARCHAR2 datatype. Using Built-in Functions with Strings Once you assign a string to a variable, you most likely need to analyze the contents of that string, change its value in some way, or combine it with other strings. Oracle Database offers a wide array of built-in functions to help you with all such requirements. Lets take a look at the most commonly used of these functions. Concatenate multiple strings. One of the most basic and frequently needed operations on strings is to combine or concatenate them together. PL/SQL offers two ways to do this:

The CONCAT built-in function

The || (concatenation) operator

The CONCAT function accepts two strings as its arguments and returns those two strings stuck together. The concatenation operator also concatenates together two strings, but it is easier to use when combining more than two strings, as you can see in this example: DECLARE l_first VARCHAR2 (10) := 'Steven'; l_middle VARCHAR2 (5) := 'Eric'; l_last VARCHAR2 (20) := 'Feuerstein'; BEGIN /* Use the CONCAT function */ DBMS_OUTPUT.put_line ( CONCAT ('Steven', 'Feuerstein')); /* Use the || operator */ DBMS_OUTPUT.put_line ( l_first || ' ' || l_middle || ' ' || l_last); END; / The output from this block is: StevenFeuerstein Steven Eric Feuerstein In my experience, you rarely encounter the CONCAT function. Instead, the || operator is almost universally used by PL/SQL developers. If either of the strings passed to CONCAT or || is NULL or (a zero-length string), both the function and the operator simply return the non-NULL string. If both strings are NULL, NULL is returned. Change the case of a string. Three built-in functions change the case of characters in a string:

UPPER changes all characters to uppercase. LOWER changes all characters to lowercase. INITCAP changes the first character of each word to uppercase (characters are delimited by a white space or non-alphanumeric character).

Listing 1 shows some examples that use these case-changing functions. Code Listing 1: Examples of case-changing functions

SQL> DECLARE 2 l_company_name VARCHAR2 (25) := 'oraCLE corporatION'; 3 BEGIN 4 DBMS_OUTPUT.put_line (UPPER (l_company_name)); 5 DBMS_OUTPUT.put_line (LOWER (l_company_name)); 6 DBMS_OUTPUT.put_line (INITCAP (l_company_name)); 7 END; 8 / ORACLE CORPORATION oracle corporation Oracle Corporation Extract part of a string. One of the most commonly utilized built-in functions for strings is SUBSTR, which is used to extract a substring from a string. When calling SUBSTR, you provide the string, the position at which the desired substring starts, and the number of characters in the substring. Listing 2 shows some examples that use the SUBSTR function. Code Listing 2: Examples of SUBSTR function DECLARE l_company_name VARCHAR2 (6) := 'Oracle'; BEGIN /* Retrieve the first character in the string */ DBMS_OUTPUT.put_line ( SUBSTR (l_company_name, 1, 1)); /* Retrieve the last character in the string */ DBMS_OUTPUT.put_line ( SUBSTR (l_company_name, -1, 1)); /* Retrieve three characters, starting from the second position. */ DBMS_OUTPUT.put_line ( SUBSTR (l_company_name, 2, 3)); /* Retrieve the remainder of the string, starting from the second position. */ DBMS_OUTPUT.put_line ( SUBSTR (l_company_name, 2)); END; / The output from this block is: O e rac racle

As you can see, with the SUBSTR function you can specify a negative starting position for the substring, in which case Oracle Database counts backward from the end of the string. If you do not provide a third argumentthe number of characters in the substring Oracle Database automatically returns the remainder of the string from the specified position. Find a string within another string. Use the INSTR function to determine where (and if) a string appears within another string. INSTR accepts as many as four arguments:

The string to be searched (required). The substring of interest (required). The starting position of the search (optional). If the value is negative, count from the end of the string. If no value is provided, Oracle Database starts at the beginning of the string; that is, the starting position is 1. The Nth occurrence of the substring (optional). If no value is provided, Oracle Database looks for the first occurrence.

Listing 3 shows some examples that use the INSTR function. Code Listing 3: Examples of INSTR function BEGIN /* Find the location of the first "e" */ DBMS_OUTPUT.put_line ( INSTR ('steven feuerstein', 'e')); /* Find the location of the first "e" starting from position 6 */ DBMS_OUTPUT.put_line ( INSTR ('steven feuerstein' , 'e' , 6)); /* Find the location of the first "e" starting from the 6th position from the end of string and counting to the left. */ DBMS_OUTPUT.put_line ( INSTR ('steven feuerstein' , 'e' , -6)); /* Find the location of the 3rd "e" starting from the 6th position from the end of string. */ DBMS_OUTPUT.put_line ( INSTR ('steven feuerstein' , 'e' , -6 , 3));

END; / The output from this block is: 3 9 11 5 INSTR is a very flexible and handy utility. It can easily be used to determine whether or not a substring appears at all in a string. Here is a Boolean function that does just that: CREATE OR REPLACE FUNCTION is_in_string ( string_in IN VARCHAR2 ,substring_in IN VARCHAR2) RETURN BOOLEAN IS BEGIN RETURN INSTR (string_in , substring_in) > 0; END is_in_string; / Pad a string with spaces (or other characters). I warned earlier about using the CHAR datatype, because Oracle Database pads your string value with spaces to the maximum length specified in the declaration. However, there are times, primarily when generating reports, when you want to put spaces (or other characters) in front of or after the end of your string. For these situations, Oracle Database offers LPAD and RPAD. When you call these functions, you specify the length to which you want your string padded and with what character or characters. If you do not specify any pad characters, Oracle Database defaults to padding with spaces. Listing 4 shows some examples that use these LPAD and RPAD padding functions. Code Listing 4: Examples of padding functions DECLARE l_first VARCHAR2 (10) := 'Steven'; l_last VARCHAR2 (20) := 'Feuerstein'; l_phone VARCHAR2 (20) := '773-426-9093'; BEGIN /* Indent the subheader by 3 characters */ DBMS_OUTPUT.put_line ('Header'); DBMS_OUTPUT.put_line ( LPAD ('Sub-header', 13, '.'));

/* Add "123" to the end of the string, until the 20 character is reached.*/ DBMS_OUTPUT.put_line ( RPAD ('abc', 20, '123')); /* Display headers and then values to fit within the columns. */ DBMS_OUTPUT.put_line ( /*1234567890x12345678901234567890x*/ 'First Name Last Name Phone'); DBMS_OUTPUT.put_line ( RPAD (l_first, 10) || ' ' || RPAD (l_last, 20) || ' ' || l_phone); END; / The output from this block is: Header ...Sub-header abc12312312312312312 First Name Last Name Steven Feuerstein

Phone 773-426-9093

Replace characters in a string. Oracle Database provides a number of functions that allow you to selectively change one or more characters in a string. You might need, for example, to replace all spaces in a string with the HTML equivalent ( ) so the text is displayed properly in a browser. Two functions take care of such needs for you:

REPLACE replaces a set or pattern of characters with another set. TRANSLATE translates or replaces individual characters.

Listing 5 shows some examples of these two character-replacement built-in functions. Notice that when you are replacing a single character, the effect of REPLACE and TRANSLATE is the same. When replacing multiple characters, REPLACE and TRANSLATE act differently. The call to REPLACE asked that appearances of abc be replaced with 123. If, however, any of the individual characters (a, b, or c) appeared in the string outside of this pattern (abc), they would not be replaced. Code Listing 5: Examples of character replacement functions DECLARE l_name VARCHAR2 (50) := 'Steven Feuerstein'; BEGIN /* Replace all e's with the number 2. Since you are replacing a single character, you can use either REPLACE or TRANSLATE. */

DBMS_OUTPUT.put_line ( REPLACE (l_name, 'e', '2')); DBMS_OUTPUT.put_line ( TRANSLATE (l_name, 'e', '2')); /* Replace all instances of "abc" with "123" */ DBMS_OUTPUT.put_line ( REPLACE ('abc-a-b-c-abc' , 'abc' , '123')); /* Replace "a" with "1", "b" with "2", "c" with "3". */ DBMS_OUTPUT.put_line ( TRANSLATE ('abc-a-b-c-abc' , 'abc' , '123')); END; / The output from this block is: St2v2n F2u2rst2in St2v2n F2u2rst2in 123-a-b-c-123 123-1-2-3-123 The call to TRANSLATE, however, specified that any occurrence of each of the individual characters be replaced with the character in the third argument in the same position. Generally, you should use REPLACE whenever you need to replace a pattern of characters, while TRANSLATE is best applied to situations in which you need to replace or substitute individual characters in the string. Remove characters from a string. What LPAD and RPAD giveth, TRIM, LTRIM, and RTRIM taketh away. Use these trim functions to remove characters from either the beginning (left) or end (right) of the string. Listing 6 shows an example of both RTRIM and LTRIM. Code Listing 6: Examples of LTRIM and RTRIM functions DECLARE a VARCHAR2 (40) := 'This sentence has too many periods....'; b VARCHAR2 (40) := 'The number 1'; BEGIN DBMS_OUTPUT.put_line ( RTRIM (a, '.')); DBMS_OUTPUT.put_line ( LTRIM (

b , 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' || 'abcdefghijklmnopqrstuvwxyz')); END; The output from this block is: This sentence has too many periods 1 RTRIM removed all the periods, because the second argument specifies the character (or characters) to trim, in this case, a period. The call to LTRIM demonstrates that you can specify multiple characters to trim. In this case, I asked that all letters and spaces be trimmed from the beginning of string b, and I got what I asked for. The default behavior of both RTRIM and LTRIM is to trim spaces from the beginning or end of the string. Specifying RTRIM(a) is the same as asking for RTRIM(a, ). The same goes for LTRIM(a) and LTRIM(a, ). The other trimming function is just plain TRIM. TRIM works a bit differently from LTRIM and RTRIM, as you can see in this block: DECLARE x VARCHAR2 (30) := '.....Hi there!.....'; BEGIN DBMS_OUTPUT.put_line ( TRIM (LEADING '.' FROM x)); DBMS_OUTPUT.put_line ( TRIM (TRAILING '.' FROM x)); DBMS_OUTPUT.put_line ( TRIM (BOTH '.' FROM x)); --The default is to trim --from both sides DBMS_OUTPUT.put_line ( TRIM ('.' FROM x)); --The default trim character --is the space: DBMS_OUTPUT.put_line (TRIM (x)); END; The output from this block is: Hi there!..... .....Hi there! Hi there! Hi there!

.....Hi there!..... With TRIM, you can trim from either side or from both sides. However, you can specify only a single character to remove. You cannot, for example, write the following: TRIM(BOTH ',.;' FROM x) If you need to remove more than one character from the front and back of a string, you need to use RTRIM and LTRIM: RTRIM(LTRIM(x,',.;'),',.;') You can also use TRANSLATE to remove characters from a string by replacing them with (or translating them into) NULL. You must, however, take care with how you specify this replacement. Suppose I want to remove all digits (0 through 9) from a string. My first attempt yields the following block: BEGIN /* Remove all digits (0-9) from the string. */ DBMS_OUTPUT.put_line ( TRANSLATE ('S1t2e3v4e56n' , '1234567890' , '')); END; / When I execute this block, however, nothing (well, a NULL string) is displayed. This happens because if any of the arguments passed to TRANSLATE are NULL (or a zerolength string), the function returns a NULL value. So all three arguments must be non-NULL, which means that you need to put at the start of the second and third arguments a character that will simply be replaced with itself, as in the following: BEGIN /* Remove all digits (0-9) from the string. */ DBMS_OUTPUT.put_line ( TRANSLATE ('S1t2e3v4e56n' , 'A1234567890' , 'A')); END; / Now, A is replaced with A and the remaining characters in the string are replaced with NULL, so the string Steven is then displayed. Good to Know

Beyond awareness of the basic properties of strings in PL/SQL and built-in functions, you can benefit by keeping the following points about long strings and maximum string sizes in mind. When the string is too long. You must specify a maximum length when you declare a variable based on the VARCHAR2 type. What happens, then, when you try to assign a value to that variable whose length is greater than the maximum? Oracle Database raises the ORA-06502 error, which is also defined in PL/SQL as the VALUE_ERROR exception. Here is an example of the exception being raised and propagated out of the block unhandled: SQL> DECLARE 2 l_name VARCHAR2(3); 3 BEGIN 4 l_name := 'Steven'; 5 END; 6 / DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 4 Here is a rewrite of the same block that traps the VALUE_ERROR exception: SQL> DECLARE 2 l_name VARCHAR2 (3); 3 BEGIN 4 l_name := 'Steven'; 5 EXCEPTION 6 WHEN VALUE_ERROR 7 THEN 8 DBMS_OUTPUT.put_line ( 9 'Value too large!'); 10 END; 11 / Value too large!

Next Steps LEARN more about PL/SQL datatypes differences between CHAR and VARCHAR2 built-in functions

Interestingly, if you try to insert or update a value in a VARCHAR2 column of a database DOWNLOAD Oracle Database 11g table, Oracle Database raises a different error, which you can see below: SQL> CREATE TABLE small_varchar2 TEST your PL/SQL knowledge 2 ( 3 string_value VARCHAR2 (2) READ PL/SQL 101, Parts 1 and 2

4 ) 5 / Table created. SQL> BEGIN 2 INSERT INTO small_varchar2 3 VALUES ('abc'); 4 END; 5 / BEGIN * ERROR at line 1: ORA-12899: value too large for column "HR"."SMALL_VARCHAR2"."STRING_VALUE" (actual: 3, maximum: 2) ORA-06512: at line 2 Different maximum sizes. There are a number of differences between SQL and PL/SQL for the maximum sizes for string datatypes. In PL/SQL, the maximum size for VARCHAR2 is 32,767 bytes, while in SQL the maximum is 4,000 bytes. In PL/SQL, the maximum size for CHAR is 32,767 bytes, while in SQL the maximum is 2,000 bytes. Therefore, if you need to save a value from a VARCHAR2 variable in the column of a table, you might encounter the ORA-12899 error. If this happens, you have two choices:

Use SUBSTR to extract no more than 4,000 bytes from the larger string, and save that substring to the table. This option clearly has a drawback: you lose some of your data. Change the datatype of the column from VARCHAR2 to CLOB. This way, you can save all your data.

In PL/SQL, the maximum size for CLOB is 128 terabytes, while in SQL the maximum is just (4 GB - 1) * DB_BLOCK_SIZE. Theres More to Data than Strings Character data plays a very large role in PL/SQL applications, but those same applications undoubtedly also rely on data of other types, especially numbers and dates. I will cover these datatypes in the next PL/SQL 101 article.

NOVEMBER 2011: TECHNOLOGY: PL/SQL Working with Numbers in PL/SQL

Take the Challenge! Each of my PL/SQL 101 articles offers a quiz to test your knowledge of the information provided in the article. The quiz questions are shown By Steven Feuerstein below and also at PL/SQL Challenge (plsqlchallenge.com), a Website that offers online quizzes for the PL/SQL language. You Part 4 in a series of articles on can read and answer the quiz here in Oracle understanding and using PL/SQL Magazine, and then check your answers in the The previous article in this introductory next issue. If, however, you take the quiz at PL/SQL series focused on working with PL/SQL Challenge, you will be entered into a strings in PL/SQL-based applications. raffle to win your choice of an e-book from Without a doubt, strings are the most OReilly Media (oreilly.com). common type of data with which PL/SQL developers will work, but it is Question 1 certainly a very rare application that does not also rely on numbers. You Assuming that the number passed to this need to keep track of the quantities of function is a positive integer, what values will it items, and much more. return? As a result, you will quite often need to Declare variables and constants CREATE OR REPLACE FUNCTION for numbers plch_ceil_and_floor (number_in IN NUMBER) RETURN PLS_INTEGER Use built-in functions to modify IS values, such as the rounding of BEGIN numbers RETURN CEIL (number_in) - FLOOR This article gives you all the information (number_in); you need in order to begin working with END; numbers in your PL/SQL programs. Question 2 Numbers in PL/SQL

PL/SQL offers a variety of numeric datatypes to suit different purposes:

NUMBER. A true decimal datatype that is ideal for working with monetary amounts. NUMBER is the only one of PL/SQLs numeric types to be implemented in a platformindependent fashion.

Which of these statements about MOD and REMAINDER are correct? a. They both return the remainder of one number divided by another. b. Given the same argument, the value returned by REMAINDER will never be larger than that returned by MOD.

c. The values returned by MOD and PLS_INTEGER. Integer REMAINDER are always integers. datatype conforming to your hardwares underlying integer representation. Arithmetic is performed with your hardwares native machine instructions. You cannot store values of this type in tables; it is a PL/SQL-specific datatype.

SIMPLE_INTEGER. Introduced as of Oracle Database 11g Release 1. The SIMPLE_INTEGER datatype results in significantly shorter execution times for natively compiled code. This datatype is not explored in this article. BINARY_FLOAT and BINARY_DOUBLE. Single- and double-precision, IEEE754, binary floating-point datatypes. These BINARY datatypes are highly specialized and are useful when you need to improve the performance of computation-intensive operations. These datatypes are not explored in this article.

In practice, you may encounter other numeric types, such as FLOAT, INTEGER, and DECIMAL. These are subtypes of the four core numeric types in the preceding list. Now lets take a closer look at NUMBER and PLS_INTEGER. The NUMBER datatype. The NUMBER data-type is by far the most common numeric datatype youll encounter in the world of Oracle and PL/SQL programming. Use it to Answers to the PL/SQL Challenge: store integer, fixed-point, or floating-point Working with Strings numbers of just about any size. Prior to Oracle Database 10g, NUMBER was the Here are the answers to the PL/SQL only numeric datatype supported directly by Challenge questions in last issues the Oracle Database engine; now you can Working with Strings article: use BINARY_FLOAT and BINARY_DOUBLE as well. NUMBER is Answer 1: The value returned by INSTR implemented in a platform-independent manner, and arithmetic on NUMBER values is 11. yields the same result no matter what Answer 2: False. A literal value may hardware platform you run on. contain single quotes within it. To To work with numbers in PL/SQL programs, accomplish this, either put two single quotes together or use the Q syntax to you declare variables to hold the number specify an alternative delimiter for your values. The following declares a variable literals. using the NUMBER datatype: Answer 3: These two lines of text will be DECLARE displayed: l_salary NUMBER; Such a declaration results in a floating-point number. Oracle Database will allocate space for a maximum of 40 digits, and the decimal point will float to best accommodate whatever values you assign to the variable. NUMBER variables can hold values as small as 10-130 (1.0E - 130) and as large as 10126 - 1 (1.0E126 - 1). Values smaller than 10-130 will get rounded down to 0, and calculations resulting in values larger than or REPLACE=stvn furstin TRANSLATE= For full explanations of these answers, visit www.plsqlchallenge.com, register or log in, and click the Closed/Taken tab in Play a Quiz, or go to http://bit.ly/r1SwvP.

equal to 10126 will be un-defined, causing runtime problems but not raising an exception. This range of values is demonstrated by the code block in Listing 1. (TO_CHAR and format masks are described later in this article.) Code Listing 1: Demonstration of the range of NUMBER datatype values DECLARE tiny_nbr NUMBER := 1e-130; test_nbr NUMBER; --big_nbr 1111111111222222222233333333334 1234567890123456789012345678901234567890 NUMBER := 9.999999999999999999999999999999999999999e125;

-1111111111222222222233333333334444444 -1234567890123456789012345678901234567890123456 fmt_nbr VARCHAR2(50) := '9.99999999999999999999999999999999999999999EEEE'; BEGIN DBMS_OUTPUT.PUT_LINE( 'tiny_nbr =' || TO_CHAR(tiny_nbr, '9.9999EEEE')); /* NUMBERs that are too small round down to zero. */ test_nbr := tiny_nbr / 1.0001; DBMS_OUTPUT.PUT_LINE( 'tiny made smaller =' || TO_CHAR(test_nbr, fmt_nbr)); /* NUMBERs that are too large throw an error: */ DBMS_OUTPUT.PUT_LINE( 'big_nbr =' || TO_CHAR(big_nbr, fmt_nbr)); test_nbr := big_nbr * 1.0001; -- too big DBMS_OUTPUT.PUT_LINE( 'big made bigger =' || TO_CHAR(test_nbr, fmt_nbr)); END; And here is the output from this block: tiny_nbr = 1.0000E-130 tiny made smaller = .00000000000000000000000000000000000000000E+00 big_nbr = 9.99999999999999999999999999999999999999900E+125 big made bigger =################################################# If you try to explicitly assign a number that is too large to your NUMBER variable, youll raise a numeric overflow or underflow exception, but if you assign calculation results that exceed the largest legal value, no exception will be raised. If your application really needs to work with such large numbers, you will need to write validation routines that

anticipate out-of-range values or consider using BINARY_DOUBLE. Using binary datatypes has rounding implications, so be sure to check the Oracle documentation on binary datatypes for details. For most uses, the chance of encountering these rounding errors will probably lead you to choose the NUMBER datatype. Often when you declare a variable of type NUMBER, you will want to constrain its precision and scale, which you can do as follows: NUMBER (precision, scale) For example, I want to declare a variable to hold a monetary amount of up to $999,999 and that consists of dollars and cents (that is, just two digits to the right of the decimal point). This declaration does the trick: NUMBER (8,2) Such a declaration results in a fixed-point number. The precision is the total number of significant digits in the number. The scale dictates the number of digits to the right (positive scale) or left (negative scale) of the decimal point and also affects the point at which rounding occurs. Both the precision and scale values must be literal integer values; you cannot use variables or constants in the declaration. Legal values for precision range from 1 to 38, and legal values for scale range from -84 to 127. When declaring fixed-point numbers, the value for scale is usually less than the value for precision. The PLS_INTEGER datatype. The PLS_INTEGER datatype stores signed integers in the range of -2,147,483,648 through 2,147,483,647. Values are represented in your hardware platforms native integer format. Here is an example of declaring a variable of type PLS_INTEGER: DECLARE loop_counter PLS_INTEGER; The PLS_INTEGER datatype was designed for speed. When you perform arithmetic with PLS_INTEGER values, the Oracle software uses native machine arithmetic. As a result, its faster to manipulate PLS_INTEGER values than it is to manipulate integers in the NUMBER datatype. Consider using PLS_INTEGER whenever your program is compute-intensive and involves integer arithmetic (and the values will never fall outside of this types range of valid integers). Bear in mind, however, that if your use of PLS_INTEGER results in frequent conversions to and from the NUMBER type, you may be better off using NUMBER to begin with. Youll gain the greatest efficiency when you use PLS_INTEGER for integer arithmetic (and for loop counters) in cases in which you can avoid conversions back and forth with the NUMBER type. Numeric Built-in Functions

Oracle Database includes an extensive set of built-in functions for manipulating numbers and for converting between numbers and strings. The following are some of the most commonly needed functions. ROUND. The ROUND function accepts a number and returns another number rounded to the specified number of places to the right of the decimal point. If you do not specify that number, ROUND will return a number rounded to the nearest integer. Listing 2 includes some examples of calls to ROUND. Code Listing 2: Calls to ROUND BEGIN DBMS_OUTPUT.put_line (ROUND (10.25)); DBMS_OUTPUT.put_line (ROUND (10.25, 1)); DBMS_OUTPUT.put_line (ROUND (10.23, 1)); DBMS_OUTPUT.put_line (ROUND (10.25, 2)); DBMS_OUTPUT.put_line (ROUND (10.25, -2)); DBMS_OUTPUT.put_line (ROUND (125, -2)); END; And here is the output from this block: 10 10.3 10.2 10.25 0 100 Note that a negative value for the second argument rounds to the nearest 10 (to the left of the decimal point). TRUNC. TRUNC is similar to round, in that you can specify the number of digits to the right or left of the decimal point. The difference is that TRUNC simply removes or truncates digits. And, like ROUND, you can specify a negative number, which truncates digits (makes them zero) to the left of the decimal point. Listing 3 includes some examples of calls to TRUNC. Code Listing 3: Calls to TRUNC BEGIN DBMS_OUTPUT.put_line (TRUNC (10.23, 1)); DBMS_OUTPUT.put_line (TRUNC (10.25, 1)); DBMS_OUTPUT.put_line (TRUNC (10.27, 1)); DBMS_OUTPUT.put_line (TRUNC (123.456, -1)); DBMS_OUTPUT.put_line (TRUNC (123.456, -2)); END;

And here is the output from this block: 10.2 10.2 10.2 120 100 FLOOR and CEIL. The FLOOR function returns the largest integer equal to or less than the specified number. The CEIL function returns the smallest integer equal to or greater than the specified number. The following block and its output demonstrate these two functions: BEGIN DBMS_OUTPUT.put_line (FLOOR (1.5)); DBMS_OUTPUT.put_line (CEIL (1.5)); END; / 1 2 MOD and REMAINDER. MOD and REMAINDER both return the remainder of one number divided by another, but that remainder is calculated differently for each function. The formula used by Oracle Database for MOD is MOD (m, n) = m - n * FLOOR (m/n) when both m and n have the same sign (positive or negative). If the signs of m and n are different, then the formula used is: MOD (m,n) = ( m - n * CEIL(m/n) ) whereas the formula used for REMAINDER is n2 - (n1*N) where n1 is not zero and where N is the integer nearest n2/n1. If n2/n1 equals x.5, then N is the nearest even integer. Listing 4 includes a block that demonstrates the effect of and differences between these two functions. Code Listing 4: Calls to MOD and REMAINDER BEGIN DBMS_OUTPUT.put_line (MOD (15, 4));

DBMS_OUTPUT.put_line (REMAINDER (15, 4)); DBMS_OUTPUT.put_line (MOD (15, 6)); DBMS_OUTPUT.put_line (REMAINDER (15, 6)); END; / And here is the output from this block: 3 -1 3 3 TO_CHAR. Use TO_CHAR to convert a number to a string. In its simplest form, you pass a single argument (the number) to TO_CHAR and it returns the string representation of that number, exactly long enough to contain all of its significant digits. Listing 5 includes a block that shows some TO_CHAR examples. As you can see, leading and trailing zeros are not in the string representation of the number. Code Listing 5: Calls to TO_CHAR BEGIN DBMS_OUTPUT.put_line (TO_CHAR (100.55)); DBMS_OUTPUT.put_line (TO_CHAR (000100.5500)); Next Steps DBMS_OUTPUT.put_line (TO_CHAR (10000.00)); LEARN more about END; PL/SQL datatypes built-in functions And here is the output from this block: 100.55 100.55 10000 Often, when you have to convert a number to a string, you need that number to fit a certain format. You might, for example, want to display the number as a currency, so that even if there are no cents, you need to include the .00in such cases, you will need to add a second argument in your call to TO_CHAR: the format mask. DOWNLOAD Oracle Database 11g

TEST your PL/SQL knowledge READ PL/SQL 101, Parts 1, 2 and 3

To specify a format for the string to which the number is converted, provide as the second argument to TO_CHAR a string that contains a combination of special format elements. Suppose, for example, that I want to display large numbers with a 1000s delimiter. In other words, I want to display 10,000 instead of 10000 so I would use the following format: BEGIN DBMS_OUTPUT.put_line (

'Amount='|| TO_CHAR ( 10000 , '9G999G999')); END; The G element indicates where in the string I would like to place the group separator. (The character used for the separator is determined by the National Language Settings database parameter NLS_NUMERIC_CHARACTERS.) The 9 element tells Oracle Database to put a significant digit or a blank space in that location. As a result, the output from this block is Amount= 10,000

If I want to have zeros appear instead of blanks, I can use 0 instead of 9, as in BEGIN DBMS_OUTPUT.put_line ( 'Amount=' || TO_CHAR ( 10000 , '0G000G999')); END; Amount= 0,010,000 If I do not want any leading zeros, extra blanks, or white space appearing in my converted number, I will use the FM element, as in BEGIN DBMS_OUTPUT.put_line ( 'Amount=' || TO_CHAR ( 10000 , 'FM9G999G999')); END; Amount=10,000 Suppose that my number is actually a monetary unit consisting of dollars (or euros) and cents and I want to show the currency symbol as well as the cents portion. I can use the following format: BEGIN DBMS_OUTPUT.put_line ( 'Salary=' || TO_CHAR ( 14500.77 , 'FML999G999D99')); END;

Salary=$14,500.77 The L element specifies the location of the local currency symbol (such as $ or ) in the return value (the NLS_CURRENCY parameter specifies the local currency symbol). The D element indicates the location of the decimal point. (The character used for the decimal point is specified by the database parameter NLS_NUMERIC_CHARACTERS.) It is outside the scope of this article to explain all of the many elements available for use in number formats (there are, for example, at least four elements just for denoting monetary units). Check Oracle Database SQL Language Reference 11g Release 2 (11.2), for a complete description. Dates, Time Stamps, and Intervals in PL/SQL Most applications require the storage and manipulation of dates and times. Unlike strings and numbers, dates are quite complicated: not only are they highly formatted data, but there are also many rules for determining valid values and valid calculations (leap days and years, daylight saving time changes, national and company holidays, date ranges, and so on). Fortunately, Oracle Database and PL/SQL provide a set of true date and time datatypes that store both date and time information in a standard internal format and also have an extensive set of built-in functions for manipulating the date and time. I will cover dates, time stamps, and intervals in the next PL/SQL 101 article.

January 2012: TECHNOLOGY: PL/SQL Working with Dates in PL/SQL By Steven Feuerstein Part 5 in a series of articles on understanding and using PL/SQL The previous articles in this introductory PL/SQL series focused on working with strings and numbers in PL/SQL-based applications. Without a doubt, strings and numbers are important, but it is certainly a very rare application that does not also rely on dates. You need to keep track of when events occurred, when people were born, and much more. As a result, you will quite often need to

Declare variables and constants for dates Use built-in functions to display and modify date values Perform computations on dates

A date is also a considerably more complex datatype than a string or a number. It has multiple parts (year, month, day, hour, and so on), and there are many rules about what constitutes a valid date. This article gives you all the information you need in order to begin working with dates in your PL/SQL programs. Dates, Time Stamps, and Intervals in PL/SQL Most applications require the storage and manipulation of dates and times. Unlike strings and numbers, dates are quite complicated: not only are they highly formatted data, but there are also many rules for determining valid values and valid calculations (leap days and years, daylight saving time changes, national and company holidays, date ranges, and so on). Fortunately, Oracle Database and PL/SQL provide a set of true date and time datatypes that store both date and time information in a standard internal format, and they also have an extensive set of built-in functions for manipulating the date and time. There are three datatypes you can use to work with dates and times:

DATEThis datatype stores a date and a time, resolved to the second. It does not include the time zone. DATE is the oldest and most commonly used datatype for working with dates in Oracle applications. TIMESTAMPTime stamps are similar to dates, but with these two key distinctions: (1) you can store and manipulate times resolved to the nearest billionth of a second (9 decimal places of precision), and (2) you can associate a time zone with a time stamp, and Oracle Database will take that time zone into account when manipulating the time stamp. INTERVALWhereas DATE and TIMESTAMP record a specific point in time, INTERVAL records and computes a time duration. You can specify an interval in terms of years and months, or days and seconds.

Listing 1 includes example variables whose declaration is based on these datatypes. Code Listing 1: Declaring DATE, TIMESTAMP, and INTERVAL variables

DECLARE l_today_date DATE := SYSDATE; l_today_timestamp TIMESTAMP := SYSTIMESTAMP; l_today_timetzone TIMESTAMP WITH TIME ZONE := SYSTIMESTAMP; l_interval1 INTERVAL YEAR (4) TO MONTH := '2011-11'; l_interval2 INTERVAL DAY (2) TO SECOND := '15 00:30:44'; BEGIN null; END;

Working with intervals and time stamps with time zones can be very complicated; relatively few developers will need these more advanced features. This article focuses on the core DATE and TIMESTAMP types, along with the most commonly used built-in functions. Choosing a datatype. With such an abundance of riches, how do you decide which of these date-and-time datatypes to use? Here are some guidelines:

Use one of the TIMESTAMP types if you need to track time down to a fraction of a second. You can, in general, use TIMESTAMP in place of DATE. A time stamp that does not contain subsecond precision takes up 7 bytes of storage, just as a DATE datatype does. When your time stamp does contain subsecond data, it takes up 11 bytes of storage. Use TIMESTAMP WITH TIME ZONE if you need to keep track of the session time zone in which the data was entered. Use TIMESTAMP WITH LOCAL TIME ZONE if you want the database to automatically convert a time between the database and session time zones. Use DATE when its necessary to maintain compatibility with an existing application written before any of the TIMESTAMP datatypes were introduced. Use datatypes in your PL/SQL code that correspond to, or are at least compatible with, the underlying database tables. Think twice, for example, before reading a TIMESTAMP value from a table into a DATE variable, because you might lose information (in this case, the fractional seconds and perhaps the time zone).

Getting the current date and time. PL/SQL developers often need to retrieve and work with the current date and time. Most developers use the classic SYSDATE function, but Oracle Database now offers several functions to provide variations of this information, as shown in Table 1. Function CURRENT_DATE CURRENT_TIMESTA MP LOCALTIMESTAMP Time Zone Session Session Datatype Returned DATE TIMESTAMP WITH TIME ZONE TIMESTAMP

Session Database SYSDATE DATE server Database TIMESTAMP WITH TIME SYSTIMESTAMP server ZONE Table 1: SYSDATE and other options for working with the current date and time Listing 2 displays the values returned by calls to SYSDATE and SYSTIMESTAMP. Code Listing 2: Calls to SYSDATE and SYSTIMESTAMP and the returned values

BEGIN DBMS_OUTPUT.put_line (SYSDATE); DBMS_OUTPUT.put_line (SYSTIMESTAMP); DBMS_OUTPUT.put_line (SYSDATE - SYSTIMESTAMP); END; / Here is the output: 07-AUG-11 07-AUG-11 08.46.16.379000000 AM -05:00 -000000000 00:00:00.379000000 Because I have passed dates and time stamps to DBMS_OUTPUT.PUT_LINE, Oracle Database implicitly converts them to strings, using the default format masks for the database or the session (as specified by the National Language Settings NLS_DATE_FORMAT parameter). A default installation of Oracle Database sets the default DATE format to DD-MON-YYYY. The default TIMESTAMP format includes both the date offset and the time zone offset. Note that it is possible to perform date arithmetic: I subtract the value returned by SYSTIMESTAMP from the value returned by SYSDATE. The result is an interval that is very close (but not quite equal) to zero. Converting dates to strings and strings to dates. As with TO_CHAR for numbers, you use another version of the TO_CHAR function to convert a date or a time stamp to a string. And, again as with numbers, Oracle Database offers a large set of format elements to help you tweak that string so it appears exactly as you need it. Here are some examples: 1. Use TO_CHAR without a format mask. If you do not include a format mask, the string returned by TO_CHAR will be the same as that returned when Oracle Database performs an implicit conversion: BEGIN DBMS_OUTPUT.put_line ( TO_CHAR (SYSDATE)); DBMS_OUTPUT.put_line ( TO_CHAR (SYSTIMESTAMP)); END; /

07-AUG-11 07-AUG-11 08.55.00.470000000 AM -05:00 2. Use TO_CHAR to display the full names of both the day and the month in the date: BEGIN DBMS_OUTPUT.put_line ( TO_CHAR (SYSDATE, 'Day, DDth Month YYYY')); END; / Sunday , 07TH August 2011 Note: The language used to display these names is determined by the NLS_DATE_LANGUAGE setting, which can also be specified as the third argument in the call to TO_CHAR, as in BEGIN DBMS_OUTPUT.put_line ( TO_CHAR (SYSDATE, 'Day, DDth Month YYYY', 'NLS_DATE_LANGUAGE=Spanish')); END; / Domingo , 07TH Agosto 2011 3. 4. Use TO_CHAR to display the full names of both the day and the month in the datebut without all those extra spaces in the date-as-string. Oracle Database, by default, pads the string with spaces to match the maximum length of the day or the month. In most situations, you dont want to include that extra text, and Oracle Database offers a format element modifier, FM, to control blank and zero padding. In the following

Answers to the Challenge Here are the answers to the PL/SQL Challenge questions in last issues Working with Numbers in PL/SQL article: Answer 1: The plch_ceil_and_floor function always returns either 1 or 0: 0 if the number passed to the function is an integer, 1 otherwise. Answer 2: (a) and (b) are correct; (c) is incorrect. For full explanations of both of these answers, visit plsqlchallenge.com, register or log in, and click the Closed/Taken tab in Play a Quiz, or go to bit.ly/r1SwvP.

block, I prefix the format mask with FM and remove the 0 (before 7) and extra spaces after August: 5. 6. BEGIN 7. DBMS_OUTPUT.put_line ( 8. TO_CHAR (SYSDATE, 9. 'FMDay, DDth Month YYYY')); 10. END; 11. / 12. Sunday, 7TH August 2011 You can also use the format mask to extract just a portion of, or information about, the date, as shown in the following examples: 1. What quarter is it? 2. TO_CHAR (SYSDATE, 'Q')

3. What is the day of the year (1-366) for todays date? 4. TO_CHAR (SYSDATE, 'DDD')
5. What are the date and time of a DATE variable? (This is a very common

requirement, because the default format mask for a date does not include the time component, which means that asking DBMS_OUTPUT.PUT_LINE to display a date leaves out the time.)

BEGIN DBMS_OUTPUT.put_line ( TO_CHAR (SYSDATE, 'YYYY-MM-DD HH24:MI:SS')); END; / You can also use EXTRACT to extract and return the value of a specified element of a date. For example 1. What year is it?

EXTRACT (YEAR FROM SYSDATE) 2. What is the day for todays date?

EXTRACT (DAY FROM SYSDATE) To convert a string to a date, use the TO_DATE or the TO_TIMESTAMP built-in function. Provide the string and Oracle Database returns a date or a time stamp, using the default format mask for the session: DECLARE l_date DATE; BEGIN l_date := TO_DATE ('12-JAN-2011'); END ; If the string you provide does not match the default format, Oracle Database will raise an exception: DECLARE l_date DATE; BEGIN l_date := TO_DATE ('January 12 2011'); END; / ORA-01858: a non-numeric character was found where a numeric was expected You should not assume that the literal value you provide in your call to TO_DATE matches the default format. What if the format changes over time? Instead, always provide a format mask when converting strings to dates, as in l_date := TO_DATE ('January 12 2011', 'Month DD YYYY'); Date truncation. Use the TRUNC built-in function to truncate a date to the specified unit of measure. The most common use of TRUNC is TRUNC (date)without any

format mask specified. In this case, TRUNC simply sets the time to 00:00:00. You can also use TRUNC to easily obtain the first day in a specified period. Here are some TRUNC examples: 1. Set l_date to todays date, but with the time set to 00:00:00: l_date := TRUNC (SYSDATE); 2. Get the first day of the month for the specified date:

l_date := TRUNC (SYSDATE, 'MM'); 3. Get the first day of the quarter for the specified date:

l_date := TRUNC (SYSDATE, 'Q'); 4. Get the first day of the year for the specified date:

l_date := TRUNC (SYSDATE, 'Y'); Date arithmetic. Oracle Database enables you to perform arithmetic operations on dates and time stamps in several ways:

Add a numeric value to or subtract it from a date, as in SYSDATE + 7; Oracle Database treats the number as the number of days. Add one date to or subtract it from another, as in l_hiredate - SYSDATE. Use a built-in function to move a date by a specified number of months or to another date in a week.

Here are some examples of date arithmetic with a date and a number (assume in all cases that the l_date variable has been declared as DATE):

1. Set a local variable to tomorrows date: l_date := SYSDATE + 1; 2. Move back one hour:

l_date := SYSDATE - 1/24; 3. Move ahead 10 seconds:

l_date := SYSDATE + 10 / (60 * 60 * 24); When you add one date to or subtract it from another, the result is the number of days between the two. As a result, executing this block: DECLARE l_date1 DATE := SYSDATE; l_date2 DATE := SYSDATE + 10; BEGIN DBMS_OUTPUT.put_line ( l_date2 - l_date1); DBMS_OUTPUT.put_line ( l_date1 - l_date2); END; returns the following output: Next Steps 10 -10 And the following function can be used to compute the age of a person, assuming that the persons correct birth date is passed as the functions only argument: LEARN more about PL/SQL datatypes functions DOWNLOAD Oracle Database 11g

TEST your PL/SQL knowledge READ PL/SQL 101, Parts 1-4

CREATE OR REPLACE FUNCTION your_age (birthdate_in IN DATE) RETURN NUMBER IS BEGIN RETURN SYSDATE birthdate_in; END your_age; Oracle Database offers several built-in functions for shifting a date by the requested amount or finding a date:

ADD_MONTHSadds the specified number of months to or subtracts it from a date (or a time stamp) NEXT_DAYreturns the date of the first weekday named in the call to the function LAST_DAYreturns the date of the last day of the month of the specified date

Here are some examples that use these built-in functions: 1. Move ahead one month: l_date := ADD_MONTHS (SYSDATE, 1); 2. Move backward three months: l_date := ADD_MONTHS (SYSDATE, -3); 3. Starting with the last day of January, move ahead one month. Starting from a different date, go back one month. Starting with the last day of February, go back one month. Listing 3 shows three different calls to the ADD_MONTHS function along with the results.

Code Listing 3: Calls to ADD_MONTHS

BEGIN DBMS_OUTPUT.put_line ( ADD_MONTHS (TO_DATE ('31-jan-2011', 'DD-MON-YYYY'), 1)); DBMS_OUTPUT.put_line ( ADD_MONTHS (TO_DATE ('27-feb-2011', 'DD-MON-YYYY'), -1)); DBMS_OUTPUT.put_line ( ADD_MONTHS (TO_DATE ('28-feb-2011', 'DD-MON-YYYY'), -1)); END; Here is the output: 28-FEB-11 27-JAN-11 31-JAN-11 You might be surprised at the third date in Listing 3. The first date (28 February) makes perfect sense. There is no 31st day in February, so Oracle Database returns the last day of the month. The second call to ADD_MONTHS moves the date from 27 February to 27 January: exactly one months change. But in the third call to ADD_MONTHS, Oracle Database notices that 28 February is the last day of the month, so it returns the last day of the month specified by the second argument. 4. Find the next Saturday after todays date: l_date := NEXT_DAY (SYSDATE, 'SAT'); -- or l_date := NEXT_DAY (SYSDATE, 'SATURDAY'); The second argument must be a day of the week in the date language of your session (specified by NLS_DATE_LANGUAGE), provided as either the full name or the abbreviation. The returned date has the same time component as the date. Bad Things HappenEven in Good Programs Now that you have a solid foundation in working with key datatypes such as strings, numbers, and dates, I will switch focus in the next article of this series to an in-depth introduction to exceptions: how they can be raised and how you can handle them.

Take the Challenge! Each PL/SQL 101 article offers a quiz to test your knowledge of the information provided in the article. The quiz questions are shown below and also at PL/SQL Challenge (plsqlchallenge.com), a Website that offers online quizzes for the PL/SQL language. You can read and take the quiz here in Oracle Magazine and then check your answers

in the next issue. If, however, you take the quiz at PL/SQL Challenge, you will be entered into a raffle to win an e-book from OReilly Media (oreilly.com). Question 1 Oracle Database provides a function for returning the date of the last day of the month. It does not, however, provide a function for returning the date of the first day. Which of the following can be used to do this? a. CREATE OR REPLACE FUNCTION plch_first_day (date_in IN DATE) RETURN DATE IS BEGIN RETURN TRUNC (date_in); END; / b. CREATE OR REPLACE FUNCTION plch_first_day (date_in IN DATE) RETURN DATE IS BEGIN RETURN TRUNC (date_in, 'MM'); END; / c. CREATE OR REPLACE FUNCTION plch_first_day (date_in IN DATE) RETURN DATE IS BEGIN RETURN TRUNC (date_in, 'MONTH'); END; / d. CREATE OR REPLACE FUNCTION plch_first_day (date_in IN DATE) RETURN DATE IS BEGIN RETURN TO_DATE (TO_CHAR (date_in, 'YYYY-MM')

|| '-01', 'YYYY-MM-DD'); END; / Question 2 Given this declaration section: DECLARE c_format CONSTANT VARCHAR2 (22) := 'YYYY-MM-DD HH24:MI:SS' ; l_new_year DATE := TO_DATE ( '2012-01-02 00:00:01' , c_format); which of the following blocks offers an exception section so that after that block is executed, the date and time 2012-01-01 00:00:01 will be displayed on the screen? a. BEGIN DBMS_OUTPUT.put_line ( TO_CHAR ( l_new_year - 24 , c_format)); END; b. BEGIN DBMS_OUTPUT.put_line ( TO_CHAR (l_new_year - 1 , c_format)); END; c. BEGIN DBMS_OUTPUT.put_line ( TO_CHAR ( l_new_year - 24 * 60 * 60 , c_format)); END; d. BEGIN DBMS_OUTPUT.put_line ( TO_CHAR ( TRUNC (l_new_year) -1 + 1 / (24 * 60 * 60) , c_format));

END;

MARCH: TECHNOLOGY: PL/SQL Error Management By Steven Feuerstein Part 6 in a series of articles on understanding and using PL/SQL Even if you write absolutely perfect PL/SQL programs, it is possible and even likely that something will go wrong and an error will occur when those programs are run. How your code responds to and deals with that error often spells the difference between a successful application and one that creates all sorts of problems for users as well as developers. This article explores the world of error management in PL/SQL: the different types of exceptions you may encounter; when, why, and how exceptions are raised; how to define your own exceptions; how you can handle exceptions when they occur; and how you can report information about problems back to your users. Exception Overview There are three categories of exceptions in the world of PL/SQL: internally defined, predefined, and user-defined. An internally defined exception is one that is raised internally by an Oracle Database process; this kind of exception always has an error code but does not have a name unless it is assigned one by PL/SQL or your own code. An example of an internally defined exception is ORA-00060 (deadlock detected while waiting for resource). A predefined exception is an internally defined exception that is assigned a name by PL/SQL. Most predefined exceptions are defined in the STANDARD package (a package provided by Oracle Database that defines many common programming elements of the PL/SQL language) and are among the most commonly encountered exceptions. One example is ORA-00001, which is assigned the name DUP_VAL_ON_INDEX in PL/SQL and is raised when a unique index constraint is violated. A user-defined exception is one you have declared in the declaration section of a program unit. User-defined exceptions can be associated with an internally defined exception (that is, you can give a name to an otherwise unnamed exception) or with an application-specific error. Every exception has an error code and an error message associated with it. Oracle Database provides functions for retrieving these values when you are handling an exception (see Table 1).

Description How to Get It The error code. This code is useful SQLCODE when you need to look up generic Note: You cannot call this function inside a SQL information about what might cause such statement. a problem. The error message. This text often SQLERRM or contains application-specific data such DBMS_UTILITY.FORMAT_ERROR_STACK as the name of the constraint or the Note: You cannot call SQLERRM inside a SQL column associated with the problem. statement. The line on which the error occurred. This capability was added in Oracle DBMS_UTILITY.FORMAT_ERROR_BACKTRA Database 10g Release 2 and is CE enormously helpful in tracking down the cause of errors. The execution call stack. This answers the question How did I get here? and shows you the path through your code to DBMS_UTILITY.FORMAT_CALL_STACK the point at which DBMS_UTILITY.FORMAT_CALL_STAC K is called. Table 1: Key error information to record A PL/SQL block can have as many as three sections: declaration, executable, and exception. (See Part 1 of this series, Building with Blocks, for more information on PL/SQL blocks.) When an exception is raised in the executable section of the block, none of the remaining statements in that section is executed. Instead, control is transferred to the exception section. The beauty of this design is that all exception-related activity is concentrated in one area in the PL/SQL block, making it easy for developers to understand and maintain all error management logic. The next paragraphs describe generally the flow of execution in a block when an error occurs (see Figure 1). The process of raising exceptions and the structure of the exception section are described more fully later in this article.

Figure 1: Exception propagation If a WHEN clause in the exception section catches that exception, the code in that clause will be executed, usually logging information about the error and then reraising that same exception. If the exception is not caught by the exception section or there is no exception section, that exception will propagate out of that block to the enclosing block; it will be unhandled. Execution of that block will then terminate, and control will transfer to the enclosing blocks exception section (if it exists). Raising Exceptions In most cases when an exception is raised in your application, Oracle Database will do the raising. That is, some kind of problem has occurred during the execution of your code and you have no control over this process. Once the exception has been raised, all you can do is handle the exceptionor let it escape unhandled to the host environment. Description How to Get It The error code. This code is useful when you need to look up generic information about what might cause such a problem. SQLCODE Note: You cannot call this function inside a SQL statement. The error message. This text often contains application-specific data such as the name of the constraint or the column associated with the problem. SQLERRM or DBMS_UTILITY.FORMAT_ERROR_STACK Note: You cannot call SQLERRM inside a SQL statement. The line on which the error occurred. This capability was added in Oracle Database 10g Release 2 and is enormously helpful in tracking down the cause of errors.

DBMS_UTILITY.FORMAT_ERROR_BACKTR ACE The execution call stack. This answers the question How did I get here? and shows you You can, however, raise exceptions in the path through your code to the point at your own code. Why would you want to which do this? Because not every error in an DBMS_UTILITY.FORMAT_CALL_STACK is application is due to a failure of internal called. processing in the Oracle Database DBMS_UTILITY.FORMAT_CALL_STACK instance. It is also possible that a certain data condition constitutes an error in your application, in which case you need to stop the processing of your algorithms and, quite likely, notify the user that something is wrong. PL/SQL offers two mechanisms for raising an exception:

The RAISE statement The RAISE_APPLICATION_ERROR built-in procedure

The RAISE statement. You can use the RAISE statement to raise a user-defined exception or an Oracle Database predefined exception. In the following example, I have

decided that if the user has supplied a NULL value for the department ID, I will raise the VALUE_ERROR exception: CREATE OR REPLACE PROCEDURE process_department ( department_id_in IN INTEGER) IS BEGIN IF department_id_in IS NULL THEN RAISE VALUE_ERROR; END IF; You can also use RAISE to reraise an exception from within the exception section (see Handling Exceptions for an example). RAISE_APPLICATION_ERROR. The RAISE statement raises an exception, stopping the current block from continuing. It also sets the current error code and error message. This error messagesuch as ORA-06502: PL/SQL: numeric or value erroris supplied by Oracle Database and is usually generic.

Answers to the Challenge Here are the answers to the PL/SQL Challenge questions in last issues Working with Dates in PL/SQL article: Answer 1: Choices 2, 3, and 4 all offer an implementation that returns the first day of the month. The best and simplest way to return this value is to use the TRUNC function.

Answer 2: Choices 2 and 4 perform the correct arithmetic on the original date the former by subtracting one day and the This kind of error message might be latter by truncating the date back to sufficient for reporting database errors, but midnight, subtracting one day, and then what if an application-specific errorsuch as adding one second. Employee is too young or Salary cannot be greater than $1,000has been raised? For full explanations of both of these A Numeric or value error message is not answers, visit plsqlchallenge.com, register going to help users understand what they or log in, and click the Closed/Taken tab did wrong and how to fix it. in Play a Quiz. If you need to pass an application-specific message back to your users when an error occurs, you should call the RAISE_APPLICATION_ERROR built-in procedure. This procedure accepts an integer (your error code), whose value must be between -20,999 and -20,000, and a string (your error message). When this procedure is run, execution of the current PL/SQL block halts immediately and an exception (whose error code and message are set from the values passed to RAISE_APPLICATION_ERROR) is raised. Subsequent calls to SQLCODE and SQLERRM will return these values. Here is an example of using RAISE_APPLICATION_ERROR: An employee must be at least 18 years old. If the date of birth is more recent, raise an error so that the INSERT or UPDATE is halted, and pass back a message to the user:

CREATE OR REPLACE PROCEDURE validate_employee ( birthdate_in IN DATE) IS BEGIN IF birthdate_in > ADD_MONTHS (SYSDATE, -12 * 18) THEN RAISE_APPLICATION_ERROR (-20500 , 'Employee must be at least 18 years old.'); END IF; END; Defining Your Own Exceptions There are two reasons you might want to define your own exception (employ a userdefined exception): to give a name to an error that was not assigned a name by Oracle Database or to define an application-specific exception such as Balance too low. To define your own exception, use the EXCEPTION datatype, as in DECLARE e_balance_too_low EXCEPTION; By default, the error code associated with this exception is 1 and User Defined Error is the error message. You can, however, associate a different error code with your exception by using the EXCEPTION_INIT pragma. In the block below, I have decided to associate the Balance too low error with code -20,000. CREATE OR REPLACE PROCEDURE process_balance ( balance_in IN NUMBER) IS e_balance_too_low EXCEPTION; PRAGMA EXCEPTION_INIT ( e_balance_too_low, -20000); BEGIN IF balance_in < 1000 THEN RAISE e_balance_too_low; END IF; END; Handling Exceptions

Oracle Database might raise an internal or predefined exception, and you can also explicitly raise an exception youve defined for your application. Next, you need to decide how you want your program to deal with, or handle, that exception. If you dont want an exception to leave your block or subprogram before it is handled, you must include an exception section that will catch the exception. The exception section starts with the keyword EXCEPTION and then contains one or more WHEN clauses. A WHEN clause can specify a single exception (by name), multiple exceptions connected with the OR operator, or any exception. Here are some examples of WHEN clauses: 1. Catch the NO_DATA_FOUND exception, usually raised when a SELECT-INTO statement is executed and finds no rows. 2. WHEN NO_DATA_FOUND 3. THEN 4. 5. Catch either the NO_DATA_FOUND or DUP_VAL_ON_INDEX predefined exceptions. 6. WHEN NO_DATA_FOUND OR 7. DUP_VAL_ON_INDEX 8. THEN 9. 10. Catch any exception: 11. WHEN OTHERS 12. THEN 13. You can have multiple WHEN clauses in your exception section, but if you have a WHEN OTHERS clause, it must come at the end. Its easy enough to define one or more WHEN clauses. The trickier part of the exception section is deciding what to do after you have caught an exception. Generally, code in an exception handler should perform the following two steps: 1. Record the error in some kind of log, usually a database table 2. Raise the same exception or a different one, so it propagates unhandled to the outer block Reraising exceptions. You could simply record information about an error and then not reraise the exception. The problem with this approach is that your application has swallowed up an error. The user (or the script that is being run) will not know that there was a problem. In some scenarios, that may be OK, but they are very rare. In almost every situation when an error occurs, you really do want to make sure that the person or the job running the code that raised the error is informed. Oracle Database makes it easy to do this with the RAISE statement. If you use RAISE in an executable section, you must specify the exception you are raising, as in

RAISE NO_DATA_FOUND; But inside an exception handler, you can also use RAISE without any exception, as in RAISE; In this form, Oracle Database will reraise the current exception and propagate it out of the exception section to the enclosing block. Note that if you try to use RAISE outside of an exception section, Oracle Database will raise a compile-time error: PLS-00367: a RAISE statement with no exception name must be inside an exception handler Recording errors. Suppose somethings gone wrong in your application and an exception was raised. You can certainly just let that exception propagate unhandled all the way out to the user, by not writing any exception sections in your subprograms. Users will then see the error code and message and either report the problem to the support team or try to fix the problem themselves. In most cases, however, youd like to store the information about the error before it is communicated to the user. That way you dont have to rely on your users to give you information such as the error code or the error message. When you record your error, you should include the information shown in Table 1, all obtainable through calls to functions supplied by Oracle Database. All of this information will help a developer or a member of the support team diagnose the cause of the problem. You may, in addition, want to record values of application-specific data, such as variables or column values. If you decide to store your error information in a table, you should not put the INSERT statements for the error log table directly inside your exception. Instead, you should build and call a procedure that does this for you. This process of hiding the way you implement and populate your log will make it easier and more productive to log errors. To understand these advantages, lets build a simple error log table and try using it in my exception section. Suppose my error log table looks like this: CREATE TABLE error_log ( ERROR_CODE INTEGER , error_message VARCHAR2 (4000) , backtrace CLOB , callstack CLOB , created_on DATE , created_by VARCHAR2 (30) )

I could write an exception handler as shown in Listing 1. Code Listing 1: Exception handling section inserting into log table EXCEPTION WHEN OTHERS THEN DECLARE l_code INTEGER := SQLCODE; BEGIN INSERT INTO error_log (error_code , error_message , backtrace , callstack , created_on , created_by) VALUES (l_code , sys.DBMS_UTILITY.format_error_stack , sys.DBMS_UTILITY.format_error_backtrace , sys.DBMS_UTILITY.format_call_stack , SYSDATE , USER); RAISE; END; No matter what error is raised in my program, this handler will catch it and store lots of extremely useful information about that error in my table. I strongly suggest, however, that you never write exception handlers like this. Problems include

Too much code. You have to write lots of code to store the error information. This leads to reduced productivity or fewer exception handlers (programmers dont feel that they have to write all this code, so they rationalize away the need to include a handler). The error log becomes part of a business transaction. I inserted a row into a table. I know that this table is different from the real tables of the application (for example, the Employees table of the human resources application). But Oracle Database makes no distinction. If a rollback is performed because of the error, the INSERT into the log table will also be rolled back. Brittle code. If I ever need to change the structure of the error_log table, I will have to change all the INSERT statements to accommodate this change.

A much better approach is to hide the table behind a procedure that does the INSERT for you, as shown in Listing 2.

Code Listing 2: Exception handling procedure inserting into log table CREATE OR REPLACE PROCEDURE record_error IS l_code PLS_INTEGER := SQLCODE; l_mesg VARCHAR2(32767) := SQLERRM; BEGIN INSERT INTO error_log (error_code , error_message , backtrace , callstack , created_on , created_by) VALUES (l_code , l_mesg , sys.DBMS_UTILITY.format_error_backtrace , sys.DBMS_UTILITY.format_call_stack , SYSDATE , USER); END; All Ive done is move the INSERT statement inside a procedure, but that simple action has important consequences. I can now very easily get around the problem of rolling back my error log INSERT along with my business transaction. All I have to do is make this procedure an autonomous transaction by adding the pragma statement and the COMMIT, as shown in Listing 3. Code Listing 3: Exception handling procedure as autonomous transaction with COMMIT CREATE OR REPLACE PROCEDURE record_error IS PRAGMA AUTONOMOUS_TRANSACTION; l_code PLS_INTEGER := SQLCODE; l_mesg VARCHAR2(32767) := SQLERRM; BEGIN INSERT INTO error_log (error_code , error_message , backtrace , callstack , created_on , created_by) VALUES (l_code , l_mesg , sys.DBMS_UTILITY.format_error_backtrace , sys.DBMS_UTILITY.format_call_stack , SYSDATE

, USER); COMMIT; END; By declaring the procedure to be an autonomous transaction, I can commit or roll back any of the changes I make to tables inside this procedure without affecting other changes made in my session. So I can now save the new row in my error log, and a later rollback of the business transaction will not wipe out this information. With this logging procedure defined in my schema, I can now very easily and quickly write an exception handler as follows: EXCEPTION WHEN OTHERS THEN record_error(); RAISE; It takes me much less time to write my exception handler, and its functionality is more robust. A win-win situation! Exceptions raised while declaring. If an exception is raised in the declaration section of a block, the exception will propagate to the outer block. In other words, the exception section of a block can catch only exceptions raised in the executable section of the block. The following block includes a WHEN OTHERS handler, which should trap any exception raised in the block and simply display the error code: DECLARE l_number NUMBER (1) := 100; BEGIN statement1; ... statementN; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line (SQLCODE); END; When I execute the block, Oracle Database will try to assign the value 100 to l_number. Because it is declared as NUMBER (1), however, 100 will not fit into the variable. As a result, Oracle Database will raise the ORA-06502 error, which is predefined in PL/SQL as VALUE_ERROR. Because the exception is raised in the process of declaring the variable, the exception handler will not catch this error. Instead Ill see an unhandled exception:

ORA-06502: PL/SQL: numeric or value error: number precision too large ORA-06512: at line 2 Consequently, you should avoid assigning values to variables in the declaration section unless you are certain that no error will be raised. You can, instead, assign the value in the executable section, and then the exception handler can trap and record the error: DECLARE l_number NUMBER (1); BEGIN l_number := 100; statement1; ... statementN; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line (SQLCODE); END; Exceptions and Rollbacks Unhandled exceptions do not automatically result in the rollback of outstanding changes in a session. Indeed, unless you explicitly code a ROLLBACK statement into your exception section or the exception propagates unhandled to the host environment, no rollback will occur. Lets look at an example. Suppose I write a block of code that performs two data manipulation language (DML) operations: 1. Remove all employees from the Employees table who are in department 20. 2. Give a raise to all remaining employees by multiplying their current salary by 200. That is very generous, but the constraint on the salary column is defined as NUMBER(8,2). The salary of some employees is already so large that the new salary amount will violate this constraint, leading Oracle Database to raise the ORA-01438: value larger than specified precision allowed for this column error. Suppose I run the following block in a SQL*Plus session: BEGIN DELETE FROM employees WHERE department_id = 20; UPDATE employees SET salary = salary * 200; EXCEPTION WHEN OTHERS

THEN DECLARE l_count PLS_INTEGER; BEGIN SELECT COUNT (*) INTO l_count FROM employees WHERE department_id = 20; DBMS_OUTPUT.put_line (l_count); RAISE; END; END; Next Steps The DELETE completes successfully, but then Oracle Database raises the ORADOWNLOAD Oracle Database 11g 01438 error when trying to execute the UPDATE statement. I catch the error and TEST your PL/SQL knowledge display the number of rows in the READ PL/SQL 101, Parts 1-5 Employees table WHERE department_id = 20. 0 is displayed, because the failure of the UPDATE statement did not cause a rollback in the session. After I display the count, however, I reraise the same exception. Because there is no enclosing block, PL/SQL returns an unhandled exception error to SQL*Plus (or whichever host environment is being used). The default behavior of SQL*Plus (and all host environments I know of) is to issue a rollback of any outstanding changes in the session and display the error information. So after this block is run, the employees in department 20 will still be in the table. Conclusions PL/SQL provides a wide range of features to help you catch and diagnose errors as well as communicate application-specific errors to your users. The exception section makes it easy to centralize all your exception handling logic and thereby manage it more effectively. In the next PL/SQL 101 article, I will explore the record datatype in PL/SQL: use of the %ROWTYPE anchor, how you can declare and use your own record types, record-level inserts and updates, and more.

Warning, No Reraise!

Oracle Database 11g Release 1 added a very useful warning to its compile-time warning subsystem: PLW-6009: handler does not end in

RAISE or RAISE_APPLICATION_ERROR. In other words, the compiler will now automatically detect exception handlers that might be swallowing up an error, by not propagating it to the enclosing block. Here is an example: SQL> ALTER SESSION SET plsql_warnings = 'ENABLE:6009' 2 / Session altered. SQL> CREATE OR REPLACE FUNCTION plw6009 2 RETURN VARCHAR2 3 AS 4 BEGIN 5 RETURN 'abc'; 6 EXCEPTION 7 WHEN OTHERS 8 THEN 9 RETURN NULL; 10 END plw6009; 11 / SP2-0806: Function created with compilation warnings SQL> show errors Errors for FUNCTION PLW6009: LINE/COL ERROR -------- ------------------------------7/9 PLW-06009: procedure "PLW6009" OTHERS handler does not end in RAISE or RAISE_APPLICATION_ERROR This is a very helpful warning, with one caveat: if I call an error logging procedure that itself calls RAISE or RAISE_APPLICATION_ERROR to propagate an unhandled exception, the compiler will not recognize this and will still issue the PLW-6009 warning for the subprogram. Take the Challenge! Each PL/SQL 101 article offers a quiz to test your knowledge of the information provided in the article. The quiz questions are shown below and also at PL/SQL Challenge (plsqlchallenge.com). a Website that offers online quizzes for the PL/SQL language. You can read and take the quiz here in Oracle Magazine and then check your answers in the next issue.

If, however, you take the quiz at PL/SQL Challenge, you will be entered into a raffle to win an e-book from OReilly Media (oreilly.com). Question 1 Which of these blocks will result in an unhandled ORA-00001 exception? a. BEGIN b. RAISE DUP_VAL_ON_INDEX; c. END; d. / e.

f. BEGIN g. RAISE -1; h. END; i. / j.

k. l. m. n. o. p. q. r. s. t. u.

CREATE TABLE plch_tab (n NUMBER PRIMARY KEY) / BEGIN INSERT INTO plch_tab VALUES (1); INSERT INTO plch_tab VALUES (1); END; /

v. BEGIN w. RAISE DUP_VAL_ON_INDEX; x. EXCEPTION y. WHEN OTHERS z. THEN

aa. RAISE; bb. END; cc. / dd.

Question 2 Assume that the plch_tab table has been created with a single numeric column. What change can I make in the following procedure so that it will compile without error? CREATE OR REPLACE PROCEDURE plch_proc (divisor_in in NUMBER) IS BEGIN INSERT INTO plch_tab VALUES (100/divisor_in); EXCEPTION WHEN DUP_VAL_ON_INDEX AND NO_DATA_FOUND THEN RAISE;

Answers to the Challenge Here are the answers to the PL/SQL Challenge questions in last issues Error Management article: Answer 1: Choices (a), (c), and (d) all raise ORA-00001, also known as DUP_VAL_ON_INDEX inside PL/SQL code.

MAY: TECHNOLOGY: PL/SQL Working with Records By Steven Feuerstein

Answer 2: To make it possible for the plch_proc procedure to compile without error, change AND to OR in the WHEN clause of the exception section. Because Part 7 in a series of articles on only one exception can be raised at a time understanding and using PL/SQL in a session, it doesnt make any sense to The Oracle PL/SQL language was designed allow you to check for two (or more) to be a portable, high-performance exceptions in a single handler. transaction processing language that is For full explanations of both of these answers, visit plsqlchallenge.com, register or log in, and click the Closed/Taken tab in Play a Quiz.

tightly integrated with the SQL language. It is rare, indeed, to find a PL/SQL program that does not either read from or make changes to tables in a database. Tables are made up of rows of data, each consisting of one or more columns, so it stands to reason that Oracle Database would make it as easy as possible to work with those rows of data inside a PL/SQL program. And it does precisely that through its implementation of the record. A record is a composite datatype, which means that it can hold more than one piece of information, as compared to a scalar datatype, such as a number or string. Its rare, in fact, that the data with which you are working is just a single value, so records and other composite datatypes are likely to figure prominently in your PL/SQL programs. This article explores how you declare records, populate them with rows from a table, and even insert or change an entire row in a table by using a record. It also takes a look at user-defined record types, which enable you to work with records that are not necessarily related to a relational table. Declare a Record with %ROWTYPE PL/SQL makes it very easy to declare records that have the same structure as a table, a view, or the result set of a cursor by offering the %ROWTYPE attribute. Suppose I have an employees table in an application that looks like this: SQL> DESCRIBE omag_employees Name Null? Type EMPLOYEE_ID NOT NULL NUMBER(38) LAST_NAME VARCHAR2(100) SALARY NUMBER Each row in the table consists of three columns, and each column has its own datatype. The following query retrieves all the columns in all the rows in the table: SELECT employee_id, last_name, salary FROM omag_employees I want to write a block of code that retrieves a single row of data from omag_employees for an employee ID and then work with the column values in that row. I could declare a variable for each column and then fetch into those variables, as follows: CREATE PROCEDURE process_employee ( employee_id_in IN omag_employees.employee_id%TYPE) IS

l_employee_id omag_employees.employee_id%TYPE; l_last_name omag_employees.last_name%TYPE; l_salary omag_employees.salary%TYPE; BEGIN SELECT employee_id, last_name, salary INTO l_employee_id, l_last_name, l_salary FROM omag_employees WHERE employee_id = employee_id_in; END; (Note that I use suffixes in my parameters to indicate their mode. Here _in indicates an IN parameter.) That is, however, an awful lot of code to write, read, and maintain. A much better approach is to fetch that row of data into a record, and the best way to declare that record is as follows: CREATE PROCEDURE process_employee ( employee_id_in IN omag_employees.employee_id%TYPE) IS l_employee omag_employees%ROWTYPE; BEGIN SELECT employee_id, last_name, salary INTO l_employee FROM omag_employees WHERE employee_id = employee_id_in; END; When this procedure is compiled, PL/SQL looks up the structure of the omag_employees table and defines a record that has a field for each column in the table, with the same name and datatype. By using %ROWTYPE to declare the record, I also tell Oracle Database that this procedure depends on the omag_employees table. If the database administrator changes the maximum length of the last_name column to 200, for instance, this procedures status will be changed to INVALID. When the

procedure is recompiled, the compiler will update the definition of the record in this procedure to match the tables new structure. I can even shorten things further and write CREATE PROCEDURE process_employee ( employee_id_in IN omag_employees.employee_id%TYPE) IS l_employee omag_employees%ROWTYPE; BEGIN SELECT * INTO l_employee FROM omag_employees WHERE employee_id = employee_id_in; END; The SELECT * syntax tells Oracle Database to fetch all the columns in the table. I can also use %ROWTYPE to declare a record that has the same structure as a SELECT statement in a cursor. This is especially helpful for fetching either a subset of columns from a table or columns from multiple tables. Heres an example: DECLARE CURSOR no_ids_cur IS SELECT last_name, salary FROM omag_employees; l_employee no_ids_cur%ROWTYPE; (Note that I usually add a _cur suffix to the names of my explicitly declared cursors.) Whenever you are fetching data from a cursor into PL/SQL variables, you should declare a record based on that cursor with %ROWTYPE and fetch into that record. This way, when and if the SELECT list of the cursor changes, the number and type of fields in the record will change accordingly and everything will stay in sync. Working with Record Variables Once you have declared a record in your block, you can both read and change the records value. You can do this at the record level or by referencing individual fields of that record, with the same dot notation used in SQL to refer to the column of a table. So if I declare a record as follows,

DECLARE l_employee omag_employees%ROWTYPE; I will be able to display the value of the last_name field of l_employee in the executable section of the block as follows: DBMS_OUTPUT.put_line ( l_employee.last_name); I can change the value of a field, using an assignment operator: l_employee.last_name := 'Picasso'; I can also perform the following record-level operations: 1. Set a record to NULL. This simple assignment will set the values of all fields to NULL. l_employee := NULL;

2. Assign one record to another. DECLARE l_employee1 omag_employees%ROWTYPE; l_employee2 omag_employees%ROWTYPE; BEGIN l_employee1 := l_employee2; END; Populating Records with Data Most of the time when you work with records, you will be assigning a row from a table to a record. You can also, however, assign values directly to individual fields or even to the record as a whole by using the PL/SQL assignment operator (:=). Lets look at examples of the ways to populate a record. 1. Declare a record with the same structures as those of the omag_employees table and then fill the record with the contents of one row from that table:

DECLARE l_employee omag_employees%ROWTYPE; BEGIN SELECT * INTO l_employee FROM omag_employees WHERE employee_id = 100; END; 2. Declare a cursor that fetches the last name and salary of all employees. Then use %ROWTYPE to declare a record that contains two fields: l_employee.last_name and l_employee.salary. Finally, open the cursor, fetch one row into the record, and close the cursor. DECLARE CURSOR no_ids_cur IS SELECT last_name, salary FROM omag_employees; l_employee no_ids_cur%ROWTYPE; BEGIN OPEN no_ids_cur; FETCH no_ids_cur INTO l_employee; CLOSE no_ids_cur; END; / 3. Populate a record by using native dynamic SQL. (Note: the SELECT statement is not dynamic; this is just to show that it is possible to populate a record with an EXECUTE IMMEDIATE . . . INTO statement). DECLARE l_employee omag_employees%ROWTYPE; BEGIN EXECUTE IMMEDIATE 'SELECT * FROM omag_employees' INTO l_employee; END; 4. Populate the fields of a record by using assignments. DECLARE l_employee omag_employees%ROWTYPE; BEGIN

l_employee.last_name := 'Renoir'; l_employee.salary := 1500; END; Note that even though I define the record based on the table, I do not have to set the values of the records fields from the table. I might, for example, want to insert a new row into the employees table by using the record (see Inserting and Updating with Records for details). 5. Assign one record to another. Oracle Database supports record-level assignments, even the assignment of NULL to a record. DECLARE l_old_employee omag_employees%ROWTYPE; l_new_employee omag_employees%ROWTYPE; BEGIN l_new_employee := l_old_employee; l_old_employee := NULL; END; Cursor FOR Loops and Implicitly Declared Records Suppose I want to write a program to display the last names of all employees. An elegant and simple way to do this in PL/SQL is to take advantage of the cursor FOR loop (which I discussed in part 2 of this PL/SQL 101 series). The cursor FOR loop is a variation on the numeric FOR loop, which looks like this: FOR index IN low_value .. high_value LOOP loop_body_statements END LOOP; The index is implicitly declared by Oracle Database as an integer and can be referenced only inside the body of this loop. A cursor FOR loop has a similar structure but replaces a numeric range with a query: FOR index IN ( SELECT_statement ) LOOP loop_body_statements END LOOP;

Oracle Database also implicitly declares this loop index as well, but in the case of a cursor FOR loop, it declares the index as a record by using %ROWTYPE against the query in the loop header. The following block uses a cursor FOR loop to fetch only the last name of each employee, deposit that name into a record, and then display the value of the last_name field of that record: BEGIN FOR employee_rec IN (SELECT last_name FROM omag_employees ORDER BY last_name) LOOP DBMS_OUTPUT.put_line ( employee_rec.last_name); END LOOP; END; / Passing Records as Parameters You can define parameters based on record types, and you can therefore pass records as arguments to subprograms. Suppose I need to write a procedure that displays an employee. I could implement it as follows: CREATE PROCEDURE show_employee ( employee_id_in IN omag_employees.employee_id%TYPE, last_name_in IN omag_employees.last_name%TYPE, salary_in IN omag_employees.salary%TYPE) IS BEGIN DBMS_OUTPUT.put_line ( employee_id_in || '-' || last_name_in || '-' || salary_in); END;

But I can also avoid having to declare each of those individual parameters (imagine a 100-column table!) by passing a record: CREATE PROCEDURE show_employee ( employee_in IN omag_employees%ROWTYPE) IS BEGIN DBMS_OUTPUT.put_line ( employee_in.employee_id || '-' || employee_in.last_name || '-' || employee_in.salary); END; / Of course, as new columns are added to the table, their contents will not automatically be displayed by this procedure. So when you use %ROWTYPE to pass arguments to subprograms, make sure to review the subprogram logic after any change to the table. Inserting and Updating with Records As you have seen, PL/SQL makes it very easy to populate a record from a row in a table. But what if you want to change the contents of a row in a table by using a record? PL/SQL offers special syntax in both the INSERT and UPDATE statements so that you can easily use records to perform those data manipulation language (DML) operations as well. The most common form of an INSERT statement is INSERT INTO table_name (column_list) VALUES (expression_list) where column_list is the list of columns that will be populated on insert and expression_list is the list of expressions that will be assigned to their respective columns. If I want to provide a value for each column in a table that has, say, 500 columns, writing and managing that code can become quite tedious. Inserting with a record comes in very handy in such a scenario. Code Listing 1: Insert of a single row with each column specified DECLARE

l_employee_id omag_employees.employee_id%TYPE := 500; l_last_name omag_employees.last_name%TYPE := 'Mondrian'; l_salary omag_employees.salary%TYPE := 2000; BEGIN INSERT INTO omag_employees (employee_id, last_name, salary) VALUES ( l_employee_id, l_last_name, l_salary); END; To perform a record-level insert, simply leave off the parentheses around the record in the VALUES clause. Listing 1 demonstrates an insert of a single row into the omag_employees table that specifies each column individually. The following demonstrates the same insert, using a record: DECLARE l_employee omag_employees%ROWTYPE; BEGIN l_employee.employee_id := 500; l_employee.last_name := Mondrian; l_employee.salary := 2000; INSERT INTO omag_employees VALUES l_employee; END; / So if you ever find yourself typing what feels like an endless list of variables in the VALUES clause of your INSERT statement, try using a record instead. For updates, use SET ROW to update all the columns in a row from the record: DECLARE l_employee omag_employees%ROWTYPE; BEGIN l_employee.employee_id := 500;

l_employee.last_name := 'Mondrian'; l_employee.salary := 2000; UPDATE omag_employees SET ROW = l_employee WHERE employee_id = 100; END; Remember: this UPDATE sets the value of every column in the table, including your primary key, so you should use the SET ROW syntax with great care. User-Defined Record Types So far youve seen how to declare a record variable based on a table or a cursor by using the %ROWTYPE attribute. You can also declare your own, user-defined record types by using the TYPE. . . RECORD statement. User-defined record types come in handy when you find yourself declaring sets of individual variables, such as DECLARE l_name1 l_total_sales1 l_deliver_pref1 -l_name2 l_total_sales2 l_deliver_pref2

VARCHAR2 (100); NUMBER; VARCHAR2 (10); VARCHAR2 (100); NUMBER; VARCHAR2 (10);

Instead, why not create your own record type and then declare two records: DECLARE TYPE customer_info_rt IS RECORD ( name VARCHAR2 (100), total_sales NUMBER, deliver_pref VARCHAR2 (10) ); l_customer1 customer_info_rt; l_customer2 customer_info_rt; (Note that when I declare types, I use a root t suffix and then add the type of type. Here I added _rt for record type.)

With this approach, you do more than avoid writing repetitive statements. You also document that those three pieces of information are all related to a customer. Next Steps And once youve moved up to using a record, you can pass that record as an DOWNLOAD Oracle Database 11g argument or perform record-level operations, further reducing the volume of code needed to implement your requirements. TEST your PL/SQL knowledge Another excellent time to use the TYPE . . . RECORD statement to create your own record type is when a field of your record READ PL/SQL 101, Parts 1-6 needs to be a PL/SQL-specific type, such as BOOLEAN. If you use %ROWTYPE, the datatypes of all the fields will be constrained to SQL types. Heres an example of a record type that contains two Boolean fields: DECLARE TYPE user_preferences_rt IS RECORD ( show_full_name BOOLEAN, autologin BOOLEAN ); l_user user_preferences_rt; Records are, themselves, PL/SQL-specific datatypes, so another nice feature of userdefined record types is that you can define a record type as a field in another record type. In the declaration section below, I have created one record type that holds the different numeric elements that make up a telephone number. I then create another record to hold the various telephone numbers for a salesperson: DECLARE TYPE phone_rt IS RECORD ( area_code PLS_INTEGER, exchange PLS_INTEGER, phn_number PLS_INTEGER, extension PLS_INTEGER ); TYPE contact_rt IS RECORD (

day_phone# phone_rt, eve_phone# phone_rt, cell_phone# phone_rt ); l_sales_rep contact_rt; Composite Datatypes to the Rescue! PL/SQLs support for records, one of several composite datatypes, enables you to write code that is simple, clean, and easy to maintain. Rather than work with long lists of variables or parameters, you can work with a record that contains all that information. User-defined records offer the flexibility to construct your own composite datatype, reflecting program-specific requirements that may not be represented by a relational table. In the next article in this PL/SQL 101 series, I will explore another key composite datatype, the collection. Collections, PL/SQLs implementation of arraylike structures, are used in some of the most important performance-related PL/SQL features, including FORALL and BULK COLLECT.

Pseudorecords in Database Triggers

Row-level triggers defined on tables can reference pseudorecords named NEW and OLD (you can override these default names with the REFERENCING clause of the trigger). They are called pseudorecords because they are similar in structure to a record defined on a table with %ROWTYPE but are restricted in their usage. Both of the pseudorecords contain a field for every column in the table on which the trigger is defined. When you execute an INSERT or UPDATE statement, the NEW pseudorecords fields contain the post values of the columns (the values after the INSERT or UPDATE has taken place). When you execute a DELETE or UPDATE statement, the OLD pseudorecords fields contain the pre values of the columnshow the row looks before the statement executes. I can, for example, use pseudorecords to validate business rules, determine whether a column value has changed, and more. In the

following trigger, I enforce a salary freeze; no one is allowed to get a raise during these tough economic times: CREATE OR REPLACE TRIGGER omag_employees_freeze_trg BEFORE INSERT ON omag_employees FOR EACH ROW DECLARE BEGIN IF :NEW.salary > :OLD.salary THEN RAISE_APPLICATION_ERROR ( -20000, 'Salary freeze in effect: '|| ' no increases allowed!'); END IF; END omag_employees_freeze_trg; There are, however, record features that do not apply to pseudorecords. I cannot, for example, pass a pseudorecord as an argument to a subprogram, even if the parameter for that subprogram is defined as tablename%ROWTYPE, where tablename is the name of the table that causes the trigger to be fired. Take the Challenge! Each PL/SQL 101 article offers a quiz to test your knowledge of the information provided in the article. The quiz questions appear below and also at PL/SQL Challenge (plsqlchallenge.com), a Website that offers online quizzes on the PL/SQL language. You can read and take the quiz here in Oracle Magazine and then check your answers in the next issue. If, however, you take the quiz at PL/SQL Challenge, you will be entered into a raffle to win an e-book from OReilly Media (oreilly.com). I create and populate this table: CREATE TABLE plch_parts ( partnum INTEGER PRIMARY KEY, partname VARCHAR2 (100) UNIQUE ) / BEGIN

INSERT INTO plch_parts VALUES (100, 'Keyboard'); COMMIT; END; / Question Which choices contain code I can use in place of the /*DECLARE*/ comment in the following block so that after the resulting block executes, Keyboard will be displayed? DECLARE /*DECLARE*/ BEGIN SELECT * INTO l_part FROM plch_parts WHERE partnum = 100; DBMS_OUTPUT.put_line (l_part.partname); END; /

a. b. c. d. e. f. g. h.

l_part plch_parts%TYPE; _part plch_parts; l_part plch_parts%ROWTYPE; >CURSOR parts_cur IS SELECT * FROM plch_parts; l_part parts_cur%ROWTYPE;

JULY: TECHNOLOGY: PL/SQL Working with Collections By Steven Feuerstein Part 8 in a series of articles on understanding and using PL/SQL In the previous article in this series, I showed you how to work with a PL/SQL record, which is a composite datatype composed of one or more fields. In this article, I will explore another composite datatype, the collection. An Oracle PL/SQL collection is a single-dimensional array; it consists of one or more elements accessible through an index value. Collections are used in some of the most important performance optimization features of PL/SQL, such as

BULK COLLECT. SELECT statements that retrieve multiple rows with a single fetch, increasing the speed of data retrieval. FORALL. Inserts, updates, and deletes that use collections to change multiple rows of data very quickly Table functions. PL/SQL functions that return collections and can be called in the FROM clause of a SELECT statement.

You can also use collections to work with lists of data in your program that are not stored in database tables. This article introduces you to collections and gives you a solid foundation in both collection syntax and features. Collection Concepts and Terminology Before exploring collections, it is helpful to have a common collections vocabulary that includes the following terms. Index value. The location of the data in a collection. Index values are usually integers but for one type of collection can also be strings.

Element. The data stored at a specific index value in a collection. Elements in a collection are always of the same type (all of them are strings, dates, or records). PL/SQL collections are homogeneous. Sparse. A collection is sparse if there is at least one index value between the lowest and highest defined index values that is not defined. For example, a sparse collection has an element assigned to index value 1 and another to index value 10 but nothing in between. The opposite of a sparse collection is a dense one. Method. A collection method is a procedure or function that either provides information about the collection or changes the contents of the collection. Methods are attached to the collection variable with dot notation (object-oriented syntax), as in my_collection.FIRST. Types of Collections Collections were first introduced in Oracle7 Server and have been enhanced in several ways through the years and across Oracle Database versions. There are now three types of collections to choose from, each with its own set of characteristics and each best suited to a different circumstance. Associative array. The first type of collection available in PL/SQL, this was originally called a PL/SQL table and can be used only in PL/SQL blocks. Associative arrays can be sparse or dense and can be indexed by integer or string. Nested table. Added in Oracle8 Database, the nested table can be used in PL/SQL blocks, in SQL statements, and as the datatype of columns in tables. Nested tables can be sparse but are almost always dense. They can be indexed only by integer. You can use the MULTISET operator to perform set operations and to perform equality comparisons on nested tables. Varray. Added in Oracle8 Database, the varray (variable-size array) can be used in PL/SQL blocks, in SQL statements, and as the datatype of columns in tables. Varrays are always dense and indexed by integer. When a varray type is defined, you must specify the maximum number of elements allowed in a collection declared with that type. You will rarely encounter a need for a varray (How many times do you know in advance the maximum number of elements you will define in your collection?). The associative array is the most commonly used collection type, but nested tables have some powerful, unique features (such as MULTISET operators) that can simplify the code you need to write to use your collection. Nested Table Example Lets take a look at the simple example in Listing 1, which introduces the many aspects of collections explored later in the article. Code Listing 1: Nested table example 1 DECLARE

2 TYPE list_of_names_t IS TABLE OF VARCHAR2 (100); 3 4 happyfamily list_of_names_t := list_of_names_t (); 5 children list_of_names_t := list_of_names_t (); 6 parents list_of_names_t := list_of_names_t (); 7 BEGIN 8 happyfamily.EXTEND (4); 9 happyfamily (1) := Veva; 10 happyfamily (2) := Chris; 11 happyfamily (3) := Eli; 12 happyfamily (4) := Steven; 13 14 children.EXTEND; 15 children (children.LAST) := Chris; 16 children.EXTEND; 17 children (children.LAST) := Eli; 18 19 parents := happyfamily MULTISET EXCEPT children; 20 21 FOR l_row IN 1 .. parents.COUNT 22 LOOP 23 DBMS_OUTPUT.put_line (parents (l_row)); 24 END LOOP; 25 END; Line Explanation s I declare a new nested table type. Each element in a collection 2 declared with this type is a string whose maximum length is 100. I declare three nested tableshappyfamily, children, and parents based on my new collection type. Note that I also assign a default 46 value to each variable by calling a constructor function that has the same name as the type. I make room in my happyfamily collection for four elements by 8 calling the EXTEND method. I assign the names of the members of my immediate family (my wife, Veva; my two sons, Chris and Eli; and myself). Note the use of typical 912 single-dimension array syntax to identify an element in the array: array_name (index_value). 14 Now I populate the children nested table with just the names of my 17 sons. Rather than do a bulk extend as on line 8, I extend one index value at a time. Then I assign the name to the just-added index value by calling the LAST method, which returns the highest defined index value in the collection. Unless you know how many elements you need in advance, this approach of extending one row and then

assigning a value to the new highest index value is the way to go. Both of my children are adults and have moved out of the ancestral home. So whos left in this place with too many bedrooms? Start with 19 the happyfamily and subtract (with the MULTISET EXCEPT operator) the children. Assign the result of this set operation to the parents collection. It should be just Veva and Steven. The result of a MULTISET operation is always either empty or densely filled and starts with index value 1. So I will iterate through all 21 the elements in the collection, from 1 to the COUNT (the number of 24 elements defined in the collection) and display the element found at each index value. When I run the block in Listing 1, I see the following output: Veva Steven Listing 1 also includes references to the lines in the code block and descriptions of how those lines contribute to the nested table example. Declare Collection Types and Variables Before you can declare and use a collection variable, you need to define the type on which it is based. Oracle Database predefines several collection types in supplied packages such as DBMS_SQL and DBMS_UTILITY. So if you need, for example, to declare an associative array of strings whose maximum length is 32767, you could write the following: l_names DBMS_UTILITY.maxname_array; In most cases, however, you will declare your own application-specific collection types. Here are examples of declaring each of the different types of collections: 1. Declare an associative array of numbers, indexed by integer:

TYPE numbers_aat IS TABLE OF NUMBER INDEX BY PLS_INTEGER;

2. Declare an associative array of numbers, indexed by string:

TYPE numbers_aat IS TABLE OF NUMBER INDEX BY VARCHAR2(100);

3. Declare a nested table of numbers:

Answers to the Challenge The PL/SQL Challenge question in last issues Working with Records in PL/SQL article tested your knowledge of how to declare a record variable based on a table or a cursor. The question asked which of the following could be used in the questions code block so that a value (Keyboard) from the questions table would be displayed. All the choices are listed below; only (c) and (d) are correct. a.

TYPE numbers_nt IS TABLE OF NUMBER;

4. Declare a varray of numbers:

TYPE numbers_vat IS VARRAY(10) OF NUMBER;

Note: I use the suffixes _aat, _nt, and _vat, l_part plch_parts%TYPE; for associative array type, nested table type, and varray type, respectively. b. You might be wondering why the syntax for defining a collection type does not use the l_part plch_parts; word collection. The answer is that the IS TABLE OF syntax was first introduced in c. Oracle7 Server, when there was just one type of collection, the PL/SQL table. l_part plch_parts%ROWTYPE; From these examples, you can draw the following conclusions about collection types:

d. If the TYPE statement contains an INDEX BY clause, the collection type is an associative array. CURSOR parts_cur If the TYPE statement contains the IS VARRAY keyword, the collection type SELECT * FROM plch_parts; is a varray. If the TYPE statement does not l_part parts_cur%ROWTYPE; contain an INDEX BY clause or a VARRAY keyword, the collection type is a nested table. Only the associative array offers a choice of indexing datatypes. Nested tables as well as varrays are always indexed by integer.

When you define a varray type, you specify the maximum number of elements that can be defined in a collection of that type.

Once youve declared a collection type, you can use it to declare a collection variable as you would declare any other kind of variable: DECLARE TYPE numbers_nt IS TABLE OF NUMBER; l_numbers numbers_nt; Initializing Collections When you work with nested tables and varrays, you must initialize the collection variable before you can use it. You do this by calling the constructor function for that type. This function is created automatically by Oracle Database when you declare the type. The constructor function constructs an instance of the type associated with the function. You can call this function with no arguments, or you can pass it one or more expressions of the same type as the elements of the collection, and they will be inserted into your collection. Here is an example of initializing a nested table of numbers with three elements (1, 2, and 3): DECLARE TYPE numbers_nt IS TABLE OF NUMBER; l_numbers numbers_nt; BEGIN l_numbers := numbers_nt (1, 2, 3); END; If you neglect to initialize your collection, Oracle Database will raise an error when you try to use that collection: SQL> DECLARE 2 TYPE numbers_nt IS TABLE OF NUMBER; 3 l_numbers numbers_nt; 4 BEGIN 5 l_numbers.EXTEND; 6 l_numbers(1) := 1; 7 END; 8 / DECLARE *

ERROR at line 1: ORA-06531: Reference to uninitialized collection ORA-06512: at line 5 You do not need to initialize an associative array before assigning values to it. Populating Collections You can assign values to elements in a collection in a variety of ways:

Call a constructor function (for nested tables and varrays). Use the assignment operator, for single elements as well as entire collections. Pass the collection to a subprogram as an OUT or IN OUT parameter, and then assign the value inside the subprogram. Use a BULK COLLECT query.

The previous section included an example that used a constructor function. Following are examples of the other approaches: 1. Assign a number to a single index value. Note that with an associative array, it is not necessary to use EXTEND or start with index value 1.

DECLARE TYPE numbers_aat IS TABLE OF NUMBER INDEX BY PLS_INTEGER; l_numbers numbers_aat; BEGIN l_numbers (100) := 12345; END; 2. Assign one collection to another. As long as both collections are declared with the same type, you can perform collection-level assignments.

DECLARE TYPE numbers_aat IS TABLE OF NUMBER INDEX BY PLS_INTEGER; l_numbers1 numbers_aat; l_numbers2 numbers_aat; BEGIN l_numbers1 (100) := 12345; l_numbers2 := l_numbers1;

END; 3. Pass a collection as an IN OUT argument, and remove all the elements from that collection: DECLARE TYPE numbers_aat IS TABLE OF NUMBER INDEX BY PLS_INTEGER; l_numbers numbers_aat; PROCEDURE empty_collection ( numbers_io IN OUT numbers_aat) IS BEGIN numbers_io.delete; END; BEGIN l_numbers (100) := 123; empty_collection (l_numbers); END; 4. Fill a collection directly from a query with BULK COLLECT (covered in more detail in the next article in this series):

DECLARE TYPE numbers_aat IS TABLE OF NUMBER INDEX BY PLS_INTEGER; l_numbers numbers_aat; BEGIN SELECT employee_id BULK COLLECT INTO l_numbers FROM employees ORDER BY last_name; END; Iterating Through Collections A very common collection operation is to iterate through all of a collections elements. Reasons to perform a full collection scan include displaying information in the collection, executing a data manipulation language (DML) statement with data in the element, and searching for specific data.

The kind of code you write to iterate through a collection is determined by the type of collection with which you are working and how it was populated. Generally, you will choose between a numeric FOR loop and a WHILE loop. Use a numeric FOR loop when

Your collection is densely filled (every index value between the lowest and the highest is defined) You want to scan the entire collection, not terminating your scan if some condition is met

Conversely, use a WHILE loop when


Your collection may be sparse You might terminate the loop before you have iterated through all the elements in the collection

You should use a numeric FOR loop with dense collections to avoid a NO_DATA_FOUND exception. Oracle Database will also raise this exception, however, if you try to read an element in a collection at an undefined index value. The following block, for example, raises a NO_DATA_FOUND exception: DECLARE TYPE numbers_aat IS TABLE OF NUMBER INDEX BY PLS_INTEGER; l_numbers numbers_aat; BEGIN DBMS_OUTPUT.PUT_LINE (l_numbers (100)); END; When, however, you know for certain that your collection isand will always be densely filled, the FOR loop offers the simplest code for getting the job done. The procedure in Listing 2, for example, displays all the strings found in a collection whose type is defined in the DBMS_UTILITY package. Code Listing 2: Display all strings in a collection CREATE OR REPLACE PROCEDURE show_contents ( names_in IN DBMS_UTILITY.maxname_array) IS BEGIN FOR indx IN names_in.FIRST .. names_in.LAST LOOP

DBMS_OUTPUT.put_line (names_in (indx)); END LOOP; END; / This procedure calls two methods: FIRST and LAST. FIRST returns the lowest defined index value in the collection, and LAST returns the highest defined index value in the collection. The following block will display three artists names; note that the index values do not need to start at 1. DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (100) := Picasso; l_names (101) := OKeefe; l_names (102) := Dali;= show_contents (l_names); END; / If your collection may be sparse or you want to terminate the loop conditionally, a WHILE loop will be the best fit. The procedure in Listing 3 shows this approach. Code Listing 3: Use WHILE to iterate through a collection CREATE OR REPLACE PROCEDURE show_contents ( names_in IN DBMS_UTILITY.maxname_array) IS l_index PLS_INTEGER := names_in.FIRST; BEGIN WHILE (l_index IS NOT NULL) LOOP DBMS_OUTPUT.put_line (names_in (l_index)); l_index := names_in.NEXT (l_index); END LOOP; END; / In this procedure, my iterator (l_index) is initially set to the lowest defined index value. If the collection is empty, both FIRST and LAST will return NULL. The WHILE loop terminates when l_index is NULL. I then display the name at the current index value and

call the NEXT method to get the next defined index value higher than l_index. This function returns NULL when there is no higher index value. I call this procedure in the following block, with a collection that is not sequentially filled. It will display the three names without raising NO_DATA_FOUND: DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (-150) := 'Picasso'; l_names (0) := 'O''Keefe'; l_names (307) := 'Dali'; show_contents (l_names); END; / I can also scan the contents of a collection in reverse, starting with LAST and using the PRIOR method, as shown in Listing 4. Code Listing 4: Scan a collection in reverse CREATE OR REPLACE PROCEDURE show_contents ( names_in IN DBMS_UTILITY.maxname_array) IS l_index PLS_INTEGER := names_in.LAST; BEGIN WHILE (l_index IS NOT NULL) LOOP Next Steps DBMS_OUTPUT.put_line (names_in (l_index)); DOWNLOAD Oracle Database 11g l_index := names_in.PRIOR (l_index); END LOOP; TEST your PL/SQL knowledge END; READ PL/SQL 101, Parts 1-7 / Deleting Collection Elements PL/SQL offers a DELETE method, which you can use to remove all, one, or some elements from a collection. Here are some examples: 1. Remove all elements from a collection; use the DELETE method READ more Feuerstein stevenfeuerstein.com toadworld.com/sf Oracle PL/SQL Programming Oracle PL/SQL Language Pocket Reference Oracle PL/SQL Best Practices

without any arguments. This form of DELETE works with all three kinds of collections.

l_names.DELETE;

2. Remove the first element in a collection; to remove one element, pass the index value to DELETE. This form of DELETE can be used only with an associative array or a nested table.

l_names.DELETE (l_names.FIRST);

3. Remove all the elements between the specified low and high index values. This form of DELETE can be used only with an associative array or a nested table.

l_names.DELETE (100, 200); If you specify an undefined index value, Oracle Database will not raise an error. You can also use the TRIM method with varrays and nested tables to remove elements from the end of the collection. You can trim one or many elements: l_names.TRIM; l_names.TRIM (3); Get Comfy with Collections It is impossible to take full advantage of PL/SQL, including some of its powerful features, if you do not use collections. This article has provided a solid foundation for working with collections, but there are still several advanced features to explore, including string-indexed and nested collections, which will be covered in a future article. The next article in this PL/SQL 101 series will explore how to use collections with PL/SQLs most important performance-related PL/SQL features: FORALL and BULK COLLECT. Take the Challenge!

Each PL/SQL 101 article offers a quiz to test your knowledge of the information provided in it. The quiz appears below and also at PL/SQL Challenge (plsqlchallenge.com), a Website that offers online quizzes on the PL/SQL language as well as SQL and Oracle Application Express. Question Which of the following blocks will display these three lines after execution: Strawberry Raspberry Blackberry a. DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (1) := 'Strawberry'; l_names (10) := 'Blackberry'; l_names (2) := 'Raspberry'; FOR indx IN 1 .. l_names.COUNT LOOP DBMS_OUTPUT.put_line (l_names (indx)); END LOOP; END; / b. DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (1) := 'Strawberry'; l_names (10) := 'Blackberry'; l_names (2) := 'Raspberry'; indx := l_names.FIRST; WHILE (indx IS NOT NULL) LOOP

DBMS_OUTPUT.put_line (l_names (indx)); indx := l_names.NEXT (indx); END LOOP; END; / c. DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (1) := 'Strawberry'; l_names (10) := 'Blackberry'; l_names (2) := 'Raspberry'; DECLARE indx PLS_INTEGER := l_names.FIRST; BEGIN WHILE (indx IS NOT NULL) LOOP DBMS_OUTPUT.put_line (l_names (indx)); indx := l_names.NEXT (indx); END LOOP; END; END; / d. DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (1) := 'Strawberry'; l_names (10) := 'Blackberry'; l_names (2) := 'Raspberry'; FOR indx IN l_names.FIRST .. l_names.LAST LOOP DBMS_OUTPUT.put_line (l_names (indx)); END LOOP; END; /

SEPTEMBER: TECHNOLOGY: PL/SQL Bulk Processing with BULK COLLECT and FORALL By Steven Feuerstein

Part 9 in a series of articles on understanding and using PL/SQL In the previous article in this series, I introduced readers to PL/SQL collections. These data structures come in very handy when implementing algorithms that manipulate lists of program data, but they are also key elements in some of the powerful performance optimization features in PL/SQL. In this article, I will cover the two most important of these features: BULK COLLECT and FORALL.

BULK COLLECT: SELECT statements that retrieve multiple rows with a single fetch, improving the speed of data retrieval FORALL: INSERTs, UPDATEs, and DELETEs that use collections to change multiple rows of data very quickly

You may be wondering what very quickly might meanhow much impact do these features really have? Actual results will vary, depending on the version of Oracle Database you are running and the specifics of your application logic. You can download and run the script to compare the performance of row-by-row inserting with FORALL inserting. On my laptop running Oracle Database 11g Release 2, it took 4.94 seconds to insert 100,000 rows, one at a time. With FORALL, those 100,000 were inserted in 0.12 seconds. Wow!

Answer to the Challenge

Given that PL/SQL is so tightly integrated with the SQL language, you might be wondering why special features would be needed to improve the performance of SQL statements inside PL/SQL. The explanation has everything to do with how the runtime engines for both PL/SQL and SQL communicate with each otherthrough a context switch. Context Switches and Performance

The PL/SQL Challenge question in last issues Working with Collections article tested your knowledge of iterating through the contents of a sparsely populated collection. Choices (b) and (c) are both correct. Choice (c) offers the simplest algorithm for accomplishing this task:

Almost every program PL/SQL developers write includes both PL/SQL and SQL statements. PL/SQL statements are run by the PL/SQL statement executor; SQL statements are run by the SQL statement executor. When the PL/SQL runtime engine encounters a SQL statement, it stops and DECLARE passes the SQL statement over to the SQL indx PLS_INTEGER := engine. The SQL engine executes the SQL l_names.FIRST; statement and returns information back to BEGIN the PL/SQL engine (see Figure 1). This transfer of control is called a context switch, WHILE (indx IS NOT NULL) and each one of these switches incurs LOOP overhead that slows down the overall DBMS_OUTPUT.put_line (l_names performance of your programs. (indx)); indx := l_names.NEXT (indx); END LOOP; END; END; /

DECLARE l_names DBMS_UTILITY.maxname_array; BEGIN l_names (1) := Strawberry; l_names (10) := Blackberry; l_names (2) := Raspberry;

Figure 1: Switching between PL/SQL and SQL engines

Lets look at a concrete example to explore context switches more thoroughly and identify the reason that FORALL and BULK COLLECT can have such a dramatic impact on performance. Suppose my manager asked me to write a procedure that accepts a department ID and a salary percentage increase and gives everyone in that department a raise by the specified percentage. Taking advantage of PL/SQLs elegant cursor FOR loop and the ability to call SQL statements natively in PL/SQL, I come up with the code in Listing 1. Code Listing 1: increase_salary procedure with FOR loop PROCEDURE increase_salary ( department_id_in IN employees.department_id%TYPE, increase_pct_in IN NUMBER) IS BEGIN FOR employee_rec IN (SELECT employee_id FROM employees WHERE department_id = increase_salary.department_id_in) LOOP UPDATE employees emp SET emp.salary = emp.salary + emp.salary * increase_salary.increase_pct_in WHERE emp.employee_id = employee_rec.employee_id; END LOOP; END increase_salary; Suppose there are 100 employees in department 15. When I execute this block, BEGIN increase_salary (15, 10); END; the PL/SQL engine will switch over to the SQL engine 100 times, once for each row being updated. Tom Kyte, of AskTom (asktom.oracle.com), refers to row-by-row switching like this as slow-by-slow processing, and it is definitely something to be avoided. I will show you how you can use PL/SQLs bulk processing features to escape from slow-by-slow processing. First, however, you should always check to see if it is

possible to avoid the context switching between PL/SQL and SQL by doing as much of the work as possible within SQL. Take another look at the increase_salary procedure. The SELECT statement identifies all the employees in a department. The UPDATE statement executes for each of those employees, applying the same percentage increase to all. In such a simple scenario, a cursor FOR loop is not needed at all. I can simplify this procedure to nothing more than the code in Listing 2. Code Listing 2: Simplified increase_salary procedure without FOR loop PROCEDURE increase_salary ( department_id_in IN employees.department_id%TYPE, increase_pct_in IN NUMBER) IS BEGIN UPDATE employees emp SET emp.salary = emp.salary + emp.salary * increase_salary.increase_pct_in WHERE emp.department_id = increase_salary.department_id_in; END increase_salary; Now there is just a single context switch to execute one UPDATE statement. All the work is done in the SQL engine. Of course, in most real-world scenarios, lifeand codeis not so simple. We often need to perform other steps prior to execution of our data manipulation language (DML) statements. Suppose that, for example, in the case of the increase_salary procedure, I need to check employees for eligibility for the increase in salary and if they are ineligible, send an e-mail notification. My procedure might then look like the version in Listing 3. Code Listing 3: increase_salary procedure with eligibility checking added PROCEDURE increase_salary ( department_id_in IN employees.department_id%TYPE, increase_pct_in IN NUMBER) IS l_eligible BOOLEAN; BEGIN FOR employee_rec IN (SELECT employee_id FROM employees WHERE department_id = increase_salary.department_id_in) LOOP check_eligibility (employee_rec.employee_id,

increase_pct_in, l_eligible); IF l_eligible THEN UPDATE employees emp SET emp.salary = emp.salary + emp.salary * increase_salary.increase_pct_in WHERE emp.employee_id = employee_rec.employee_id; END IF; END LOOP; END increase_salary; I can no longer do everything in SQL, so am I then resigned to the fate of slow-by-slow processing? Not with BULK COLLECT and FORALL in PL/SQL. Bulk Processing in PL/SQL The bulk processing features of PL/SQL are designed specifically to reduce the number of context switches required to communicate from the PL/SQL engine to the SQL engine. Use the BULK COLLECT clause to fetch multiple rows into one or more collections with a single context switch. Use the FORALL statement when you need to execute the same DML statement repeatedly for different bind variable values. The UPDATE statement in the increase_salary procedure fits this scenario; the only thing that changes with each new execution of the statement is the employee ID. I will use the code in Listing 4 to explain how these features affect context switches and how you will need to change your code to take advantage of them. Code Listing 4: Bulk processing for the increase_salary procedure 1 CREATE OR REPLACE PROCEDURE increase_salary ( 2 department_id_in IN employees.department_id%TYPE, 3 increase_pct_in IN NUMBER) 4 IS 5 TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE; 6 7 l_employee_ids employee_ids_t; 8 l_eligible_ids employee_ids_t; 9 10 l_eligible BOOLEAN; 11 BEGIN 12 SELECT employee_id

13 BULK COLLECT INTO l_employee_ids 14 FROM employees 15 WHERE department_id = increase_salary.department_id_in; 16 17 FOR indx IN 1 .. l_employee_ids.COUNT 18 LOOP 19 check_eligibility (l_employee_ids (indx), 20 increase_pct_in, 21 l_eligible); 22 23 IF l_eligible 24 THEN 25 l_eligible_ids (l_eligible_ids.COUNT + 1) := 26 l_employee_ids (indx); 27 END IF; 28 END LOOP; 29 30 FORALL indx IN 1 .. l_eligible_ids.COUNT 31 UPDATE employees emp 32 SET emp.salary = 33 emp.salary 34 + emp.salary * increase_salary.increase_pct_in 35 WHERE emp.employee_id = l_eligible_ids (indx); 36 END increase_salary;

Line Description s Declare a new nested table type and two collection variables based on this type. One variable, l_employee_ids, will hold the IDs of all employees in the 58 department. The other, l_eligible_ids, will hold the IDs of all those employees who are eligible for the salary increase. 12 Use BULK COLLECT to fetch all the IDs of employees in the specified department 15 into the l_employee_ids collection. Check for salary increase eligibility: If ineligible, an e-mail is sent. (Note: 17 Implementation of check_eligibility is not included in this article.) If eligible, add the 28 ID to the l_eligible_ids collection. 30 Use a FORALL statement to update all the rows identified by employee IDs in the

35

l_eligible_ids collection.

Listing 4 also contains an explanation of the code in this new-and-improved increase_salary procedure. There are three phases of execution: 1. Fetch rows with BULK COLLECT into one or more collections. A single context switch is needed for this step. 2. Modify the contents of collections as required (in this case, remove ineligible employees). 3. Change the table with FORALL using the modified collections. Rather than move back and forth between the PL/SQL and SQL engines to update each row, FORALL bundles up all the updates and passes them to the SQL engine with a single context switch. The result is an extraordinary boost in performance. I will first explore BULK COLLECT in more detail, and then cover FORALL. About BULK COLLECT To take advantage of bulk processing for queries, you simply put BULK COLLECT before the INTO keyword and then provide one or more collections after the INTO keyword. Here are some things to know about how BULK COLLECT works:

It can be used with all three types of collections: associative arrays, nested tables, and VARRAYs. You can fetch into individual collections (one for each expression in the SELECT list) or a single collection of records. The collection is always populated densely, starting from index value 1. If no rows are fetched, then the collection is emptied of all elements.

Listing 5 demonstrates an example of fetching values for two columns into a collection of records. Code Listing 5: Fetching values for two columns into a collection DECLARE TYPE two_cols_rt IS RECORD ( employee_id employees.employee_id%TYPE, salary employees.salary%TYPE ); TYPE employee_info_t IS TABLE OF two_cols_rt; l_employees employee_info_t; BEGIN SELECT employee_id, salary

BULK COLLECT INTO l_employees FROM employees WHERE department_id = 10; END; If you are fetching lots of rows, the collection that is being filled could consume too much session memory and raise an error. To help you avoid such errors, Oracle Database offers a LIMIT clause for BULK COLLECT. Suppose that, for example, there could be tens of thousands of employees in a single department and my session does not have enough memory available to store 20,000 employee IDs in a collection. Instead I use the approach in Listing 6. Code Listing 6: Fetching up to the number of rows specified DECLARE c_limit PLS_INTEGER := 100; CURSOR employees_cur IS SELECT employee_id FROM employees WHERE department_id = department_id_in; TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE; l_employee_ids employee_ids_t; BEGIN OPEN employees_cur; LOOP FETCH employees_cur BULK COLLECT INTO l_employee_ids LIMIT c_limit; EXIT WHEN l_employee_ids.COUNT = 0; END LOOP; END; With this approach, I open the cursor that identifies all the rows I want to fetch. Then, inside a loop, I use FETCH-BULK COLLECT-INTO to fetch up to the number of rows specified by the c_limit constant (set to 100). Now, no matter how many rows I need to fetch, my session will never consume more memory than that required for those 100 rows, yet I will still benefit from the improvement in performance of bulk querying. About FORALL

Whenever you execute a DML statement inside of a loop, you should convert that code to use FORALL. The performance improvement will amaze you and please your users. The FORALL statement is not a loop; it is a declarative statement to the PL/SQL engine: Generate all the DML statements that would have been executed one row at a time, and send them all across to the SQL engine with one context switch. As you can see in Listing 4, lines 30 through 35, the header of the FORALL statement looks just like a numeric FOR loop, yet there are no LOOP or END LOOP keywords. Here are some things to know about FORALL:

Each FORALL statement may contain just a single DML statement. If your loop contains two updates and a delete, then you will need to write three FORALL statements. PL/SQL declares the FORALL iterator (indx on line 30 in Listing 4) as an integer, just as it does with a FOR loop. You do not need toand you should not declare a variable with this same name. In at least one place in the DML statement, you need to reference a collection and use the FORALL iterator as the index value in that collection (see line 35 in Listing 4). When using the IN low_value . . . high_value syntax in the FORALL header, the collections referenced inside the FORALL statement must be densely filled. That is, every index value between the low_value and high_value must be defined. If your collection is not densely filled, you should use the INDICES OF or VALUES OF syntax in your FORALL header.

FORALL and DML Errors Suppose that Ive written a program that is supposed to insert 10,000 rows into a table. After inserting 9,000 of those rows, the 9,001st insert fails with a DUP_VAL_ON_INDEX error (a unique index violation). The SQL engine passes that error back to the PL/SQL engine, and if the FORALL statement is written like the one in Listing 4, PL/SQL will terminate the FORALL statement. The remaining 999 rows will not be inserted. If you want the PL/SQL engine to execute as many of the DML statements as possible, even if errors are raised along the way, add the SAVE EXCEPTIONS clause to the FORALL header. Then, if the SQL engine raises an error, the PL/SQL engine will save that information in a pseudocollection named SQL%BULK_EXCEPTIONS, and continue executing statements. When all statements have been attempted, PL/SQL then raises the ORA-24381 error. You canand shouldtrap that error in the exception section and then iterate through the contents of SQL%BULK_EXCEPTIONS to find out which errors have occurred. You can then write error information to a log table and/or attempt recovery of the DML statement.

Listing 7 contains an example of using SAVE EXCEPTIONS in a FORALL statement; in this case, I simply display on the screen the index in the l_eligible_ids collection on which the error occurred, and the error code that was raised by the SQL engine. Code Listing 7: Using SAVE EXCEPTIONS with FORALL BEGIN FORALL indx IN 1 .. l_eligible_ids.COUNT SAVE EXCEPTIONS UPDATE employees emp SET emp.salary = emp.salary + emp.salary * increase_pct_in WHERE emp.employee_id = l_eligible_ids (indx); EXCEPTION WHEN OTHERS THEN IF SQLCODE = -24381 THEN FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP DBMS_OUTPUT.put_line ( SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX || : || SQL%BULK_EXCEPTIONS (indx).ERROR_CODE); END LOOP; ELSE RAISE; END IF; END increase_salary; From SQL to PL/SQL This article talks mostly about the context switch from the PL/SQL engine to the SQL engine that occurs when a SQL statement is executed from within a PL/SQL block. It is important to remember that a context switch also takes place when a user-defined PL/SQL function is invoked from within an SQL statement. Suppose that I have written a function named betwnstr that returns the string between a start and end point. Heres the header of the function: FUNCTION betwnstr ( string_in IN VARCHAR2 , start_in IN INTEGER , end_in IN INTEGER ) RETURN VARCHAR2 I can then call this function as follows:

SELECT betwnstr (last_name, 2, 6) FROM employees WHERE department_id = 10 If the employees table has 100 rows and 20 of those have department_id set to 10, then there will be 20 context switches from SQL to PL/SQL to run this function. You should, consequently, play close attention to all invocations of user-defined functions in SQL, especially those that occur in the WHERE clause of the statement. Consider the following query: SELECT employee_id FROM employees WHERE betwnstr (last_name, 2, 6) = 'MITHY' In this query, the betwnstr function will be executed 100 timesand there will be 100 context switches. FORALL with Sparse Collections If you try to use the IN low_value .. high_value syntax with FORALL and there is an undefined index value within that range, Oracle Database will raise the ORA-22160: element at index [N] does not exist error.

Next Steps

DOWNLOAD Oracle Database 11g script for this article TEST your PL/SQL knowledge READ PL/SQL 101, Parts 1-8 READ more about INDICES OF and VALUES OF

To avoid this error, you can use the INDICES OF or VALUES OF clauses. To see how these clauses can be used, lets go back to the code in Listing 4. In this version of increase_salary, I declare a second collection, l_eligible_ids, to hold the IDs of those employees who are eligible for a raise. Instead of doing that, I can simply remove all ineligible IDs from the l_employee_ids collection, as follows: FOR indx IN 1 .. l_employee_ids.COUNT LOOP check_eligibility (l_employee_ids (indx), increase_pct_in, l_eligible); IF NOT l_eligible THEN l_employee_ids.delete (indx); END IF; END LOOP;

But now my l_employee_ids collection may have gaps in it: index values that are undefined between 1 and the highest index value populated by the BULK COLLECT. No worries. I will simply change my FORALL statement to the following: FORALL indx IN INDICES OF l_employee_ids UPDATE employees emp SET emp.salary = emp.salary + emp.salary * increase_salary.increase_pct_in WHERE emp.employee_id = l_employee_ids (indx); Now I am telling the PL/SQL engine to use only those index values that are defined in l_employee_ids, rather than specifying a fixed range of values. Oracle Database will simply skip any undefined index values, and the ORA-22160 error will not be raised. This is the simplest application of INDICES OF. Check the documentation for morecomplex usages of INDICES OF, as well as when and how to use VALUES OF. Bulk Up Your Code! Optimizing the performance of your code can be a difficult and time-consuming task. It can also be a relatively easy and exhilarating experienceif your code has not yet been modified to take advantage of BULK COLLECT and FORALL. In that case, you have some low-hanging fruit to pick!

Steven Feuerstein (steven.feuerstein@quest.com) is Quest Softwares PL/SQL evangelist. He has published 10 books on Oracle PL/SQL (OReilly Media) and is an Oracle ACE Director. More information is available at stevenfeuerstein.com.

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