Oracle 10g PLSQL
This book belongs to
Name
: ______________________________________
Batch
: ______________________________________
SQL STAR INTERNATIONAL LTD. SQL Star House, No. 8-2-293/174/A 25, Road No. 14, Banjara Hills, Hyderabad - 500 034, Andhra Pradesh, INDIA
© SQL Star International Ltd.
Copyright © 2008 Second Edition
SQL STAR INTERNATIONAL LIMITED SQL Star House,8-2-293/174/A25, Road No.14, Banjara Hills, Hyderabad - 500 034. Tel. No. 91- 40-23101600(30 lines) Fax No. 23101663 Toll Free No: 1800 425 2944
Email:
[email protected]
No part of this publication may be reproduced (incl. photocopying) in any way, without prior agreement and written permission of SQL Star International Ltd., Hyderabad. SQL Star International Ltd., Hyderabad assumes no responsibility for its use, nor for any infringements of patents or other rights of third parties which could result.
Table of Contents 10g PL /SQL CHAPTER No.
CHAPTER TITLE
PAGE NO
1.
Introduction to PL/SQL
1-25
2.
DML’s in PL/SQL
26-38
3.
Control Structure
39-59
4.
Composite DataTypes
5.
Cursors and advanced cursors
105-128
6.
Handling exception in PL/SQL
129-146
7.
Creating subprograms
147-170
8.
Managing subPrograms
171-184
9.
Creating Packages
185-204
10.
Using More Package Concepts
205-223
11.
Oracle Supplied Packages
224-250
12.
PL/SQL Performance Enhancements
251-277
13.
Large Objects
278-295
14.
Database Triggers
296-320
15.
Creating advanced Database Triggers
321-339
60-104
-3-
© SQL Star International Ltd.
Chapter 1
Introduction to PL/SQL Need for PL/SQL Benefits of Using PL/SQL PL/SQL Block Types and Constructs PL/SQL Block Structure Operators and SQL Functions in PL/SQL Variables Nested Blocks
1 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to: State the need for a procedural language in Oracle Create PL/SQL blocks Write nested blocks
2 © SQL Star International Ltd.
Introducing PL/SQL In the journey so far, you have learnt about the Structured Query Language (SQL). The Oracle software provides a language, Procedural Language/SQL (PL/SQL), which is an extension of SQL. PL/SQL implements modularity in the codes you write. It allows you to perform iterations and handle errors. Features like data hiding and error handling make PL/SQL a state-of-the-art language for databases. PL/SQL also allows data manipulation and SQL query statements to be included within procedural units of code. This makes PL/SQL a powerful transaction processing mechanism.
Need for PL/SQL The requirement for PL/SQL was mainly due to the need to centralize automated business tasks. For example, if each employee in an organization maintains separate programs to manage their tasks and updates them at their discretion, it could create confusions, such as: •
New employees or existing employees who are handed over someone else’s work, would have a problem understanding the process followed by the employee vis-à-vis the organization
•
Any change in business policy or functionality would have to be updated by all the employees individually in all relevant programs.
In a centralized functionality this is avoided because all changes need to be made at one place. Changes will get reflected in the relevant codes and all users can access updated information.
Benefits of Using PL/SQL The reasons for using PL/SQL are as follows: Integrating with the Oracle server and Oracle development tools Implementing performance enhancements in an application Modularizing the development of programs Implementing Portability Declaration of variables Programming with procedural language control structures Handling errors
3 © SQL Star International Ltd.
Integration PL/SQL plays a key role in: Oracle server, through stored procedures and functions, database triggers and packages Oracle development tools, through Oracle Developer component triggers PL/SQL supports the use of SQL datatypes. With direct access provided by SQL, these shared datatypes integrate PL/SQL with the Oracle server data dictionary. Hence, PL/SQL successfully bridges the gap between access to database technology and the need for procedural language capabilities. Most applications such as Oracle Forms, Oracle Reports and Oracle Graphics, use shared libraries (that hold code), which can be accessed locally or remotely. To execute these stored codes, the Oracle tools have their own PL/SQL engine (independent of the engine present in the Oracle server). The engine first filters out the SQL statements to send them individually to the SQL statement executor in the Oracle server. It then processes the remaining procedural statements in the procedural statement executor in the PL/SQL engine. The procedural statement executor processes data, which is already inside the client environment and not in the database. This reduces the workload on the Oracle server and also the amount of memory required.
Performance Enhancements of Applications When SQL statements are sent to the Oracle server one at a time, each statement results in a call to the Oracle server, leading to performance overhead and network traffic. If your application is SQL intensive, then instead of sending SQL statements individually to the server, you can put them into one block using PL/SQL and send the entire block to the server at one time. This reduces network traffic. PL/SQL also operates with Oracle development tools, thereby adding procedural processing power to these tools and enhancing performance.
4 © SQL Star International Ltd.
Modularized Program Development PL/SQL programs are made up of one or more blocks. These blocks may be individual ones or may be nested within another. That is, a block may represent a small part of another block, which may in turn be part of a whole unit of code. The units (procedures, functions or anonymous blocks) making up a PL/SQL program are called logical blocks.
A diagrammatic representation of modularization is:
The advantages associated with modularized development of programs are: Logical groupings of related statements within blocks Nesting blocks within larger blocks help build powerful programs Breaking down complex problems into manageable sets of logical, well-defined modules, which can be implemented within blocks
Portability PL/SQL is portable. That means, PL/SQL programs can be run wherever the Oracle server exists. There is no need to tailor them to suit each new environment. This is achieved because PL/SQL is native to the Oracle server, and therefore can be moved to any environment (operating system or platform) that supports the Oracle server. PL/SQL code can also be moved between the Oracle server and Oracle Developer applications by writing programs and creating libraries in different environments.
5 © SQL Star International Ltd.
Variable Declaration In PL/SQL, you can declare variables: To use them in SQL and procedural statements Belonging to different data types Dynamically based on the structure of tables and columns in the database
Programming with Procedural Language Control Structures PL/SQL allows the usage of control structures, which enable you to execute: Sequence of statements conditionally Sequence of statements iteratively in a loop Individually the rows returned by multiple-row query Handling Errors PL/SQL implements error handling functionality by: Processing Oracle server errors with error handling routines Declaring your own error conditions and process them with error handlers
PL/SQL Block Types and Constructs There are two block types and different types of constructs in PL/SQL. A block is a PL/SQL code. A construct is the way in which a block is written to implement different functionality. Block Types Logical blocks are the basic units of code that make up a PL/SQL program. The two kinds of PL/SQL blocks are: Anonymous blocks Named blocks or Subprograms Anonymous Blocks Anonymous blocks are declared at that point in an application from where they are to be executed and are passed to the PL/SQL engine for execution at runtime. These blocks are unnamed blocks. They can be embedded in the iSQL*Plus environment. An application trigger consists of these blocks. Subprograms Unlike anonymous blocks, named blocks or subprograms are given a name. These blocks can be invoked for execution and they can also accept parameters.
6 © SQL Star International Ltd.
Subprograms can be declared as Procedures or Functions. You declare a subprogram as a Procedure to perform an action, and you declare a subprogram as a Function to compute a value. Subprograms can be written and stored either at the server side or at the client side. Constructs There are various PL/SQL program constructs available that use the basic PL/SQL block. The availability of program constructs depends on the environment in which they are executed.
The different program constructs are given below.
7 © SQL Star International Ltd.
PL/SQL Block Structure To write a PL/SQL block, you need to know the different parts of a PL/SQL block and what each part should hold. The structure of all PL/SQL blocks is the same. The only difference is that, if it is an anonymous block it is not given a name. A PL/SQL block has three sections . They are : Declarative section Executable section Exception handling section The declarative section is where the variables and constants used in the body of the block are declared. Any cursors or user-defined error handlers that are used in the body are declared here. This section is optional. The executable section holds the set of statements or the logic of the tasks that are to be performed. You can include SQL statements to make changes to the database and PL/SQL statements to manipulate data in the block. The statements in this block are enclosed within the keywords BEGIN and END. This section is mandatory. The last section of a block is the exception section. Here a set of statements is written to handle any errors that might occur when the statements in the executable section are being executed. This section is optional .
8 © SQL Star International Ltd.
The diagramatic expression of PL/SQL block structure is given below.
The syntax for writing a PL/SQL block is: DECLARE datatype(size); BEGIN SQL statements; PL/SQL statements; EXCEPTION WHEN THEN … END; / Some points that will help you write a block:
A line of PL/SQL text which contains group of characters known as lexical units. These units are classified as follows:
Delimiters are simple or compound symbols that have a special meaning to PL/SQL. The following table presents both the simple as well as the compound symbols:
9 © SQL Star International Ltd.
Identifiers are used to name PL/SQL program constructs such as Constants, Variables and Exceptions. The following points need to be kept in mind while using identifiers: •
Identifiers can be up to 30 characters in length, but ensure they start with an alphabet.
•
Do not give the same name for the identifiers as the name of columns in a table used in the block. If so, then the Oracle server assumes that the table column is being referred.
•
An identifier consists of a letter, followed (optional) by other letters, numerals, underscores, dollar signs and number signs. The following characters are however illegal: Abc&efg – illegal ampersand Abc-efg – illegal hyphen Abc/efg – illegal slash Abc efg – illegal space
•
Identifiers should not be reserved words except when they are used within double quotes, for instance “INSERT”.
Literals are made up of definite values such as character, numeric, string or Boolean values, not represented by identifiers. These are case sensitive.
10 © SQL Star International Ltd.
Literals are of two types: •
Character literals: are all the printable characters such as letters, numerals, spaces, and special symbols. Specify character literals in single quotes.
•
Numeric literals: are represented either by a simple value such as –12 or by a scientific notation such as 3E6.
Comments are extra information given by the programmer to improve readability documenting in each phase and debugging.
in a code, to enable
PL/SQL code can be commented in two ways:
•
Single line comment represented by --
•
Multi line comment represented by /*
*/
Use a semicolon (;) at the end of all SQL and PL/SQL control statements and the END keyword.
Do not use semicolons after the keywords DECLARE, BEGIN and EXCEPTION.
Increase the readability of the block by writing the keywords in uppercase.
Use a slash (/) to terminate a PL/SQL block on a line by itself.
Using Quotes in Literals Oracle 10g allows you to define your own string delimiters to remove the need to double up any single quotes. SET SERVEROUTPUT ON BEGIN --original code DBMS_OUTPUT.PUT_LINE(‘He is New Jersey Library ‘’s Member!’); -- using quote symbol DBMS_OUTPUT.PUT_LINE(q’# He is New Jersey Library‘s Member!#’);
DBMS_OUTPUT.PUT_LINE(q’[ He is New Jersey Library‘s Member !]’); 11 © SQL Star International Ltd.
END; / He is New Jersey Library‘s Member! He is New Jersey Library‘s Member! He is New Jersey Library‘s Member ! PL/SQL procedure successfully completed
Operators in PL/SQL The following operators are supported in PL/SQL: Arithmetic Logical Relational or Comparison Concatenation Exponentiation [Represented by (**)]
SQL Functions in PL/SQL Statements All the SQL functions can also be used in procedural statements in a block. These include:
Single-row number and character functions
Datatype conversion functions
Date functions
Timestamp functions
GREATEST and LEAST functions
The functions that cannot be used in procedural statements are:
DECODE Group functions like AVG, MIN, MAX, COUNT, SUM, STDDEV and VARIANCE. These functions work only on a group of rows in a table and hence, they can be used only with the SQL statements in a PL/SQL block.
Variables A variable are named memory locations used to store data temporarily. The data stored in variables is used in the blocks and then processed. When the processing is completed, the data held in the variables may be written to the database or simply erased in case of session wide variables. Why is a Variable used? A Variable stores data temporarily. It manipulates the stored data and performs calculations with the data without accessing the database. Once declared, variables can be used repeatedly in an application by referencing them in other statements in the block.
12 © SQL Star International Ltd.
When variables are declared using %TYPE and %ROWTYPE (more information is provided later in the chapter), you are infact basing the variable declaration on the column definition of a table. In this case, if the column definition changes, then the variable declarations also changes accordingly. This helps in data independence, reduces maintenance cost and allows the program to adjust to the new business logic. How to handle variables in PL/SQL? In a PL/SQL block, variables are:
Declared and initialized in the declarative section of the block.
Assigned new values in the executable section. On doing so, the existing value is replaced with the newly assigned value. Care must be taken to see that a variable being referred to is already declared in the declarative section. Forward references cannot be made.
Types of Variables Variables are of two types. They are:
PL/SQL variables
Non-PL/SQL variables
PL/SQL Variables PL/SQL variables have a data type that specifies a storage format, constraints and also valid range of values. The data types used to declare PL/SQL variables are: Scalar data types are those that correspond to database column types. These data types hold only a single value. The base scalar data types include: CHAR VARCHAR2 LONG LONG RAW NUMBER BINARY_INTEGER PLS_INTEGER
BINARY_FLOAT BINARY_DOUBLE BOOLEAN DATE TIMESTAMP TIMESTAMP WITH TIMEZONE TIMESTAMP WITH LOCAL TIMEZONE INTERVAL YEAR TO MONTH INTERVAL DAY TO SECOND Binary_Float and Binary_Double are the two new Datatypes introduced in Oracle10g.They represent floating point numbers in IEEE 754 format (Institute of
13 © SQL Star International Ltd.
Electrical and Electronic Engineers) and require 5 byte and 9 bytes to store the values respectively. IEEE format s supported by most of the computer system operating through native processor instructions thereby helping us to carry out complex computations using floating point data.
Composite data types are those that are defined by users. They enable you to manipulate groups of data in PL/SQL blocks Reference data types are those that hold values pointing to other objects. These are also known as pointers. LOB (large object) data types: are those that hold values, which specify the location of large objects such as graphic images. These values are known as locators. Large objects are stored in the same database as the table but not within the table. LOB data type allows you to store unstructured data of a maximum of 8-128 terabytes. This data could be a movie clip or a graphic image or a sound wave form. LOBs are further classified into:
CLOB (Character Large Objects) is used to store large blocks of character data of a single byte. BLOB (Binary Large Object) is used to store large binary objects within the database. BFILE (Binary File) is used to store large binary objects that are in the operating system files outside the database. NCLOB (National Language Character Large Objects), are used to store large blocks of NCHAR data that may be single-byte or fixed-width multiple bytes
The syntax for declaring PL/SQL variables is: identifier [CONSTANT] datatype [NOT NULL] [:= | DEFAULT expr]; Where, identifier is the name assigned to the variable declared. CONSTANT specifies a constraint that the value of the variable cannot change. Constant variables must be initialized. While declaring a variable as a constant, the CONSTANT keyword must precede the datatype specification. DATATYPE specifies the type of data the variable can hold. It could be scalar, composite, reference or LOB datatype. NOT NULL specifies a constraint that the variable must contain a value. Therefore, NOT NULL variables must be initialized. := is the assignment operator used to assign an expression to a variable. Instead of the assignment operator, the DEFAULT expr (expression) can be used to assign values to the variables. By default, all variables are initialized to NULL. To prevent null values, variables are initialized using the DEFAULT keyword.
14 © SQL Star International Ltd.
The following code snippet shows how PL/SQL variables are declared in the declarative section of a block: DECLARE FirstName CHAR(20); BranchID CHAR(7) NOT NULL: = ‘09RANNJ’; FeeAmt CONSTANT NUMBER(2): = 15; CatgName CHAR(15) DEFAULT ‘Fiction’; % TYPE Attribute If you need to store a database column value in a variable or write a value from a variable to a database column, then the data type of the variable needs to be the same as that of the database column. You can use the %TYPE attribute to declare a variable to be of the same data type as that of a previously declared variable or database column. Incorrect variable data types generate PL/SQL errors during execution. When you want to declare a variable using a table attribute instead of the datatype the syntax is table.columnname%TYPE For example, in case you want to declare a variable that will store the address of a library member, you will write in the following syntax: DECLARE Address VARCHAR2(45); But, the above declaration will result in an error when an attempt is made to populate the variable with address details of members from the database table. This is because there is a mismatch in type specification of the variable. The datatype width of the vAddress column in table Member is 50, but you have declared it as 45. To overcome this, declare the variable using %TYPE attribute as follows: DECLARE Address Member.vAddress%TYPE; This statement declares a variable whose datatype and width is based on the vAddress column. If you want to declare a variable of the same type as a previously declared variable then the syntax is variable_name%TYPE Using %TYPE attribute to declare a variable based on a previously declared variable, is illustrated in the section dealing with the iSQL*Plus variables within PL/SQL blocks.
15 © SQL Star International Ltd.
Datatype and Variable size is determined when the block is compiled. So even if there is a change in the database column datatype the code manages the changed datatype information.
Data Conversion Functions In any programming language generally, we have to deal with different datatypes simultaneously or receive data which is not in the default format. In such cases, Oracle server implicitly converts data into valid datatypes wherever feasible. Explicit conversions come into the scenario where automatic conversions are not possible. Oracle Server takes care of implicit conversion between 1. Character and Number 2. Character and Date But how is it done? Let us take an example. DECLARE cons_nfine NUMBER(3):=50; cons_extra_fine VARCHAR2(20):=’5'; tot_fine Transaction.nfine%TYPE; BEGIN tot_fine:= cons_nfine+cons_extra_fine; DBMS_OUTPUT.PUT_LINE(‘The total fine Rs.‘||tot_fine); END; /
payable
is
So, did you notice something? Variable cons_nfine is of number datatype and cons_extra_fine is of VARCHAR2 datatype. While assigning the result to tot_fine variable, cons_extra_fine is converted to number by PL/SQL executer and then the operation is performed.
16 © SQL Star International Ltd.
To perform explicit conversion, following built in functions can be used. to_char() to_number() to_date() to_Binary_float() to_Binary_double()
DECLARE v_Date date:= to_Date( ‘April 04 2007’,’Month dd YYYY’); BEGIN DBMS_OUTPUT.PUT_LINE(‘You have entered ‘ ||v_Date ||’ as input’); END; / Example to show the usage of new datatypes. DECLARE l_binary_float l_binary_double
BINARY_FLOAT; BINARY_DOUBLE;
BEGIN l_binary_float := 2.1f; l_binary_double := 2.00001d; DBMS_OUTPUT.PUT_LINE(l_binary_double); DBMS_OUTPUT.PUT_LINE(l_binary_float); l_binary_float := TO_BINARY_FLOAT(2.1); l_binary_double := TO_BINARY_DOUBLE(2.00001); DBMS_OUTPUT.PUT_LINE(l_binary_double); DBMS_OUTPUT.PUT_LINE(l_binary_float); END; /
17 © SQL Star International Ltd.
Non-PL/SQL Variables Since PL/SQL has neither input nor output capabilities, it relies on the environment in which it is executed in order to pass values into and out of a PL/SQL block. The following non-PL/SQL variables can be used within PL/SQL blocks:
Substitution variables Host variables
Substitution variables are those that you can use to pass values to a PL/SQL block at runtime. To reference a substitution variable in a block, prefix it with an ampersand (&). Before the block is executed the values for the variables are substituted with the values passed. Hence, you cannot input different values for the substitution variables using a loop. The substitution variable can be replaced only by one value. Here is an example showing the use of substitution variables within PL/SQL blocks. Suppose, the library wants to calculate its quarterly income based on its annual income. To do this, it declares a substitution variable, which would prompt the user to enter the figure of annual income. Based on the value entered, the quarterly income would be calculated. DECLARE AnnualIncome NUMBER (7): = &annualinc; QuarterlyInc AnnualIncome%TYPE; -—declaring variable based on previously declared variable using --%TYPE attribute. BEGIN QuarterlyInc:= AnnualIncome/4; DBMS_OUTPUT.PUT_LINE (‘The quarterly income of the library is: ’|| QuarterlyInc); END; / In the above block, to display the quarterly income calculated, you can specify the DBMS_OUTPUT.PUT_LINE in the PL/SQL block. DBMS_OUTPUT is an Oracle supplied package and PUT_LINE is a procedure within it. To use this you need to specify the information you want printed, in parentheses, following the DBMS_OUTPUT.PUT_LINE command as shown in the above code:
is:
DBMS_OUTPUT.PUT_LINE (‘The quarterly income of the library ’|| QuarterlyInc);
For DBMS_OUTPUT.PUT_LINE to work, you need to run the iSQL*Plus command SET SERVEROUTPUT ON. iSQL*Plus host variables (also known as bind variables) are used to pass runtime values from the PL/SQL block back to the iSQL*Plus environment. These variables can be referenced in a PL/SQL block by placing a colon(:) before the variable. The keyword VARIABLE is used to declare a bind variable. The syntax is:
18 © SQL Star International Ltd.
VARIABLE datatype The syntax to display the variable value is: PRINT Bind variables cannot be referred within the PL/SQL block of a function, procedure or a package. In a PL/SQL block, to differentiate between host variables and declared PL/SQL variables, prefix the former with a colon(:). The code wherein you had calculated the quarterly income using substitution variables can be re-written using host variables as follows:
VARIABLE QuarterlyInc NUMBER DECLARE AnnualIncome NUMBER(7):= &AnnInc; BEGIN :QuarterlyInc:= AnnualIncome/4; END; / old 2: AnnualIncome NUMBER(7):= &AnnInc; new 2: AnnualIncome NUMBER(7):= 80000; PL/SQL procedure successfully completed. PRINT QuarterlyInc
QUARTERLYINC -----------20000
The above example shows the Host variable being assigned a value inside a PL/SQL block. To assign a value to a host variable outside the Pl/SQL block following can be done:SQL > VARIABLE QuarterlyInc NUMBER SQL > Exec :QuarterlyInc := 20000 SQL> PRINT QuarterlyInc QUARTERLYINC -----------20000 Where,
19 © SQL Star International Ltd.
Exec stand for Execute privilege.
Nested Blocks PL/SQL allows blocks to be nested wherever you can have an executable statement. [This makes the nested block a statement.] Hence, the executable part of a block can be broken down into smaller blocks. Even the exception section can contain nested blocks. Variable Scope Issues that concern references to identifiers can be resolved taking into account their scope and visibility. By scope, we mean that region of a program unit from which an identifier can be referenced. For instance, a variable in the declarative section can be referenced from the exception section of that block.
The diagram shown below illustrates the concept of nested blocks and variable scope.
20 © SQL Star International Ltd.
By visibility, we mean the regions from which an identifier can be referenced without using a qualified name. The following code snippet shows how to qualify identifiers: --Block label DECLARE --This is the parent block JoiningDt DATE; BEGIN DECLARE --his is the child block JoiningDt DATE; BEGIN … outer_blk.JoiningDt :=TO_DATE (’20- JAN-1998’, ‘DD-MONYY’); END; … END; / In the code snippet, a variable with the same name as the variable declared in the outer block is declared in the inner block. To reference the outer block variable in the inner block, the variable is qualified by prefixing it with the block name. Identifiers are considered local to the PL/SQL block in which they are declared, and are considered global to all its sub-blocks. Within the sub-block, only local identifiers are visible because to reference the global identifiers you must use a qualified name. If a block cannot find the identifier declared locally, it will look up to declarative section of the enclosing block (parent block). However, the block will never look down to the enclosed blocks (child blocks). Look at the following code and determine the variable scope: DECLARE vSal NUMBER(8,2) := 50000; vComm NUMBER(8,2) := vSal * 0.10; vNote VARCHAR2(200) := ‘Eligible for commission’; BEGIN DECLARE vSal NUMBER(8,2) := 90000; vComm NUMBER (4) := 0; vAnnualComp NUMBER(8,2) := vSal + vComm; BEGIN vNote := ‘Manager not’||vNote; OUTER_BLK.vComm := vSal * 0.20; END; vNote := ‘Salesman’||vNote;
END;
21 © SQL Star International Ltd.
/
Based on the rules of scoping, determine values of: vNote at position 1 vAnnualComp at position 2 vComm at position 1 OUTER_BLK.vComm at position 1 vComm at position 2 vNote at position 2 Guidelines for Writing PL/SQL Code Programs can be indented using carriage return,tabs and aligning keywords in the same line at different levels.Writing so, the task of debugging is made simpler. While writing programs we can follow a case convention, though PL/SQL is not case sensitive. For instance:• • •
All keywords should be written in uppercase. They must be aligned in the same line. Table names and column names are to be in Initcap and lower case respectively.
Indented programs improve the performance in those cases where similar statements are to be issued repeatedly because there is no need to parse the statements again. Let us write a program following all the rules stated above. DECLARE AnnualIncome NUMBER(7):= 60000; QuarterlyInc NUMBER(8,2); BEGIN QuarterlyInc:= AnnualIncome/4; DBMS_OUTPUT.PUT_LINE(‘The Quarterly ‘||QuarterlyInc); END;
income
is
/ Here, the keywords are aligned in the same line but at different levels. This improves readability of the code.
22 © SQL Star International Ltd.
Summary In this chapter, you have learnt that: PL/SQL bridges gap between SQL and a procedural language. Two types of PL/SQL blocks are: 1. Anonymous or unnamed blocks which are not stored in database and compiled each time they are executed. 2. Named Blocks which are compiled only once and stored in the database. The three sections of a PL/SQL Block are: 1. Declarative Section, where all the variables used in the program are declared. This is optional. Keyword: DECLARE 2. Executable Section where actual logic of the program lies. Keyword: BEGIN 3. Exception Section where all the errors are handled. This section is optional. Keyword: EXCEPTION 4. END to end the PL/SQL program. PL/SQL variables are declared and are accessible only within that PL/SQL block. Non- PL/SQL variables are declared outside the PL/SQL block and are available throughout the session. Two new datatypes FLOAT and DOUBLE introduced in this release help users in computing complex calculations. Nesting of blocks is allowed to have good control on the scope of the variables. Nested Blocks can also have exception sections.
23 © SQL Star International Ltd.
Lab Exercises 1.
Identify whether the following declarations are correct or incorrect: a) DECLARE empID NUMBER(5); b) DECLARE str1, str2, str3 VARCHAR2(20); c) DECLARE hiredate
DATE NOT NULL;
d) DECLARE on BOOLEAN := 5; 2. Create an anonymous block to display the phrase “Welcome to the world of PL/SQL”.
3. Create a block that declares two variables, one of VARCHAR2 type and the other of NUMBER type. Assign the following values to the variables and display the output on the screen.
4. Write a PL/SQL code to display the ID, Last Name, job ID and salary of an employee?
24 © SQL Star International Ltd.
5.
6.
Among the following which datatypes are enhancements of Oracle10g? a)
BINARY_INTEGER
b)
BINARY_FLOAT
c)
BINARY_DOUBLE
Display the following text using quote operator q: I’m Oracle certified,you’re not.
25 © SQL Star International Ltd.
Chapter 2
DMLs in PL/SQL SELECT Statements in PL/SQL DML Statements in PL/SQL
26 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to: Use SELECT statements within PL/SQL blocks Perform Data Manipulations within PL/SQL blocks
27 © SQL Star International Ltd.
SELECT Statements in PL/SQL Blocks The use of a PL/SQL block would be incomplete without its ability to interact with the database. To interact with the database you must use SQL. PL/SQL therefore, supports the use of Data Manipulation Language, Transaction Control commands, SQL functions for extracting data and also applying changes to the database. Embedding SELECT Statements SELECT statements within PL/SQL blocks help retrieve data from the database. The syntax is the same as when issued from iSQL*Plus, with a slight difference. SELECT INTO (variable_name[, variable_name] | record_name) FROM WHERE condition; In the syntax, select_list specifies a list of columns whose values are to be retrieved. They could also include SQL expressions, row functions or group functions. variable_name is the variable which has been declared to hold the values that are retrieved. They are also known output variables. record_name is the PL/SQL record which has been declared to hold the retrieved values. [PL/SQL records will be dealt with in a later session] table_name is the database table from which values are retrieved. condition comprises of various operators used to compare column names, constants, expressions and PL/SQL input variables. The main factor that differentiates this syntax from the usual SELECT syntax written in iSQL*Plus, is the INTO clause. This clause is mandatory and is placed between the SELECT clause and the FROM clause. It specifies the variables into which the values returned by the SELECT clause are to be stored. For each item selected in the SELECT clause there must be corresponding output variables in the INTO clause. The order of the output variables too must correspond to the columns selected. Embedded SELECT statements are within the scope of ANSI (American National Standard Institute) Classification of Embedded SQL, which states that ‘Queries must return one and only one row’. Therefore, if queries return more than one row the Oracle server generates an error.
28 © SQL Star International Ltd.
The following example will illustrate the use of SELECT statements within PL/SQL blocks: SET SERVEROUT ON DECLARE vBkname Book.cBookName%TYPE;
Output
vAuthor Book.cAuthorName%TYPE;
Variables BEGIN SELECT cBookName, cAuthorName INTO vBkname, vAuthor FROM Book WHERE cBookID = ‘HUM020000323’; DBMS_OUTPUT.PUT_LINE(vBkname||’ written by: ‘||vAuthor); END; /
On executing the code, the following result is generated: Rapunzel meets Santa Claus in Hell written by: Tom Boyle PL/SQL procedure successfully completed.
In the above program, the block retrieves the book name and author name for the specified book. In the following code SQL functions are used in the SELECT statement within the block: DECLARE vTotcopies NUMBER(4); — Output variable vBrID Book.cBranchID%TYPE:=’02CHSNJ’; BEGIN SELECT SUM(nNoOfCopies) INTO vTotcopies FROM Book WHERE cBranchID = vBrID;
29 © SQL Star International Ltd.
DBMS_OUTPUT.PUT_LINE(‘The
total
stock
of
books
stored
at
the Chester NJ Library is: ‘||vTotcopies); END; /
On executing the code, the following result is generated: The total stock of books stored at the Chester NJ Library is: 176 PL/SQL procedure successfully completed. The block retrieves the total number of copies of books stored in the specified branch. There is a possibility of ambiguity in the WHERE clause if the variable name and the column name are identical. This is because the Oracle server on encountering a variable on the right side of the comparison operator in the WHERE clause, checks whether it is a column in the database. If it is not, then it assumes it to be a PL/SQL variable. However, if the variable name is identical to the column name, the server considers it to be a column and therefore generates an erroneous result. Why don’t you try this out to check what erroneous result the server gives? There is another interesting aspect of the WHERE clause. As you know, it is used in the SELECT statement to restrict the rows that are to be selected. However, if no rows are selected, the server generates an error. For example, if the branch ID specified does not exist in the Branch table and you use it in the WHERE clause, the query would be unable to retrieve any details pertaining to the branch ID specified. The following code would clarify this: DECLARE vBrName Branch.cBranchName%TYPE; BEGIN SELECT cBranchName INTO vBrName FROM Branch WHERE cBranchID = ’00SCHNJ’; END; /
30 © SQL Star International Ltd.
On executing the code, the following error is generated: DECLARE * ERROR at line 1: ORA-01403: no data found
While embedding SELECT statements within PL/SQL blocks you must remember the following points: •
Each SQL statement must be terminated with a semicolon (;).
•
Do not forget the INTO clause in the SELECT statement.
•
The WHERE clause specifies a condition using constants, literals or PL/SQL input variables (for example, vBrID). This clause is optional.
•
The number of output variables in the INTO clause must be the same as the number of columns selected in the SELECT clause.
•
Make sure that the output variables in the INTO clause are mapped correctly to the selected columns, and that their datatypes are also compatible.
•
To ensure that the datatypes of variables match that of the columns selected in the SELECT clause, use the %TYPE attribute.
•
You can use group functions in the SELECT statement but not in the PL/SQL statements.
•
Do not give names to variables that are identical to column names. This could create ambiguity in the WHERE clause.
31 © SQL Star International Ltd.
DML Statements in PL/SQL You can manipulate data stored in the database by embedding DML statements within PL/SQL blocks. This has its benefits. When you issue DML statements in iSQL*Plus, they are sent to the Oracle server for execution one at a time. This results in high performance overhead especially in a networked environment. Therefore, to improve performance, club all the DML statements into one PL/SQL block, because the entire block gets sent to the server for execution in one go. The transaction control options COMMIT, SAVEPOINT and ROLLBACK, that define the beginning, breakpoint and end of a logical unit of activity in SQL statement processing, are also available in PL/SQL. The option that provides locking mechanism, which enables only one user at a time to change a record in the database, is also supported in PL/SQL. However, an important difference between issuing SQL statements within PL/SQL blocks and in iSQL*Plus is that the beginning and end of a PL/SQL block does not necessarily imply the beginning or end of a transaction. The execution of the first DML statement in the PL/SQL block begins a transaction. To save or discard the changes made, the PL/SQL code should explicitly contain COMMIT or ROLLBACK statements.
Embedding INSERT, UPDATE, DELETE and MERGE Statements The INSERT statement within a PL/SQL block is embedded just as you would enter them in iSQL*Plus. For example, insert details of a new member ‘Ann Judd’ into the Member table. BEGIN INSERT INTO Member VALUES(‘CAJ040501’,’ANN’,’JUDD’,’26A,Due Drops Apts,Blue Mountain Villa’,‘Elizabeth’,’78099', NULL,22,SYSDATE,’C’,’N’,’04RANNJ’); END; / You can confirm the insertion of a new row by issuing a SELECT statement: SELECT * FROM Member WHERE cMemberID=’CAJ040501';
32 © SQL Star International Ltd.
This block does not have a declarative section, as you have not declared any variables that are to be used in the INSERT statement. The VALUES clause requires: •
The actual values ‘Ann’ and ‘Judd’ to be entered into the cFirstName and cLastName columns respectively
•
The SYSDATE function to enter the current date into the dMembershipDt column.
You can also insert the above details in the following manner:
DECLARE cFirstName Member.cFirstName%TYPE DEFAULT ‘Ann’; BEGIN INSERT INTO Member(cMemberID,cFirstName,cLastName,vAddress, cArea,cZipcode,cPhone,nAge,dMembershipDt, cGrade,cMaritalStatus,cBranchID) VALUES (‘CAJ040501’,cFirstName,’Judd’, ’26A,Due Drops Apts,Blue Mountain Villa’, ‘Elizabeth’,’78099',NULL,22,SYSDATE,’C’,’N’,’04RANNJ’); END; / The block declares an input variable cFirstName of Member.cFirstName%TYPE and initializes it with a value ‘Ann’ using the DEFAULT keyword instead of the assignment operator. This variable is then used in the VALUES clause to populate cFirstName column with the value ‘Ann’. Therefore, in the INSERT statement you can: •
Use functions such as SYSDATE
•
Use database sequences to generate sequential numbers
•
Add default column values
33 © SQL Star International Ltd.
You can make changes to data or remove data stored in the database by embedding UPDATE and DELETE statements within PL/SQL blocks.
For example, the librarian has to: •
Update the number of copies of the book ‘On The Street Where You Live’ in the Book table
•
Remove details pertaining to one Mr. Derian Bates from the Member table
DECLARE vCopiesIncrease Book.nNoOfCopies%TYPE:=3; vMemID Member.cMemberID%TYPE: = ‘CDB028504’; BEGIN UPDATE Book SET nNoOfCopies = nNoOfCopies + vCopiesIncrease WHERE cBookName = ‘On The Street Where You Live’; DELETE FROM Member WHERE cMemberID = vMemID; COMMIT; END; / It is important to remember that there could be ambiguity in the SET clause if the declared variable has the same name as that of the column whose value is to be updated. This is because the name on the left of the assignment operator in the SET clause always corresponds to a database column, but on the right side, it could correspond either to a database column or a PL/SQL variable.
34 © SQL Star International Ltd.
For instance, in the above code, SET nNoOfCopies = nNoOfCopies + vCopiesIncrease; nNoOfCopies to the left of the operator corresponds to the nNoOfCopies column of the Book table, where as nNoOfCopies to the right also corresponds to the same column. But vCopiesIncrease to the right of the operator corresponds to the input variable. Therefore, there is no ambiguity in this SET clause. Why don’t you see the outcome of naming a variable as that of a column name?
Use the MERGE statement to update or insert rows in a table using data from another table. Rows are inserted or updated based on a specified equijoin condition. For example, DECLARE vMemID Member.cMemberID%TYPE:= ‘CDB028504’; BEGIN MERGE INTO MemberTabCopy mc USING Member m ON (m.cMemberID=vMemID) WHEN MATCHED THEN UPDATE SET mc.cFirstName = m.cFirstName, mc.cLastName = m.cLastName, mc.vAddress = m.vAddress, mc.cArea = m.cArea, mc.cZipcode = m.cZipcode, mc.cPhone = m.cPhone,
35 © SQL Star International Ltd.
mc.nAge = m.nAge, mc.dMembershipDt = m.dMembershipDt, mc.cGrade = m.cGrade
WHEN NOT MATCHED THEN INSERT VALUES (m.cMemberID, m.cFirstName, m.cLastName, m.vAddress, m.cArea, m.cZipcode, m.cPhone, m.nAge, m.dMembershipDt, m.cGrade); END; / [For the purpose of this code, we have created a table MemberTabCopy, which is similar in structure to the Member table] The code matches the member ID in the MemberTabCopy table to the member ID in the Member table. If they match, the row is updated to match the row in the Member table or else the row is inserted into the MemberTabCopy table. But, where do the SQL statements issued within the blocks get parsed (that is, checked whether the tables the statements are accessing are the ones on which accessing privileges have been granted) and executed? This brings in the concept of cursors which will be discussed in a later chapter.
36 © SQL Star International Ltd.
Summary In this chapter, you have learnt that:
Embedded SELECT statement has a INTO clause in it. One or more variable follows after the INTO clause.
Variable names should not collide with the names of the database columns. Data fetched from the database are stored in these variables.
WHERE clause restricts the number of rows to be fetched into these scalar variables.
DML Statements INSERT, UPDATE, DELETE and MERGE can be used to manipulate the data in the database by embedding these statements inside the PL/SQL block.
By doing so, one can reduce the network traffic, resulting in the performance improvement.
37 © SQL Star International Ltd.
Lab Exercises 1.
Create a PL/SQL block that retrieves the sum of salary of department 80 and stores it in an iSQL*Plus variable. Print the value to the screen.
2.
Create a PL/SQL block that will: Retrieve the maximum employee ID in the Employees table. Insert data regarding a new employee into the Employees table. For the employee ID of the new employee, add 1 to the maximum employee ID retrieved. Leave the phone number, commission and manager ID as null. Display the new row created.
38 © SQL Star International Ltd.
Chapter 3
Control Structures Conditional Constructs Case Constructs Loop Constructs GOTO Statement
39 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to:
Use Branching structures
Use Case Control structures
Use Loop Control Structures
40 © SQL Star International Ltd.
Control Structures Oracle PL/SQL provides a range of constructs that allow you to control the flow of process and there by produce well-structured programs. The selection structure tests a condition and then executes one sequence of statements instead of another, based on whether the condition is true or false. A condition is any variable or expression that returns a Boolean value (TRUE or FALSE). The iteration structure executes a sequence of statements repeatedly as long as a condition holds true. The sequence structure simply executes a sequence of statements in the order in which they occur. Hence, Control structures are the backbones of a program, where logic of the transactions are specified. Control Structures are broadly divided into • Conditional constructs • IF Statements • CASE Statement and CASE Expression • Loop constructs • Basic Loop • WHILE Loop • FOR Loop CONDITIONAL CONSTRUCTS USING IF Statements When you need to perform actions based on conditions, you use the IF statements. The structure of the IF statement is the same as those used in other procedural programming languages. It has three variations. They are: IF - THEN - END IF IF - THEN - ELSE - END IF IF - THEN - ELSIF - END IF Their syntaxes are shown below.
IF condition THEN statements; END IF;
IF condition THEN statements; [ELSE statements;] END IF;
IF condition THEN statements; [ELSIF condition THEN statements;] [ELSE statements;] END IF;
41 © SQL Star International Ltd.
Where, condition is an expression, which when executed will return a value of TRUE or FALSE or NULL. If the result of the expression is TRUE then the set of statements, following the THEN clause is performed else the statements following the ELSE or ELSIF clauses are performed. THEN is the clause that connects the value returned by the condition to the statements that are to be executed statements include the SQL or PL/SQL statements that would perform the required actions. They may include further IF statements. The ELSIF keyword is associated with another set of SQL or PL/SQL statements that should be executed in case the condition does not return the desired value. The ELSE keyword is also associated with a set of SQL or PL/SQL statements that will be executed, if none of the statements higher up in the construct has been executed, as the condition associated with each was not satisfied. Let us now look into a few examples of the various IF statements, to have a better understanding of its functioning. Example of a simple IF statement: The library does not permit a person to be its member if his/her age happens to be below five. This could be checked using the following IF-THEN-END IF statement: DECLARE Age Member.nAge%TYPE:= &age; BEGIN IF Age ReturnDt THEN Fine := (ActualRetDt – ReturnDt)*1.5; ELSE Fine: = 0; END IF; In the statement, if the actual return date is later than the due date, fine amount is calculated. If not, the fine amount is set to 0. Example of an IF-THEN-ELSE-ENDIF Statement using Logical Operators IF LibraryName=’Jersey’ AND ActualRetDt>ReturnDt THEN Fine:=(ActualRetDt-ReturnDt)*1.5; ELSE Fine:=0; END IF; In the statement, the fine is calculated only if the Library Name is Jersey and actual return date is later than the due date. If not, the fine amount is set to 0. Nested IF Statements Either set of actions of the first IF statement can include further IF statements. Such a condition is known as a nested IF statements. Its syntax is as follows: IF condition1 THEN Statement1; ELSE IF condition2 THEN Statement2; END IF; END IF; The nested IF statement is terminated with its corresponding END IF. Example of nested IF statements The library charges fee from members based on their ages. To illustrate this execute the following code: DECLARE Age NUMBER :=&age ;
43 © SQL Star International Ltd.
Fee NUMBER; BEGIN IF Age < 5 THEN DBMS_OUTPUT.PUT_LINE (‘Membership denied: Age below permissible membership age’); ELSE IF Age BETWEEN 5 AND 13 THEN Fee := 10; ELSE IF Age BETWEEN 14 AND 20 THEN Fee := 15; ELSE IF Age BETWEEN 21 AND 50 THEN Fee := 20; ELSE IF Age > 50 THEN Fee := 5; END IF; END IF; END IF; END IF; END IF; DBMS_OUTPUT.PUT_LINE (‘Fee for Member is:’|| Fee); END; / If you execute the above code, you can see the following output: Enter value for age: 44 old 2: Age NUMBER : = new 2: Age NUMBER : = Fee for Member is:20
&age ; 44 ;
PL/SQL procedure successfully completed. The above code does not permit a person to be the library’s member if his age is below five. However, if his age is above five, then membership fee is charged based on a set of nested IF conditions. If the age is between 5 and 13, then the fee amount is 10. If the age is between 14 and 20, then the fee amount is 15. If the age is between 21 and 50, then the fee amount is 20. If the age is above 50, then the fee amount is 5. Example of IF – THEN – ELSIF – END IF Statements The above code was quite cumbersome due to large number of nested IF statements. Instead you can use the ELSIF clause to make the code easier to read.
44 © SQL Star International Ltd.
DECLARE Age NUMBER := &age ; Fee Number; BEGIN IF Age < 5 THEN DBMS_OUTPUT.PUT_LINE (‘Membership denied: Age below permissible membership age’); ELSIF Age BETWEEN 5 AND 13 THEN Fee := 10; ELSIF Age BETWEEN 14 AND 20 THEN Fee := 15; ELSIF Age BETWEEN 21 AND 50 THEN Fee := 20; ELSIF Age > 50 THEN Fee := 5; END IF; DBMS_OUTPUT.PUT_LINE (‘Fee for Member is:’|| Fee); END; / On executing the above code, you get the following result: Enter value for age: 55 old 2: Age NUMBER : = &age ; new 2: Age NUMBER : = 55 ; Fee for Member is:5 PL/SQL procedure successfully completed. After having used all the variations of the IF constructs, there are certain guidelines to remember while writing them in a program. They are:
Every IF is followed by a THEN after stating the condition.
Every IF must have a matching END IF.
An IF construct may have any number of ELSIF statements.
END IF is not one word but two words and ELSIF is one word.
Every IF construct can have only one ELSE statement.
The ELSIF keyword does not have a matching END IF.
Do not use a semicolon (;) on the lines with the IF…THEN, ELSIF, and ELSE keywords. END IF must be followed by a semicolon (;).
You could indent the SQL and PL/SQL statements to be executed to increase readability of the code.
CASE Expressions and CASE Statements In PL/SQL, two types of CASE constructs are supported:
45 © SQL Star International Ltd.
CASE expressions: The expressions select a result and return it. CASE expressions can be assigned to variables, can be part of SQL statements and can be used in logical expressions. The syntax begins with CASE and ends with END.
CASE statements: They are independent statements just like other PL/SQL statements such as IF…THEN…ELSE statements. The syntax begins with CASE and ends with END CASE.
CASE Expressions CASE expressions select a result and return it. To understand better, look at the following syntax of a CASE expression: CASE selector WHEN expr1 THEN result1 WHEN expr2 THEN result2 … WHEN exprn THEN resultn [ELSE resultn+1;] END; From the syntax, it becomes clear that, to select the result, the CASE expression uses a selector, which is an expression whose value is used to select one of the several alternatives provided. The selector is followed by one or more WHEN clauses. The value of the selector determines which clause is executed. This is the syntax of a simple CASE expression.
Execute the following code of a simple CASE expression: DECLARE vGrade CHAR(1):= ‘&grade’; vGradeWiseAge VARCHAR2(25); BEGIN VGradeWiseAge := CASE vGrade WHEN ‘A’ THEN ‘Age between 5 and 13’ WHEN ‘B’ THEN ‘Age between 14 and 20’ WHEN ‘C’ THEN ‘Age between 21 and 50’ WHEN ‘D’ THEN ‘Age above 50’ END; DBMS_OUTPUT.PUT_LINE (‘Grade : ’||vGrade||‘ Stands for: ’||vGradeWiseAge); END; / In the code, the CASE expression is being assigned to a variable vGradeWiseAge. The CASE expression uses the value in the vGrade variable (The value is accepted
46 © SQL Star International Ltd.
using a substitution variable) as the expression. Depending on the value entered by the user, the CASE expression assigns the value to variable vGradeWiseAge. PL/SQL also supports a searched CASE expression. It has the following syntax: CASE WHEN searchCondition1 THEN result1 WHEN searchCondition2 THEN result2 … WHEN searchConditionn THEN resultn [ELSE resultn+1] END; Searched CASE expressions have no selectors. The WHEN clauses contain search conditions, which return a Boolean value, and not expressions that can return values of any type. Each WHEN clause can have different conditional expressions, which can take the form of searchCondition = operator . The operator can be any comparison operator. Execute following code of a searched CASE expression: DECLARE vAge NUMBER(3):= ‘&age’; vAgeWiseGrade CHAR(25); BEGIN vAgeWiseGrade := CASE WHEN vAge20; LOOP . . . EXIT outerloop WHEN condition; . . . EXIT WHEN condition; . . . END LOOP innerloop; . . . END LOOP outerloop; END; Where, BEGIN is the start of the block. LOOP on the second line marks the beginning of the first loop, labeled outerloop. counter_variable is the counter that is used to check the number of times the loop is being executed and is defined earlier. EXIT WHEN is the condition to exit the outer loop. The next LOOP marks the beginning of the nested or inner loop labeled innerloop. EXIT outerloop will exit both the loops based on the condition. EXIT WHEN on the next line will exit only the nested loop if the condition is satisfied. The control goes back to the outerloop. END LOOP innerloop ends the inner loop and execution of the outer loop resumes if there are any statements to be executed.
56 © SQL Star International Ltd.
END LOOP outerloop ends and exits the outer loop. END marks the end of the block. Let us look at a complete example illustrating the Nested loop. DECLARE counter number:=0; counter1 number:=0; total_done varchar2(4); inner_done varchar2(4); BEGIN LOOP counter:=counter+1; EXIT WHEN counter>10; LOOP IF counter = 10 THEN total_done:=’yes’; END IF; EXIT outer_loop WHEN total_done= ‘yes’; counter1:=counter1+1; IF (MOD(counter1,2)!=0) THEN inner_done:=’yes’; END IF; EXIT WHEN inner_done =’yes’; DBMS_OUTPUT.PUT_LINE(‘—Printing Even numbers from inner loop-—’||counter1); END LOOP
inner_loop;
inner_done:=’No’; DBMS_OUTPUT.PUT_LINE(‘*****Printing Whole Number from outer
loop***** ‘||counter);
END LOOP outer_loop; END; /
57 © SQL Star International Ltd.
Summary In this chapter, you have learnt that: Statements such as IF and CASE give a directive approach to a program logic. Using IF statement, multiple nested conditions can be solved. Where as CASE statement improves the readability of a program. The Iteration of a program can be controlled using 1. Basic LOOP that runs at least once 2. WHILE LOOP that runs till the specified condition is true 3. FOR loop that executes till the specified Iteration in the loop. These loops can be nested within one another. Jumping from one point to other in a program can be done with GOTO Statement using the LABEL Option.
58 © SQL Star International Ltd.
Lab Exercises
1.
Create a PL/SQL block that rewards an employee by appending an asterisk in the STARS column for every $1000 of the employee’s salary. Save your PL/SQL block in a file called c3q1.sql by clicking on the save script button. Remember to save script with .sql extension. a. Use the DEFINE command to provide the employee id. Pass the value to the PL/SQL block through a iSQL*Plus substitution variable. SET VERIFY OFF; DEFINE p_empno=104; b. Initailize a v_asterisk variable that contains a NULL. c. Append an asterisk to the string for every $1000 of the salary amount. For example, if the employee has a salary amount of $8000, the string of asterisks should contain eight asterisks. If the employee has a salary amount of $12500, the string of asterisks should contain 13 asterisks. d. Update the STARS column for the employee with the string of asterisks. e. Commit. f.
Test the block for the following values. i. employee_id=174 ii. employee_id=176
59 © SQL Star International Ltd.
Chapter 4 Composite Data-types Types of Composite Data-types INDEX BY Tables INDEX BY Table of Records Varying Arrays Nested Tables Ref Cursor
60 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to:
Identify the different composite datatypes
Process data using different composite datatypes
61 © SQL Star International Ltd.
What are Composite Datatypes? In the previous chapters, you have used variables of scalar datatypes such as VARCHAR2, NUMBER or DATE. These are primitive datatypes. In addition to scalar variables, there are also variables of composite datatypes. Composite datatypes are created by users and hence are also known as user defined datatypes. They are made up of a collection of different predefined datatypes. Therefore, composite datatypes are also referred to as collections. In this chapter you will learn how to create PL/SQL records, INDEX BY tables, nested tables and varrays. But, what is the need of composite variables? The answer lies in the issue of reducing network traffic. A major concern for programmers is to keep the network traffic as minimal as possible. Every time you embed an SQL statement within the PL/SQL block, a request is sent to the server for execution. This increases the network traffic and also takes up a lot of server’s memory. To overcome these problems, variables are declared within PL/SQL blocks. Scalar variables can store only one column value from the database. Therefore, the need was felt to declare variables, which could create temporary memory area within the client machine for storing collections of database items. Once declared, these collections of data could repeatedly be used for data manipulations in any application, without having to access the database. In other words, composite datatypes are reusable. This need led to the usage of composite datatypes.
Types of Composite Datatypes Composite datatypes store collections of data items and the result set (of data) is treated as one logical unit of data. Once you have this result set stored within the client memory, you can use the values returned by the result set instead of accessing the database. This reduces network traffic. Following are the different composite datatypes that a user can define: •
PL/SQL record type
User-defined records
%ROWTYPE records
62 © SQL Star International Ltd.
•
•
Collections
INDEX BY Table
Nested Table
VARRAY
Reference (REF)
PL/SQL Records A record is defined as a collection of different but related data items stored in fields. For example, the New Jersey Central Library has different kinds of data regarding its members, such as their ID, name, address, membership date, age, etc. Each piece of information or data item is dissimilar in type, but it is all logically related to members. Therefore, a member record is made up of a collection of these logically related data items. In PL/SQL these data items are stored in fields and a PL/SQL record is made up of a collection of such fields. A PL/SQL record is similar in concept and structure to a row in a database table. Just as a row in the Member table contains information about members stored in columns, similarly a record contains information of members stored in fields. Therefore, a record is a composite data structure, as it comprises more than one element or field. The record as a whole does not have any value of its own. Instead, each individual field in a record has a value. The record enables you to store and access these values as a group rather than accessing them from the database. To create a record, you should first create a record type and then declare records of that type. There are two types of PL/SQL records: •
User-defined records
User-defined records are based on record types where you can specify the fields along with datatypes.
•
%ROWTYPE records
%ROWTYPE records on the other hand are based on record types created using the %ROWTYPE attribute. Here, you cannot specify your own fields because using %ROWTYPE attribute creates a record that represents a row in a database table.
User-defined Records A user-defined record is a composite datatype that allows you to declare fields of your own choice. You can define their datatypes based on database columns using %TYPE attribute. The syntax is:
63 © SQL Star International Ltd.
TYPE IS RECORD (field_name1 {field_type | variable%TYPE | table.column%TYPE
}[NOT
NULL][:=| DEFAULT value], (field_name2 {field_type | variable%TYPE | table.column%TYPE } [NOT NULL] [:= |DEFAULT value], ...); Where, type_name is the name you give to the record type you are creating. field_name1| field_name2 is the unique name given to each field contained within the record. field_type is the datatype assigned to fields. You can use %TYPE or %ROWTYPE attribute. You can also define the NOT NULL constraint to prevent null values from being entered into the fields or assign values to fields by using the assignment operator. (: =) or the DEFAULT keyword. After creating record types, you can create identifiers of that type using the following syntax: ; Where, record_name is the name given to the record you are creating. type_name is the record type based on which you are creating the record.
Before you start using PL/SQL records, you need to understand how to create them.
Creating user-defined PL/SQL Records As there are no predefined datatypes based on which a PL/SQL record can be created, you should first create a datatype and then create a record based on that type. The following PL/SQL block illustrates how to create a user-defined record type and a record based on it: DECLARE TYPE rectpBk IS RECORD (BookID CHAR(13),BkName Book.cBookName%TYPE, Author Book.cAuthorName%TYPE); -- Create a record
64 © SQL Star International Ltd.
recBook rectpBk;
In the above code snippet: •
A record type rectpBk has been created which is made up of three fields namely BookID, BkName and Author. The %TYPE attribute is used to specify the datatypes of BkName and Author.
•
A record recBook is then created using rectpBk.
If you have not used the NOT NULL or the DEFAULT keyword to initialize fields, you must reference or initialize them explicitly.
Initializing Records You can reference or initialize record fields using the following syntax: . In the syntax the dot notation is used between the record name and the field name. In the above code snippet, you would reference the Bkname field as follows, recBook.Bkname and initialize it with a value, RecBook.Bkname:= ‘Illusions’; However, you cannot assign a list of values to a record by using an assignment operator. The following syntax is illegal: := (value1, value2, value3, ...); For instance, you cannot assign the values of book id, book name and author name all together to the record recBook in the following manner: recBook:=(‘0109918818122’,‘The Jewel In The Crown’, ‘Paul Scott’); Also, you cannot test records for equality or inequality. For instance, the following IF statement is illegal: IF record_name1 = record_name2 THEN END IF; You now know the syntax for assigning values to fields, but there are different ways to do it. Way of assigning values to Records There are two ways to assign values to fields: •
Instead of assigning values to fields individually, you can assign values to all the fields at once. This can be done using a SELECT statement in the code where you had declared recBook record.
65 © SQL Star International Ltd.
DECLARE BkID Book.cBookID%TYPE:=’&Bkid’; TYPE rectpBk IS RECORD (Bookid CHAR(13), Bkname Book.cBookName%TYPE, Author Book.cAuthorName%TYPE); recBook rectpBk; BEGIN SELECT cBookID,cBookName,cAuthorName INTO recBook FROM Book WHERE cBookID=BkID; DBMS_OUTPUT.PUT_LINE(recBook.Bookid||’ ‘|| recBook.Bkname||’ ‘||recBook.Author); END; / In the code, the fields of recBook have been populated with values retrieved from the Book table by the SELECT statement. But you must ensure that the column names in the SELECT statement appear in the same order as the fields in the record. Also, their datatypes should be compatible with one another. On executing this code, the values are retrieved from the record rather than from the database table Book. This reduces your network traffic. Enter value for bkid: FIC011111111 FIC011111111
•
Jewel In The Crown Paul Scott
The second way is to assign one record to another. The following code illustrates this:
DECLARE TYPE rectpBk IS RECORD (Bkid CHAR (15),
66 © SQL Star International Ltd.
Bkname CHAR (100)); TYPE rectpBkCtg IS RECORD (Bk rectpBk, Ctname CHAR (5), Pbyr DATE); --nested record TYPE rectpBrn IS RECORD (Brid CHAR (7), Brname CHAR (25), Bk rectpBk); --nested record recCtg rectpBkCtg; recBks rectpBkCtg; recBrn rectpBrn; BEGIN recCtg.Ctname:=’Humor’; recCtg.Bk.Bkid:=’0117955574699'; recCtg.Bk.Bkname:=‘Very Good Jeeves’; recBks.Pbyr:=’12-MAY-1965'; recBks.Bk:=recCtg.Bk; recBrn.Bk:=recCtg.Bk; END; /
This code snippet illustrates the declaration of nested records, i.e. a record defined as an element or a field of another record. For instance, Bk is a nested record as it is defined as a field (which is of rectpBk type) of recCtg, recBks and recBrn records. You can assign the values of a nested record to another if they are of the same datatype. For instance, recCtg and recBks are both of the same datatype, rectpBkCtg. The nested record Bk has been assigned values: …recCtg.Bk.Bkid:=‘0117955574699’; recCtg.Bk.Bkname:=‘Very Good Jeeves’; … You can now assign them to recBks record in the following manner:
67 © SQL Star International Ltd.
recBks.Bk:=recCtg.Bk; Such assignments are also permissible if the records that contain the nested records are of different datatype. For instance, recBrn record of rectpBrn record type, which contains the nested record Bk. Yet, the values of Bk initialized by recCtg record can be assigned to recBrn. recBrn.Bk:=recCtg.Bk; The problem of not knowing the column details of a database table and unexpected changes taking place in the number and datatype of columns can be overcome using the %ROWTYPE attribute. The % ROWTYPE attribute enables you to create %ROWTYPE records, which are similar to rows in a database table.
%ROWTYPE Records Sometimes you may want to create a record whose record type is same as a row of a database table (or a view). Such records are known as %ROWTYPE records. Just as you can create a record based on a user-defined record type, similarly, you can create a %ROWTYPE record using the %ROWTYPE attribute. The %ROWTYPE attribute enables you to declare a record type that represents a row in a table or a view. Creating %ROWTYPE Records Prefixing % ROWTYPE with the table name creates %ROWTYPE records. The syntax for declaring a %ROWTYPE record is:
reference%ROWTYPE;
Where, record_name is the name you assign for a record. reference specifies the name of a table, view or cursor on which you want the record to be based.
The following code snippet illustrates how to declare a %ROWTYPE record: DECLARE recTran Transaction%ROWTYPE; In the code snippet, recTran record is created using %ROWTYPE attribute to store the same transaction details, as are stored in the Transaction table. The structure of recTran record consists of fields, each of which represents a column in the Transaction table. Therefore, the fields of the recTran record have the same name and datatype as the columns in the Transaction table. Any change in the number or datatype of columns in the Transaction table will get reflected in the recTran record. The structure of the composite variable recTran would be as follows (This is not the code, but only the structure of recTran record). (cTranID CHAR (11),
68 © SQL Star International Ltd.
cMemberID CHAR (7), cBookID CHAR (15), dIssueDt DATE, dReturnDt DATE, dActualReturnDt DATE, nFine NUMBER (2))
However, you cannot include an initialization clause using an assignment operator, or assigning values using NOT NULL or DEFAULT keyword within %ROWTYPE declaration. If you have to reference or assign values to individual fields of %ROWTYPE record, you must use the dot notation syntax: . For instance, assign value to the field nFine in recTran record as follows: recTran.nFine: = 2; Instead of assigning values to individual record fields, you can assign values to the record by using a SELECT statement or fetching values into it using cursors. The following code illustrates assigning of values using a SELECT statement: DECLARE vTranID Transaction.cTranID%TYPE:= ‘&tranid’; recTran Transaction%ROWTYPE; BEGIN SELECT * INTO recTran FROM Transaction WHERE cTranID=vTranID; DBMS_OUTPUT.PUT_LINE(recTran.cMemberId ||‘ ’||recTran.cBookID||‘ ’|| recTran.dIssueDt||‘ ’|| recTran.dReturnDt||‘ ’|| recTran.dActualReturnDt||‘ ’|| recTran.nFine); END; / Following is the output of the above code: Enter value for tranid: T0000020594
69 © SQL Star International Ltd.
CBW109702 CLS020005771
22-MAY-94 05-JUN-94 09-JUN-94 6
The code creates a record recTran of %ROWTYPE that represents an entire row of Transaction table. The column values in the Transaction table are assigned to the fields in the recTran record using the SELECT statement. The same code can be written using cursors. Try it yourself. As mentioned earlier in the chapter, records are mainly created to reduce network traffic and save server resources. But how is the New Jersey Central Library achieving this? The following scenario will make it clear how the library is making use of %ROWTYPE records. The library maintains a table called NonMembers, that stores the details of all members whose membership has been terminated. As the details are similar to the Member table, you can use this to insert values into NonMembers table, by writing the following code: DECLARE vMemID Member.cMemberID%TYPE:=’&memid’; recMember Member%ROWTYPE; BEGIN SELECT * INTO recMember FROM Member WHERE cMemberID=vMemID; INSERT INTO NonMember (nSerialNo,cMemberID,cFirstName, cLastName,cPhone,dMembershipDt,cBranchID)
VALUES(sqSerialNo.nextval,recMember.cMemberID,recMember.cFirstName, recMember.cLastName,recMember.cPhone,recMember.dMembershipDt, recMember.cBranchID); COMMIT; END; / In the code, the programmer declares a recMember record to store the details of a member, as it is stored in the Member table, by using the %ROWTYPE attribute. Values are assigned to the fields of recMember record by the SELECT statement that retrieves details of the member whose ID is supplied.
70 © SQL Star International Ltd.
The assigned values are then inserted into NonMember table by using the dot notation syntax of recMember.cFirstName, recMember.cLastName, and so on. Whenever the membership of a member has to be terminated, his ID is supplied to execute the above PL/SQL block. This block will select his details from the Member table and store it in the record created. His details are then inserted into the NonMember table from the record and not from the database table. This code serves two purposes. Firstly, it automates entries into the NonMember table. Secondly, it reduces the server’s network traffic and memory usage.
Inserting a Record using %ROWTYPE The above example can be re-written as below: DECLARE vMemID Member.cMemberID%TYPE:=’&memid’; recMember MemberCopyTab%ROWTYPE; BEGIN SELECT * INTO recMember FROM Member WHERE cMemberID=vMemID; INSERT INTO MemberCopyTab VALUES recMember; COMMIT; END; / Compare the INSERT statement in the previous example with the INSERT statement in this example. The recMember record is of type MemberCopyTab. The number of fields in the record must be equal to the number of field names in the INTO clause. You can use this record to insert values into a table. This makes the code more readable.
Updating a row in the table using %ROWTYPE DECLARE vMemID Member.cMemberID%TYPE:=’&memid’; recMember MemberCopyTab%ROWTYPE; BEGIN SELECT * INTO recMember
71 © SQL Star International Ltd.
FROM Member WHERE cMemberID=vMemID; UPDATE
MemberCopyTab
SET ROW=recMember WHERE Memberid=vMemID; COMMIT; END; / The example shows how to update a row using a record. The keyword ROW is used to represent the entire row. The code shown in the example updates the MemberCopyTab table with MemID detail of the Member table. By now, as you should be familiar with PL/SQL records, let us move on to the next type of composite variable INDEX BY tables.
INDEX BY Tables Like PL/SQL records, INDEX BY tables are also composite variables and are created based on a composite datatype TABLE. INDEX BY tables are objects of TABLE type, which are designed as database tables but are not the same as database tables. For example, an INDEX BY table of book names is designed as a database table with two columns, one that stores the primary key and the other that stores the character data, book names. But unlike a database table you cannot manipulate INDEX BY table by using SQL statements. However, the primary key column enables you an array-like access to rows. Consider the primary key values as index and rows of the INDEX BY table as elements of a one-dimensional array. A one-dimensional array is an ordered list of elements of the same type. An array element is accessed by its index. An index number determines the position of an element in an ordered list. All array indexes start at zero. For instance, an array contains 12 elements of character datatype representing names of months. Each element is assigned an index with which they are to be accessed.
To access the fourth month of a year, you should access index number 3. However,
72 © SQL Star International Ltd.
INDEX BY tables are different from arrays in two ways: •
Arrays have their lower and upper limit defined. For instance, the array created above has a limit of 12 elements. It cannot accommodate a single element exceeding its limit. However, the size of an INDEX BY table is not fixed and can be increased dynamically.
•
Secondly, arrays necessarily must have consecutive index numbers. But this is not a constraint in INDEX BY tables and this characteristic is known as sparsity. For instance, to index book names in an INDEX BY table, you can use book IDs that are not consecutive.
Defining INDEX BY Tables To create INDEX BY tables, you must follow two steps : 1. Define a TABLE type 2. Declare INDEX BY tables of the TABLE type
TABLE types are declared in the declarative section of PL/SQL blocks using the following syntax:
TYPE IS TABLE OF {column_type | variable%TYPE | table.column%TYPE} [NOT NULL] table.%ROWTYPE [INDEX BY BINARY_INTEGER|PLS_INTEGER]; Where, type_name is the name of the TABLE type. It is this TABLE type based on which subsequent INDEX BY tables are declared. column_type specifies the type of the column that would be contained in the INDEX BY table. It can be any scalar datatype such as VARCHAR2, NUMBER and DATE. If the column type is a record type then every field of the record must be of a scalar datatype. You can also use the %TYPE to provide the datatype of a variable or a database column. NOT NULL constraint prevents null values from being stored in the INDEX BY table. INDEX BY BINARY_INTEGER|PLS_INTEGER specifies the datatype of INDEX BY clause. The key can be numeric, either BINARY_INTEGER or PLS_INTEGER. BINARY_INTEGER and PLS_INTEGER require less storage than NUMBER. They are used to represent mathematical integers compactly and implement arithmetic operations by using machine arithmetic. Arithmetic operations on these data types are faster than NUMBER arithmetic. The key can also be of type VARCHAR2 or one of its subtypes. Both BINARY_INTEGER and PLS_INTEGER has a magnitude range of
73 © SQL Star International Ltd.
–2,147,483,647..2,147,483,647. Therefore, the primary key value can be negative. Note that PLS_INTEGER requires less storage and are faster than BINARY_INTEGER. The following code snippet illustrates how to define a TABLE type based on the member IDs. DECLARE TYPE tbtypMemID IS TABLE OF NonMember.cMemberID%TYPE INDEX BY PLS_INTEGER;
The code snippet creates tbtypMemID TABLE type that will have the column type of cMemberID. After declaring a TABLE type, you need to create an INDEX BY table of the TABLE type. You can create an INDEX BY table by using the following syntax: identifier
;
Where, identifier is the name assigned to the INDEX BY table declared of type_name. The following code snippet illustrates the declaration of an INDEX BY table of tbtypBookName TABLE type:
DECLARE TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE INDEX BY PLS_INTEGER; tabBkName tbtypBookName;
This code creates tabBkName INDEX BY table that is based on tbtypBookName TABLE type. Before you start using INDEX BY tables, remember that you are not allowed to initialize them. An INDEX BY table cannot be populated when you are declaring it. Even when you define the NOT NULL constraint, do not initialize any values. You can populate the INDEX BY table by explicitly using executable statements. The next example illustrates that you can define NOT NULL constraint without initializing the INDEX BY table. DECLARE TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE NOT NULL INDEX BY BINARY_INTEGER; tabBkName tbtypBookName;
74 © SQL Star International Ltd.
By defining NOT NULL constraint on the tbtypBookName TABLE type, null values are prevented from being entered into tabBkName INDEX BY table. The INDEX BY table tabBkName has the following structure:
tabBkName INDEX BY table has one column and a primary key neither of which are named. The column is of scalar datatype and the primary key is of PLS_INTEGER type. The primary key plays an important role when it comes to referencing an INDEX BY table. The size of tabBkName is unconstrained or unbounded. Its number of rows can increase dynamically as more books are added into the already existing list of books in the Book table. You cannot initialize tabBkName INDEX BY table in its declaration. By doing so, you are limiting the number of books. In a library, there would always be additions to the Book table. By defining a limit, your INDEX BY table would not reflect changes made to its underlying database column.
Referencing INDEX BY Tables To reference elements of an INDEX BY table, use the primary key index number in the following syntax: (primarykey_value) Where, index_by_table_name is the name of the INDEX BY table whose element you want to reference. primarykey_value is the expression that yields the PLS_INTEGER value. Reference an element in the third row of tabBkName INDEX BY table in the following manner: tabBkName(3) So far, you have declared a TABLE type and INDEX BY table of the TABLE type in the declarative section. You can now use INDEX BY table within the executable section. For instance, use tabBkName INDEX BY table to update the number of copies of the book ‘Jewel In The Crown’.
75 © SQL Star International Ltd.
DECLARE vBrId Book.cBranchID%TYPE:=’&BrId’; TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE INDEX BY BINARY_INTEGER; tabBkName tbtypBookName; BEGIN tabBkName(1):=’Jewel In The Crown’; UPDATE Book SET nNoOfCopies = nNoOfCopies + 3 WHERE cBookName = tabBkName(1) AND cBranchID=vBrId; END; / On executing the code, you get the following result: Enter value for brid: 02CHSNJ PL/SQL procedure successfully completed. INDEX BY tables are mainly used to move collections of data into and out of database tables to reduce network traffic and save server resources. For example, the New Jersey Central Library may want to temporarily store the details of nonmembers. The programmer will write the following code to retrieve the member IDs from the NonMember table and store them in a INDEX BY table in the following way:
DECLARE TYPE tbtypMemID IS TABLE OF NonMember.cMemberID%TYPE INDEX BY BINARY_INTEGER; tabMemID tbtypMemID; nCount NUMBER (2); BEGIN
76 © SQL Star International Ltd.
SELECT COUNT (*) INTO nCount FROM NonMember; FOR i IN 1..nCount LOOP SELECT cMemberID INTO tabMemID(i) FROM NonMember WHERE nSerialNo= i; END LOOP; FOR i IN 1..nCount LOOP DBMS_OUTPUT.PUT_LINE (tabMemID (i)); END LOOP; END; / In the above code, a loop is used to retrieve the IDs of non-members from the NonMember table and store them in tabMemID INDEX BY table. Each of the rows has been assigned serial numbers (nSerialNo) in the NonMember table, which are incremental by 1. Therefore, the following WHERE clause: WHERE nSerialNo = i ; The above code also uses another loop to retrieve them from tabMemID and print them on to the screen using DBMS_OUTPUT.PUT_LINE. Therefore, each time the loop iterates, the values are displayed from the INDEX BY table rather than from the database table in the following manner: This reduces a lot of network traffic. On executing the code, you get the following result: CAC033105 DSP023205 ……………………… PL/SQL procedure successfully completed.
77 © SQL Star International Ltd.
This list proves to be helpful when the librarian wishes to compare it with the existing list of members in the Member table. By doing so, he will be able to identify those who are no longer members of the library. So far, the INDEX BY tables you created were based on TABLE types consisting of scalar datatypes. You can also base them on record types. Such INDEX BY tables are known as INDEX BY table of records.
INDEX BY Table of Records In INDEX BY table of records, %ROWTYPE is used to define its element type. For example, you can declare an INDEX BY table of records type, which is based on the Book table. The following code snippet illustrates the usage of %ROWTYPE to creating INDEX BY tables: DECLARE TYPE tbtypBk Book%ROWTYPE INDEX BY BINARY_INTEGER; tabBk tbtypBk; In the above code snippet, tabBk INDEX BY table holds details of all the fields of the database table Book. You can reference tabBk fields by using the following syntax: indexbytable_name (index).field Usage of the syntax is as follows: tabBk(5).cAuthorName:=‘Shakespeare’; cAuthorName represents a field in tabBk. Now, based on the INDEX BY table of records, tabBk created in the above code, display the author name and published year of books published between the years 1950 and 1975.
DECLARE CURSOR curBk IS SELECT * FROM Book WHERE dPublishedYr between ’01-JAN-1950' AND ’01-JAN-1975’; TYPE tbtypBk IS TABLE OF Book%ROWTYPE INDEX BY BINARY_INTEGER;
78 © SQL Star International Ltd.
tabBk tbtypBk; i NUMBER := 0; BEGIN OPEN curBk; LOOP i := i+1; FETCH curBk INTO tabBk(i); EXIT WHEN curBk%NOTFOUND; DBMS_OUTPUT.PUT_LINE tabBk(i).cBookName
(‘The ||’written
tabBk(i).cAuthorName ||’was published
book’||’
‘||
by’||’
‘||
in
the
year’||’ ‘||tabBk(i).dPublishedYr); END LOOP; CLOSE curBk; End; / On executing this code, you will get the following output: The book Jewel In The Crown written by Paul Scott was published in the year 25-NOV-66 PL/SQL procedure successfully completed.
To further enhance the functionality of INDEX BY tables, there are some built-in procedures or functions that operate on them. These built-ins are covered next.
INDEX BY Table Methods INDEX BY tables use methods such as EXISTS, COUNT, FIRST, LAST, PRIOR etc. These methods are built-in procedures or functions that make the using the INDEX BY tables easier. They are accessed using the dot(.) notation. The syntax is:
.[(parameters)]
The various INDEX BY table methods are:
79 © SQL Star International Ltd.
•
EXISTS
•
COUNT
•
FIRST
•
LAST
•
PRIOR
•
NEXT
•
TRIM
•
DELETE
Of these methods, EXISTS, PRIOR, NEXT, EXTEND and DELETE take parameters. These parameters must be expressions that produce a BINARY_INTEGER value or values that can implicitly be converted in to a value of that type.
EXISTS Method EXISTS (n) returns TRUE if the nth element exists in an INDEX BY table. This attribute is used to avoid the error that would be generated while referencing nonexistent elements. In the following example, PL/SQL executes the UPDATE statement only if the element tabBkName (1) exists: DECLARE vBrId Book.cBranchID%TYPE:= ‘&BrId’; TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE INDEX BY BINARY_INTEGER; tabBkName tbtypBookName; BEGIN tabBkName(1):=’The Crown’; IF tabBkName.EXISTS(1) THEN UPDATE Book SET nNoOfCopies = nNoOfCopies + 3 WHERE cBookName = tabBkName(1) AND cBranchID = vBrId; END IF; COMMIT;
80 © SQL Star International Ltd.
END; /
COUNT Method The COUNT method returns the number of elements contained within an INDEX BY table. You can use COUNT wherever integer expression is allowed. For example, the following code creates a tabMemID INDEX BY table. Using COUNT you can ascertain the total number of books contained in it.
DECLARE TYPE tbtypMemID IS TABLE OF NonMember.cMemberID%TYPE INDEX BY BINARY_INTEGER; tabMemID tbtypMemID; nCount NUMBER (2); BEGIN SELECT COUNT(*) INTO nCount FROM NonMember; FOR i IN 1..nCount LOOP SELECT cMemberID INTO tabMemID(i) FROM NonMember WHERE nSerialNo = i; END LOOP; FOR i IN 1..nCount LOOP DBMS_OUTPUT.PUT_LINE (tabMemID (i)); END LOOP; DBMS_OUTPUT.PUT_LINE (‘The total number of books contained in tabBkName is:’||tabMemID.COUNT); END;
81 © SQL Star International Ltd.
/
The COUNT method is useful because the future size of an INDEX BY table is unconstrained. For example, running the above code displays the total number of elements contained in tabMemID. CAC033105 DSP023205 CLK029204 DMA029204 DAC078801 CKB109305 The total number of books contained in tabMemID is:6 PL/SQL procedure successfully completed. But if you run the same code after few days, you would find an increase in the number of elements because the INDEX BY table is unconstrained and reflects any change in the database table.
FIRST and LAST Method The FIRST and LAST methods return the first (smallest) and last (largest) index numbers in an INDEX BY table. These methods return null if the INDEX BY table is empty or return the same index number if the INDEX BY table contains only one element. For instance, in the above code you can identify the book names positioned at the first and the last index numbers. The snippet of the relevant code: DBMS_OUTPUT.PUT_LINE
(‘The
book
contained
(‘The
book
contained
in
the
first
index
is:
last
index
is:
’||tabMemID.FIRST); DBMS_OUTPUT.PUT_LINE
in
the
’||tabMemID.LAST); END;
PRIOR and NEXT Method The PRIOR (n) method returns the index number that precedes index n in an INDEX BY table. NEXT (n) returns the index number that succeeds index n in an INDEX BY table. However, if n has no predecessor or successor, then both PRIOR and NEXT return NULL. For example, the following statement returns NULL because the first element in an INDEX BY table cannot have a predecessor: n: = tabMemID.PRIOR(tabBkName.FIRST)
82 © SQL Star International Ltd.
--NULL is assigned to n
You can use PRIOR and NEXT in tabMemID to display the member IDs that appear prior to the last one and next to the first one as follows: DBMS_OUTPUT.PUT_LINE (tabMemID.PRIOR (tabMemID.LAST)); DBMS_OUTPUT.PUT_LINE (tabMemID.NEXT (tabMemID.FIRST));
TRIM Method The TRIM method removes one element from the end of an INDEX BY table. TRIM (n) removes n elements from the end of an INDEX BY table. For example, you can trim one element from the end of tabBkName INDEX BY table in the following manner: tabBkName.TRIM;
DELETE Method The DELETE method has three forms. They are: •
DELETE removes all the elements
•
DELETE (n) removes the nth element
•
DELETE (m, n) removes all the elements in the range of m to n from an INDEX BY table
For example, the following snippet deletes the 20th element from tabBk INDEX BY table. tabBk.DELETE (20);
You can confirm this by issuing the following loop in the executable section of the block: FOR i IN 1..tabBk.COUNT LOOP DBMS_OUTPUT.PUT_LINE (tabBk (i). cBookName); END LOOP; END; This statement uses the COUNT method to specify the upper limit of the FOR loop. The COUNT attribute will result in one element less than the total number of elements and therefore the 20th element is not displayed.
83 © SQL Star International Ltd.
Varying Arrays Collections also include Varrays and Nested Tables. This section covers varrays. If a record has more than one value for any of its attributes, then you would have to enter the record into the table, as many times as there are values for the attribute. This violates the primary key constraint and causes redundancy as the same record gets stored multiple times. This problem can be taken care of by using varying arrays. They are used to store multiple values of attributes of a record in the same row.
Creating Varying Arrays Varrays are created either as an Oracle datatype or a PL/SQL type. The syntax to create varrays as a stored Oracle datatype is: CREATE [OR REPLACE] TYPE AS VARRAY(size) OF datatype(size); Where, varray_name is the name you want to give the varray AS VARRAY (size) are the key words to create the varray specifying the size of the varray in the parentheses. The size specifies how many values can be held for the same attribute of one record. OF datatype(size) is the type of data that the varray will store. This datatype is an Oracle provided datatype.
84 © SQL Star International Ltd.
For instance, in a library people borrow more than one book at a time. The library has a rule that they will issue three books at a time to a member. Hence instead of recording three transactions with different transaction IDs for the same customer, the database should allow the library clerk to enter only one record with details of the three books that the member wants to borrow. The attribute of book ID could be created as a varray as shown below: CREATE TYPE vaBooksID AS VARRAY(3) of CHAR(13); / Type created. This code creates a varray vaBooksID that can be used in the Transaction table to record the IDs of the books borrowed by the member. To use this in the Transaction table, you need to create the table specifying the varray in the CREATE TABLE statement: CREATE TABLE objTransaction (cTranID CHAR(4) PRIMARY KEY, cMemberID CHAR (9), BookID vaBooksID, dIssueDt DATE, dReturnDt DATE, dActualReturnDt DATE, nFine NUMBER (4,2), CONSTRAINT TranMemIDFK FOREIGN KEY (cMemberID) REFERENCES Member (cMemberID) ON DELETE CASCADE); Table created. When viewing the table structure using data dictionary views, the column BookID will be displayed as a column of type vaBooksID. To see the attributes of the varying array, query the view, USER_COLL_TYPE. It will display the datatypes and their characteristics. You could also specify the particular datatype about which you want information. For instance the following SELECT statement: SELECT COLL_TYPE, ELEM_TYPE_OWNER, ELEM_TYPE_NAME, UPPER_BOUND, LENGTH FROM USER_COLL_TYPES WHERE Type_Name = ‘VABOOKSID’; The query displays the following information about the varray vaBooksID:
85 © SQL Star International Ltd.
COLL_TYPE
ELEM_TYPE_OWNER ELEM_TYPE_NAME UPPER_BOUND
--------
--------------- -------------- ----------
VARYING ARRAY
CHAR
3
LENGTH -------13
Inserting Rows into Varrays Varrays are abstract datatypes. Hence, to insert records into them you need to use the constructor methods as you had done when inserting records into object tables. For instance, to insert records into the objTransaction table created with a varray type column, the code is:
INSERT INTO objTransaction VALUES (‘T001’, ‘BTK119705’, vaBooksID (‘FIC00400213’, ‘ROM0020670’), ‘1-NOV-2000’, ’15-NOV-2000', NULL,NULL); 1 row created. In the code vaBooksID is the constructor of the varray type that is used to enter values into the column.
Creating Varray Based on Abstract Dataype In case the varying array is created using an abstract datatype, then to insert values into the column the constructors of the abstract datatypes will have to be used within the varray. Example of a varray created using an abstract data type: Create an object type as shown below.
86 © SQL Star International Ltd.
CREATE OR REPLACE TYPE typBook AS OBJECT (Bk_ID VARCHAR2(15)); / Type created. Create a varray based on typBook as follows: CREATE OR REPLACE TYPE vaBkID AS VARRAY(3) OF typBook; / Type created. Now, create a table objTrans having a column BookID of varray type vaBkID as shown below. CREATE TABLE objTrans (cTranID CHAR(4) PRIMARY KEY, cMemberID CHAR (9), BookID
vaBkID,
dIssueDt DATE, dReturnDt DATE, dActualReturnDt DATE, nFine NUMBER (4,2), CONSTRAINT TransMemIDFK FOREIGN KEY (cMemberID) REFERENCES Member (cMemberID) ON DELETE CASCADE); / Table created.
To add a row into the table, issue the following INSERT statement: INSERT INTO objTrans VALUES (‘I001’, ‘BTK119705’, vaBkID( TypBook(‘FIC00400213’), TypBook( ‘ROM002067’)), ‘25/01/2001’, ‘08/02/2001’, NULL, NULL); 1 row created.
87 © SQL Star International Ltd.
An important point to remember is that, you should not insert more values into the varray than what is specified when creating it. This is known as the LIMIT of a varray and is specified when creating the varray. You can query the USER_COLL_TYPES view to confirm the maximum values that the varray can hold. Selecting Data from a Varray You retrieve data stored in a varray by using a loop within a cursor. This can be understood better by the code given below. This code, when executed extracts the book IDs borrowed by a member. (To be able to see the output set the SERVEROUTPUT on) DECLARE CURSOR curBookIssue IS SELECT * FROM objTransaction; BEGIN FOR BookRec in CurBookIssue LOOP DBMS_OUTPUT.PUT_LINE(‘Books borrowed by:’ || BookRec.cMemberID); FOR I IN 1 .. BookRec.BookID.Count LOOP DBMS_OUTPUT.PUT_LINE (BookRec.BookID(I)); END LOOP; END LOOP; END; / This code displays the result as shown below. Books borrowed by:BTK119705 FIC00400213 ROM0020670
88 © SQL Star International Ltd.
PL/SQL procedure successfully completed. Another method to extract data from a varray is by using the Table() function. The varray column name is passed as a parameter to this function and its output is given an alias. SELECT t.cTranID , B.* FROM objTransaction t, Table(t.BookID) B;
The output for this code is: CTRA COLUMN_VALUE ----
-----------
T001 FIC00400213 T001 ROM0020670
Creating VARRAY as PL/SQL Type :The maximum size of a VARRAY is 2 gigabytes (GB) as in nested tables which is discussed later. In this case, the elements of a VARRAY are stored contiguously in memory and not in the database. Example: TYPE location_type IS VARRAY(4) OF Member.carea%TYPE; address location_type; / Type created. DECLARE TYPE location_type IS VARRAY(3) OF Member.carea%TYPE; address location_type; table_count NUMBER; BEGIN address:= location_type(‘Bedminster’,’Allenbury’,’Ridgeland’,’Rockland’ ); table_count := address.count(); FOR i in 1..table_count LOOP
89 © SQL Star International Ltd.
DBMS_OUTPUT.PUT_LINE(address(i)); END LOOP; END; /
In the above code, the size of a VARRAY is restricted to 4. You can initialize a VARRAY by using constructors. If you try to initialize the VARRAY with more than four elements, then a “Subscript outside of limit” error message is displayed. If you want more flexibility when working with Composite Datatypes, you can use Nested tables.
90 © SQL Star International Ltd.
Nested Tables As you have seen, varying arrays have a limit on the maximum number of values that can be entered. In case you cannot define a maximum number of values to be entered into a record, you can use nested tables. It can be created either as a schema object or as a PL/SQL type. A nested table is a table within a table, just as a nested query or a subquery is a query within a query.
A nested table is an abstract datatype created consisting of another abstract type, and the former abstract datatype is used to define a column when creating a table. The syntax to create a nested table is: CREATE TABLE ( datatype(size), abstract datatype, . . .) NESTED TABLE
STORE
AS
Where, CREATE TABLE creates a relational table specifying the table name. datatype(size) are columns defined with an Oracle provided datatype. abstract datatype are columns defined using abstract datatypes, that are table types.
91 © SQL Star International Ltd.
NESTED TABLE STORE AS ; This statement specifies the name of the table that will hold the data of the nested table. Data of columns that are of Oracle provided datatypes are stored within the main table being created, but the data of nested tables are stored in another table. In other words nested tables are not stored inline with the rest of the table data. There are pointers from the parent table to the nested tables. For instance, to enter the book IDs that are issued to members, you created a varray. But as you saw there was a maximum limit that the varray can hold. Assume the library issues as many books as the member wants, and you want to store the book’s IDs, category and author of the book, then a nested table will serve the purpose. First create an abstract datatype typBookDeta to store the attributes BookID, BookCategory and Author. CREATE TYPE typBookDeta AS OBJECT (BookID CHAR(15),BookCategory CHAR(5),Author CHAR(30)); / Type created. Use this object type to create a type, called BookDetaNest that will be the basis of a nested table. CREATE TYPE BookDetaNest AS TABLE OF typBookDeta; / Type created. This code creates type BookDetaNest allowing it to hold many rows. This type can now be used to create a table of members and the books that they borrow as these two bits of information are required very frequently to generate status reports on the movement of books. CREATE TABLE MemberBook (MemberName VARCHAR(50), Books BookDetaNest) NESTED TABLE Books STORE AS BookNestTab; Table created. In this code, a table called MemberBook is created, with one VARCHAR2 column and the other of an abstract datatype BookDetaNest that is created from an abstract datatype. The values of the column Books are stored in another table called BookNestTab, which is a nested table while the member names are stored in the table MemberBook.
Inserting Records into a Nested Table To insert values into a table that has a nested table the syntax is:
92 © SQL Star International Ltd.
INSERT INTO VALUES (‘value’, abstract datatype constructor( abstract datatype constructor(),abstract datatype constructor(),. . .)); Where, is the relational table that is created. The outer abstract datatype constructor is the constructor of the abstract datatype to which the column for the nested table belongs. abstract datatype constructor() is the set of attributes of the abstract datatype that in turn are of another abstract datatype. To populate the MemberBook table, the INSERT statement would be: INSERT INTO MemberBook VALUES (‘Jane Willow’, BookDetaNest ( typBookDeta(‘FIC00400213’,’Fic’, ‘Robin Cook’), typBookDeta(‘ROM002067’,’Rom’, ‘Danielle Steel’), typBookDeta(‘ROM002099’,’Rom’, ‘Danielle Steel’), typBookDeta(‘CLS002067’,’Cla’, ‘Mark Twain’))); 1 row created. This code when executed stores the member name Jane in one table and the details of the books borrowed by Jane in another table.
Confirming the Structure To confirm the structure of the columns and abstract datatypes used in tables, you can query the data dictionary views as was shown in the case of varrays. You can make queries for a specific table and/or a datatype, as shown below. SELECT COLL_TYPE, ELEM_TYPE_NAME, ELEM_TYPE_OWNER FROM USER_COLL_TYPES WHERE Type_Name=’BOOKDETANEST’; On executing the SELECT statement the following result is displayed: COLL_TYPE ---------TABLE
ELEM_TYPE_NAME ------------TYPBOOKDETA
ELEM_TYPE_OWNER --------------SCOTT
Displaying Data Nested table as you have seen is a column within a table.
93 © SQL Star International Ltd.
Oracle provides a special keyword, THE, to query from nested tables. You cannot query a nested table with a SELECT statement. You need to enclose the SELECT query that selects the nested table column from the table, within THE keyword. This statement is then used as the source for the FROM clause of another SELECT query to extract the required vale from the nested table. The syntax to query from nested tables is as follows: THE (SELECT nested table column FROM WHERE column_name = value); If you want to know the details of the books borrowed by Jane Willow you need to execute the following code: SELECT * FROM THE (SELECT Books FROM MemberBook WHERE MemberName = ‘Jane Willow’); On executing the code, the following result is displayed:
BOOKID
BOOKC
AUTHOR
------
-----
------
FIC00400213
Fic
Robin Cook
ROM002067
Rom
Danielle Steel
ROM002099
Rom
Danielle Steel
CLS002067
Cla
Mark Twain
Inserting Data Using THE The THE function can also be used to insert data into the nested table. In case you want to perform an insert into the main table leaving the contents of the nested table intact, you use the THE function. When you want to populate a table with contents from another table you use the INSERT AS SELECT statement. Here you cannot use the SELECT statement within the INSERT statement, hence it is enclosed within the keyword THE. Let us assume that Jane, who had borrowed four books when she came, brings along a friend who wants to borrow the same books. An entry has to be made into the MemberBook table to include a new member but the details of the nested table remain the same. In this case, the SELECT query within the keyword is: THE (SELECT Books FROM MemberBook WHERE MemberName = ‘Jane Willow’) nt
94 © SQL Star International Ltd.
This query selects the name of the member from the MemberBook table. The query should now be made the SELECT query for the INSERT, to add the new member’s issue transaction. INSERT INTO MemberBook SELECT ‘Kety’,nt.BookID,nt.BookCategory,nt.Author FROM THE (SELECT Books FROM MemberBook WHERE MemberName=’Jane Willow’) nt; INSERT INTO MemberBook * ERROR at line 1: ORA-00913: too many values
This statement fails because the statement attempts to insert more values than there are columns in the MemberBook table. The MemberBook table has two columns. Though one of the columns is a nested table, you cannot directly insert values into its columns in the manner done above. Hence, the statement needs to be rewritten using two keywords, CAST and MULTISET. CAST represents the result of a query as a nested table and MULTISET allows the CAST query to have multiple rows. These two keywords can be used together. Now the INSERT transaction you want to perform can be done as follows: INSERT INTO MemberBook VALUES (‘James Willow’, CAST (MULTISET (SELECT * FROM THE (SELECT Books FROM MemberBook WHERE MemberName = ‘Jane Willow’)) AS BookDetaNest)); 1 row created. To confirm the insertion of a row, you can query the MemberBook table as follows: SELECT nt.Author FROM THE (SELECT Books FROM MemberBook WHERE MemberName = ‘James Willow’) nt; The query displays the following result set:
95 © SQL Star International Ltd.
AUTHOR ——————— Danielle Steel Danielle Steel Mark Twain Robin Cook Nested tables are very powerful in their functionality. With a combination of the keywords provided you could perform queries and other data manipulations on nested tables and also use them with commands to manipulate the main tables.
Creating Nested table as PL/SQL Type The functionality of nested tables is similar to INDEX BY tables. however, there are differences in the nested table implementation. The nested table is a valid datatype in a schema-level table but an INDEX BY table is not.
• • • •
The key type for nested tables is not PLS_INTEGER. The key cannot be a negative value unlike in the INDEX BY table. There is a column with numbers in sequence which is considered as the key column. When you retrieve values from a nested table, the rows are given consecutive subscripts starting from 1. Nested tables can be stored in the database unlike INDEX BY tables.
Syntax to create Nested table: TYPE
type _name
IS TABLE OF
{column _type | variable%TYPE | table.column%TYPE} [NOT NULL] | table.%ROWTYPE
The following program illustrates the use of PL/SQL type Nested table.
96 © SQL Star International Ltd.
TYPE location_type IS TABLE OF Member.carea%TYPE; address location_type; If you do not initialize an INDEX BY table, it is empty. If you do not initialize a nested table, then it is automatically initialized to NULL. You can initialize the Address nested table by using a constructor as shown below: address:= location_type(‘Bedminster’,’Allenbury’,’Ridgeland’,’Roxbury’); Complete example: SET SERVEROUTPUT ON DECLARE TYPE location_type IS TABLE OF Member.carea%TYPE; address location_type; table_count NUMBER; BEGIN address:= location_type(‘Bedminster’,’Allenbury’,’Ridgeland’,’Roxbury’); table_count := address.count(); FOR i in 1..table_count LOOP DBMS_OUTPUT.PUT_LINE(address(i)); END LOOP; END; /
Output Bedminster Allenbury Ridgeland Roxbury PL/SQL procedure successfully completed.
97 © SQL Star International Ltd.
Managing Nested Tables and Varrays There are some issues regarding the management of collections like nested tables and varrays. A list of these issues is: 1. Indexing data when it becomes large. When the rows in a table increase, relational tables can be indexed but varrays cannot. Hence when a varray increases in size it could impair its performance. 2. You should know when to use a varray, when to create nested tables or a different relational table. Varrays have limitations(2 GB) in the size of data they can hold, hence, you need to decide when to use a nested table or a relational table. 3. If you want methods for your data use nested tables instead of relational as the former are abstract datatypes. 4. If your data requires it to be related to lots of other tables, use relational tables instead of nested ones. It is easier to manage data relationships.
Introduction to Ref Cursor A REF CURSOR is basically a datatype. A variable created based on such a data type is generally called a cursor variable. A cursor variable can be associated with different queries at runtime. The primary advantage of using cursor variables is their capability to pass result sets between subprograms (like stored procedures, functions and packages) An example for Ref Cursor is shown below: DECLARE TYPE Type r_cursor is REF CURSOR; c_book
type_r_cursor;
Book_rec Book%rowtype; BEGIN OPEN c_book FOR
SELECT * FROM Book;
98 © SQL Star International Ltd.
LOOP FETCH
c_emp INTO book_rec;
EXIT WHEN
c_book%rowcount> 20 or c_book%notfound ;
DBMS_OUTPUT.PUT_LINE(book_Rec.cBookname || ‘ - ‘ || book_Rec.cAuthorName); END LOOP; CLOSE c_book; END; / Type type_r_cursor is REF CURSOR; The above statement simply defines a new data type called “type_r_cursor,” which is of the type REF CURSOR. We declare a cursor variable named “c_BOOK” based on the type “type_r_cursor” as follows: c_book
type_r_cursor;
Every cursor variable must be opened with an associated SELECT statement as follows: OPEN c_book FOR SELECT * FROM Book; Fetch is done with in a Loop to retrieve the records from the dynamic cursor. Once the processing is done the cursor is closed before the block ends. Then, what differentiates a cursor Variable from an Explicit Cursor? •
A cursor variable can be associated with more than one query at runtime.
•
An explicit cursor is associated with one query at compilation time.
Summary In this chapter, you have learnt that: Composite datatypes (also known as collections) store multiple data of different datatypes due to its internal logical division.
99 © SQL Star International Ltd.
They are not pre-defined hence created by the user. PL/SQL records, INDEX BY TABLE, VARRAY, NESTED TABLE and REF cursor are collectively called Composite Datatypes. PL/SQL records are used to store data fetched from multiple columns of a table and %rowtype can be used to store an entire record using SELECT * statement. INDEX BY table holds data of one or more columns and INDEX BY table of records hold entire database table data either from the vertical selection of columns or entire table data. Varray stores multiple values for a particular column as per the limit and Nested table can have an embedded table within a column specifying unlimited values. Cursor variable declared using REF Cursor can be associated with more than one query dynamically.
100 © SQL Star International Ltd.
Lab Exercises 1. Create a PL/SQL block to display the information about a given job, say ‘ST_CLERK’.
Use a PL/SQL record, which is based on the structure of the Jobs table.
Print the information about the job using DBMS_OUTPUT.PUT_LINE
The information should resemble as shown below:
2.
Write a PL/SQL block to print information about a given country using PL/SQL
Record. a. Use the DEFINE command to provide the country ID. Pass the value to the PL/SQL block through a iSQL*Plus substitution variable. SET SERVEROUTPUT ON SET VERIFY OFF DEFINE p_countryid=CA b. Use DBMS_OUTPUT.PUT_LINE to print information about country details. C. Execute and test the PL/SQL block for the countries with the Ids CA,DE,UK,US.
3.
Create a PL/SQL block that will store the information of a retired employee
101 © SQL Star International Ltd.
into a table called RetiredEmpsData.
•
Declare a PL/SQL record variable based on the structure of the Employees table.
•
Supply the employee ID
•
Retrieve the record of the employee specified and store it in the variable declared
•
Query the RetiredEmpsData table
[Note: Create the RetiredEmpsData table with the columns: EmpID, EName, Job, MgrID, HireDate, RetiredDate, Sal, Comm, and DeptID]
4.
Create a PL/SQL block that uses INDEX BY table to retrieve the names of cities of each location ID from the Locations table, and print the same on the screen. The block should do the following:
•
Declare an INDEX BY table to store the names of the cities
•
Retrieve the names of all current cities from the Locations table into the INDEX BY table using a loop. Assign value for the Location_ID column based on the following counter values:
102 © SQL Star International Ltd.
•
Use another loop to retrieve the city names from the INDEX BY table and print them using the DBMS_OUTPUT.PUT_LINE
The output from the block should be as shown below
Roma Venice Tokyo Hiroshima Southlake South San Francisco South Brunswick Seattle
103 © SQL Star International Ltd.
Toronto Whitehorse Beijing Bombay Sydney Singapore London Oxford Stretford Munich Sao Paulo Geneva Bern Utrecht Mexico City
PL/SQL procedure successfully completed
5.
Create a table EMPPHDETAIL with following specification
EMPLOYEE_ID NUMBER(6),FIRST_NAME VARCHAR2(26). Phone number should be a Varray column. Insert 3 phone numbers into each employee’s detail. Select the data from this table.
6.
Create an Object type for employee ID. Use this in the creation of the Varray
which is restricted to 10. Create a table EmpInDept that contains a column of the employee ID varray, which holds Ids of the employees working in a department. Confirm whether the table has been created. Insert few values into this table and view them.
7.
Create a nested table column to hold the details of the projects handled by each employee. Insert a few record into this table and confirm the records inserted by querying them.
104 © SQL Star International Ltd.
Chapter 5
Cursors and Advanced Cursors Implicit and Explicit Cursors Cursors and PL/SQL Records Cursor FOR Loops Ensuring Faster Updates Subqueries in Cursors
105 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to: Differentiate between Implicit and Explicit Cursors Use explicit cursors Test the cursor status Write cursors using parameters Ensure faster update during cursor processing
106 © SQL Star International Ltd.
What are Cursors? A Cursor is an area of memory that the Oracle server opens to parse and execute an SQL statement. There are two types of cursors: Implicit Cursors and Explicit Cursors.
Implicit Cursors The Oracle server creates a work area implicitly when any SQL statement is issued within its executable section. It is known as an Implicit Cursor. It is called implicit because it is created and managed by the server automatically. The programmer need not create it. But what purpose do these implicit cursors serve? On executing a SELECT statement within a PL/SQL block, there are a lot of issues the programmer would be concerned of. For instance, if a SELECT statement within the block does not retrieve any records, it displays the message stating no data found. This situation can be handled by using some attributes that help programmers evaluate what must have happened when the implicit cursor was used. Implicit cursors are called SQL cursors, as the user does not specifically create them. You cannot control the functioning of implicit cursors but can view the information about the latest cursor by using implicit cursor attributes.
Attributes SQL%ROWCOUNT
Purpose Returns the number of rows retrieved by the most recently Issued SELECT statement.
SQL%FOUND
Evaluates to TRUE if the recently issued SELECT statement retrieves one or more rows.
SQL%NOTFOUND
Evaluates to TRUE if the recently issued SELECT statement does not retrieve any rows.
SQL%ISOPEN
It always evaluates to FALSE because implicit cursors are immediately closed after they execute the statement.
The use of these attributes will be clear after you try out these examples. If the librarian wants to know the number of rows that got updated when he changed the number of copies of ‘Alien Legacy’ stored in branch ‘02CHSNJ’ and ‘03HAMNJ’, he will have to use SQL%ROWCOUNT as follows:
107 © SQL Star International Ltd.
VARIABLE RowsUpd VARCHAR2(25) DECLARE vBrID1 Book.cBranchID%TYPE:=’02CHSNJ’; vBrID2 Book.cBranchID%TYPE:=’03HAMNJ’; BEGIN UPDATE Book SET nNoOfCopies=nNoOfCopies+2 WHERE cBranchID IN (vBrID1, vBrID2) AND cBookName=’Alien Legacy’; IF SQL%FOUND THEN DBMS_OUTPUT.PUT_LINE(‘Record Updated’); ELSIF SQL%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(‘Record not Updated’); END IF; :RowsUpd:=(SQL%ROWCOUNT || ‘ rows updated’); END; / Displays: Record updated. In the code, a bind variable RowsUpd has been declared using the VARIABLE command. Its value is displayed by using the PRINT command. PRINT RowsUpd ROWSUPD --------2 rows updated.
What are Explicit Cursors? Explicit cursors have the same functionality as implicit cursors, but are explicitly declared by the developer. When you place a SELECT statement within an explicit cursor you have more control over the way it functions. During the execution of any SQL statement, an implicit cursor is used. However, in case you want to work on every row that the SQL statement fetches, you should use explicit cursors.
108 © SQL Star International Ltd.
Explicit cursors are used to: •
Process and control the cursors in a PL/SQL block explicitly
•
Fetch and process more than one row returned by a query, one at a time
•
Keep track of the current row being processed
A difference between an implicit and explicit cursor is that although implicit cursor performs an array fetch, the existence of a second row always raises the exception TOO_MANY_ROWS. Explicit cursors can be used to fetch multiple rows and reexecute parsed queries in the work area.
Explicit Cursors Developers use explicit cursors in order to process the results of the SQL statements by manipulating individual records in the result set. Follow these steps to use an explicit cursor: •
Declare and define the cursor
•
Open the cursor
•
Fetch data from the cursor
•
Close the cursor
Declaring a Cursor A cursor is declared in the declarative section of the PL/SQL block. Syntax for declaring a cursor is: CURSOR IS SELECT statement
[This is not a complete syntax.] Where, cursor name is the name given to the explicit cursor declared. SELECT statement is the statement that returns multiple rows to be processed by the cursor. Once the cursor has been declared, it is ready for use. However, only when the cursor is opened, the SELECT statement defined within the cursor is executed and parsed. The following code shows how cursors are declared:
DECLARE
109 © SQL Star International Ltd.
vBkName Book.cBookName%TYPE; CURSOR curCategoryBooks IS SELECT DISTINCT (cBookName) FROM Book WHERE cCategoryID=‘01FIC’ AND cBranchID=‘02CHSNJ’; This code declares a cursor curCategoryBooks, which defines a SELECT statement to retrieve books of fiction category at the Chester branch. A variable vBkName is also declared which holds the values fetched by the cursor. Remember two points while declaring cursors: •
Variables used within the SELECT statement in the cursor must be declared before the cursor is declared.
•
Do not use the INTO clause while declaring the cursor as it is used in the FETCH statement.
Opening a Cursor You invoke a cursor by opening it. A cursor comes into existence only when you open it and not when you declare it. A cursor is opened in the executable section of a PL/SQL block. The syntax to open a cursor is: OPEN
[This is not a complete syntax.] To open the cursor you have just declared, type the following code: BEGIN OPEN curCategoryBooks; When the cursor is opened, the SQL query is executed and the rows are fetched. The record pointer is positioned at the first row fetched. The activities that occur when a cursor is opened are: 1. Memory is allocated to an area that will hold data for processing. 2. The SQL statement is parsed. 3. The memory addresses of the input variables are retrieved and values are set to them. 4. The returned rows (active set) are identified. 5. The record pointer is positioned before the first row in the active set. If there are no records in the active set, when the cursor is opened, PL/SQL does not raise an exception. The status of the cursor is checked after the FETCH statement.
110 © SQL Star International Ltd.
Fetching Data in a Cursor After a cursor is opened, the result set or active set of the executed SQL statement is brought into the cursor work area. The syntax for fetching the records into the cursor is: FETCH INTO [ variable1, variable2, …variableN | record_name]; Where, FETCH is the keyword used. cursor_name is the name of the cursor that is already declared. INTO keyword is used to put the retrieved records into the output variables. variable1, variable2…variableN are the output variables previously declared to store the results. record_name is the name of the row where the retrieved data is stored. Use the %ROWTYPE attribute to declare the record variable. The OPEN statement implicitly parses and executes the SQL statements. The parsing happens till all the records in the cursor are processed. The FETCH statement can be implemented on the explicit cursors with SELECT statements. When you fetch data into a cursor, following changes take place: •
The record pointer is moved to the next row in the active set.
•
The output variables are set with values retrieved from the cursor.
The records fetched are then processed using SQL and PL/SQL statements. The values retrieved from the query, must have corresponding variables of compatible datatypes in the INTO statement. The FETCH statement works with two kinds of variables: •
Stand-alone variables that are used to store the values of each column of the record fetched by the cursor. The values are set depending on positional specifications.
•
Declared records that contain the same set of attributes as the columns defined by the cursor. The order of columns in the declared record type and the columns defined in the cursor must be the same as the cursor values are stored in the declared record type based on positional specification.
The cursor you have just declared and opened is ready to fetch rows one by one into the variable declared. The following code will illustrate this: LOOP FETCH curCategoryBooks INTO vBkName; Since the explicit cursor processes multiple rows by fetching one row at a time, you need to therefore place the FETCH statement within a loop.
111 © SQL Star International Ltd.
Points to remember when using the FETCH statement: •
Ensure that the variables in the INTO clause of the FETCH statement is of the same number and datatype as that of the SELECT statement.
•
The variables should match the columns in position.
•
If there are no rows left to process, then FETCH will not retrieve any values.
Closing the Cursor Once you have processed the data fetched by the cursor it must be closed explicitly. Closing the cursor releases the work area used by the cursor and thereby frees system resources. You can exit the PL/SQL block without closing the cursor. However, it is a good practice to close the cursor to free the system resources used by the cursor. After you complete work with the records fetched by the cursor, you should close the cursor using the CLOSE command. The syntax is: CLOSE cursor_name; [This is not a complete syntax.] Once you have finished processing rows fetched by curCategoryBooks cursor, you can close it by issuing the CLOSE statement as follows: CLOSE curCategoryBooks; You can reestablish connection with the active set once a cursor is closed, but do not try to fetch data from a closed cursor. This causes the INVALID_CURSOR exception to be raised. There is a default number set in the system parameters for each user as to how many cursors can be opened by the user at one time. This default number is 50. This number is decided by the OPEN_CURSORS parameter of the database parameters.
Explicit Cursor Attributes Attributes of cursors are essentially meant to check the status of the cursors. The attributes with the implicit cursors are used to find out if the execution was successful on the records returned by the SQL statement. Attributes for explicit cursors are the same as those for implicit cursors. To refer to the implicit cursor attribute you must prefix SQL to the attributes. For instance, SQL%ROWCOUNT or SQL%NOTFOUND. When referring to these attributes in an explicit cursor, prefix the attributes with the name of the cursor. For example, cursor_name%ROWCOUNT Use of Cursor Attributes:
112 © SQL Star International Ltd.
%ISOPEN
:-
Checks whether the cursor is open or not.
%ROWCOUNT :-
Retrieves an exact number of rows affected.
%NOTFOUND :-
Determines when to exit the loop.
Now that you know how cursors are declared and what its various attributes are, you need to complete the code wherein you had declared the cursor curCategoryBooks. This is how it can be done: DECLARE vBkName Book.cBookName%TYPE; CURSOR curCategoryBooks IS SELECT cBookName FROM Book WHERE cCategoryID = ‘01FIC’ AND cBranchID = ‘02CHSNJ; BEGIN
IF NOT curCategoryBooks%ISOpen THEN OPEN curCategoryBooks; END IF; LOOP FETCH curCategoryBooks INTO vBkName; EXIT WHEN curCategoryBooks%ROWCOUNT>2 OR curCategoryBooks%NOTFOUND DBMS_OUTPUT.PUT_LINE (vBkName); END LOOP; CLOSE curCategoryBooks; END; / The PL/SQL block is created to satisfy the requirement of a particular enquiry, requiring the library desk officer to retrieve only two fiction books belonging to the Chester branch. You could use a simple SELECT statement to display the result. Would this work? Oracle does not provide a clause with the SELECT statement to restrict the number of rows to be retrieved after satisfying the WHERE condition and then perform the manipulations required.
113 © SQL Star International Ltd.
However, this requirement could be met using explicit cursors and its attributes. Therefore, the library database developer decided to generate the above code. In the above code, curCategoryBooks cursor retrieves all the fiction books that are stored in the Chester branch. Out of the total number of fiction books in the Chester branch, the requirement is to fetch only two of them. This is achieved by using the %ROWCOUNT attribute. On executing the above code you get the following result: Triumph Of Katie Byrne Woman Of Substance
Parameters in Cursors Cursors get values in the SELECT statement from a table with hard-coded values. However, at times you may have to reuse the cursors and it is quite tedious to rewrite the cursor structure. Therefore, you need to use a method by which you can specify where the data needs to be picked up from. This should be specified when the cursor is opened. This method is implemented by the use of parameters in cursors. Parameters are variables used to pass values to a block during runtime. Parameters pass values to the cursor when the latter is opened and the values are used in a query. This allows you to open and close a cursor many times in a block. Each time you pass a different set of values you get the appropriate results. Every parameter in the cursor must have a corresponding parameter in the OPEN statement. Datatypes for cursor parameters are the same as those for scalar variables, but you cannot specify a size for the parameter when declaring it. The parameter names are used in the query expression in the cursor for referencing.
The syntax of using parameters in the cursor is: CURSOR [( datatype, …)] IS SELECT statement; Where, cursor_name is the name given to declare the cursor parameter_name is the name of the parameter declared. You can have any number of parameters.
114 © SQL Star International Ltd.
datatype is the datatype for the parameter. Width should not be specified. SELECT statement is the query associated with the cursor where the parameters are used. Values are passed to the cursor when you open it. You can use PL/SQL variables, host variables or literals to pass values to the parameters. You can also set high and low levels to parameters allowing the cursor to select data within the range. Parameters in cursors also allow you to have more control over the processing of the data. For instance, the library desk officer is frequently faced with enquiries requiring him to retrieve a list of all the books belonging to particular categories. To help him perform this task the database developer generates the following code wherein the desk officer is required to feed in only the category name based on which the list of books belonging to the category specified are displayed. This code makes use of a parameterized cursor where the parameter takes in the category name. DECLARE vBookName Book.cBookName%TYPE; vCatgName Category.cCategoryName%TYPE:=‘&catname’; CURSOR curCategoryBooks (Catgname CHAR)IS SELECT DISTINCT (cBookName) FROM Book B,Category C WHERE B.cCategoryID = C.cCategoryID AND C.cCategoryName = vCatgName; BEGIN OPEN curCategoryBooks(vCatgName); LOOP FETCH curCategoryBooks INTO vBookName; EXIT WHEN curCategoryBooks%NOTFOUND; DBMS_OUTPUT.PUT_LINE(vBookName); END LOOP; CLOSE curCategoryBooks; END; In this code a substitution variable catname is declared, which prompts the user for a category name every time the code is executed. The value entered is stored in vCatName and then passed into the cursor parameter Catgname when the cursor is opened. Accordingly, the books belonging to the category entered are retrieved by the cursor.
115 © SQL Star International Ltd.
Enter value for catname: Science Fiction Alien Legacy Animal Farm The Catchers Of Heaven: A Trilogy
Cursors and PL/SQL Records Records can be declared to match the columns of a table or the list of selected columns in the SELECT statement of an explicit cursor. This kind of declaration makes it easy to store the active set as all you need to do is fetch the values into the record and the values of the fetched row are directly set to the corresponding fields of the record. For instance, whenever a member’s membership is terminated his details are entered into a NonMember table. To make this insertion an easier task, the developer generates a code that prompts the user for the ID of the member whose membership has been terminated and accordingly inserts his details into the NonMember table. To accomplish this, the developer uses a cursor, which retrieves details of members and then declares a record based on the cursor. Therefore after having fetched rows from the cursor into the record, the record values can be used to insert into the NonMember table.
DECLARE vMemberID Member.cMemberID%TYPE:=’&memid’; CURSOR curNonMember IS SELECT cMemberID,cFirstName,cLastName,cPhone, dMembershipDt,cBranchID FROM Member; recNonMember curNonMember%ROWTYPE; BEGIN OPEN curNonMember; LOOP FETCH curNonMember INTO recNonMember; EXIT WHEN curNonMember%NOTFOUND; IF recNonMember.cMemberID=vMemberID THEN INSERT INTO NonMember
116 © SQL Star International Ltd.
VALUES
(sqSerialNo.NEXTVAL,
recNonMember.cMemberID, recNonMember.cFirstName, recNonMember.cLastName, recNonMember.cPhone, recNonMember.dMembershipDt, recNonMember.cBranchID); END IF; END LOOP; CLOSE curNonMember; END; / On execution of the code, you are prompted to enter the ID of the member whose membership has been terminated in the following way: Enter value for memid: APP039601 PL/SQL procedure successfully completed. You can verify whether the details of member with ID APP039601 has been inserted into the NonMember table by issuing a SELECT statement as follows: SELECT * FROM NonMember WHERE cMemberID=’APP039601';
The query displays the following result: NSERIALNO CMEMBERID CFIRSTNAME
CLASTNAME CPHONE
DMEMBERSH CBRANCH
--------
--------- ------
--------- -------
27
--------- ---------APP039601
Perry
Paine
9875566643 12-MAR-96 01ADLNJ
Cursor FOR Loops By now you know that some processing is done on each row of the active set fetched by the cursor. This creates overhead on the server, as all the rows fetched by the cursor must be processed in a loop. Instead, you can use a simple loop-exit statement, where the code is written within the loop syntax and an exit test
117 © SQL Star International Ltd.
condition defined explicitly. This could be slightly complicated as compared to the easier techniques available. The CURSOR FOR loop statement allows you to process groups of rows with some specified set of operations done implicitly.
The implicit operations performed by CURSOR FOR LOOP are: •
Opening
•
Parsing
•
Executing
•
Fetching rows of data from the cursor for each iteration
•
Declaring variables to handle the fetched data (thereby reducing the size of the declaration section)
•
Handling the CursorName%NOTFOUND attribute and terminating the loop if the value of this attribute is TRUE
The syntax of a CURSOR FOR loop is: FOR IN LOOP statement1; statement2; . . . END LOOP; Where, record_name is the name of the record declared implicitly. cursor_name is the name of the explicit cursor declared. Points to remember when using the CURSOR FOR loop: •
Do not declare the record that controls the loop, as its scope is limited to within the loop.
•
Test the status of the cursor attributes during the loop if required.
•
Use cursor parameters by defining them in parentheses after the cursor name in the CURSOR FOR loop.
•
If the cursor operations need to be done manually, then do not use the CUSOR FOR loop.
•
Specify the SELECT statement instead of the cursor name in the CURSOR FOR loop. This SELECT statement is an explicit cursor without a name and hence its attributes cannot be tested. The scope of the cursor in this case is limited to the CURSOR FOR loop.
118 © SQL Star International Ltd.
The usage of CURSOR FOR loop is to accomplish the three major processes (opening, fetching and closing) of a cursor in a single step. Use CURSOR FOR loop to rewrite the code written to retrieve fiction books belonging to the Chester branch. DECLARE CURSOR curCategoryBooks IS SELECT cBookName FROM Book WHERE cCategoryID=‘01FIC’ AND cBranchID=‘02CHSNJ’; BEGIN FOR recCatgBooks IN curCategoryBooks LOOP DBMS_OUTPUT.PUT_LINE (recCatgBooks.cBookName); END LOOP; END; / Wings Of The Storm Triumph Of Katie Byrne Woman Of Substance PL/SQL procedure successfully completed.
Ensuring Faster Updates There are some clauses that can be used to enhance the processing of a cursor. Ensure that while processing records in a table, there is no inconsistency in the data and all users must be able to see the correct and same version of the data. Ensure that while one user is manipulating data and the data is being processed in the
119 © SQL Star International Ltd.
cursor, no other user must be able to access it. Two clauses are used for this purpose: •
The FOR UPDATE clause
•
The WHERE CURRENT OF clause
The FOR UPDATE Clause When you update records fetched by a cursor from a table, other users accessing the table should not view incorrect data. Also the database should be able to handle more than one user manipulating the same database. Hence, when you update data using a cursor, you can lock the rows in the database that is being updated. Locking records ensures that no other user can manipulate data in those records while they are being updated by the cursor. Locking can be done by using the FOR UPDATE clause. The syntax to use the clause is: SELECT . . . FROM FOR UPDATE [OF column_reference][NOWAIT];
[This is not a complete syntax] Where, column_reference is the name of the column in the table that the query references to. The NOWAIT keyword returns an error message if the rows are locked by another session. The FOR UPDATE clause is always placed last in the SELECT statement. When the FOR UPDATE refers to some columns in a table, the rows of that table are locked. When the FOR UPDATE clause is used, rows in the active set are locked exclusively before the OPEN returns values. In prior releases, when the SELECT FOR UPDATE clause required a lock, and the server could not obtain one, it had only two alternatives. One was to wait for the lock to be released, and the other was using the NOWAIT clause, which would test and return immediately with an error message that there was a failure in trying to get locks for the loop. Oracle9i had introduced another alternative to tackle this problem, where the user can specify the time interval to wait for, before returning with the error message. This alternative makes use of the FOR UPDATE WAIT clause. This clause: •
Prevents indefinite wait on rows that are locked by other sessions
•
Gives applications more control over the wait time for locks
120 © SQL Star International Ltd.
•
Benefits interactive applications because users indeterminate time intervals for release of locks
would
not
appreciate
You can specify an integer along with the FOR UPDATE WAIT clause to specify the number of seconds to wait for a lock. Once the number of seconds specified elapses, and if the server is unable to obtain the lock within that time interval, it returns the following error: ORA-30006: resource busy; acquire with WAIT timeout expired. The library database developer may need to generate a code for the purpose of updating the number of copies of books. But to avoid the number of copies from being updated by other users at the same time, he decides to lock it using the FOR UPDATE OF clause in the following way: DECLARE CURSOR curBranchBkCopies IS SELECT cBookName,nNoOfCopies FROM Book WHERE cBranchID=‘04RANNJ’ FOR UPDATE OF nNoOfCopies WAIT 30;
In this code, the developer declares a cursor that selects the number of copies of books stored in a specified branch. But the developer locks the rows retrieved by the cursor as he intends to update the number of copies. This prevents others from updating the same rows. In the code, the query waits for 30 seconds to obtain a lock.
WHERE CURRENT OF Clause Cursors point to the rows in the active set one at a time and move to the next row retrieved. The row that the cursor marks is the current row in the active set. The WHERE CURRENT OF clause can be used in these situations. This clause allows you to perform operations on the row that is currently being referred to by the cursor and not by using the pseudocolumn ROWID. Use the FOR UPDATE clause in the cursor query so that the rows are locked for update when the cursor is opened.
121 © SQL Star International Ltd.
The syntax to use the clause is: WHERE CURRENT OF cursor;
[ This is not a complete syntax] Using the WHERE CURRENT OF clause in place of FOR UPDATE OF clause in the previous code, enables the user to update the number of copies currently fetched by the cursor, prohibiting any other user from updating the column at the same time. DECLARE CURSOR curBranchBkCopies IS SELECT cBookName,nNoOfCopies FROM Book WHERE cBranchID=‘04RANNJ’ FOR UPDATE OF nNoOfCopies WAIT 30; BEGIN FOR recBranchBkCopies IN curBranchBkCopies LOOP UPDATE Book SET nNoOfCopies = recBranchBkCopies.nNoOfCopies + 1 WHERE CURRENT OF curBranchBkCopies; END LOOP; COMMIT; END; /
Some points to remember when using the WHERE CURRENT OF clause are: •
The cursor containing query statement should be declared with the FOR UPDATE clause.
•
You can use the clause in the DELETE or UPDATE statements to refer to the latest row processed by the FETCH command.
•
You do not have to refer to the rows using ROWID.
122 © SQL Star International Ltd.
Subqueries in Cursors Subqueries are generally used in the WHERE clause of a query. You can also use them in the FROM clause. In the latter case, it results in a temporary data source for the query. Subqueries in cursors function the same way as they do when used in a query not involving cursors. For example, the library management wishes to have a report that displays a list of those members who have paid a fine amount more than the average fine received on the same issue date as that of the member. This could be achieved by declaring the following cursor: DECLARE CURSOR curIssDtFine IS SELECT T.dIssueDt IssueDt,T.cMemberID MemberID,T.cBookID BookID,T.nFine Fine, TR.FineAvg FineAvg FROM Transaction T,(SELECT dIssueDt, AVG (nFine) FineAvg FROM Transaction GROUP BY dIssueDt) TR WHERE T.dIssueDt = TR.dIssueDt AND T.nFine > TR.FineAvg; recIssDtFine curIssDtFine%ROWTYPE; BEGIN OPEN curIssDtFine; LOOP FETCH curIssDtFine INTO recIssDtFine; EXIT WHEN curIssDtFine%NOTFOUND; DBMS_OUTPUT.PUT_LINE(recIssDtFine.IssueDt||’‘ ||recIssDtFine.MemberID||’‘ ||recIssDtFine.BookID||’ ‘||recIssDtFine.Fine||’‘ ||recIssDtFine.FineAvg); END LOOP;
123 © SQL Star International Ltd.
CLOSE curIssDtFine; END; / This code declares a cursor curIssDtFine, which selects the member’s IDs along with the date on which the books are issued. But the cursor only selects those members who have paid a fine amount greater than the average fine received by the library on each issue date. This is achieved by using subquery in the FROM clause. The subquery returns the average fine received on each issue date. The outer query compares the fine amount of each member against the average fine calculated.
On executing the above code, you get the following result: 05-SEP-96 CRB038901 ROM030000013
6 4.5
15-JUN-98 BJB019503 ROM030003210
7.5 6
12-FEB-99 BKF079702 MAT020003334 08-AUG-00 AVE119705 CHI050001509
6 3.75 10.5 6.75
06-JAN-01 CKB109305 SFI050001993
12 6.75
14-APR-01 BKF079702 MEC020000103
6 4.5
PL/SQL procedure successfully completed.
Summary In this chapter, you have learnt that:
Cursors are private work areas used to execute SQL statements. They are of two types. 1. Implicit cursor are created, maintained by the server and named internally as SQL. Its status can be checked using the attributes SQL%ISOPEN, SQL%FOUND, SQL%NOTFOUND and SQL%ROWCOUNT.
124 © SQL Star International Ltd.
2. Explicit cursors defined and controlled by the user have same attributes as implicit cursors but prefixed by name given by the user. E.g. Emp_Cursor%NOTFOUND
An Explicit cursor needs to be declared, opened, fetched and closed explicitly by the user. One can practically automate the entire process of using Explicit cursor through CURSOR FOR LOOP.
Modification in the table through cursor is done through FOR UPDATE and WHERE CURRENT OF clauses.
FOR UPDATE clause locks the rows to be updated and WHERE CURRENT OF points to the row currently being referred to, by the cursor.
125 © SQL Star International Ltd.
Lab Exercises 1.Create a PL/SQL block that deletes the data from employees table based
on
the
department id passed. Save the code in the file. a. Use the DEFINE command to provide the department ID. SET VERIFY OFF VARIABLE g_result VARCHAR2(40) DEFINE p_deptno = 280 b. Pass the value to the PL/SQL block through a iSQL*plus substitution variable. Print to the screen the number of rows affected.
2.Create a PL/SQL block that will retrieve the first 5 employees one after the other. [Note: You need to create a table TopEarners. The
structure of the table should be
as shown below.]
3.Create a PL/SQL block that will determine the top 10 earners of the organization. The block should: • Gather the salaries of the top 10 earners within a loop. Ensure that salaries are not duplicated. Store the salaries gathered in the TopEarners table 4.Create a PL/SQL block to retrieve the list of employees (their first name and department number) working for the IT department (department ID is 60).
5.Create a PL/SQL block that will retrieve the first name, job ID, and salary of employees belonging to department number 80. If the salary is less than $10,000 and if job ID is SA_REP then display the message “ is due for a salary raise”, or else display the message “ is not due for any salary raise” as shown below.
John is not due for any salary raise Karen is not due for any salary raise Alberto is not due for any salary raise
126 © SQL Star International Ltd.
Gerald is not due for any salary raise Eleni is not due for any salary raise Peter is not due for any salary raise ………………………………………………………………………… ………………………………………………………………………… PL/SQL procedure successfully completed. 6. Write a PL/SQL code to create a parameterized cursor, which will display the name and salary of all employees from the Employees table who have a salary less than the specified value, which is passed as a parameter.
7.
Create a PL/SQL block that will retrieve the department ID and department name of departments whose ID is less than 80. Pass the department IDs retrieved as parameters to another cursor that would retrieve the first name, hire date, job ID and salary of employees belonging to that department and whose employee ID is less than 130. The block result should be as follows:
Dept No.: 10 Dept Name: Administration
Dept No.: 20 Dept Name: Marketing
Dept No.: 30 Dept Name: Purchasing ID: 114 Name: Den Job: PU_MAN Hired on: 07-DEC-94 earns: 11000 ID: 115 Name: Alexander Job: PU_CLERK Hired on: 18-MAY-95 earns: 3100 …………………………………………………………….. Dept No.: 40 Dept Name: Human Resources
Dept No.: 50 Dept Name: Shipping ID: 120 Name: Matthew Job: ST_MAN Hired on: 18-JUL-96 earns: 8000 `
ID: 121 Name: Adam Job: ST_MAN Hired on: 10-APR-97 earns: 8200
127 © SQL Star International Ltd.
……………………………………………………………………….. Dept No.: 60 Dept Name: IT ID: 103 Name: Alexander Job: IT_PROG Hired on: 03-JAN-90 earns: 9000 ID: 104 Name: Bruce Job: IT_PROG Hired on: 21-MAY-91 earns: 6000 …………………………………………………… Dept No.: 70 Dept Name: Public Relations PL/SQL procedure successfully completed.
8.
Create a PL/SQL block that will increase the salary by 10% of employees whose salary is less than $7000 and who belong to department number 50. [Hint: Use FOR UPDATE and WHERE CURRENT OF clause].
128 © SQL Star International Ltd.
Chapter 6
Handling Exceptions in PL/SQL Types of Exceptions Raising and Handling Exceptions
129 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to: Describe the different types of Exceptions Raise and Handle Exceptions
130 © SQL Star International Ltd.
Introduction There can be situations when a PL/SQL code might generate a runtime error. This error in PL/SQL is known as an Exception which is an identifier that is raised when it encounters an unacceptable situation during the execution thereby causing the block to terminate. The exceptions are handled by a special code called exception handlers.
Working with Exceptions
The activities that are included when working with exceptions are: Declaring an exception Raising an Exception Trapping and Handling the Exception Propagating the Exception Let us look into each of them in detail. Declaring Exceptions Exceptions are also known as Named Identifiers. These are declared in the declarative section of the PL/SQL block. Syntax for declaring Exceptions: DECLARE exception_name EXCEPTION; Raising Exceptions An exception is raised when unacceptable situations are encountered. It can be raised implicitly or explicitly. Syntax for raising an exception explicitly using the RAISE keyword: BEGIN ............. RAISE exception_name; Trapping Exceptions Exceptions are trapped when the control encounters a RAISE EXCEPTION in the executable section of a PL/SQL code. Then the control is given to the corresponding
131 © SQL Star International Ltd.
exception handler in the exception section of the block. After an exception is handled it terminates the PL/SQL code and leaves the block. The syntax to trap an exception is:
EXCEPTION WHEN exception1 [or excpetionN. . .] THEN statement1; statement2; [WHEN excetpion 2 [or exceptionN . . .] THEN statement1; statement2; . . .] [WHEN OTHERS THEN statement1; statement2; . . .]
Where, exception1 …exceptionN are the names of the exceptions handler. statement1 . . . are SQL or PL/SQL statements OTHERS is a clause that traps unspecified exceptions. This is an optional clause. Only the exceptions specified in the executable section of the block can be handled by the exception handlers. The exceptions not specified in the executable block are handled by the WHEN OTHERS clause. Hence, this clause is always placed last. All untrapped exceptions are trapped with this clause.
Points to remember while writing code for trapping exceptions: •
Begin that part of the PL/SQL block with the keyword EXCEPTION.
•
Define handlers within the block with their respective set of actions.
•
PL/SQL executes only one exception handler before terminating the block.
•
The OTHERS clause is to be placed after all the exception handlers are coded. There can only be one OTHERS clause.
132 © SQL Star International Ltd.
Propagating Exceptions
When the block that raised the exception handles it, the block terminates as per the instructions in the handler. Then the END statement of the block is encountered and the control is given back to the enclosing block for execution. In case of Nested blocks, when an exception is raised and there is no corresponding handler within the block, the control is shifted to successive enclosing blocks in order of execution. When the control is passed to these blocks the executable transactions of that block are skipped. The advantage of doing this is, you can code handlers for specific exceptions within the block. The enclosing successive blocks can handle the general exceptions. If the exception is not handled by any of these blocks then the control is passed on to the calling or host environment. All the other blocks are terminated. Given below is a list of calling environments and the messages they generate when they have to handle an exception. •
The SQL * PLUS environment displays the error number and its message on screen.
•
The Procedure Builder also displays the error number and its message.
•
In Oracle Developer Forms, the error number and message is trapped by the in-built functions ERROR_CODE and ERROR_TEXT in a trigger.
•
A pre-compiler application accesses the error number using the SQLCA data structure.
Finally, an enclosing PL/SQL block traps the exception in a handler in its exception handling section.
Types of Exceptions The three types of exceptions are: •
Predefined exceptions
•
User-defined exceptions
•
Non-predefined exceptions
Predefined Exceptions While writing SELECT statements you would have encountered errors like too many rows or no data found. This is an error generated by the Oracle server. Errors like these are trapped and handled by the server itself. These errors are called Predefined Exceptions, as these are already defined within the server. These errors occur due to some common situations in a database. Instead of you invoking and trapping these errors explicitly, they are done automatically. A list of some of the
133 © SQL Star International Ltd.
common pre-defined exceptions is given below in a table, along with their error number and what the error implies.
134 © SQL Star International Ltd.
When predefined errors occur, if you want your code to execute in a different manner then code must be included in the exception section. For instance, the desk officer of the New Jersey Central Library handles frequent enquiries pertaining to books written by particular authors by querying the Book table. But to save him the task of querying the table repeatedly, the library database developer decides to embed the requisite query into a PL/SQL block. The database developer writes a code that will retrieve books written by the author whose name the desk officer is prompted to enter. But while writing such a code, the database developer needs to handle commonly encountered exceptions such as NO_DATA_FOUND or TOO_MANY_ROWS and word them in a way that would make it easy for the desk officer to interpret. The following code illustrates this: DECLARE vAuthorName Book.cAuthorName%TYPE:=’&author’; vBkName Book.cBookName%TYPE; BEGIN SELECT cBookName INTO vBkName FROM Book
135 © SQL Star International Ltd.
WHERE cAuthorName=vAuthorName; EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE(‘more than one book written by author’||‘- ’||vAuthorName); WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(‘no book written by author’||‘’||vAuthorName); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(‘An the author
error
has
occurred:
check
name you have just entered’);
END; /
This code returns easily understandable error messages on being executed. Such as: •
more than one book written by author-Shakespeare: If the author name entered causes the embedded query to return more than one book written by the same author
•
no book written by author-Jane Austen: If the author name entered causes the query to return no books by the same author
•
An error has occurred: check the author name you have just entered: If any other error such as misspelling an author’s name or entering a name that does not exist in the database table.
However, instead of displaying your own error message in the WHEN OTHERS clause, you could easily identify the error code and error message associated with errors (other than NO_DATA_FOUND and TOO_MANY_ROWS) using the following two functions: •
SQLCODE, which returns the numeric value of an error
•
SQLERRM, which returns the message associated with the error number
Some of the SQLCODE values are: 0 – if no exception is encountered 1 – for user defined exceptions +100 – for NO_DATA_FOUND exception negative number – for any other Oracle server error
136 © SQL Star International Ltd.
Using the two functions, the WHEN OTHERS clause of the above code could be rewritten. But you need to declare two variables to hold the values returned by the functions in the following way:
DECLARE vErrCode NUMBER; vErrMssg VARCHAR2(255); vAuthorName Book.cAuthorName%TYPE:= ‘&author’; vBkName Book.cBookName%TYPE; BEGIN SELECT cBookName INTO vBkName FROM Book WHERE cAuthorName=vAuthorName; EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE(‘more than one book written by author’||’- ’||vAuthorName); WHEN OTHERS THEN vErrCode:= SQLCODE; vErrMssg:= SQLERRM; DBMS_OUTPUT.PUT_LINE(vErrCode||’-’||vErrMssg); END; /
On executing the above code, the functions SQLCODE and SQLERRM return the error number and error message respectively on encountering an author’s name whose books are not stored in the New Jersey Central Library. Enter value for author: Jane Austen 100-ORA-01403: no data found The Oracle server provides another method (other than using DBMS_OUTPUT.PUT_LINE) to communicate predefined exceptions interactively. The method uses RAISE_APPLICATION_ERROR procedure. This procedure returns a non-standard error code and error message because they are user-defined rather than 137 © SQL Star International Ltd.
system defined. Therefore, this procedure returns error messages that are consistent with other Oracle server errors. The syntax for using RAISE_APPLICATION_ERROR is: RAISE_APPLICATION_ERROR (error_number, error_message); Where, error_number is a number the user specifies for the exception. The number specified must be between -20000 and –20999. error_message is a message the user specifies for the exception. It must be a character string and can be up to 2,048 bytes. The above code could be rewritten using RAISE_APPLICATION_ERROR as follows: DECLARE vAuthorName Book.cAuthorName%TYPE:=’&author’; vBkName Book.cBookName%TYPE; BEGIN SELECT cBookName INTO vBkName FROM Book WHERE cAuthorName=vAuthorName; EXCEPTION WHEN TOO_MANY_ROWS THEN RAISE_APPLICATION_ERROR(-20002,’More than one book written by author’||’’||vAuthorName); WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20111,’No book written by author’||vAuthorName); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE (‘An error has occurred: check the
author name you have just entered’); END; /
On executing the code, RAISE_APPLICATION_ERROR procedure displays the error number and message defined within it. Enter value for author: Shakespeare 138 © SQL Star International Ltd.
ORA-20002: more than one book written by author Shakespeare User-defined Exceptions
Predefined exceptions are handled implicitly. However, you might want to handle an exception explicitly. An exception that is raised and handled explicitly is called userdefined exception. User-defined exceptions are used to implement business rules that are not trapped by the server. In case of user-defined exceptions, you need to write code in all the sections of the block: •
Declare the exception in the declarative section. The syntax for declaring an exception is: EXCEPTION, where exception_name is the name of the exception.
•
In the execution section, write the code for the exception and raise it if the test conditions are met. The syntax to raise an exception is: RAISE exception_name, where exception_name is the name of the exception declared.
•
In the exception handling section, include the WHEN clause, the name of the exception and the code to handle it. When the exception is raised, control of the block is transferred to this section. Include the WHEN OTHERS clause so that it can catch all those exceptions that do not have a handler within the code.
The following example shows how to handle exceptions that are not trapped by the server. A desk officer enters an invalid author name while updating the number of copies of their books. While doing this, an exception is raised. To trap this exception, he would first declare an exception, raise the exception and then handle it. DECLARE exInvalidAuthor EXCEPTION; vAuthorName Book.cAuthorName%TYPE:=’&author’; BEGIN UPDATE Book SET nNoOfCopies=nNoOfCopies+1 WHERE cAuthorName=vAuthorName; IF SQL%NOTFOUND THEN RAISE exInvalidAuthor; END IF;
139 © SQL Star International Ltd.
EXCEPTION WHEN exInvalidAuthor THEN DBMS_OUTPUT.PUT_LINE(‘The author name you have entered is not
a valid one’); END; /
On executing the code, if the desk officer enters a name not listed in the Book table, he would get an error. The error is handled by declaring a user-defined exception exInvalidAuthor in the declarative section, which is raised when the embedded query returns no rows. exInvalidAuthor raised is then handled in the exception section, which displays the following error message: The author name you have entered is not a valid one
The above code could be re-written using RAISE_APPLICATION_ERROR procedure. You had seen the usage of this procedure in an earlier code. The procedure had been used in the exception section of the code. You could also use it in the executable section as shown: DECLARE vAuthorName Book.cAuthorName%TYPE:=‘&author’; BEGIN UPDATE Book SET nNoOfCopies=nNoOfCopies+1 WHERE cAuthorName=vAuthorName; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20201,‘the have
author
name
you
entered is not a valid one’); END IF; END; /
On entering an invalid author’s name, you get the following error message: Enter value for author: Ayn Rand ORA-20201: the author name you have entered is not a valid one
140 © SQL Star International Ltd.
Non-predefined Exceptions You have seen how to handle an exception explicitly. You might want to customize the predefined exceptions and thus extend the list. These errors are associated with predefined errors. You can customize the predefined errors by assigning names to the error number and writing code to handle these exceptions. You can implement this by using the PRAGMA EXCEPTION_INIT keyword. This statement is a compiler directive. It instructs the compiler to associate the exception name given with the Oracle error number. When you use this statement, you need not raise the exception. You need to declare it and write exception handling code. Each time an error occurs during execution, the control is shifted to the exception section and the error is handled. There are situations where non-predefined exceptions(also known as Internal Exceptions) need to be handled. For instance, whenever the database users of the New Jersey Central Library try to delete any member’s details from the Member table the server displays an error message, indicating an integrity constraint violation. This is because the foreign key column in the Transaction table is referencing the cMemberID column of the Member table. But the error message displayed is not very descriptive. Also, there is no named predefined exception to handle this. Therefore, the database developer should declare his own exception and associate it with the Oracle error number for integrity constraint violation. DECLARE exMemberIssBks EXCEPTION; PRAGMA EXCEPTION_INIT(ExMemberIssBks,-2292); vMemberID Member.cMemberID%TYPE:=‘&memberid’; BEGIN DELETE FROM Member WHERE cMemberID = vMemberID; COMMIT; EXCEPTION WHEN exMemberIssBks THEN DBMS_OUTPUT.PUT_LINE (‘Cannot remove the details of member with ID ’||vMemberID||‘because books issued by him have not yet been returned’); END; / Enter value for memberid: CDB028504
141 © SQL Star International Ltd.
In this code, an exception exMemberIssBks is declared and associated with the Oracle server error number –2292 (integrity constraint violation) using PRAGMA EXCEPTION_INIT. Any attempt to delete details of a member raises the exception implicitly. The control is passed to the exception handling section and the following message is displayed: Cannot remove the details of member with ID CDB028504 because books issued by him have not yet been returned.
Handling Exceptions using DBMS_UTILTY Package Usually, we give a User-defined message when we handle an exception. If we want to see Oracle defined message along with User-defined message or display the line number at which the error has occurred, we can use DBMS_UTILITY package supplied by Oracle. Let us see how to use this package while handling exceptions. Below is an example cited, for Zero_divide predefined exception. This exception is raised when we are trying to divide a number by 0. CREATE OR REPLACE PROCEDURE my_proc IS v_num1 NUMBER:=1; v_num2 NUMBER:=0; v_result NUMBER; BEGIN DBMS_OUTPUT.PUT_LINE(‘Performing Calculation’); v_result:= v_num1/v_num2; EXCEPTION WHEN ZERO_DIVIDE
THEN
DBMS_OUTPUT.PUT_LINE(‘Cannot divide by zero’); END; Procedure created. SQL> exec my_proc
142 © SQL Star International Ltd.
Performing Calculation Cannot divide by zero PL/SQL procedure successfully completed. The output gives you a message before handling the error and after handling the error. Let us now modify the code a bit. Suppose your requirement is to view the information about all the oracle defined errors raised during the execution of this program along with your messages, then this is solved by using DBMS_UTILITY.FORMAT_ERROR_STACK. This package actually gives you the information about the errors raised, which are stored in the form of Stack. CREATE OR REPLACE PROCEDURE my_proc IS v_num1 NUMBER:=1; v_num2 NUMBER:=0; v_result NUMBER; BEGIN DBMS_OUTPUT.PUT_LINE(‘Performing Calculation’); v_result:= v_num1/v_num2; EXCEPTION WHEN ZERO_DIVIDE
THEN
DBMS_OUTPUT.PUT_LINE(‘Cannot divide by zero’); DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK); END; / Procedure created. SQL> exec my_proc Performing Calculation Cannot divide by zero ORA-01476: divisor is equal to zero PL/SQL procedure successfully completed. Moving further, if you want to know the line number at which an error has occurred, then you can use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE which is a new feature in Oracle10g. Let us see how it works, with an example. 1
CREATE OR REPLACE PROCEDURE my_proc
143 © SQL Star International Ltd.
2
IS
3
v_num1 NUMBER:=1;
4
v_num2 NUMBER:=0;
5
v_result NUMBER;
6
BEGIN
7
DBMS_OUTPUT.PUT_LINE(‘calculation’);
8
v_result:= v_num1/v_num2;
9
EXCEPTION
10
WHEN ZERO_DIVIDE
THEN
11
DBMS_OUTPUT.PUT_LINE(‘Cannot divide by zero’);
12 DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK); 13 DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE); 14*
END; /
Procedure created. SQL> EXEC my_proc Performing Calculation Cannot divide by zero ORA-01476: divisor is equal to zero ORA-06512: at “SCOTT.MY_PROC”, line 8
PL/SQL procedure successfully completed.
144 © SQL Star International Ltd.
Summary In this chapter, you have learnt that: Exception are errors which arises during the program execution. They can be handled in an exception block (EXCEPTION) or left unhandled. Smooth termination, customized error message and transaction intact are the advantages of handling exceptions.
Three different types of exceptions are: 1. Predefined: Named internally and raised automatically 2. Non-Predefined: Named by the user associated with a pre-defined error number using PRAGMA EXCEPTION_INIT and raised implicitly by Oracle server.
3. User-Defined: Named and raised by the user depending on the application. Error information can be trapped using two functions namely SQLCODE and SQLERRM.
Customized message in oracle defined format can be shown using RAISE_APPLICATION_ERROR procedure.
Error messages along with line numbers can be identified using DBMS_UTILITY.FORMAT_ERROR_BACKTRACE package.
145 © SQL Star International Ltd.
Lab Exercises 1. Create a PL/SQL block wherein a non-predefined error is displayed if a user attempts to delete department number 40. This is because the department has employees working in it. 2. Create a PL/SQL block that declares a user-defined exception in case a user updates the department name of a department that does not exist (that is, by passing a department number that does not exist in the table).
146 © SQL Star International Ltd.
Chapter 7
Creating Subprograms Introduction to Subprograms Invoking Subprograms Creating Procedures Effects of Handled and Unhandled Exceptions Creating Functions
147 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to:
Identify the need for stored codes
Use Procedures
Use Functions
148 © SQL Star International Ltd.
What are Stored Codes? You executed anonymous blocks either directly from iSQL*Plus or stored them in files to keep the long codes intact. But, take a situation where you need to execute the same logic n times a day. Each time you want to execute the logic, you would have to run the script file that you had created and stored on the client side. This increases the network traffic because the files have to be sent to the server and then executed. There is also a possibility of the script file getting deleted. All these generated the need for storing the codes in the database itself. Stored codes (or subprograms) include: •
Procedures
•
Functions
•
Packages
Introduction to Subprograms Subprograms are named PL/SQL blocks, which are based on a standard PL/SQL block structure that comprises of a declarative section, an executable section and an optional exception section. Subprograms are compiled and stored in the database. This helps reduce network traffic. No file is required to be passed to the server to execute the program logic. All that is required for the user is to refer to the name of the PL/SQL block and execute it. Subprograms provide modularity. Modularization refers to the process of breaking large blocks of code into smaller groups of code known as modules. You can create modules so that they can be used in more than one application. The advantages of developing subprograms are as follows: •
Controlling data security and integrity: Access to database objects can be provided through subprograms, and users can invoke the subprograms only if the required EXECUTE privilege is granted to them.
•
Improving performance: Codes are parsed at compile time and not runtime. It uses the shared SQL area to avoid re-parsing the data. Since the commands are grouped together, there will be lesser number of calls to the database and in this process the network traffic also gets reduced.
149 © SQL Star International Ltd.
Block structure for PL/SQL subprograms and anonymous PL/SQL blocks differ. This is illustrated in the following diagram:
From the above diagram you will observe that subprograms are made up of two parts: subprogram specification and subprogram body. The subprogram specification contains: •
: It determines how the program unit is to be invoked (or called). The header specifies: • The subprogram type, that is, procedure, function or package • The subprogram name • The parameter list, if any • The RETURN clause (only in case of functions)
•
The mandatory IS or AS keyword
The subprogram body contains:
150 © SQL Star International Ltd.
•
The declaration section between IS | AS and BEGIN keywords. The DECLARE keyword otherwise used in anonymous blocks is not used here.
•
The mandatory executable section between the BEGIN and END keywords
•
The optional exception section between the EXCEPTION and END keywords
Steps to create a Subprogram: 1. Define your subprogram source code and save it in a script file. Use a text editor to create a SQL script file. 2. From the iSQL*Plus browser window, select the Browse button to locate the script file. 3. Click the Load Script button to load the file contents into the iSQL*Plus buffer. 4. Click the Execute button to run the code.
Invoking Subprograms To make a Subprogram work, you need to invoke it. Environments that allow you to invoke subprograms include other subprograms, pre-compiler applications, Oracle Developer, Oracle Discoverer, Oracle WebDB and other Oracle tools. In the list below are the ways in which you can call a subprogram: •
In the iSQL*Plus environment the syntax to execute a subprogram is: EXECUTE
•
In Oracle Developer or Discoverer simply type the name of the subprogram that is to be invoked.
•
When invoking from another subprogram, include the name of the subprogram in the executable section of the invoking subprogram at the point where you want the subprogram to execute.
Subprograms include both procedures and functions. Though the way in which they are created is the same, they are slightly different in their functionality. You will learn about these two individually in separate sections.
151 © SQL Star International Ltd.
Procedures Procedures are one of the subprograms used to perform some action. They may or may not accept values from the users. They generally do not return a value. A procedure has a header, a declarative section, an executable and an exception handling section. One of the main features promoted by procedures is reusability. Once a procedure is validated, it can be used in any number of applications. In case the requirement changes, you need to update only the procedure.
System Privileges for Creating Procedures Database users according to their role and requirement are given permissions in the database. Earlier in the course you learnt about the various privileges and permissions that users require to access and use the objects in the database. Similarly there are restrictions on creating procedures also. All users are not allowed to see database structure and create procedures. You need to have the CREATE PROCEDURE privilege. The CREATE PROCEDURE privilege permits you to create a procedure and store it in the server. The DBA grants this right to a particular user with the following syntax: GRANT CREATE PROCEDURE TO ; The right to execute a procedure is given by the owner of the procedure or the DBA. The EXECUTE privilege is granted to allow a user to execute a particular procedure. The syntax is: GRANT EXECUTE ON TO ;
Creating Procedures Once you have the permission to create a procedure use the following syntax to create one: CREATE [OR REPLACE] PROCEDURE
152 © SQL Star International Ltd.
(argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .) IS | AS PL/SQL block; Where, CREATE [OR REPLACE] PROCEDURE are the keywords that either create a procedure or replace an existing procedure. is the name you want to give to the procedure. [(argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .)] are the parameters that can be passed to the procedure. (Will be covered in the next section). PL/SQL block is the set of SQL and PL/SQL statements that are to be executed to perform the database activity.
The main steps involved in developing a stored procedure are: 1. Writing the code: Enter the code in a text editor or word processor and save it as a SQL script file 2. Compiling the code: In iSQL*Plus, load and run the script file. Running the code causes the stored code to be stored in the data dictionary even if the procedure contains compilation errors. The source code is compiled into P code, and then the procedure is created. You cannot successfully invoke a procedure that contains compilation or runtime errors. In iSQL*Plus, use SHOW ERRORS to see any compilation errors. Fix the errors using the editor and recompile the code. To see the errors in the procedure compiled previously you can also use: SHOW ERRORS PROCEDURE Procedure_name 3. Executing the procedure to perform the desired action: After the successful creation of a procedure, it can be executed any number of times using the EXECUTE command from iSQL*Plus Passing Parameters To accept user values in procedures or any named PL/SQL block, we use parameters. Parameters are variables of a certain data type, declared ,defined and used in the code. Parameters are of two types: formal and actual parameters Formal parameters refer to those variables, which are declared in the parameter list of a subprogram specification. One or more formal parameters can be declared, with each parameter separated by a comma. For example, CREATE PROCEDURE procMember (vMemID CHAR, vAge NUMBER) .......
153 © SQL Star International Ltd.
END; In the procedure procMember, the variables vMemID and vAge are formal parameters. Actual parameters refer to variables or expressions, which are referenced in the parameter list of a subprogram call. For example, in the call procMember (vID, 26) to the procedure procMember, the variable vID and ‘26’ are actual parameters.
Points to remember: •
During a subprogram call, actual parameters are evaluated and their results are assigned to formal parameters.
•
Actual parameters can also have expressions such as procMember (vID, nAge+2).
•
Use different names for formal as well as actual parameters. It is only a good coding practice.
•
Ensure that formal and actual parameters are of compatible datatypes.
A parameter has a mode. It specifies what the parameter is used for. Parameters can be passed in three modes: •
IN: accepts a value from the calling environment and passes it to the procedure. It can be assigned a default value. This parameter acts as a constant. The values in it can be a constant or an expression. It is the default mode.
•
OUT: passes a value from the procedure to the calling environment. It cannot be assigned a Default value.
•
IN OUT: passes a value from the calling environment to the procedure and returns a different value from the procedure into the environment using the same parameter.
The datatype parameter is declared without a size specification. It can be specified: As an explicit datatype Using the %TYPE definition Using the %ROWTYPE definition The New Jersey Central Library requires some of its automated processes to be stored in the server as they had a bad experience of all the files getting corrupted and deleted a couple of times. One of their frequent requirements is the generation of member detail reports. Hence, the following procedure can help them in getting this data each time they go for generating the report. CREATE OR REPLACE PROCEDURE prMemberDetails (vMemberID IN Member.cMemberID%TYPE, vFirstName OUT Member.cFirstName%TYPE,
154 © SQL Star International Ltd.
vLastName OUT Member.cLastName%TYPE, vAddress OUT Member.vAddress%TYPE) IS BEGIN SELECT cFirstName, cLastName, vAddress INTO vFirstName, vLastName, vAddress FROM Member WHERE cMemberID = vMemberID; DBMS_OUTPUT.PUT_LINE(vFirstName||’ ‘||vLastName||’ ‘ ||vAddress); END; / In the example, the IN and the OUT parameters are used. You may want to know how to use an IN OUT parameter. For this the simplest example could be that of accepting some data from the user and displaying it in a different format after modifying the same value. When the clerk enters a phone number, he wants a more readable format from which it is clear as to which are the country code, the area code and the number. Let us create the following procedure to help him understand the phone number better. CREATE OR REPLACE PROCEDURE prFormatPhone (vPhone IN OUT VARCHAR2) IS BEGIN vPhone := ‘(‘||SUBSTR(vPhone,1,3)|| ‘)’||SUBSTR(vPhone,4,3)|| ‘-’||SUBSTR(vPhone,7); DBMS_OUTPUT.PUT_LINE(vPhone); END; /
155 © SQL Star International Ltd.
Methods of Passing Parameters
There are three ways in which you can pass parameters to procedures. They are: •
Positional: Here, the user has to specify the values according to the position of the parameter in the parameter list
•
Named Association: Here, it accepts values in an order irrespective of the parameter order, but you need to link each value with its parameter name using the symbol “=>”
•
Combination: Here, the first values are listed based on the position and the rest of the values are listed using the special syntax of the named method
DEFAULT option in FORMAL parameters. CREATE OR REPLACE PROCEDURE prMemberDetails (vMemberID IN Member.cMemberID%TYPE, vFirstName IN Member.cFirstName%TYPE, vAddress IN Member.vAddress%TYPE DEFAULT ‘Unknown’) IS BEGIN INSERT INTO member(cMemberID,cFirstName,vAddress) VALUES (vMemberID ,vFirstName, vAddress); END; / There is a DEFAULT option mentioned in the above program. How does it help? Let me explain it. In the example, while invoking the procedure if address details are not supplied by you then the procedure will insert a string ‘Unknown’ inside the vaddress column. Invoking Procedures You can invoke a procedure directly or call it from an anonymous block. For example, to view the values of the OUT parameter, invoke prMemberDetails procedure as follows:
1. Create host variables in iSQL*Plus
156 © SQL Star International Ltd.
VARIABLE FName
CHAR(20)
VARIABLE LName
CHAR(20)
VARIABLE Address
VARCHAR2(50)
2. Invoke the procedure and supply the host variables as the OUT parameters. EXECUTE prMemberDetails (‘CBW109702’, :FName, :LName, :Address);
3. View the values passed to the calling environment PRINT FName PRINT Lname PRINT Address
On performing these steps, the following result is displayed:
To view the values of the IN OUT parameter, invoke the prFormatPhone procedure as follows:
1. Create a host variable VARIABLE phone_no VARCHAR2(15)
2. Populate the host variable using an anonymous block and view the value entered BEGIN
157 © SQL Star International Ltd.
:phone_no := ‘0403730853’; END; / PRINT phone_no 3. Invoke the procedure and provide the host variable as the IN OUT parameter EXECUTE prFormatPhone(:phone_no);
4. View the value passed back to the calling environment PRINT phone_no On performing these steps, the following result is displayed:
The procedure prFormatPhone can also be invoked from an anonymous block by specifying the name of the procedure with its arguments in the executable section of the block as shown below. SET SERVEROUT ON DECLARE p VARCHAR2(15); BEGIN p:=’1234567890'; prFormatPhone(p); DBMS_OUTPUT.PUT_LINE(p); END;
158 © SQL Star International Ltd.
/ (123)456-7890 (123)456-7890 PL/SQL procedure successfully completed.
Calling a Procedure from a Named Block In case you want to invoke procedures from another stored procedure, the method is the same. You can directly include the name of the procedure in the declarative section. For example, in case there is a need to update the stock of a particular book, you can create a procedure as follows: CREATE PROCEDURE prUpdateBookQty (vBookID IN Book.cBookID%TYPE, vNum IN NUMBER) IS BEGIN UPDATE Book SET nNoOfCopies = nNoOfCopies + vNum WHERE cBookID = vBookID; COMMIT; END; / Assume that the library needs to update all the books available with them. For this you could create a procedure to update the entire book table. This procedure could invoke the prUpdateBookQty procedure as shown below. CREATE PROCEDURE prUpdateBranchStock (vBranchID IN Book.cBranchID%TYPE) IS CURSOR curBranchBooks IS SELECT * FROM Book WHERE cBranchID = vBranchID; BEGIN
159 © SQL Star International Ltd.
FOR RecBranchBook IN curBranchBooks LOOP prUpdateBookQty(RecBranchBook.cBookID,1); END LOOP; END; /
Effects of Handled and Unhandled Exceptions When developing procedures that will be called from other procedures, you need to be aware of the effects that handled and unhandled exceptions have on the transactions and the calling procedures. Handled Exceptions Affecting the Calling Procedure When a called procedure raises an exception, the control goes to the exception section of that block. Once the exception is handled, the block terminates and the control returns to the calling procedure. The DML statements issued before the exception is raised, remain as part of the transaction. For example, look at the following code: CREATE
OR
REPLACE
PROCEDURE
prInsBrLocation
(pLocID
NUMBER,
pLocName VARCHAR2) IS LocName VARCHAR2(30); BEGIN DBMS_OUTPUT.PUT_LINE
(‘Main
Procedure
–
Calling
Procedure’); INSERT INTO BrLocation (nBrLocID, vLocName) VALUES (pLocID, pLocName); SELECT vLocName INTO LocName FROM BrLocation WHERE nBrLocID = pLocID; DBMS_OUTPUT.PUT_LINE
(LocName||‘
inserted
into
BrLocation
table’);
160 © SQL Star International Ltd.
prInsBranch(pLocID); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(‘No such branch/ location for the New Jersey Central Library’ END; / CREATE OR REPLACE PROCEDURE prInsBranch IS vBrID NUMBER(4); BEGIN DBMS_OUTPUT.PUT_LINE(‘Called Procedure’); INSERT INTO Branch VALUES(‘06CALNJ’, ‘Elizabeth
Library Branch’, ’26,
Elizabeth Rd’, pLocID, ‘986754311’, ’30-JAN-2002’); SELECT cBranchID INTO vBrID FROM Book WHERE cBookID = ‘11111122211’; END; /
From the code, we can observe the following: •
Procedure prInsBrLocation BrLocation table.
•
Procedure prInsBranch inserts a new branch in the new location inserted through prInsBrLocation.
•
Procedure prInsBrLocation invokes prInsBranch procedure.
•
The SELECT statement in procedure prInsBranch selects a branch ID for a book that does not exist in the Book table. This raises an exception.
•
This exception is not handled in procedure prInsBranch.
inserts
a
new
branch
location
into
the
161 © SQL Star International Ltd.
•
The control returns to the calling procedure prInsBrLocation, where the exception is handled.
•
Since the exception is handled, the DML performed in prInsBranch is not rolled back, and it continues to be part of the transaction of prInsBrLocation procedure.
For the purpose of the above example, we have assumed that: The library database maintains a table BrLocation, which has two columns nBrLocID and vLocName The Branch table does not have a cBranchLocation column, but instead has a nBrLocID column Unhandled Exceptions Affecting the Calling Procedure In the previous example, when the called procedure prInsBranch raised an exception, the control went to the exception section of that block. But since the exception was not handled, the block terminated, and the control was passed to the exception section of the calling procedure prInsBrLocation. The calling procedure prInsBrLocation handled the exception, and hence all the DMLs in the calling as well as the called procedure remained as part of the transaction. However, if the calling procedure prInsBrLocation had failed to handle the exception, then the calling procedure prInsBrLocation would have terminated. In such a case, •
The exception would propagate to the calling environment
•
The DML statements issued both in the calling as well as the called procedures would be rolled back.
Removing Procedures If you do not require any or all of your procedures, you can delete them just as it is done for any other database object. You can drop the server-side procedures from the iSQL*Plus environment by using the syntax: DROP PROCEDURE ; To remove the procedure prMemberDetails type the following command: DROP PROCEDURE prMemberDetails;
Viewing Procedures in Data Dictionary The source code for PL/SQL subprograms both successful and unsuccessful, is stored in the data dictionary tables. To view the PL/SQL source code stored in the data dictionary, execute a SELECT statement on the following tables: USER_SOURCE table to display PL/SQL code that you own ALL_SOURCE table to display PL/SQL code to which you have been granted the EXECUTE right by the owner of that subprogram code.
162 © SQL Star International Ltd.
SELECT TEXT FROM USER_SOURCE WHERE NAME=’PRINSBRANCH’; To view the names of procedures presently existing in your schema, issue the following SELECT statement. SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_TYPE=’PROCEDURE’;
Functions Functions are also stored subprograms and are usually used to calculate values. They are created and invoked the same way as procedures but with a slight difference. A function must compulsorily return a value to the calling environment.
163 © SQL Star International Ltd.
Creating Functions The syntax for creating functions is the same as that for procedures, except that the keyword FUNCTIONS and RETURN are to be used. The syntax is: CREATE [OR REPLACE] FUNCTION (argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .) RETURN datatype IS | AS PL/SQL block; Where, CREATE [OR REPLACE] FUNCTION are the keywords that enable you to create a function or replace an existing one. is the name of the function you want to create. (argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .) are the parameters that the function accepts from the calling environment. RETURN datatype is the clause that is mandatory, as a function must return a value. Do not specify a size for the datatype. PL/SQL block is the set of SQL and PL/SQL statements to execute the task to be performed. You must include a RETURN datatype in the executable part of the block. You can use multiple RETURN statements within an IF statement. In this case, however, only one RETURN statement will be executed.
Let us look at a scenerio and create a functions based on it. Due to a crisis on a certain day, the clerk collected the books returned by the members. He does not have access to the database tables but needs to find out the fines to be paid by the members who returned the books. The user DBA creates a function to calculate the fine of a particular member. CREATE OR REPLACE FUNCTION fnFineCal (vMemberID Member.cMemberID%TYPE) RETURN NUMBER IS vFine NUMBER; vReturnDt DATE;
164 © SQL Star International Ltd.
vActReturnDt DATE; BEGIN SELECT dReturnDt,dActualReturnDt INTO vReturnDt,vActReturnDt FROM Transaction WHERE cMemberID=vMemberID; IF vReturnDt >= vActReturnDt THEN vFine:=0; ELSE vFine:=(vActReturnDt - vReturnDt)* 1.50; END IF; DBMS_OUTPUT.PUT_LINE (‘The member has to pay a fine of: ‘||vFine); RETURN(vFine); END; / Invoking Functions Functions are invoked from the iSQL*Plus environment using the EXECUTE command. You need to declare a host variable to store the value returned by the function. The value can then be printed by passing the host variable name with the PRINT command after executing the function. Execute it with the following syntax: EXECUTE :host_variable_name
:=
function_name(value);
For example: VARIABLE P NUMBER EXECUTE :P := fnFineCal (‘BJH029405’);
You can also invoke functions from SQL statements in case any ad hoc queries are to be made. For example, if you want to see the name of a particular member and phone number and the fine the member has to pay you can execute the following statement: SELECT cFirstName, cLastName, cPhone, fnFineCal(‘BJH029405’) Fine FROM Member
165 © SQL Star International Ltd.
WHERE cMemberID=’BJH029405';
The result of the above SELECT statement will return the following result: CFIRSTNAME
CLASTNAME
CPHONE
--------
------
-------
Jessica
Hatcher
9642211309
FINE ------0
Using a function from an SQL statement has the following advantages: •
Queries can be made more efficient by performing functions in them than in applications
•
You can perform calculations that are very complex and not available in SQL
•
You can enhance data independence by not retrieving data into an application and processing complex data in the server
You can invoke functions as: •
An item in the SELECT list
•
A part of a condition in the WHERE and HAVING clauses
•
As part of the CONNECT BY, START WITH, ORDER BY and GROUP BY clauses
•
A value in the VALUES clause of the INSERT statement
•
A part of the expression in the SET clause of the UPDATE statement
Restrictions on Calling Functions
Though there are several advantages of using functions in queries, they have some restrictions. They are: •
The function necessarily has to be a stored function. Stored procedures cannot be called in this way.
•
The function must be a row function.
•
Accept only IN parameters with valid SQL datatype, not PLSQL specific types.
•
Return Valid SQL datatypes, not PLSQL specific types.
•
You cannot use these functions in the CHECK constraint of a CREATE or ALTER table statement.
•
You need to have the EXECUTE privilege for the function so that you can invoke it.
•
The function should not modify any table of the database with DML statements. In other words, the function should not contain any DML statements.
166 © SQL Star International Ltd.
Removing Functions Just as you can delete procedures, you can also delete functions. From the iSQL*Plus environment you can delete functions by using the DROP command. In case you want to delete the fnFineCal function that you created, execute the following command. DROP FUNCTION fnFineCal;
Viewing Function in Data Dictionary To view the PL/SQL function code stored in the data dictionary, execute a SELECT statement on the following tables where the TYPE column value is ‘FUNCTION’: The USER_SOURCE table to display the PL/SQL code that you own The ALL_SOURCE table to display the PL/SQL code to which you have been granted EXECUTE right by the owner of that subprogram code SELECT TEXT FROM USER_SOURCE WHERE NAME=’FNFINECAL’ ORDER BY LINE; To view the names of all function present in your schema SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_TYPE=’FUNCTION’; Since we have an in dept knowledge of both procedure and function now, let us differentiate it.
167 © SQL Star International Ltd.
Summary In this chapter, you have learnt that:
Subprograms are named and compiled PL/SQL blocks stored within the database. They are compiled once and invoked many times.
Subprograms contribute to modularity, memory management and data security features of a PL/SQL program.
Procedures and functions are collective known as Subprograms. Data values are passed into and out of the block through parameter modes IN, OUT and INOUT in both subprograms.
Procedures are used to perform an action and may return a value. Functions are used to do calculations, which can be used in a SELECT expression and must return a value through RETURN statement.
Exceptions in Procedures if unhandled, rollback the transaction and remain intact if handled.
168 © SQL Star International Ltd.
Source code of the procedure and the function can be viewed in USER_SOURCE data dictionary.
169 © SQL Star International Ltd.
Lab Exercises Note: Save all programs as .sql files. 1. Create a procedure called prInsJobs to insert a new row into the Jobs table. The procedure should accept two parameters, one for job ID and the other for job title. Compile the procedure and invoke it in iSQL*Plus with IT_ADMIN as the job ID and ‘System Administrator’ as the job title. View the row inserted into the Jobs table. 2. Create a procedure called prRaiseSal, which increases the salary by 10% for the employee ID accepted as the input parameter. Invoke the procedure in iSQL*Plus with 110 as the employee ID. Prior to invoking the procedure, view the salary earned by employee 110. After the successful execution of the procedure, view the updated salary of employee 110. 3. Create a procedure called prViewEmp that returns the job ID, department ID and salary for a specified employee ID. Use host variables to view the OUT parameters of the procedure in iSQL*Plus. 4. Create a procedure called prUpdJobs to update the job title. The procedure should accept job ID and job title. The procedure should also have necessary exception handling in case no update occurs. Invoke the procedure and update the job title of IT_ADMIN to ‘SysDate Administrator’. To check the exception handling, try and update a job that does not exist. 5. Create a procedure called prEmpRaiseSal that invokes the prRaiseSal procedure (created in Lab question 02). The procedure prEmpRaiseSal must process all the records in the Employees table, and then pass each employee ID to the procedure prRaiseSal. 6. Create a function called fnDept that returns the department name corresponding to a department number passed as the input parameter. The function should return the department name to a host variable defined in iSQL*Plus. 7. Create a function called fnAnnualInc that returns the annual salary earned by accepting monthly salary and commission. The function should return an annual salary even if both the values passed are NULL. Annual salary is calculated as follows: (Salary * 12) + (Salary * Commission * 12) Use the function in a SELECT statement against the Employees table.
170 © SQL Star International Ltd.
Chapter 8
Managing Subprograms System and Object Privilege Requirements Definer’s Rights and Invoker’s Rights Detecting Compilation errors Displaying Parameters Using DESCRIBE Command Debugging Program Units
171 © SQL Star International Ltd.
Objectives
At the end of this chapter, you will be able to:
Identify the system and object privilege requirements Identify views in the data dictionary to manage stored objects Debug subprograms using the DBMS_OUTPUT package
172 © SQL Star International Ltd.
System and Object Privilege Requirements In the previous chapter, we have looked into the creation of functions and procedures that are collectively known as Subprograms. As it is equally significant to effectively maintain these subprograms, we will now be learning the various ways to successfully manage them. System privileges are those privileges that use the words CREATE or CREATE ANY, such as GRANT CREATE ANY TABLE TO User1. There are more than 100 system privileges. The user SYSTEM or SYS grants them. Object privileges on the other hand are the privileges assigned to a specific object within a schema. They always include the name of the object, for example, user Paul can assign privileges to user Robert to alter his Member table as follows: GRANT ALTER ON Member TO ROBERT;
In order to create PL/SQL subprograms, you must have been assigned the system privilege CREATE PROCEDURE. Once this privilege has been assigned, you can alter, drop or execute the subprogram without any further privileges being assigned. If the keyword ANY has been used, then you can create, alter, drop and execute not only your subprograms, but also those belonging to other schemas. The keyword ANY is optional only for the CREATE PROCEDURE privilege. To invoke a subprogram if you are not an owner and do not have the EXECUTE ANY system privilege, then you must have the EXECUTE object privilege assigned to you. By default PL/SQL subprograms execute under the security domain of the owner. If subprograms refer to objects not in the same schema, then access to these objects must be assigned explicitly, and not through a role.
173 © SQL Star International Ltd.
Definer’s Rights and Invoker’s Rights Subprograms by default execute under the security domain of the owner. This is known as the definer’s right. To understand the definer’s right, look at the assumptions made: •
The Member table is located within the Library schema.
•
The table is owned by user DBA.
•
There is a developer named Paul.
•
Paul creates a procedure procMember, which queries records from the Member table.
•
There is an end user named Robert.
The requirement is that Robert should to be able to access the Member table only through the procMember procedure that Paul created. This requires the granting of both direct and indirect access. Direct Access A user can be given access permissions directly on an object. For instance, from the Library schema object privileges on the Member table can be provided to Paul. Based on the object privilege, Paul can create the procMember procedure, which queries the Member table. Indirect Access A user can access an object by using another object on which he has access permissions. For instance, Paul grants the EXECUTE object privilege on the procMember procedure to Robert. Robert can now execute the procedure under the security domain of the owner [this is the definer’s right]. Robert can retrieve information from the Member table using the procMember procedure because Paul has direct privileges to Member table and has created the procedure.
174 © SQL Star International Ltd.
The following diagram depicts the direct as well as indirect access:
Executing a subprogram from the security of the executing user, and not the owner, is referred to as Invoker’s rights. To implement invoker’s right, use AUTHID CURRENT_USER. This ensures that the subprogram executes with the privileges of its current user. The following diagram depicts the procedure procMember, being executed with the privileges of the user Robert:
175 © SQL Star International Ltd.
Access Methods Used to Manage Stored PL/SQL Objects Users can use different access methods to manage different kinds of stored information. The different access methods and the kind of stored information they provide are listed below.
Data Dictionary views: •
USER_OBJECTS: contains general information of all the database objects.
•
USER_SOURCE: contains the source code, that is the text of the procedure.
•
USER_ERRORS: contains compilation errors, that is PL/SQL syntax errors.
iSQL*Plus commands: •
DESCRIBE: displays information pertaining to parameters, that is whether the mode is IN/ OUT/ IN OUT and the parameter datatype.
•
SHOW ERRORS: displays compilation errors, that is PL/SQL syntax errors.
Oracle supplied information.
package,
DBMS_OUTPUT,
provides
run-time
debug
The following diagram shows the access methods:
176 © SQL Star International Ltd.
USER_OBJECTS View
A user or developer, like you, would typically want general information about all objects created such as the name of the object, its owner, when was it created or what type of object it is. The USER_OBJECTS is a view that stores such information. On querying the view, the following column list is displayed (we are providing you with the abridged column list):
Issue the following SELECT statement to view all the procedures you have created: SELECT OBJECT_NAME, OBJECT_TYPE FROM USER_OBJECTS WHERE OBJECT_TYPE=’PROCEDURE’ ORDER BY OBJECT_NAME; /
177 © SQL Star International Ltd.
OBJECT_NAME
OBJECT_TYPE
---------------
----------
PRACCEPTMSSG
PROCEDURE
PRFORMATPHONE
PROCEDURE
PRMEMBERDETAILS
PROCEDURE
PRTRANSMITMSSG
PROCEDURE
PRUPDATEBOOKQTY
PROCEDURE
PRUPDATEBRANCHSTOCK
PROCEDURE
If you want to find information about objects created by the DBA or any other user, you query the DBA_OBJECTS or ALL_OBJECTS. Both the views include the OWNER column in addition to the ones mentioned in the USER_OBJECTS view. USER_SOURCE View
To view the code of procedures or functions, you can query the USER_SOURCE data dictionary view. The columns that this data dictionary contains are: •
NAME that stores the name of the object.
•
TYPE stores the type of object, that is, whether it is a function, procedure, package or package body.
•
LINE stores the line numbers of the source code.
•
TEXT stores the actual text of the code.
For example, to view the text of prUpdateBookQty procedure, use USER_SOURCE data dictionary view as follows: SELECT TEXT FROM USER_SOURCE WHERE NAME=’PRUPDATEBOOKQTY’; Displays: TEXT -------------------PROCEDURE prUpdateBookQty
178 © SQL Star International Ltd.
(vBookID IN Book.cBookID%TYPE, vNum IN NUMBER) IS BEGIN UPDATE Book SET nNoOfCopies = nNoOfCopies + vNum WHERE cBookID = vBookID; -- COMMIT; END; / Detecting Compilation Errors
Compilation errors can be detected using either USER_ERRORS data dictionary view or iSQL*Plus command SHOW ERRORS. To be able to see the errors that you may get when compiling your object, query the USER_ERRORS data dictionary view. You can view the error text message. The columns that are included in this data dictionary are: •
NAME stores the name of the object.
•
TYPE stores the type of object created. Whether it is package, function, package body or procedure.
•
SEQUENCE contains the sequence number for ordering the error.
•
LINE stores the line number for the source code at which the error occurs.
•
POSITION holds information about the position in the line at which the error occurs.
•
TEXT stores the text of the error message.
You can view the structure of USER_ERRORS using the DESCRIBE command as follows: DESC USER_ERRORS This command displays the following output: Name
Null?
Type
-----
------
------
NAME
NOT NULL
VARCHAR2(30)
TYPE
VARCHAR2(12)
179 © SQL Star International Ltd.
SEQUENCE LINE
NOT NULL
NUMBER
NOT NULL NUMBER
POSITION NOT NULL NUMBER TEXT
NOT NULL VARCHAR2(4000)
The SHOW ERRORS command executed along with the name of the procedure displays the line and column numbers of the errors along with the text of the error message. If you execute the command without parameters, you get the error data of the last object that you compiled. The syntax to use this command is SHOW ERRORS [FUNCTION | PROCEDURE | PACKAGE | PACKAGE BODY | TRIGGER | VIEW] [schema.] name] For example, executing the following code would result in errors: CREATE OR REPLACE PROCEDURE prErr (MemID Member.cMemberID%TYPE) IS MemDt DATE; BEGIN SELECT dMembershipDt INTO MemDt FROM Member; END; / Warning: Procedure created with compilation errors.
The errors could be rectified only when you know what the errors are. Hence, you could view the compilation errors using SHOW ERRORS as follows: SHOW ERRORS Errors for PROCEDURE PRERR: LINE/COL
ERROR
--------
-------------------------
7/6
PLS-00103: Encountered the symbol “IN” when expecting
one
of the following:
180 © SQL Star International Ltd.
. ( , * @ % & - + / at mod rem as from into || bulk
The above error could also be viewed using USER_ERRORS data dictionary view as follows: SELECT LINE||’-’||POSITION LINE, TEXT FROM USER_ERRORS WHERE NAME=’PRERR’;
Displays: LINE
TEXT
-------
-------
7-6
PLS-00103: Encountered the symbol “IN”
7-7
when expecting one of the following: . ( , * @ % & - + / at mod rem
as
from
into || bulk
Displaying Parameters Using the DESCRIBE Command You can view the structure of database objects using the iSQL*Plus command ‘DESCRIBE‘. The structure of a subprogram includes the parameters used. The DESCRIBE command along with the name of the subprogram displays the list of parameters in the code, their datatype, the mode of parameter and the parameter has a default value or not. If you want to see the structure of prIncCopies procedure issue the following DESCRIBE command:
DESCRIBE prIncCopies PROCEDURE princcopies Argument Name -------------
Type ------
VBOOKID
In/Out Default? -------------
CHAR(13)
IN 181
© SQL Star International Ltd.
VCOPIES
NUMBER(2)
IN
Debugging Program Units Subprograms are the main database objects that help you to automate your system. To make the database system of a business area functional, you need to write and build lots of codes. It is not possible for a person to write and execute all codes without a single mistake. Imagine a situation where you wrote thousands of lines of code and when executed, it does not perform the task you want, and better still, you don’t know what is wrong and where. The process of trapping error in a code is known as Debugging. Oracle offers you a built-in package DBMS_OUTPUT to help debug your subprograms. DECLARE Age Member.nAge%TYPE: = &age; BEGIN IF Age= vActReturnDt THEN vFine:=0; ELSE
193 © SQL Star International Ltd.
vFine:=(vActReturnDt-vReturnDt)*1.50; END IF; DBMS_OUTPUT.PUT_LINE (‘The member has to pay a fine of Rs. ||vFine); RETURN(vFine); END fnFineCal; END pkMemberInfo; / Once the package is created the body needs to be compiled. Packages can be compiled either on the client-side or the server-side. On compiling the package body, you get the message confirming the successful compilation and creation of the package body: Package body created.
Some issues to remember while compiling a package : •
You must ensure that the package specification is compiled before the package body.
•
The package body should contain the program codes for all units defined in the specifications else it will not get compiled.
•
Private components that are called by some public components necessarily have to be declared and defined, before the calling public components are defined.
One good point about the package creation and compilation is that, the components in the package do not have to be in the same order as they have been defined. In case you have made changes in the code of any of the programs then you need to only recompile the body. In case the change is in the number or type of the parameters, then the specification also needs to be recompiled. You can also create a package without a body. The only time when a package body is unnecessary is when a specification declares only user-defined types, constants, variables and user-defined exceptions. For example, consider the following package specification: CREATE OR REPLACE PACKAGE pkForConversions IS kilo_to_mile CONSTANT NUMBER := 0.6214;
194 © SQL Star International Ltd.
mile_to_kilo CONSTANT
NUMBER := 1.6093;
END packForConversions; / Package created. In the code, two identifiers kilo_to_mile and mile_to_kilo declared as constants specify rates for converting kilometer to mile and mile to kilometer respectively. A package body is not required for this package specification because the constructs of the specification require no implementation details. You can directly execute the package as follows: EXECUTE DBMS_OUTPUT.PUT_LINE (‘ 15 kilometers = ’|| 15* pkForConversions.kilo_to_mile||‘ miles’);
Invoking Package Constructs
To invoke the package constructs you need to prefix the name of the construct with the name of the package. The syntax for it is: . In case you are calling a construct from within the package then you need not prefix the package name. To invoke a package construct from the iSQL*Plus environment use the EXECUTE command followed by the package and procedure name. When required you can also specify the schema to which the package belongs. To invoke the function fnFineCal of the pkMemberInfo for calculating fine payable by the members, you must first declare a variable to hold the value returned by the function: SQL>VARIABLE Fine NUMBER After having declared a variable Fine, you can invoke the fnFineCal function using the EXECUTE command. SQL>EXECUTE :Fine :=pkMemberInfo.fnFineCal(‘BJH029405’); This displays the following result:
The member has to pay a fine of Rs.0 SQL>EXECUTE :Fine :=pkMemberInfo.fnFineCal(‘DDG019503’); The member has to pay a fine of Rs.3 Similarly you can invoke the prMemberDetails procedure from within the package. But before you invoke the procedure, you must declare variables to hold the first names, last names and the addresses of members in the following manner: SQL>VARIABLE FNAME CHAR(20) SQL>VARIABLE LNAME CHAR(20)
195 © SQL Star International Ltd.
SQL>VARIABLE ADD VARCHAR2(50) After having declared the variables, you can invoke the procedure using the EXECUTE command as follows: EXECUTE pkMemberInfo.prMemberDetails(‘DDG019503’,:FNAME,:LNAME,:ADD);
On executing the above procedure, you get the following result: Darla
Green
—49, Naughty Kids Apts,Far Away Street
Referencing Variables Apart from invoking and calling the package procedures and functions, you can also refer to the variables in a package. Referencing to package variables can be done from within the package as well as from outside. When referencing from within the package you need not prefix the package name. If you want the variables to be referenced from outside the package from another stand-alone procedure, then you have to ensure that you declare them publicly when creating the package. For example, create a package with function to validate the age of a new member as shown below.
CREATE OR REPLACE PACKAGE pkCheckAge IS gMemberAge NUMBER(2); PROCEDURE prCheckAge (MemberAge Member.nAge%TYPE); END; / CREATE OR REPLACE PACKAGE BODY pkCheckAge IS FUNCTION fnValidateAge (MemAge Member.nAge%TYPE) RETURN BOOLEAN IS MinAge NUMBER; BEGIN
196 © SQL Star International Ltd.
SELECT MIN(nAge) INTO MinAge FROM Member; IF MemAge=1; END LOOP; END prFirstSet; PROCEDURE prSecondSet IS BEGIN LOOP FETCH curCategoryBooks INTO BookName; DBMS_OUTPUT.PUT_LINE(BookName); EXIT WHEN curCategoryBooks%ROWCOUNT>=3; END LOOP; CLOSE curCategoryBooks; END prSecondSet; END pkCatgBookDisplay; /
On compiling the above package code, you get the following message: Enter value for catgname: Fiction Package created. Package body created. Execution of this package causes the cursor to fetch all the rows whose display is controlled by the two procedures. The first fiction book is displayed by prFirstSet. The cursor is not closed and hence the control moves onto the second procedure. At this point the cursor status is at the 2nd row. The second procedure prSecondSet hence displays the names of the remaining books starting from the 2nd book. EXECUTE pkCatgBookDisplay.prFirstSet; Triumph Of Katie Byrne PL/SQL procedure successfully completed. EXECUTE pkCatgBookDisplay.prSecondSet; Wings Of The Storm
200 © SQL Star International Ltd.
Woman Of Substance
Three points to remember about the state of package cursors and variables are that: •
They persist for transactions in a session. The cursor and variable states will persist for all transactions in a session until you close the cursor or end the package.
•
They change from one session to another for a single user. If a certain user logs out of one session and starts another session invoking the same package, the cursor and variable states is set to its initial value. In other words, the cursor and variable states do not persist across sessions for the same user.
•
They change from one user to another. If there are different users in different sessions accessing the same package, the state of the cursors and variables are all set to their initial values for both users.
Removing Packages Package like any other database object can be removed when it is no longer required. You must be sure that the database users no longer require the procedures and functions in the package. You can drop the entire package, that is, specification and body, by issuing the DROP PACKAGE command, followed by the package name. The syntax is: DROP PACKAGE ; To drop the package pkCatgBookDisplay issue the following command: DROP PACKAGE pkCatgBookDisplay; Package dropped. If you only want to drop the package body then use the following syntax: DROP PACKAGE BODY ; To drop the pkCatgBookDisplay issue the following command: DROP PACKAGE BODY pkCatgBookDisplay;
201 © SQL Star International Ltd.
Summary In this chapter, you have learnt that: Packages are schema objects, which enclose collection of related procedures and functions, cursors, exception and variables.
Package are made of two separate components sharing the same name. They are Package specification and Package body.
Package Specification is the main component. It has only the declaration of a collection of procedures, functions, cursors, exceptions and variables which have global accessibility. ♦
Package Body is based on the specification. It includes private subprograms declaration and definition, local variables, private variables and definition of global subprograms.
♦
The constructs within the package are invoked by prefixing package name with the construct name.
♦
The cursor and variable states will persist for all transactions in a session until you close the cursor or end the package.
♦
Persistence of a Package variable is within a session but not across a session.
Package information can be retrieved using USER_SOURCE data dictionary view.
202 © SQL Star International Ltd.
Lab Exercises
1. Create a package specification and body called pkEmp that contains the following: •
•
Procedure called prInsEmp, which is a public construct. This procedure adds a new employee to the Employees table. But, a new row is to be added only if a function called fnValidateDeptID returns TRUE. In case the function returns FALSE, the procedure should display an appropriate message. The procedure should have input parameters for the following:
Last name
First name
Email ID
Job ID (give a default value ‘ST_CLERK’)
Manager ID (give a default value 122)
Salary (give a default value 1500)
Commission (give a default value 0)
Department ID (give a default value 80)
For employee ID use a sequence sqEmpNo
Function fnValidateDeptID, which is a private construct that checks whether the department ID specified for the new employee exists in the Department table. It returns a Boolean value. Invoke prInsEmp procedure with 12 as the department ID. Check whether the exception handler handles it. Next, invoke the procedure with 80 as the department ID.
203 © SQL Star International Ltd.
2. Create a package specification and body called pkCheck that contains the following two procedures (public):
Procedure prCheckHireDt checks whether a specified hire date is between sysdate–50 years and sysdate+2 months. If the date specified is invalid or NULL, raise an application error with an appropriate message
Procedure prResetComm resets the prevailing commission. [In the package specification declare a global variable and initialize it to say 0.10]. The procedure invokes a private function fnValidateComm, which checks that the commission value specified cannot be greater than the highest commission available. Test prCheckHireDt procedure by passing a date such as ’01-MAR-45’.
Similarly, check prResetComm procedure. Make the prevailing commission as 0.5. 3.
Create a bodiless package containing exceptions and PL/SQL tables.
204 © SQL Star International Ltd.
Chapter 10
Using More Package Concepts Object Oriented Features of Packages Automatic One Time Procedure Package Function in SELECT Expression PL/SQL Wrapper Utility Dependencies Recompiling PL/SQL Blocks
205 © SQL Star International Ltd.
Objectives
At the end of this chapter, you will be able to:
Implement the object oriented features with packages Manage packages
206 © SQL Star International Ltd.
Object Oriented Features of Packages You need to be able to enhance packages so that they can be made more functional and can exploit all the advanced features of PL/SQL. Implementing the objectoriented features like overloading and forward-declarations can make packages more efficient. Overloading Overloading is a feature that allows you to create two or more procedures with the same name but different parameters. The server recognizes the called procedure by its signature, or in other words the number, and type of parameters it uses. Points to remember while overloading procedures are: ♦
Overload only local or packaged subprograms.
♦
The parameters of the two procedures cannot differ only in name and mode but should also differ in number and datatype.
♦
The procedures cannot be overloaded if they differ only in datatypes which are a subset of the same main type. Such as VARCHAR and STRING are the subtypes of the main type, which is VARCHAR2.
♦
You cannot overload if only the return parameters in the two functions are different even if they belong to different datatype families.
When you execute overloaded programs, the Oracle compiler searches for the declarations that match the subprograms. The search takes place first in the current scope and then if required moves on to successive enclosing scopes. If the compiler finds one or more subprogram signatures in which the subprogram name matches that of the called program, then it compares the number, data type and order between the actual and formal parameter. The user DBA creates two procedures to add rows into the PublisherDetails table and calls both the procedures prAddPublisher. He places both procedures in a package. One procedure accepts the values of cPublisherID and cPublisherName. The other procedure accepts cPublisherID, cPublisherName and cPublisherAddress. CREATE OR REPLACE PACKAGE pkLoad
207 © SQL Star International Ltd.
IS PROCEDURE prAddPublisher (PublisherID IN PublisherDetails.cPublisherID%TYPE, PublisherName IN PublisherDetails.cPublisherName%TYPE); PROCEDURE prAddPublisher (PublisherID IN PublisherDetails.cPublisherID%TYPE, PublisherName IN PublisherDetails.cPublisherName%TYPE, Address IN PublisherDetails.vPublisherAddress%TYPE); END pkload; / CREATE OR REPLACE PACKAGE BODY pkLoad IS PROCEDURE prAddPublisher (PublisherID IN PublisherDetails.cPublisherID%TYPE, PublisherName IN PublisherDetails.cPublisherName%TYPE) IS BEGIN INSERT
INTO
PublisherDetails(cPublisherID,cPublisherName) VALUES(PublisherID,PublisherName); END prAddPublisher; PROCEDURE prAddPublisher (PublisherID IN PublisherDetails.cPublisherID%TYPE, PublisherName IN PublisherDetails.cPublisherName%TYPE, Address IN PublisherDetails.vPublisherAddress%TYPE) IS BEGIN INSERT INTO PublisherDetails(cPublisherID, cPublisherName,vPublisherAddress)
208 © SQL Star International Ltd.
VALUES(PublisherID,PublisherName,Address); END prAddPublisher; END pkLoad; /
You get the following message on compiling the code: Package created. Package body created.
To determine which of these procedures will execute, issue the following EXECUTE statements: EXECUTE pkLoad.prAddPublisher(‘RQ0865’,’Raj Quartet’); PL/SQL procedure successfully completed. EXECUTE
pkLoad.prAddPublisher(‘BW0780’,’Booksware’,’New
Orleans’); PL/SQL procedure successfully completed.
To verify whether the values have been inserted into the PublisherDetails table, issue a SELECT statement as shown below. SELECT * FROM PublisherDetails WHERE cPublisherID IN (‘RQ0865’,’BW0780');
The query displays the following result set: CPUBLI
CPUBLISHERNAME
VPUBLISHERADDRESS
-------
---------------
-----------------
BW0780
Booksware
RQ0865
Raj Quartet
New Orleans
Forward Declarations
PL/SQL does not support forward references. That is, an identifier or a subprogram must be declared before calling it. Look at the example given below. 209 © SQL Star International Ltd.
CREATE OR REPLACE PACKAGE BODY pkCheckAge IS PROCEDURE prCheckAge (MemberAge Member.nAge%TYPE) IS BEGIN IF fnValidateAge(MemberAge)--illegal reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . END prCheckAge;
FUNCTION fnValidateAge (MemAge Member.nAge%TYPE) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . END fnValidateAge; END pkCheckAge; In the example, you cannot reference the function fnValidateAge because it has not yet been declared. Reversing the order of the two programs could solve such an illegal reference. But such a solution does not always work, especially if the programs are calling one another, or you want the programs to be defined in some order, such as in alphabetical order. To solve this kind of problem you can implement forward declarations. Forward declarations require the subprogram’s specification declarations to be ended using a semicolon. For example, in case you want to define your programs in an alphabetical order then mention only the name and arguments of the private program before the public one and define the private program later according to its alphabetic occurrence. When compiling this package the server will read the public procedure, encounter the name of the private procedure, and search for the same in the package body above the public procedure. When it finds the procedure name, it calls the actual procedure body that is defined later in the package body.
210 © SQL Star International Ltd.
Using forward declarations you can: •
Define the programs in a set order, be it logical or alphabetical
•
Define mutually recursive programs
•
Group programs into a package
The best example to illustrate forward declaration is the pkCheckAge package created earlier. The prCheckAge procedure within the package references the private function fnValidateAge only after the function has been declared. CREATE OR REPLACE PACKAGE BODY pkCheckAge IS FUNCTION fnValidateAge (MemAge Member.nAge%TYPE); -- Forward declaration PROCEDURE prCheckAge (MemberAge Member.nAge%TYPE) IS BEGIN IF fnValidateAge(MemberAge) . . . . . . . . . . . . . . . . END prCheckAge; FUNCTION fnValidateAge (MemAge Member.nAge%TYPE) . . . . . . . . . . . . . . BEGIN . . . . . . . . . . . . . . . END fnValidateAge;
211 © SQL Star International Ltd.
END pkCheckAge; /
Automatic One-time Procedure A package definition is similar to a procedure and function definition, except that it has no parameters. Similarly, the bodies of packages and procedures are also alike. Hence, it is possible to include a PL/SQL code directly inside a package. This code will execute as soon as you submit the package. In other words it is an automatic onetime procedure that executes only once when the package is invoked in a user session. You can use this type of procedure to initialize variables if their derivation is very complex. Here, you need not initialize the variables in the package specifications, as it will be initialized by the automatic procedure. The following example shows a one-time-only procedure: CREATE OR REPLACE PACKAGE pkFine IS vFine NUMBER; ... –-public procedures/functions declarations END pkFine; / CREATE OR REPLACE PACKAGE BODY pkFine IS ... –- declaration of private variables ... –- definition of public/private procedures/functions BEGIN SELECT nFine INTO vFine
Automatic initialization of
FROM Transaction
vFine variable
WHERE cBookID = ‘...’; END pkFine;
212 © SQL Star International Ltd.
In the example, current value for vFine is set with the value of nfine from the Transaction table the first time the package pkFine is referenced.
Using Package Function in SELECT expression We have seen earlier how functions can be a part of SELECT expression. This feature mainly differentiates functions from the procedures. Now let us see, how the packaged function is accessed by the user in the SELECT expression. Isn’t it better if we look at an example. Here it is.
CREATE OR REPLACE PACKAGE pkincrfine IS FUNCTION fnincrfine (vfine IN NUMBER) RETURN NUMBER; END pkincrfine; / CREATE OR REPLACE PACKAGE BODY
pkincrfine
IS FUNCTION fnincrfine (vfine IN NUMBER) RETURN NUMBER IS BEGIN RETURN vfine*.01; END fnincrfine; END pkincrfine; / Package created. Package Body created. In order to access the above package following statement has to be executed. SELECT Transaction;
cmemberid,
nfine,
pkincrfine.fnincrfine(nfine)
FROM
If this package is granted to SCOTT, then he can access it by prefixing USERNAME with PACKAGENAME.FUNCTIONNAME. In this case it is SCOTT.pkincrfine.fnincrfine(nfine). The same restrictions that were applied to standalone functions, are also followed by functions within the packages.
213 © SQL Star International Ltd.
PL/SQL WRAPPER UTILITY In real time scenario, it becomes necessary to protect our code from misuse. To tackle this we can encrypt our code which makes it unreadable. Here, wrapper utility comes to our rescue. We can use the Wrapper to deliver PL/SQL applications without exposing our source code. Sounds Interesting? Let us see, what a wrapper is all about. Wrapper is a standalone utility that converts PL/SQL source code into intermediate form of portable object code. WRAP is the command to be entered in the DOS prompt and has the following syntax. WRAP INAME=input_file_name [ONAME=output_file_name] INAME indicates the input file containing the code to be wrapped. ONAME indicates the file name where the unreadable code is stored. By default the extension given to these files is .plb. One can execute this file to store the wrapped version of the source code. It is optional. If omitted, it takes the same name as the input filename. STEPS TO USE THE WRAPPER UTITILTY 1. Prepare the source code for the package and save it with .Sql extension. 2. Execute this file. The package will be created and stored in the database. 3. To confirm, you can query user_source. 4. To convert this code from readable code to unreadable code, run the wrapper utility in the DOS command prompt. WRAP INAME=d:\source_code\PRFNINCRFINE.SQL ONAME=d:\source_code\wrapprfnincrfine.plb 5. Leave no space around the equal signs because spaces delimit individual arguments 6. Drop unwrapped package body PRFNINCRFINE. 7. Now, execute d:\source_code\wrapprfnincrfine.plb file in SQL*PLUS. 8. If you query this package body again in the data dictionary, you will see the unreadable format of the code.
Instructions for Wrapping 1. Wrap only the Package body since it contains the business logic thereby hiding its information from unauthorized users.
214 © SQL Star International Ltd.
2. Input File ignores semantic errors. However .plb file containing the wrapped code reports the error once executed. Alternatively, one can compile a package body and then wrap to avoid this. 3. Changes made to the original source have to be wrapped again.
Managing Packages Managing packages involve the privileges needed to access them, tracking their execution, and ensuring that they do not violate data integrity rules already defined. To be able to manage packages you can: Access the source codes of the procedures using the USER-SOURCE data dictionary view. View their parameters using the DESCRIBE command.
Compile time errors can be tracked and viewed with the USER_ERRORS data dictionary view and the command SHOW ERRORS. Some database objects are dependent on other database objects. The objects that require data or support from other database objects are called dependant objects and the objects that are referred to are called referenced objects.
Dependencies When managing packages and other independent subprograms you need to include all dependency issues. Changing the structure of a referenced object may cause a dependent to stop functioning. The Oracle server manages all dependencies, and to do so, it takes the help of the Status column of the data dictionary view USER_OBJECTS. The status of an object at any time can either be VALID or INVALID. The difference in the two is that a VALID object has been compiled and is ready for use, whereas an INVALID object has to be compiled before it is used.
215 © SQL Star International Ltd.
Direct and Indirect Dependencies A reference to an object can be made directly or indirectly. A procedure or function can directly and indirectly refer to tables, views, sequences, procedures, functions, and packaged procedures and functions. In case of indirect referencing, it does so by using intermediate views, procedures, functions, or packaged procedures and functions. USER_DEPENDECIES You can view direct dependencies by using the data dictionary view USER_DEPENDENCIES. In case you need to manually recompile any database object then you can query this data dictionary to find the required dependencies information. DESC USER_DEPENDENCIES NAME
NULL?
TYPE
---------
-----------
-------------
NAME
NOT NULL
VARCHAR2(30)
TYPE
VARCHAR2(12)
REFERENCED_OWNER
VARCHAR2(30)
REFERENCED_NAME
VARCHAR2(64)
REFERENCED_TYPE
VARCHAR2(12)
REFERENCED_LINK_NAME
VARCHAR2(128)
SCHEMAID
NUMBER
DEPENDENCY_TYPE
VARCHAR2(4)
The data that you can query from this view are: •
Names of the dependent objects from the NAME column
•
Type of the dependent object, whether it is a procedure, or a function, from the TYPE column
•
The schema of the referenced object from the REFERENCED_OWNER column
•
Name of the referenced object from the REFERENCED_NAME column
•
The type of object referenced from the REFERENCED_TYPE column ♦
The database link used REFERENCED_LINK_NAME
to
reference
the
object
from
the
In the ALL_DEPENDENCIES and DBA_DEPENDENCIES there is an extra column called OWNER, which displays the name of the owner of the object.
216 © SQL Star International Ltd.
UTLDTREE Another method of viewing dependencies is to query the DEPTREE view, which lists the dependency information that is stored in an underlying table called DEPTREE_TEMPTAB, and the IDEPTREE view, which lists dependency information such that dependent objects are shown below the objects they are dependent on, and they are indented. To query the views, you need to first run the Oracle provided script file UTLDTREE (found in the rdbms/admin subdirectory under the Oracle software home directory). You need to then execute a procedure called DEPTREE_FILL, which populates the underlying table for your use. The syntax to execute the procedure is: EXECUTE DEPTREE_FILL( , , ); The above syntax is used to execute the procedure to populate the table DEPTREE_TEMPTAB with information of a referenced object. The three parameters passed are the type of the object being referred, the owner of the object and the name of the object. For example, EXECUTE DEPTREE_FILL(‘TABLE’,’SCOTT’,’TRANSACTION’); PL/SQL procedure successfully completed. Query the DEPTREE view as shown below. SELECT NESTED_LEVEL, TYPE, NAME FROM DEPTREE ORDER BY SEQ#; Displays: NESTED_LEVEL
TYPE
-------------- ----
NAME ------
0
TABLE
TRANSACTION
1
VIEW
TI
1
PACKAGE BODY
PKMEMBERINFO
Local and Remote Dependencies Dependencies can be classified into two kinds, local and remote. Local Dependencies Dependencies are said to be local when the database objects are all on the same node of the same database. In this case when there is a change in the definition of a
217 © SQL Star International Ltd.
referred object, the dependent object becomes invalid. The next time the invalid object is called, it is recompiled by the server automatically and hence, refers to the modified referenced object. If you change the name of the referenced object then it will invalidate your dependent object. Even if you have two different types of objects with the same name, check to see which object will your dependent object refer to. Remote Dependencies Dependencies are said to be remote when the database objects are residing on different nodes in the database. Dependencies among remote objects are not handled automatically, but the server handles local-procedure-to-remote-procedure dependencies. If there is a change in the structure of a referenced object, then all its dependent objects will be invalid. They are not automatically recompiled when called the next time. Points to remember with regards to remote dependencies are: ♦
Check the status of the objects with the USER_OBJECTS view to ensure that the recompilation of local (implicit) and remote (explicit) procedures has happened successfully.
♦
Do not rely on the automatic recompilation of local dependent objects. If the recompilation fails then the status is invalid and a runtime error is generated. To prevent disruptions, manually recompile local dependent objects.
Two concepts of remote dependencies are: ♦
TIMESTAMP checking: The time when a PL/SQL program is created and recompiled is always noted for all PL/SQL programs. If a program is altered, all its dependent modules become invalid and need to be recompiled before they can be executed.
♦
SIGNATURE checking: Along with the timestamp the signature of every PL/SQL unit is recorded. The signature contains the type of construct, the number of parameters, their base types and their modes.
Whenever there is a call from a local procedure to a remote procedure a timestamp check takes place. If the timestamps match then the calls are executed without a problem. In case there is a timestamp mismatch, the signatures are matched to verify that the calls are safe and legal. If these are compatible and neither of them is changed, then execution continues, else an error message is displayed.
There are some parameters to set the remote dependency mode. This can be done at three levels as listed below. •
As an init.ora parameter with the following syntax:
REMOTE_DEPENDENCIES_MODE=value •
At the system level with:
218 © SQL Star International Ltd.
ALTER SYSTEM SET REMOTE_DEPENDENCIES_MODE=value •
At the session level with:
ALTER SESSION SET REMOTE_DEPENDENCIES_MODE=value Where, value is either TIMESTAMP or SIGNATURE. Some points to remember are: •
The remote dependency mechanism is not the same as the local one. When a remote recompiled procedure is invoked, you will get an error and the local program is invalidated. The next time it is invoked, it gets implicitly recompiled.
•
If a local procedure that calls a remote procedure is compiled before the remote procedure, then it is invalidated.
•
When a procedure is compiled, the time stamps of both, the local procedure and that of the remote procedure it calls are recorded in the p-code of the local procedure. When you invoke the local procedure it displays an error message saying that the timestamp of the remote procedure has changed. To rectify this, re-invoke the local procedure.
•
If timestamps do not match, the local procedure is invalidated and a runtime error is generated. When you invoke the local procedure for a second time, the server recompiles it.
Procedure Dependencies Dependency of database objects essentially comes into focus when you need to recompile the objects due to some alteration that you may have done on the blocks. This requires you to recompile your blocks. There are some issues that you should know while programming and developing and later implementing procedures and functions. Recompiling PL/SQL Blocks Blocks can be explicitly or implicitly recompiled. When recompiling an object, the server will first recompile any invalid object on which the former is dependent. •
Any object that depends on a recompiled procedure becomes invalid.
•
Recompiled packages cause objects dependent on it to become invalid. Even the package body depends on its package specification. Packages are recompiled using the COMPILE PACKAGE syntax. If you want to recompile only the body, use the COMPILE BODY syntax.
219 © SQL Star International Ltd.
•
Triggers are enabled or validated by default and when invalidated, it does not fire when the triggering statement is executed.
Recompiling dependent blocks does not happen if the: •
Referenced object is dropped
•
Datatype of the referenced column is changed
•
Columns of a referenced view changes and the required columns are not part of the new view
•
Parameters of a referenced procedure change
Recompiling dependent blocks is not hampered if: •
New columns are added to the referenced table
•
Datatype of the referenced column has not changed and no new column is NOT NULL
•
If the referenced object is a private table that is dropped and a public table exists. When the private table is dropped, the dependent object becomes invalid. But if a public table is created with the same name and structure, then the object refers to the public table and is recompiled.
You can reduce recompilation errors if you: •
Declare records with %ROWTYPE
•
Declare variables with %TYPE
•
Perform queries using SELECT *
•
Include the column list when inserting values with INSERT
•
Group the blocks into packages
Package Dependencies Package dependency includes the package body being dependent on the package specifications. When you create and compile a package, the details of both the specification and the body are found in the USER_OBJECTS view. Some points to remember about compilation are: •
The package body compilation will remain successful as long as the package specification does not change. In case there is some alteration made to a package function and the package body is recompiled, the recompilation is successful. This procedure independence is very useful, as you need not recompile all the dependent procedures and functions, only the altered procedure needs to be recompiled.
•
In case there is any change in the package specification, the recompilation of the body will certainly fail. The body can only recompile successfully if the specification is valid.
220 © SQL Star International Ltd.
•
In case a package function is referring to an external package function, then the latter should be compiled before the former. There is no order for compiling package specifications.
•
In case there is a syntax error in the calling procedure body and it is compiled, the package is invalidated. The referenced package procedure is still valid. When a call to the latter package is made from the former, the procedure is rendered invalid. This is a delayed compilation effect.
•
In case of a syntax error in a package procedure and the package specification is compiled, then the referenced procedure is also invalid. This is an immediate compilation effect.
•
Cyclic dependencies are also not a problem because, when one package is getting compiled it will check the specification of the referenced package for the referenced procedure.
•
Forward referencing is a good way to take care of cyclic dependency in a package.
221 © SQL Star International Ltd.
Summary In this chapter, you have learnt that:
Packages are said to be overloaded when they have functions and procedures with same name, but differ in the argument datatype and number. Forward Declaration in package body helps compiling of those subprograms, which are invoked first and later defined. By doing so, an ordered subprogram can be maintained in the package body. ONE-TIME ONLY procedure helps to dynamically initialize the package variable.
PL/SQL wrapper utility is used to convert the source code into unreadable format for security reasons.
Oracle server manages dependencies by taking the help of the Status column of the data dictionary view USER_OBJECTS. The status of an object at any time can either be VALID or INVALID.
Dependencies may be direct or indirect dependencies. It’s related information can be viewed in USER_DEPENDENCIES, DEPTREE and IDEPTREE.
LOCAL dependencies and REMOTE dependencies are classified based on the objects present on the same and different servers respectively.
222 © SQL Star International Ltd.
Lab Exercises 1. Create a package called pkOverLoadDisplay. The package contains two functions having the same name fnDisplay. •
The first function accepts a date (in DD-MON-YY format) and displays it in fmDdspth, Month, YYYY format.
•
The second function accepts a number in character format and displays it as a number.
Test both the versions of the function. 2.
Convert the following PL/SQL code into portable object code so that you can
deliver a PL/SQL procedure without exposing its source code.
CREATE PROCEDURE raise_salary (emp_id INTEGER, amount NUMBER) AS BEGIN UPDATE Employees SET salary = salary + amount WHERE employee_id = emp_id; END; /
223 © SQL Star International Ltd.
Chapter 11
Oracle Supplied Packages DBMS_SQL Package EXECUTE IMMEDIATE Statement DBMS_OUTPUT Package HTP Package Scheduling Jobs using DBMS_SCHEDULER UTL_FILE Package DBMS_METADATA Package Compile Time Warnings
224 © SQL Star International Ltd.
Objective At the end of this chapter, you will be able to: Use Oracle supplied packages
225 © SQL Star International Ltd.
Oracle Supplied Packages So far you have learnt how to create your own procedures and packages. You also used a few of the Oracle supplied packages and procedures. There are other Oracle supplied packages that will aid PL/SQL to access SQL features to enhance the functionality of the blocks. The list of the packages with their usage is as follows: •
DBMS_ALERT displays notifications of database events.
•
DBMS_JOB schedules the periodic execution of the PL/SQL blocks.
•
DBMS_APPLICATION_INFO is used by applications to inform the database of the activities they are currently being performed.
•
DBMS_DDL recompiles subprograms and analyzes indexes, tables and clusters.
•
DBMS_LOCK performs lock management related issues like, requesting for locks, converting and releasing them.
•
DBMS_DESCRIBE describes the arguments of a specific stored procedure.
•
DBMS_OUTPUT displays any messages from triggers and other PL/SQL blocks.
•
DBMS_SQL access the database using dynamic SQL.
•
DBMS_SESSION gives you access to session information and SQL ALTER SESSION statements.
•
DBMS_TRANSACTION controls and improves the performance of logical, short and non-distributed transactions by making them discrete.
•
DBMS_SHARED_POOL maintains objects in shared memory so that they will not be exhausted with the normal LRU mechanism.
•
DBMS_UTILITY performs all utility tasks like, analyzing objects of a certain schema, checking whether the server is running and returns the time.
•
UTL_FILE gives PL/SQL input and output capabilities.
•
UTL_MAIL is used to send email. This is the enhanced feature of UTL_SMTP in Oracle10g.
•
UTL_COMPRESS is used to compress and decompress Binary Data (RAW, BLOB, BFILE), which is a new feature in Oracle10g.
Some of these packages are used often and you need to know why and how they are used. You will learn about the following packages in detail. •
DBMS_SQL
•
DBMS_OUTPUT
226 © SQL Star International Ltd.
•
HTP
•
UTL_FILE
•
DBMS_SCHEDULER
•
DBMS_METADATA
•
DBMS_WARNING
We will also discuss about EXECUTE IMMEDIATE statement which works similar to DBMS_SQL.
DBMS_SQL This package enables you to write PL/SQL blocks using dynamic SQL. Dynamic SQL comprises statements that are not coded into the block but are built by the block during runtime. That is, SQL statements can be created dynamically at runtime using variables. For example, you can perform DML operations on tables accepting the name of the table at runtime. You can also issue DDL statements using this package. For example you can drop a table from within a procedure. To use SQL statements in PL/SQL you need to use cursors. The regular flow of a program using SQL statements and cursors is as follows: 1. Open the cursor to process the SQL statement. The cursor returns the cursor ID number. 2. The SQL statements are parsed to check for syntax and privileges. A private SQL area is allotted for the statement. 3. Each data input at runtime must have a bind variable to supply the values to a placeholder. Use BIND_VARIABLE for this purpose. 4. Use DEFINE_COLUMN to specify the variables that will store the values from the SELECT statement. 5. The SQL statements need to be executed. 6. The rows that satisfy the condition will be fetched with the FETCH_ROWS statement. 7. Verify the values of the columns or the variables fetched by using the COLUMN_VALUE or the VARIABLE_VALUE statements respectively. 8. Close the cursor with the CLOSE_CURSOR statement to de-allocate the memory space.
The features of the DBMS_SQL package are: •
SQL statements need not be included in PL/SQL blocks. They can be stored as strings and passed to the program at runtime. This allows the programmer to write generic modules that can be reused.
227 © SQL Star International Ltd.
•
The package allows parsing, hence DDL statements can be parsed when used in PL/SQL.
•
These operations are performed under the current user and hence if there is an anonymous block calling the subprogram then the privileges of the current user prevails. If the call is from a stored procedure then the privileges of its owner are applied.
•
This package implements bind by values when executing.
The DBMS_SQL procedures are used in the executable section of a block. You can create a procedure that uses dynamic SQL to delete rows from a specified table as follows: CREATE OR REPLACE PROCEDURE prDeleteTableRows (vTableName IN VARCHAR2, vRowsDeleted OUT NUMBER) IS vCursorName
INTEGER;
BEGIN vCursorName := DBMS_SQL.OPEN_CURSOR; -- opens a cursor and assigns an ID DBMS_SQL.PARSE (vCursorName, ‘ DELETE FROM ’|| vtableName, DBMS_SQL.NATIVE); -- parses the DML/DDL statements vRowsDeleted := DBMS_SQL.EXECUTE (vCursorName); -- executes the SQL statements and returns a message of how many rows were processed DBMS_SQL.CLOSE_CURSOR (vCursorName); -- closes the specified cursor
228 © SQL Star International Ltd.
END; /
The code can be tested as follows in iSQL*Plus: VARIABLE NoOfRowsDeleted NUMBER EXECUTE prDeleteTableRows (‘Member’, :NoOfRowsDeleted); PRINT NoOfRowsDeleted
For better performance of native dynamic SQL, use the EXECUTE IMMEDIATE statement. Its syntax is as follows: EXECUTE IMMEDIATE dynamicSQL_string [INTO {define_variable [, define_variable] … | record}] [USING [IN | OUT | IN OUT] bind_argument, [IN | OUT | IN OUT] bind_argument] …]; [{RETURNING | RETURN} INTO bind_argument [, bind_argument]...];
Where, dynamicSQL_string is a string expression representing a dynamic SQL statement (no terminator) or a PL/SQL block (terminator) define_variable is a variable to store the selected column value record is a %ROWTYPE or user-defined record to store a selected row input bind_argument : is the expression whose value is passed to the dynamic SQL statement output bind_argument : is a variable in which values returned by the dynamic SQL statement is stored.
bind_argument is an expression whose value is passed to the dynamic SQL statement or the PL/SQL block At runtime, placeholders in the dynamic SQL are replaced by bind arguments. Hence, every placeholder must be associated with a bind argument in the USING clause. Bind arguments can be numeric, character or string literals, but they cannot be Boolean literals.
The procedure prDeleteTableRows can be re-written as follows: CREATE OR REPLACE PROCEDURE prDeleteTableRows (vTableName IN VARCHAR2, vRowsDeleted OUT NUMBER)
229 © SQL Star International Ltd.
IS BEGIN EXECUTE IMMEDIATE ‘DELETE FROM ’||vTableName; vRowsDeleted := SQL%ROWCOUNT; END; /
In the code, the EXECUTE IMMEDIATE statement parses and immediately executes the dynamic SQL statement.
Another illustration for EXECUTE IMMEDIATE statement using DML commands. CREATE OR REPLACE PROCEDURE prAdd_rows (table_name VARCHAR2, id NUMBER, name VARCHAR2) IS BEGIN EXECUTE IMMEDIATE ‘ INSERT INTO ‘ ||table_name ||’ VALUES (:1,:2) ‘ USING ID, NAME; END; / The above code inserts 2 column values into the table that is passed dynamically at runtime. Example of Dynamic SQL with DDL Statement: CREATE OR REPLACE PROCEDURE prcreate_table(table_name VARCHAR2, col_spec VARCHAR2) IS BEGIN EXECUTE IMMEDIATE ‘ CREATE TABLE ‘||table_name || ‘(‘ || col_spec ||’)’; END; / Issue the following statements to invoke the above procedure prcreate_table. BEGIN prCreate_table(‘EMPLOYEES’,’NAME VARCHAR2(20),DEPTNO Number(5)); END; An example to illustrate compilation of PL/SQL objects with Native Dynamic SQL: CREATE OR REPLACE PROCEDURE prcomple_plsql (name VARCHAR2,plsql_type VARCHAR2,options VARCHAR2:= NULL)
230 © SQL Star International Ltd.
IS stmt VARCHAR2(100):=’ALTER ||’COMPILE’;
‘||plsql_type||’
‘||
name
BEGIN IF options IS NOT NULL THEN stmt := stmt ||’ ‘||options; END IF; EXECUTE IMMEDIATE stmt; END; /
Need for Native Dynamic SQL The situations in which you need dynamic SQL are as follows: • To execute SQL data definition statements, data control statements, or session control statements (like ALTER SESSION), because in PL/SQL these statements cannot be executed statically • To make programs flexible. For instance, you may want to postpone your choice of schema objects until runtime, or you may want the program to build different search conditions for the WHERE clause of SQL statement.
DBMS_OUTPUT Package We have been using this package for displaying the output. Let us have an indept knowledge about this package now. The DBMS_OUTPUT package is made up of the following subprograms. PUT, NEW_LINE, PUT_LINE, GET_LINE AND GET_LINES.
PUT( ) AND PUT_LINE( ) PUT () procedure puts the information in the output buffer and PUT_LINE () procedure puts the data as well as displays it from the buffer on your screen. A code snippet written by the programmer, using the two procedures in the function fnFineCal is as follows: The following example illustrates this package further: CREATE OR REPLACE FUNCTION fnFineCal (MemberID Member.cMemberID%TYPE) RETURN NUMBER IS vReturnDt DATE;
231 © SQL Star International Ltd.
vFine NUMBER; vActReturnDt DATE; BEGIN SELECT dReturnDt,dActualReturnDt INTO vReturnDt,vActReturnDt FROM Transaction WHERE cMemberID=MemberID; IF vReturnDt=vActReturnDt OR vReturnDt>vActReturnDt THEN vFine:=0; ELSE vFine:=(vActReturnDt-vReturnDt)*1.50; END IF; DBMS_OUTPUT.PUT(vFine); DBMS_OUTPUT.PUT_LINE (‘The member has to pay a fine of Rs. ‘||vFine); RETURN(vFine); END; / Function created. On executing the function, EXECUTE :FN:=fnFineCal(‘BJH029405’); You get the following result: 0 The member has to pay a fine of Rs. 0 In the code, DBMS_OUTPUT.PUT (vFine) puts the fine amount into the buffer. This amount is then displayed along with the other text from the buffer by the use of DBMS_OUTPUT.PUT_LINE().
NEW_LINE () This procedure puts information into the output buffer with the carriage return line feed. This character is represented as CHR(10) in an SQL SELECT statement. This procedure does not take any parameters. DBMS_OUTPUT.PUT_LINE() is a combination of put and New_line. ENABLE () and DISABLE ()
232 © SQL Star International Ltd.
The ENABLE () procedure allows the code to access the buffer so that the output of the code can be written within an iSQL*Plus session. One parameter value that you need to provide is the size of the buffer to be allocated. On the other hand if you dont want the code to be written to the buffer, use the procedure DISABLE (). This does not accept any parameters. A point to be remembered is that the buffer size that you pass should be large enough to hold all the data coming through. In case there is data over and above the size you passed then that data is discarded automatically. The maximum size of the buffer is 1,0000,000 bytes. Default size is 2000. Earlier, programmer for the library database, created a function to calculate the fine if any, to be paid by a member when he returns a book he borrowed. You already know how to create a function. All you need to do is include the procedure ENABLE () in the body of the code at the beginning before the transaction starts. Include DISABLE () at the end of all the transaction statements. GET_LINE () It retrieves data from the output buffer and returns it into the variables passed as its parameters. It accepts all data, converts them into VARCHAR2 data and displays it. Two out parameters are passed with this procedure. One that displays each line of information from the buffer and the other is a counter or status parameter that checks if the procedure was successful and if there are any more lines for display in the buffer. You need to declare variables for the parameters to use this procedure. However, to display the values passed into the parameter variables, you need to use PUT_LINE () procedure. For example, to display the content put in the buffer when compiling fnFineCal, you use the GET_LINE () procedure as follows: CREATE OR REPLACE FUNCTION fnFineCal (MemberID Member.cMemberID%TYPE) RETURN NUMBER IS vReturnDt DATE; vFine NUMBER; vActReturnDt DATE; vLineStatus NUMBER;
BEGIN SELECT dReturnDt,dActualReturnDt INTO vReturnDt,vActReturnDt FROM Transaction WHERE cMemberID=MemberID; IF vReturnDt=vActReturnDt OR vReturnDt>vActReturnDt THEN vFine:=0; ELSE vFine:=(vActReturnDt-vReturnDt)*1.50;
233 © SQL Star International Ltd.
END IF; DBMS_OUTPUT.GET_LINE(vFine,vLineStatus); DBMS_OUTPUT.PUT_LINE (vFine); DBMS_OUTPUT.PUT_LINE (vLineStatus); RETURN(vFine); END; / The result obtained on executing the above function is: EXECUTE :FN:=fnFineCal(‘DDG019503’); 3 1 The above result displays the fine amount to be paid by member having ID ‘DDG019503’ and also whether the GET_LINE () procedure was successful or not.
GET_LINES () In case you need to retrieve more than a line from the buffer then the GET_LINE () will not serve the purpose. You need to use the GET_LINES () procedure. This procedure also takes in two parameters. One is a character array called CHARARR, which stores the lines to be displayed. The other is of NUMBER type that stores the number of lines to be displayed.
DBMS_DDL This package provides you with access to some DDL statements. The procedures in this package include:
ALTER_COMPILE that recompiles subprograms
ANALYZE_OBJECT that analyzes objects like indexes, tables and clusters.
You cannot use this package in triggers and procedure calls from Forms Builder or remote sessions.
234 © SQL Star International Ltd.
HTP Package HTP Package is a web-driven version of DBMS_OUTPUT that helps us to create well-formed HTML tags. It originates from PL/SQL toolkit which contains packages for developing web applications. HTP package is made up of the following procedures. 1. Anchor: Generates an anchor tag. 2. Print: Prints any value passed as a parameter. 3. Wrapper Procedure: HTML tag.
contains a collection of procedures that help us to create
Steps to use this package: In SQL*Plus, you must manually execute:
SET SERVEROUTPUT ON command
The OWA_UTIL.SHOWPAGE procedure to display the buffer contents
Let us create a HTML web page using this package in an iSQLPlus interface. BEGIN HTP.HTMLOPEN; HTP.HEADOPEN; HTP.TITLE(‘Welcome to New Jersey Library’); HTP.HEADCLOSE; HTP.BODYOPEN; HTP.PRINT(‘Home page for this library under HTP.BODYCLOSE;
HTP.HTMLCLOSE;
END; / SQL> Execute
OWA_UTIL.SHOWPAGE;
Output will be: Welcome to New Jersey Library Home page for this library is under construction
235 © SQL Star International Ltd.
To specify the special features, the text must be enclosed in the HTML tag pair. With the help of Htp.print the HTML tag can be generated thereafter. For example HTP.PRINT(‘ SqlStar ’);
DBMS_SCHEDULER Job Scheduling has been enhanced in Oracle10g through this package. It aims at creating, scheduling and managing the jobs in a modular fashion thereby simplifying the task. Collectively the subprograms within the package are called Scheduler, which can be invoked from any PL/SQL program.
Architectural components of the Scheduler: A job is made of a program and a schedule. Arguments required by the program can be provided with the program or the job. All job names are of the form [schema.]name. When you create a job, you specify the job name, a program, a schedule and (optionally) job characteristics that can be provided through a job class.
PROGRAM:- A program indicates what action has to be performed, for instance, whether it should run a PLSQL Block or a stored procedure etc. A program provides metadata about a particular executable and may require a list of arguments. SCHEDULER:- Indicates when and how many times a job is to be executed.
CREATING A JOB DBMS_SCHEDULER.CREATE_JOB procedure is used to create job, which is in disable state initially. A job becomes active and scheduled when it is explicitly enabled. To create a job: • You need to have CREATE JOB privilege. • You need to specify a job name prefixed by schema name. Schema name is
236 © SQL Star International Ltd.
optional.
A job can be created in two ways. 1. Creating a job with in-line parameters 2. Defining Program and scheduling components as a separate unit The second method is much more flexible and reusable.
Creating a Job with In-Line Parameters In this method, all the job parameters and scheduling arguments are given in a single program. It takes the following parameters: JOB_NAME: indicates the name of the job to be created. JOB_TYPE:- indicates what has to be executed. Options are PLSQL_BLOCK, STORED_PROCEDURE, EXECUTABLE(executable command-line OS application). JOB_ACTION: indicates the action to be performed. START_DATE: indicates when the action has to begin. REPEAT_INTERVAL: indicates the time interval for which the action has to be repeated. END_DATE: indicates when the action has to end. Example to create a job and schedule it. BEGIN DBMS_SCHEDULER.CREATE_JOB( JOB_NAME=>’Job_1', JOB_TYPE=>’PLSQL_BLOCK’, JOB_ACTION=>’BEGIN INSERT INTO Countertab VALUES(SEQ.NEXTVAL);END;’, START_DATE=>SYSTIMESTAMP, REPEAT_INTERVAL=>’FREQUENCY=MINUTELY;INTERVAL=1', ENABLED=>TRUE);
237 © SQL Star International Ltd.
END; / The above code creates a job Job_1 which inserts values into a table using sequence SEQ every minute.
Creating a Job using Program Name and Schedule components This method follows a modular approach. It involves: 1. Creating program components using DBMS_SCHEDULER.CREATE_PROGRAM 2. Creating schedule components using DBMS_SCHEDULER.CREATE_SCHEDULE two
3. Creating Job using DBMS_SCHEDULER.CREATE_JOB, by integrating the above components.
Let us now create a job using the above modular approach. 1. Creating program components:
BEGIN DBMS_SCHEDULER.CREATE_PROGRAM( PROGRAM_NAME=>’SEQ_GEN’, PROGRAM_TYPE=>’PLSQL_BLOCK’, PROGRAM_ACTION=>’BEGIN INSERT INTO Countertab VALUES (SEQ.NEXTVAL);END;’); END; / The above code creates a program SEQ_GEN which inserts values into Countertab table. 2. Creating Schedule Components. BEGIN DBMS_SCHEDULER.CREATE_SCHEDULE(‘SCHED_NAME’, START_DATE=>SYSTIMESTAMP, REPEAT_INTERVAL=>’FREQ=DAILY’,
238 © SQL Star International Ltd.
END_DATE=>SYSTIMESTAMP+15); END; / The above code creates a schedule SCHED_NAME which runs for 15 days only. Following string expressions can be specified for REPEAT_INTERVAL: REPEAT_INTERVAL=>’FREQ=HOURLY;INTERVAL=2' hours
job runs every
2
REPEAT_INTERVAL=>’FREQ=DAILY’ job runs every day REPEAT_INTERVAL=>’FREQ=MINUTELY;INTERVAL=3' job runs every 15 mins. REPEAT_INTERVAL=>’FREQ=YEARLY BYMONTH=JAN,MAR,JUN;BYMONTHDAY=25' job runs every 25th in the specified month, each year. PL/SQL expression for REPEAT_INTERVAL can be given as follows: REPEAT_INTERVAL=>’SYSDATE+1' 3. Now, we can integrate the above created program and the schedule to create a job as below. BEGIN DBMS_SCHEDULER.CREATE_JOB(‘JOB_2’, PROGRAM_NAME=>’SEQ_GEN’, SCHEDULE_NAME=>’SCHED_NAME’, ENABLED=>TRUE); END; / Managing Jobs In order to manage Jobs, following built-ins can be used: Run a Job: DBMS_SCHEDULER.RUN_JOB(‘SCHEMA.JOB_NAME’); Stop a Job: DBMS_SCHEDULER.STOP_JOB(‘SCHEMA.JOB_NAME’); Drop a job while it is running: DBMS_SCHEDULER.DROP_JOB(‘JOB_NAME’,TRUE);
Querying Data Dictionary Views for Jobs
239 © SQL Star International Ltd.
To view the state of your jobs, execute the following query: SELECT JOB_NAME, PROGRAM_NAME, JOB_TYPE, STATE FROM USER_SCHEDULER_JOBS; To determine which instance a job is running on, execute the following query: SELECT OWNER, JOB_NAME, RUNNING_INSTANCE, RESOURCE_CONSUMER_GROUP FROM DBA_SCHEDULER_RUNNING_JOBS;
UTL_FILE Package The UTL_FILE package extends I/O to text files within PL/SQL. This package implements: Client-side security using normal operating system file permission checking Server-side security through restrictions on the directories that can be accessed The UTL_FILE package provides security for the directories on the server through the init.ora file, by setting the initialization parameter UTL_FILE_DIR to the accessible directories desired. You can use the procedures and functions of the package to: •
Open files
•
Get text from files
•
Put text into files
•
Close files
The package also contains seven exceptions to account for possible errors that may rise during execution. The file processing involved when using UTL_FILE package is diagrammatically represented below.
Before you use the UTL_FILE package to read from or write to a text file, ensure whether the text file is open by using the IS_OPEN function. If the file is not open, open 240 © SQL Star International Ltd.
the file using FOPEN function. You can then read from the file or write to the file until the processing is done. After the file processing is done, close the file using FCLOSE procedure. UTL_FILE Procedures, Functions and Exceptions
The table below lists the procedures, functions and exceptions that are specific to the UTL_FILE package.
241 © SQL Star International Ltd.
The syntax for functions FOPEN and IS_OPEN are: FOPEN Syntax FUNCTION FOPEN (location IN VARCHAR2, filename IN VARCHAR2, open_mode IN VARCHAR2) RETURN UTL_FILE.FILE_TYPE;
Where, location is the operating system specific string, which specifies the area (or directory) in which the file has to be opened. filename is the name of the file along with its extension. The path information is not specified. open_mode is a string that specifies how a file is to be opened. For instance, ‘r’- read text (use GET_LINE) ‘w’- write text (use PUT, PUT_LINE, NEW_LINE, PUTF, FFLUSH) ‘a’- append text (use PUT, PUT_LINE, NEW_LINE, 242 © SQL Star International Ltd.
PUTF, FFLUSH) IS_OPEN Syntax FUNCTION IS_OPEN (file_handle IN FILE_TYPE) RETURN BOOLEAN;
This function tests a file handle to see if it identifies an opened file.
DBMS_METADATA DBMS_METADATA is the PL/SQL package that implements Metadata API(application programming interface). It allows us to:
Retrieve an object’s metadata as XML
Transform this XML in a variety of ways, including SQL DDL
Submit this XML to re-create the extracted object
Though this package was introduced in Oracle 9i, the actual use was introduced in Oracle10g in “the data pump” to load and unload metadata.
Let us extract the information about ‘Member’ table in SCOTT SCHEMA using DBMS_METADATA.
CREATE OR REPLACE FUNCTION get_table RETURN CLOB IS h NUMBER; t1 NUMBER; t2 NUMBER;
243 © SQL Star International Ltd.
DOC CLOB; BEGIN h:=DBMS_METADATA.OPEN(‘TABLE’); DBMS_METADATA.SET_FILTER(h, ‘SCHEMA’, ‘SCOTT’); DBMS_METADATA.SET_FILTER(H, ‘NAME’, ‘MEMBER’); t1:= DBMS_METADATA.ADD_TRANSFORM(h, ‘DDL’); DOC:=DBMS_METADATA.FETCH_CLOB(H); DBMS_METADATA.CLOSE(H); RETURN DOC; END; /
Browsing APIs Browsing APIs are designed for casual use within SQL clients such as SQL*Plus. Using these functions, metadata for objects can be fetched with a single call. Object type definition can be extracted based on XML version or DDL using the appropriate functions. More than one function can also be used for certain objects. For example, GET_XXX can be used to fetch an index by name and the same index can be fetched by using GET_DEPENDENT_XXX by specifying the table on which it is defined. When you invoke the above functions in iSQL*Plus to retrieve complete, uninterrupted output, SET LONG and SET PAGESIZE commands must be used. SET LONG 2000000 SET PAGESIZE 300 An example for using DBMS_METADATA: SELECT DBMS_METADATA.GET_XML(‘TABLE’,’MEMBER’,’SCOTT’) FROM DUAL; Issue the following statement to fetch the DDL for all the object grants on SCOTT.MEMBER: SELECT DBMS_METADATA.GET_DEPENDENT_DDL ‘OBJECT_GRANT’,’MEMBER’,’SCOTT’) FROM DUAL;
Compile Time Warnings In order to make PL/SQL programs more efficient and error free at runtime, you can enable or disable certain warning conditions based on the requirement of business logic.
244 © SQL Star International Ltd.
The warnings issued by the Oracle PL/SQL compiler can be selectively enabled and disabled by using 1)PLSQL_WARNINGS initialization paramerter 2)DBMS_WARNINGS PACKAGE Warnings can be Categorized as •
SEVERE
•
PERFORMANCE
•
INFORMATIONAL
•
ALL
Using compiler warnings we can: • Make our programs more robust and avoid problems at runtime • Identify problems that are a hindrance to the performance • Know the reason for generating undesired output.
Setting Compiler Warning Levels Using PLSQL_WARNINGS setting, one can enable or disable the errors based on its characterisitic. Also, one can specify which warnings are to be shown as errors. It can be set at system level or session level. To set at system level one can use Init.ora file or ALTER SYSTEM command. Issue the following statements: PLSQL_WARNINGS=’ENABLE:SEVERE’,’DISABLE:INFORMATIONAL’; PLSQL_WARNINGS=’DISABLE:ALL’; PLSQL_WARNINGS=’DISABLE:4000',’ENABLE:4001',’ERROR:4002'; PLSQL_WARNINGS=’ENABLE:(4000,4001)’,’DISABLE:(5000)’; To set at session level Use ALTER SESSION. By default, the value is set to DISABLE:ALL.
DBMS_WARNING Package: This package helps us to read and change the setting done by PLSQL_WARNINGS programitically. It provides useful subprograms to select, modify and delete current system or session level settings. A list of subprograms are listed below. • ADD_WARNING_SETTING_CAT(w_category,w_Value,scope): Modifies the current session or system warning settings of the warning_category previously supplied.
245 © SQL Star International Ltd.
• ADD_WARNING_SETTING_NUM(w_number,w_value,scope): Modifies the current session or system warning settings of the warning_number previously supplied. • SET_WARNING_SETTING_STRING(W_value,scope): Replaces previous settings with the new value. Here, wr_category: Permissible values are SEVERE, PERFORMANCE, INFORMATIONAL and ALL. wr_value
: Permissible values are ENABLE, DISABLE and ERROR.
s_scope
: Permissible values are SESSION and SYSTEM.
For example:DBMS_WARNING.SET_WARNING_SETTING_STRING(‘ENABLE:ALL’,’SESSION’); DBMS_WARNING.ADD_WARNING_SETTING_CAT(‘PERFORMANCE’,’DISABLE’);
•
GET_CATEGORY: Returns the category name for the given message number.
• GET_WARNING_SETTING_CAT: Returns the current session warning setting for the specified category.
• GET_WARNING_SETTING_NUM: Returns the current session warning setting for the specified message number. • GET_WARNING_SETTING_STRING: Returns the entire warning string for the current session. Example :EXECUTE DBMS_OUTPUT.PUT_LINE(DBMS_WARNING.GET_WARNING_SETTING_STRING); Below is an example to set the warnings at the session level using PLSQL_WARNINGS parameter. Example :-
ALTER SESSION SET PLSQL_WARNINGS=’ENABLE:SEVERE’,’DISABLE:PERFORMANCE’, ’DISABLE:INFORMATIONAL’;
246 © SQL Star International Ltd.
Summary In this chapter, you have learnt that:
Oracle supplied packages help PL/SQL to enhance the functionality of the blocks. For instance:
DBMS_SQL: Using this package, one can write codes using DDL and DCL statements within a block which is normally not allowed. EXECUTE IMMEDIATE: This statement is also used to execute DML, DDL and DCL statements dynamically. DBMS_OUTPUT: This package help to show the output to the user’s console. DBMS_DDL: Through this package, one can dynamically compile the objects and analyze the database objects. HTP : HTML code can be generated to develop web pages using this package. DBMS_SCHEDULER: This allows us to schedule the jobs and manage them in a 247 © SQL Star International Ltd.
modular fashion. UTL_FILE: Input-output facilities are given by using this package. DBMS_METADATA: Database object’s metadata can be retrieved as XML code output or DDL code output using this package. DBMS_WARNING: This helps to dynamically enable or disable the warning settings at session level or at system level.
248 © SQL Star International Ltd.
Lab Exercises 1. Create a procedure prDeleteRows that will dynamically delete rows from a specified table. Use an OUT parameter to display the number of rows deleted as a result of the execution of dynamic SQL. To test the procedure, create a table Employee, which is a copy of the Employees table. Now, execute the prDeleteRows procedure to delete rows from the table created. 2. Use EXECUTE IMMEDIATE to perform the same dynamic SQL, as done with DBMS_SQL package in question 4. [Note: Remember to drop the table Employee and re-create it for the purpose of this question] 3. Create a procedure prAnalyzeDBObjects, which analyzes the object specified as the input parameter. The procedure tables two parameters, one for type of type of object and the other the name of the object. Test the procedure on the Employees table. Query the USER_TABLES to verify that the procedure has run.
249 © SQL Star International Ltd.
[Note: Use DBMS_DDL package and COMPUTE method] 4.
Write a procedure that generates a HTML page that prints “Hello, World!”:
5.
Write down the steps for installing UTL_MAIL.
6.
Schedule a job named job_tab_stat in SCOTT schema to gather table statistics.
7.
Get XML representation of SCOTT.EMP using DBMS_METADATA.
250 © SQL Star International Ltd.
Chapter 12
PL/SQL Performance Enhancements Standardizing Constants and Exceptions Autonomous Transactions Bulk Bind Enhancements Bulk Dynamic SQL
251 © SQL Star International Ltd.
Objectives At the end of this chapter, you will be able to: Describe the Native Compilation of PL/SQL programs Use Autonomous Transactions Identify Bulk Bind Enhancements of Oracle10g Use Table Functions
252 © SQL Star International Ltd.
Native Compilation Oracle supports the following different languages: •
PL/SQL
•
C, using the Oracle Call Interface (OCI)
•
C or C++, using Pro*C / C++ pre-compiler
•
COBOL, using Pro*COBOL pre-compiler
•
Visual Basic, using Oracle Objects for OLE (OO4O)
•
Java, using JDBC Application Programmers Interface (API)
Each of these languages provides different advantages, such as ease of use, the need for portability, or the existence of programmers with specific expertise. For instance, PL/SQL is a powerful tool, which is specialized for SQL transaction processing, C language is effective in executing some computation-intensive tasks and Java offers portability coupled with security. How would you, therefore, choose between these different implementation possibilities? Your choice would depend on how your application intends to work with Oracle. Only PL/SQL and Java methods / functions run within the address space of the server. C / C++ methods / functions are dispatched as external procedures. External procedures are procedures stored in a Dynamic Link Library (DLL) [that is, stored as a library in the file system]. This indicates that they run on the server machine, but outside the address space of the database server. Prior to Oracle8.0, the Oracle database supported SQL and the stored procedure language PL/SQL. Oracle8.0, introduced external procedures, which offered the capability of writing C functions as PL/SQL bodies. These functions could be called from PL/SQL and SQL. With 8.1, Oracle offered the capability of calling these external procedures from other languages using a special-purpose interface called call specification. You could register these external procedures with any base language (language that can call these procedures, such as PL/SQL, Java or C), and then call it to perform any special purpose processing. For instance, when you register a C function with PL/SQL, the PL/SQL language loads the library (the DLL file that contains all the C functions) dynamically at runtime, and then calls the specified C function as if it were a PL/SQL subprogram. In order to set external procedures written in C to use, you (as a DBA) need to take the following steps: 1. Setup the environment in order to call external procedures by adding entries to tnsname.ora and listener.ora files. You need to enter the agent that would handle the external procedures. The agent is named extproc and runs on the same database server as your application. 253 © SQL Star International Ltd.
2. Identify the DLL, that is the dynamically loadable Operating System file, which stores the external procedures. The DBA controls access to the DLL by creating a schema object LIBRARY, which represents the DLL. CREATE LIBRARY AS ; In the LIBRARY object, you must specify the full path to the DLL. The DBA can then grant the EXECUTE privilege on the LIBRARY object to any user. 3. Publish external procedures in a PL/SQL program through a call specification. Call specification maps names, parameter types, and return types for the C or Java external procedures to their SQL counterparts. The PL/SQL program is written like any other PL/SQL stored subprogram except that, instead of declarations and BEGIN…END block, you code the AS LANGUAGE clause. For example, the code of a PL/SQL standalone function that publishes a C function is written as follows: CREATE OR REPLACE FUNCTION fnCallsCFunction (/* the function finds the greatest common divisor of x and y/* x BINARY_INTEGER, y BINARY_INTEGER) RETURN BINARY_INTEGER AS LANGUAGE C LIBRARY NAME ;
The following diagram illustrates how external procedures are used:
There are certain disadvantages associated with external procedures. They are: •
The developer has to write the C or Java program, store them in DLL files (which have to be created explicitly), and then call them from PL/SQL subprograms. This proves to be quite cumbersome especially if a person is not proficient in C or Java languages.
254 © SQL Star International Ltd.
•
PL/SQL library units are compiled into bytecode (code, which is machine dependent), which is then loaded into the library cache part of the shared pool in the System Global Area (SGA). Compilation of PL/SQL program units into bytecode reduces their execution speed.
To overcome the limitations of external procedures, Oracle9i introduced the concept of native compilation which has been enhanced in Oracle10g. The Oracle10g database supports the compilation of PL/SQL library units as native code. When PL/SQL library units are natively compiled, they are stored as shared library (DLL files) in the file system. That is, in native compilation, PL/SQL library units are translated into C code, which is then compiled into native code.
When a PL/SQL library unit is referenced by a user session, its corresponding shared library is mapped to the virtual address space of the Oracle process. This indicates that the compiled program is located in the PGA. When multiple users connected to an Oracle instance reference the same natively compiled PL/SQL library unit, the shared library is mapped to the virtual address space of each Oracle process. Since it is a shared library, there is a single copy of the compiled PL/SQL program in the physical memory. Benefits of Native Compilation of PL/SQL
There are two phases involved in natively compiling PL/SQL program units. These phases are: •
Translation of programs to C code
•
Compilation of C code to native code
Native compilation improves the program execution speed due to the following reasons: •
Generation of native C code instead of bytecode results in faster execution of PL/SQL programs
•
Elimination of overhead, otherwise associated with interpreting bytecode
•
Faster control flow in native code than in interpreted code
•
Compiled code of a PL/SQL program is mapped to the PGA instead of the SGA into which the bytecode is loaded. This results in less contention for SGA
255 © SQL Star International Ltd.
•
PL/SQL code not containing SQL reference is 2-10 times faster. However, it does not speed up SQL execution
Enabling Native Compilation
To enable native compilation, you need to set the following parameters: •
PLSQL_COMPILER_FLAGS: This parameter specifies a list of compiler flags represented as a comma-separated list of strings. The PL/SQL compiler uses this parameter. If one of the flags is set as INTERPRETED (the default mode in releases prior to Oracle9i), it indicates that the PL/SQL programs will be compiled into bytecode format, and will be executed by the PL/SQL interpreter engine. If one of the flags is set as NATIVE, then PL/SQL programs will be compiled to native (machine) code, and will be executed natively without incurring interpreter overhead. This parameter can be set at system level using the ALTER SYSTEM command, or at session level using the ALTER SESSION command as follows: ALTER SESSION SET PLSQL_COMPILER_FLAGS = ‘NATIVE’;
•
PLSQL_NATIVE_LIBRARY_DIR: The database administrator can choose the location (that is, create directories) of the shared objects (DLL) produced by the native compiler for each PL/SQL program. The parameter PLSQL_NATIVE_LIBRARY_DIR specifies the name of the directory where the shared objects produced by the native compiler are stored. The PL/SQL compiler uses this parameter. This parameter can only be set by a DBA through an ALTER SYSTEM command or by setting a value in the INIT.ORA file before the instance is started. ALTER SYSTEM SET PLSQL_NATIVE_LIBRARY_DIR = ‘D:/nativePLSQL’;
Native compilation is switched ON and OFF using the PLSQL_CODE_TYPE parameter which can be set at instance and session level using the ALTER SYSTEM and ALTER SESSION commands respectively.
256 © SQL Star International Ltd.
Restrictions to Native Compilation
There are certain restrictions to native compilation of PL/SQL. They are: •
Native compilation is performed for a library unit, that is, for a package specification, a package body, a top-level procedure or function. You cannot natively compile individual procedures and functions in a package. Also, package body and type specification must be compiled in the same mode (that is, INTERPRETED or NATIVE).
•
Anonymous blocks are not natively compiled. They are always compiled for interpreted execution. This is to avoid complications with regards to naming the anonymous DLLs, or cleaning up these DLLs in case of a server crash.
Standardizing Constants and Exceptions Here is an attempt to create a generic code rather than a specific code. It is done by standardizing constants and exceptions. The advantages of standardizing constants and exceptions are:
We can use a consistent approach to handle errors across the entire application
Reusability of the code due to its generic nature
Easy Maintenance
To implement naming and coding standards, commonly used constants and exceptions is a good place to start with.
257 © SQL Star International Ltd.
Standardizing Exceptions Create a Standalized error handling package that includes all programmer defined exceptions to be used in an application.
named
and
CREATE OR REPLACE PACKAGE error_pkg IS fk_err EXCEPTION; seq_nbr_err EXCEPTION; PRAGMA EXCEPTION_INIT(fk_err,-2292); PRAGMA EXCEPTION_INIT(seq_nbr_err,-2277); END error_pkg; / In the example above , the error_pkg package is a standardized exception package. It declares a set of programmer-defined exception identifiers. Because many of the Oracle database predefined exceptions do not have identifying names, PRAGMA EXCEPTION_INIT directive is used in the example package to associate selected exception names with an Oracle database error number. This enables you to refer to any of the exceptions in a standard way in your applications. The package can then be implemented in the following way. BEGIN DELETE FROM departments WHERE department_id = deptno; ... EXCEPTION WHEN error_pkg.fk_err THEN ... WHEN OTHERS THEN ... END;
Standardizing Constants According to definition, variable is one whose value varies, whereas a constant is one whose value can never change. If we want local variables used in our programs not to vary, we can convert them into constants. This makes maintenance and debugging much easier. Let us create a package containing constant variable and see how to access it. CREATE OR REPLACE PACKAGE CONSTANT_PKG IS
258 © SQL Star International Ltd.
cfine CONSTANT NUMBER:=5.0; c_dmembershipdt CONSTANT DATE:=’01-Jan-2007' END CONSTANT_PKG; / Now, you can access the the package variable using this method. EXEC DBMS_OUTPUT.PUT_LINE(‘The Fine to be payable by Derian is’||constant_pkg.cfine);
Autonomous Transactions The word ‘Autonomous’ means independent and transaction is a set of statements that does a logical unit of work. AUTONOMOUS_TRANSACTION is a pragma (a compiler directive), enabling PL/SQL program units to maintain their own transaction states. But how does it work? An autonomous transaction starts within the context of another transaction, known as parent transaction, though it is independent of it. This feature allows developers to handle transactions with more ease and finer granularity. These transactions can be committed or rolled back without affecting the parent one. i.e., in an existing transaction, an autonomous transaction commit or roll back changes without affecting the outcome of the main transaction. To make the concept clear we will go through the following code snippet: CREATE OR REPLACE PROCEDURE prautomonous IS cmemberid VARCHAR2(20); BEGIN cmember_id:=’CDB028504'; COMMIT; INSERT ....................... prindependent; DELETE................. COMMIT; END;
CREATE OR REPLACE PROCEDURE PRINDEPENDENT IS PRAGMA AUTONOMOUS_TRANSACTION; CTRANID VARCHAR2(20):=’T0000300100'; BEGIN UPDATE......... INSERT .................. COMMIT; END PRINDEPENDENT ;
259 © SQL Star International Ltd.
Following occurs during an autonomous transaction. 1. The main or the parent transaction begins. 2. A prindependent procedure is called to start the autonomous transaction. 3. The main transaction is suspended. 4. The autonomous transactional operation begins. 5. The autonomous transaction ends with a commit or roll back operation. 6. The main transaction is resumed. 7. The main transaction ends. Autonomous transactions have the following features.
They branch out of the main transaction, complete processing and then branches in to resume the main or the calling transaction.
When an autonomous transaction commits, changes made by it are seen by other transactions.
When the main transaction rollback, autonomous transaction doesn’t roll back.
Any number of autonomous transactions can be called recursively.
An autonomous transaction has to be committed or rolled back explicitly, else it will give an error when attempting to return to the calling transaction.
PRAGMA cannot be used to mark all subroutines in a package as autonomous. Only individual routines can be marked autonomous.
Bulk Bind Enhancements The Oracle server uses two engines to execute PL/SQL blocks and subprograms. One is the PL/SQL engine, which runs procedural statements, and the other is the SQL engine, which runs SQL statements.
260 © SQL Star International Ltd.
The following figure depicts the execution of a PL/SQL block:
Context Switches The figure illustrates that the PL/SQL engine executes the procedural statements, but sends the SQL statements to the SQL engine. Therefore, every SQL statement encountered during execution would cause a switch (known as context switch) between the two engines. Each context switch would add to the performance overhead. Performance overhead occurs when SQL statements are executed inside a loop using collections (collections include varrays, nested tables or index by tables). For instance, in the following code snippet, the DELETE statement is sent to the SQL engine with each iteration of the FOR loop: DECLARE TYPE deptList IS VARRAY(20) OF NUMBER; depts deptList := deptList(10, 30, 70); --department numbers BEGIN ... FOR i IN depts.FIRST..depts.LAST LOOP DELETE FROM Emp WHERE deptno = depts(i); END LOOP; END;
261 © SQL Star International Ltd.
What is Bulk Bind? You can improve performance by reducing the number of context switches needed to run a particular block or subprogram. If the SQL statements embedded within PL/SQL blocks affect four or more database rows, you can improve performance by using bulk binds. Binding implies assigning of values to PL/SQL variables in SQL statements. The binding of an entire collection at once is known as bulk binding. Bulk binding improves performance by reducing the number of context switches between the engines. Bulk binds pass the entire collections back and forth instead of just the individual elements. The code snippet (shown above) could be rewritten as follows so as to make use of bulk binds: DECLARE TYPE deptList IS VARRAY (20) OF NUMBER; depts deptList := deptList (10, 30, 70); -- department numbers BEGIN ... FORALL i IN depts.FIRST..depts.LAST LOOP DELETE FROM Emp WHERE deptno = depts(i); END LOOP; END;
In the code snippet, the FORALL statement is used. On encountering the FORALL keyword, the PL/SQL engine bulk-binds collections before passing them to the SQL engine. Its syntax is: FORALL index IN lower_bound..upper_bound Sql_statement;
262 © SQL Star International Ltd.
You can compare the performance benefit achieved by using bulk binds. To do so, first create a table Test, then create a PL/SQL block to insert 9000 records using a FOR loop as well as a FORALL statement. Compare the time taken to execute the INSERT statements. CREATE TABLE test (TestID NUMBER (4), TestName VARCHAR2 (15)); Table created.
DECLARE TYPE IDTab is TABLE OF NUMBER (4)INDEX BY BINARY_INTEGER; TYPE BINARY_INTEGER;
NameTab
IS
TABLE
OF
VARCHAR2(15)
INDEX
BY
vID IDTab; vName NameTab; vTime1 NUMBER (5); vTime2 NUMBER (5); vTime3 NUMBER (5);
PROCEDURE prGetTime (t OUT NUMBER) IS BEGIN SELECT TO_CHAR (SYSDATE, ‘SSSSS’) INTO t FROM DUAL; END; BEGIN FOR j IN 1..9000 –-loading 9000 names and IDs INTO INDEX-BY TABLE LOOP vID(j):=j; vName(j):=’Part No.’||TO_CHAR(j); END LOOP; prGetTime (vTime1);
263 © SQL Star International Ltd.
FOR i in 1..9000 LOOP INSERT INTO test VALUES (vID(i), vName(i));
END LOOP; prGetTime (vTime2); FORALL i IN 1..9000 INSERT INTO test VALUES (vID(i), vName(i)); prGetTime (vTime3); DBMS_OUTPUT.PUT_LINE(‘Execution Time Taken(in secs)’); DBMS_OUTPUT.PUT_LINE(‘—————————————’); DBMS_OUTPUT.PUT_LINE(‘FOR loop: ‘ || TO_CHAR(vTime2 - vTime1)); DBMS_OUTPUT.PUT_LINE(‘FORALL:
‘
|| TO_CHAR(vTime3 - vTime2)); END; /
In the example, 9000 names and IDs are loaded into index-by tables. Next, all table elements are inserted into the database table Test twice. First, they are inserted using the FOR loop, which takes 5 seconds. Next, they are bulk inserted using a FORALL statement, which takes just 1 second. On executing the block, the following result is displayed: Execution Time Taken (in secs) --------------------------------FOR loop: 5 FORALL:
1
PL/SQL procedure successfully completed.
FORALL Statement and Unhandled Exceptions Prior to the release of Oracle9i, if the execution of an SQL statement within the FORALL statement raised an unhandled exception, all changes made by the previous executions 264 © SQL Star International Ltd.
were rolled back. For instance, you created a table Product that stored product number and product name as follows: CREATE TABLE Product (ProdNo NUMBER(2), ProdName VARCHAR2(15)); Table created. You inserted few records into the table, such as: PRODNO PRODNAME ------ ---------10 Mouse (5 characters length) 20
Printer (7 characters length)
30
Processor (9 characters length)
40
Monitor (7 characters length)
You updated certain records by appending the 9 character string ‘ -Reorder’ to certain product names as follows: DECLARE TYPE ProdNumLst IS TABLE OF NUMBER; ProdNum ProdNumLst := ProdNumLst(20, 30, 40); BEGIN FORALL i IN ProdNum.FIRST..ProdNum.LAST UPDATE Product SET ProdName = ProdName || ‘ -Reorder’ WHERE ProdNo = ProdNum(i); END; /
In the block, the UPDATE statement would be executed thrice by the SQL engine, once for each index number in the specified range. That is, once for product number 20, once for product number 30, and once for product number 40. The first execution would be successful, but the second execution would fail because the value ‘Processor –Reorder’ is large for the product name column. In such a case, only the second execution would be rolled back, and the third execution would never be done.
265 © SQL Star International Ltd.
Implementing Error Handling Mechanism for Bulk Binds
Oracle10g has incorporated FORALL Support for Non-Consecutive Indexes for more convenient and efficient bulk bind operations. Earlier, Oracle9i had incorporated an error handling mechanism so that exceptions raised during a bulk bind operation are collected and returned together once the operation is complete. That is, it enables bulk-bind operation to save information regarding exceptions and continue processing. In order to complete the bulk bind operation despite errors, add the keywords SAVE EXCEPTIONS in the FORALL statement. The syntax is as follows: FORALL index IN lowerbound..upperbound [SAVE EXCEPTIONS] sql_statements; All errors occurring during the execution are saved in a new cursor attribute %BULK_EXCEPTIONS. %BULK_EXCEPTIONS store values that always refer to the most recently executed FORALL statement. This attribute stores a collection of records, where each record has two fields: The first field is %BULK_EXCEPTIONS (i). ERROR_INDEX, stores the iteration of the FORALL statement during which the exception occurred. The second field is %BULK_EXCEPTIONS (i). ERROR_CODE, which stores the corresponding Oracle error code, SQLCODE. You can get the corresponding error message by calling SQLERRM with SQL error code as the parameter. The number of exceptions that have occurred during the execution of the FORALL statement is saved in the count attribute of %BULK_EXCEPTIONS (that is, %BULK_EXCEPTIONS.COUNT). Its values range from 1 to COUNT.
The following code shows the use of the cursor attribute %BULK_EXCEPTIONS: DECLARE
266 © SQL Star International Ltd.
TYPE NumTyp IS TABLE OF NUMBER; NumTab NumTyp := NumTyp(10,12,0,20,15,0); vCntErrors NUMBER; BEGIN FORALL i IN Numtab.FIRST..Numtab.LAST SAVE EXCEPTIONS DELETE FROM TRANSACTION WHERE nFine>200/NumTab(i); EXCEPTION WHEN OTHERS THEN vCntErrors:=SQL%BULK_EXCEPTIONS.COUNT; DBMS_OUTPUT.PUT_LINE(‘No. of errors is:‘||vCntErrors); FOR i in 1..vCntErrors LOOP DBMS_OUTPUT.PUT_LINE(‘Error‘||i||’ occured during’||’ iteration’||SQL%BULK_EXCEPTIONS(i). ERROR_INDEX); DBMS_OUTPUT.PUT_LINE(‘The Oracle error is: ‘||SQLERRM(SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); END LOOP; END; /
In the code, PL/SQL raises the ZERO_DIVIDE exception when i=0. The exception occurs when the iteration equals 3 and 6. But since SAVE EXCEPTIONS keyword is used, the bulk bind operation gets completed. Once the bulk bind operation is complete, SQL%BULK_EXCEPTIONS.COUNT returns 2, and SQL%BULK_EXCEPTIONS contains the information about the exception such as (3, 1476) and (6, 1476). The output on executing the code is: No. of errors is: 2 Error 1 occured during iteration 3 The Oracle error is: -1476: non-ORACLE exception Error 2 occured during iteration 6 The Oracle error is: -1476: non-ORACLE exception
267 © SQL Star International Ltd.
FORALL Support for Non-Consecutive Indexes Oracle10g introduces support for the FORALL syntax with non-consecutive indexes in collections. The INDICES OF clause allows the FORALL syntax to be used with sparse collections. The following example shows its usage:
--Example for indices in Oracle10g. DECLARE TYPE lib_mem IS TABLE OF NUMBER; s_no lib_mem:= lib_mem(1,2,3,4,5,6,7,8,9,10); BEGIN FOR i IN s_no.FIRST..s_no.LAST LOOP INSERT INTO lib_id VALUES(s_no(i)); END LOOP; s_no.DELETE(2); s_no.DELETE(4); FORALL i IN
INDICES OF s_no
INSERT INTO lib_id VALUES (s_no(i)); END; In the above code, s_no.DELETE(2) and s_no.DELETE(4) deletes the data at index numbers 2 and 4 respectively, thereby, creating a Sparse Data. This discontinuous collection of data is inserted again successfully due to INDICES OF clause.
VALUES OF clause in FORALL statement This clause can be used to point to one collection based on the other collection during a DML operation. For example consider a scenario were there are two INDEX BY tables. 1. c_memberid 2. v_memberid c_memberid stores the Member id of the members of library. v_memberid stores the index at which these member id are stored in c_memberid INDEX BY table.
268 © SQL Star International Ltd.
The following code explains the above scenario. DECLARE TYPE member_tab IS TABLE OF member.cmemberid%TYPE INDEX BY PLS_INTEGER; c_memberid
member_tab;
TYPE value_tab IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER; v_memberid value_tab; BEGIN c_memberid(10):='CDB028504'; c_memberid(12):='BAD109001'; c_memberid(14):='ACS099903'; v_memberid(1):=10; v_memberid(2):=12; v_memberid(3):=14; FORALL l_index IN VALUES OF v_memberid UPDATE Member SET cmaritalstatus='Y' WHERE cmemberid=c_memberid(l_index); END; /
The c_memberid INDEX BY table is used to stored values of the member id at the index number 10, 12 and 14 respectively. The v_memberid INDEX BY table, holds the index number of the c_memberid Index by table, but at the index number 1,2 and 3. Now, using a VALUES OF clause along with v_memberid Index by table in the FORALL statement, passes the value to the c_memberid index by table used in the WHERE clause of the UPDATE statement thereby updating the member id.
269 © SQL Star International Ltd.
Bulk Dynamic SQL In the previous chapter, we had learnt about Dynamic SQL. It can further be enhanced by using Bulk Binding. For example, you can use the EXECUTE IMMEDIATE statement to retrieve several member IDs with one call to the database using BULK COLLECT keyword as shown below. DECLARE TYPE tyIDList IS TABLE OF CHAR(9); memberids tyIDList; BEGIN EXECUTE
IMMEDIATE
‘SELECT
cMemberID
FROM
Member
WHERE
ROWNUM vMaxCopies
THEN RAISE_APPLICATION_ERROR (-20002, ‘Number of copies is not within the expected range -’||vMinCopies|| ‘ and ’||vMaxCopies); END IF; END; / Trigger created.
This trigger trigchkcopies will prevent the number of copies of books stored in branch ‘01ADLNJ’ to be updated below the minimum number of copies stored in the branch or updated above the maximum number of copies stored in the branch. The trigger should enforce the same restriction in case a new row is inserted into the Book table for branch ‘01ADLNJ’. Testing the trigger trigChkCopies You can check the maximum and minimum number of copies of books stored in branch ‘01ADLNJ’ by querying the Book table before testing the trigger. The output of the query would be as follows:
Now, to test the trigger, Issue the following INSERT statement. The trigger fires and fails the INSERT statement: INSERT INTO BOOK VALUES (‘PSH010012345’,’Tough Times Never Last, But Tough People Do’,’Robert Schuller’,’02PSH’,’BW1265',’16-OCT1989',’01ADLNJ’,1);
Displays the following error: INSERT INTO BOOK *
308 © SQL Star International Ltd.
ERROR at line 1: ORA-20002: Number of copies is not within the expected range -2 and 9 ORA-06512: at “SCOTT.TRIGCHKCOPIES”, line 11 ORA-04088: error during execution of trigger ‘SCOTT.TRIGCHKCOPIES’
AFTER Row trigger Example to show how the AFTER row trigger works: For the purpose of this example, create a MemberAudit table, which should have the following structure and data:
The trigger code is as follows: CREATE OR REPLACE TRIGGER trigAuditMember AFTER INSERT OR DELETE OR UPDATE OF vAddress, cPhone ON Member FOR EACH ROW BEGIN IF INSERTING THEN UPDATE MemberAudit SET Ins = Ins + 1
309 © SQL Star International Ltd.
WHERE Table_Name=’Member’ AND Column_Name is NULL; ELSIF DELETING THEN UPDATE MemberAudit SET Del = Del + 1 WHERE Table_Name=’Member’ AND Column_Name IS NULL; ELSIF UPDATING (‘vAddress’) THEN UPDATE MemberAudit SET Upd = Upd + 1 WHERE Table_Name=’Member’ AND Column_Name = ‘vAddress’; ELSIF UPDATING (‘cPhone’) THEN UPDATE MemberAudit SET Upd = Upd + 1 WHERE Table_Name=’Member’ AND Column_Name = ‘cPhone’; ELSE UPDATE MemberAudit SET Upd = Upd + 1 WHERE Table_Name=’Member’ AND Column_Name IS NULL; END IF; END; / Trigger created. The trigger trigAuditMember will update an audit table MemberAudit to keep a count of the inserts, deletes and updates of vAddress and cPhone columns performed by different users on the Member table.
310 © SQL Star International Ltd.
To test the trigger trigAuditMember, execute the following UPDATE statement: UPDATE Member SET cPhone = ‘9822101779’ WHERE cMemberID = ‘CDB028504’; 1 row updated.
The trigger trigAuditMember fires and populates the MemberAudit table as follows:
AFTER Row trigger Using OLD and NEW identifiers Create a trigger that will populate a MemberHistory table with the old and new values of columns vAddress and cPhone, when users perform DMLs on the Member table. For the purpose of this example, create a MemberHistory table, which should have the following structure:
Create the trigger as shown below: CREATE OR REPLACE TRIGGER trigMemberHist AFTER INSERT OR DELETE OR UPDATE OF vAddress, cPhone ON Member
311 © SQL Star International Ltd.
FOR EACH ROW BEGIN INSERT INTO MemberHistory VALUES (USER,SYSDATE,:OLD.cMemberID, :OLD.vAddress,:NEW.vAddress, :OLD.cPhone,:NEW.cPhone); END; / Trigger created.
To test the trigger trigMemberHist execute the following UPDATE statement: UPDATE Member SET cPhone = ‘981011724 WHERE cMemberID = ‘CDB028504’; 1 row updated.
The trigger trigMemberHist fires and populates the MemberHistory table as follows:
Creating INSTEAD OF trigger You can associate triggers to the views you are working with while performing DML operations on them. It is to be remembered that data in complex views cannot be manipulated. Hence, when you perform a DML transaction on these views, the trigger associated with it will fire. Instead of the DML statements in the triggering statement being executed, the code of the trigger will be executed. Such triggers are referred to as INSTEAD OF triggers.
312 © SQL Star International Ltd.
The syntax to create an INSTEAD OF trigger:
CREATE
[OR REPLACE] TRIGGER INSTEAD OF {DML event1 [OR event2 OR event3] ON [REFERENCING OLD AS old | NEW AS new] [FOR EACH ROW] PL/SQL block;
Where, trigger_name is the name of the trigger INSTEAD OF is the type of trigger used. DML event1 are any of the DML statements of INSERT, UPDATE or DELETE. view_name is the name of the view that the trigger is associated with [REFERENCING OLD AS old | NEW AS new] specifies the correlating names for the old and new data. [FOR EACH ROW] is specified to designate the trigger to each row as is done in case of row triggers. PL/SQL block is the code of SQL and PL/SQL statements that defines the transactions that the trigger should perform.
INSTEAD OF Trigger example INSTEAD OF triggers are helpful in cases when the view has more than one base table. If it is a multiple table view, the DML transactions could address different transactions to different tables. You might have to perform an INSERT on one table and an UPDATE on another. The INSTEAD OF trigger fires instead of the DML transactions against the views. Thus the DML transactions are performed directly on the base tables of the view.
To create an INSTEAD OF trigger as said above on a complex view, you need to first do the following: 313 © SQL Star International Ltd.
1. Alter the MemberFee table to include a column named nCountAge. ALTER TABLE MemberFee ADD nCountAge NUMBER (2); Table altered.
2. Populate the nCountAge column with values taken from the Member table as shown below. UPDATE MemberFee SET nCountAge = (SELECT COUNT (nAge)
FROM Member
WHERE Member.cGrade = MemberFee.cGrade); 4 rows updated. Query the MemberFee table to view the values added to the newly created column.
3. Create a complex view having Member and MemberFee as its underlying tables. CREATE OR REPLACE VIEW VwMemberGrade AS SELECT cMemberID, cFirstName, cLastName, vAddress, cArea, cZipcode, nAge, M.cGrade, nFeeAmount FROM Member M, MemberFee MF WHERE M.cGrade = MF.cGrade; View created.
4. Create an INSTEAD OF trigger such that when a row is inserted into the view vwMemberGrade, instead of inserting the rows into the view, the rows are added to the Member and MemberFee table. CREATE OR REPLACE TRIGGER trigVwMemberGrade INSTEAD OF INSERT ON VwMemberGrade FOR EACH ROW BEGIN
314 © SQL Star International Ltd.
INSERT INTO Member (cMemberID, cFirstName, cLastName, vAddress, cArea, cZipcode, nAge, cGrade) VALUES (:NEW.cMemberID, :NEW.cFirstName, :NEW.cLastName, :NEW.vAddress, :NEW.cArea, :NEW.cZipcode, :NEW.nAge, :NEW.cGrade); UPDATE MemberFee SET nCountAge = nCountAge + 1 WHERE cGrade = :NEW.cGrade; END; / 5. Insert a row in the view vwMemberGrade and verify whether the rows got inserted in its underlying tables. INSERT INTO vwMemberGrade(cMemberID,cFirstName,cLastName, vAddress,cArea,cZipcode,nAge,cGrade) VALUES(‘CPS010204’,’Phil’,’Spring’,’12-A101,Spring Rd’,’Randolph’,’NJ09123',25,’C’); 1 row created. On executing the INSERT statement, the INSTEAD OF trigger fires, causing the respective data to be inserted into the Member and MemberFee table. Verify the row inserted into the Member table:
SELECT * FROM MEMBER WHERE cFirstName = ‘Phil’;
Verify the row updated in the MemberFee table: SELECT * FROM MemberFee;
315 © SQL Star International Ltd.
Trigger Modes Mode of a trigger is the state a trigger is in, that is, whether it is enabled or disabled. When a trigger is created it is enabled by default. When a trigger is enabled, the Oracle server provides some resources to the trigger like, locking the tables for read consistency, managing dependencies, and also performing a two-phased commit if the trigger performs updates on remote tables. If you do not want the trigger to fire, then you have to explicitly disable it using a command. You enable / disable a trigger with the following syntax: ALTER TRIGGER DISABLE | ENABLE;
If you want to enable / disable all the triggers on a particular table, then use the following syntax: ALTER TABLE DISABLE | ENABLE ALL TRIGGERS
Triggers could be disabled: •
To improve performance when loading a large amount of data.
•
When you don’t want the data integrity checks to be performed when loading a huge amount of data.
•
When the table associated to the trigger is not available due to various reasons like no network connection, system hard disk crash, and offline data file or offline tablespace.
Recompiling Triggers Triggers are compiled like any other PL/SQL block. When the trigger is compiled without any errors you will get a message, saying that the trigger has been created. In case a trigger is invalid, you need to explicitly recompile the trigger. This is done using the ALTER TRIGGER statement. The ALTER TRIGGER statement with the COMPILE option compiles the trigger irrespective of the fact that the trigger is valid or invalid. The syntax for the same is: ALTER TRIGGER COMPILE
316 © SQL Star International Ltd.
You can recompile the trigger trigvwMemberGrade as follows: ALTER TRIGGER trigvwMemberGrade COMPILE; Trigger altered. Dropping a Trigger You can delete a trigger from the database, using the DROP TRIGGER statement. The syntax is: DROP TRIGGER For example, you can drop the trigger trigvwMemberGrade as follows: DROP TRIGGER trigvwMemberGrade; Trigger dropped.
Viewing Trigger Information To access the information about the triggers which are created in your schema, following Data Dictionary views can be queried. 1. USER_OBJECTS: It gives us information regarding the status of the trigger and when it was created. 2. The USER_ERRORS: It stores the information regarding the compilation errors of unsuccessful compiled triggers. 3. The USER_TRIGGERS: This gives information details of components of the trigger such as name, type, triggering event, the table on which the trigger is created and the body of the trigger.
317 © SQL Star International Ltd.
Summary In this chapter, you have learnt that:
A block of code that gets executed implicitly based on an event is called a trigger. System triggers on a database fire for each event for all the users, whereas, system triggers on a schema fire for each event for a specific user.
You need to have certain privileges (Like CREATE TRIGGER, CREATE ANY TRIGGER, ALTER TRIGGER etc) to create and modify a trigger.
The components of a trigger include: Trigger timing (Before or After an event) Trigger Event (INSERT,UPDATE or DELETE) Trigger Type (Row or Statement) Trigger Code (Actual task to be performed when the trigger is fired)
When a DML operation is performed on a complex view, the trigger associated is fired. Instead of the DML statement, the code of the trigger is executed. Such a trigger is called INSTEAD OF Trigger.
Triggers can be enabled or disabled using ENABLE and DISABLE commands and can be modified as (ALTER, DROP) as is done to tables.
USER_TRIGGERS data dictionary view gives information about the components of a trigger.
318 © SQL Star International Ltd.
Lab Exercises 1. Create a procedure called prCheckDML that prevents any DML statement from being executed outside normal office hours, Monday through Friday, and returns a message saying “You are allowed to make changes only during office hours.” 2. Create a statement trigger trigSecureEmp on the Employees table that invokes the procedure prCheckDML. 3. Create a row trigger trigRestrictSal, which will allow a new row to be inserted into the Employees table or salary to be updated only if the new department ID happens to be 20 or 60, and new salary happens to be more than $12,000. Your trigger code should display an appropriate message if the insert or update on Employees table fails. For instance, on attempting to update the salary of employee 101 (who does not belong to departments 20 or 60) to $14,000, the trigger trigRestrictSal should fire, and the following message must be displayed: UPDATE Employees * ERROR at line 1: ORA-20001: You are not eligible to earn this amount ORA-06512: at “HR.TRIGRESTRICTSAL”, line 4 ORA-04088: error during execution of trigger ‘HR.TRIGRESTRICTSAL’ [Note: For the purpose of question 3, you need to create a table AuditEmp, which tracks a user’s activity on the Employees table.] The structure of AuditEmp table should be as follows:
319 © SQL Star International Ltd.
4. Create a trigger called trigAuditEmp, which will record the values of columns of Employees table (both before and after changes made) to the table AuditEmp. Insert a new row into the Employees table, and update the salary and department ID of employee 104. Query the AuditEmp table and view the result. 5.
Disable all the triggers created.
320 © SQL Star International Ltd.
Chapter 15
Creating Advanced Database Triggers
Creating Triggers on DDL Events Database Event Triggers CALL Statement Managing Triggers Triggers vs. Stored Procedures Mutating and Constraining Tables Enhancing Server Functionality
321 © SQL Star International Ltd.
Objectives In this chapter, you will be able to:
Create Advanced Database triggers
Optimize trigger functionality
Understand Mutating Table
Create triggers to enhance server functionality
322 © SQL Star International Ltd.
Creating Advanced Database Triggers You have seen the effect of triggers that fire due to DML statements. Apart from this, the use of triggers can be extended to DDL and database events as well. DDL Event Triggers
Triggers that fire due to DDL transactions are called DDL event triggers. You create these triggers when using the CREATE, ALTER and DROP commands. One of the reasons for creating these triggers could be the security of the database to prevent illegal manipulation of database objects.
A DDL event trigger can be associated with all database objects. The syntax to create this trigger is: CREATE [OR REPLACE] TRIGGER timing [ON SCHEMA] [ddl_event1 [OR ddl_event2 OR ddl_event] ] ON {DATABASE
SCHEMA}
PL/SQL block; Where, ddl_event1 could be any of the statements CREATE, ALTER or DROP as and when they are implemented on any of the database objects. ON SCHEMA clause is specified if you want to create the trigger associated to some data dictionary objects in your schema. The rest of the syntax remains the same as that for creating DML triggers. An example for this trigger is: CREATE OR REPLACE TRIGGER trigPreventDrop BEFORE DROP ON SCOTT.SCHEMA BEGIN IF DICTIONARY_OBJ_OWNER = ‘SCOTT’ AND DICTIONARY_OBJ_NAME LIKE ‘MEM%’
323 © SQL Star International Ltd.
AND DICTIONARY_OBJ_TYPE = ‘TABLE’ THEN RAISE_APPLICATION_ERROR (-20001, ‘YOU ARE NOT PERMITTED TO DROP THE TABLE’); END IF; END; / Trigger created.
To test this trigger trigPreventDrop, first create a table Members, which is a duplicate of the Member table: CREATE TABLE MEMBERS AS SELECT * FROM MEMBER; Table created.
Now, when you attempt to drop this newly created table, the DDL trigger prevents the operation from succeeding and displays a message conveying the same: DROP TABLE MEMBERS; DROP TABLE SCOTT.MEMBERS * ERROR at line 1: ORA-00604: error occurred at recursive SQL level 1 ORA-20001: YOU ARE NOT PERMITTED TO DROP THE TABLE ORA-06512: at line 6
Database Event Triggers Database events are those that occur for the database as a whole and all the objects in the schema may be affected by those events. Events like server shutdown and start up and server error are all system or database events. The syntax to create a database trigger is: CREATE [OR REPLACE] TRIGGER timing [ON SCHEMA]
324 © SQL Star International Ltd.
[database_event1 [OR database_event2 OR database_event] ] ON {DATABASE|SCHEMA} PL/SQL block;
The database event triggers include: •
AFTER SERVERERROR trigger that will fire when an error is logged in the database
•
AFTER LOGON, which fires as soon as you log on to the database
•
BEFORE LOGOFF trigger, which fires when you try to log off from the database
•
AFTER STARTUP trigger, which fires when you open the database
•
BEFORE SHUTDOWN trigger that fires whenever you shutdown the database
Log On and Log Off Triggers
These triggers are created to track the number of times a user logs on and off from the database. This trigger could be specified for a database or a schema. If for the former, then the data you retrieve is the count of number of users accessing the database. If the trigger is on a schema, then it keeps track of the number of time a users logs on and off from his schema. The following codes show you the use of log on and the log off triggers: Note: For the following two examples, create a table LogTable, which should have the following structure:
Now, create the AFTER LOGON trigger as follows: CREATE OR REPLACE TRIGGER trigToLogon AFTER LOGON ON SCOTT.SCHEMA BEGIN INSERT INTO LogTable (Usr_Name, Log_Dt, Operation) VALUES (USER, SYSDATE, ‘logging on’); END; / Trigger created.
325 © SQL Star International Ltd.
To test the trigger, log off the database and log in again. Now, query the LogTable. The table displays the following result:
Create the BEFORE LOGOFF trigger as follows: CREATE OR REPLACE TRIGGER trigToLogoff BEFORE LOGOFF ON scott.schema BEGIN INSERT INTO LogTable (Usr_Name, Log_Dt, Operation) VALUES (USER, SYSDATE, ‘logging off’); END; / Trigger created.
To test the trigger, log off the database and log in again. Now, query the LogTable. The table displays the following result:
The CALL Statement
You have seen how triggers are created for DML, DDL and database events. There are a few more operations that can be done with triggers. They include: Using the CALL statement in triggers You can call any existing stored procedure instead of coding a PL/SQL block for the trigger to perform some actions. The procedure could be implemented on any software platform like PL/SQL, Java or C. You reference trigger attributes using this statement with the :NEW and :OLD parameters. An example for this trigger is: CREATE OR REPLACE TRIGGER trigChkFine AFTER UPDATE OF dActualReturnDt ON Transaction 326 © SQL Star International Ltd.
FOR EACH ROW WHEN (NEW.dActualReturnDt > OLD.dReturnDt) BEGIN — Call to stored function fnFineCal CALL fnFineCal(:NEW.dActualReturnDt); END; / Trigger created. Referencing system attributes in triggers. With the CALL statement you can also refer to the system attributes by calling some procedure that uses the system attributes as parameters. An example of this is: CREATE OR REPLACE TRIGGER trigSchemaObj AFTER CREATE ON CLERK.SCHEMA BEGIN CALL prInsertAudit(SYS.Dictionary_Obj_Name); END; / Trigger created. The system parameters that you can refer to are: •
SYSEVENT: names the event that fires the trigger
•
INSTANCE_NUM: displays the number of the instance
•
DATABASE_NAME: displays the name of the database
•
SERVER_ERROR: displays the error number of the error at the specified position in the stack.
•
IS_SERVERERROR: returns a BOOLEAN value stating TRUE if there is an error and FALSE if there is no error.
•
LOGIN_USER: returns the name of the user who has logged in.
•
DICTIONARY_OBJ_NAME: returns the name of the object
•
DICTIONARY_OBJ_TYPE : displays the type of database object that is being accessed.
•
DICTIONARY_OBJ_OWNER: displays the object’s owner’s mails.
327 © SQL Star International Ltd.
•
DES_ENCRYPTED_PASSWORD: captures the password when a user account is being created or altered.
Managing Triggers When creating triggers, you must manage them in a way to optimize their functionality. Managing Trigger Development
The three types of DML triggering statements are INSERT, UPDATE and DELETE. Based on when they fire, triggers are classified as BEFORE and AFTER. In addition, they are also classified as row and statement level triggers. So taking a combinations of these types, you can create 12 types of triggers for one table in an Oracle database. There is no need to create 12 triggers for all tables, as there is in-built functionality to handle various situations. You could have triggers on your table, which in turn may fire other triggers. However, these should be designed carefully so that there is no excess trigger processing that happens. For example you can create an INSERT trigger, Trg1 on table Tab1. When this trigger fires, it inserts a row into table Tab2. An insert into Tab2 causes another INSERT trigger, Trg2, against it to fire. Trg2 in turn inserts a row into Tab1. This is a badly designed set of triggers as it causes a loop of executions to form and as a result no insertions happen. When a trigger causes another trigger to fire and that in turn causes a third one to fire, it is said to be cascading of triggers. A maximum of 32 cascading triggers is allowed to fire. Setting the MAX_OPEN_CURSORS parameter when starting up the database can restrict this figure. Some restrictions when working with triggers are: •
You can use a trigger to insert data into a column of LONG or LONG RAW datatype, but you cannot make use of these datatypes to declare variables in a trigger. To extract data from a column of this datatype, you have to convert your trigger column to a VARCHAR2 datatype and then perform the select. NEW and OLD references also cannot be made.
•
If a cascading trigger on a table called from a package with a function or procedure in turn calls the package then the updates will clash, the trigger will re-fire before the problem is solved.
328 © SQL Star International Ltd.
•
Triggers do not process rows according to an order you want. If you want to specify the order of processing rows, then the trigger should include an ORDER BY clause.
•
You need to explicitly handle exceptions in triggers. Triggers are the same as any other PL/SQL block so they can also include an exception in their body.
Mutating and Constraining Tables
The two basic rules that apply when using triggers are that: •
Your trigger should not change data in the primary key, foreign key or unique key columns of a constraining table.
•
Your trigger should not read data from a mutating table.
You can understand the rules better, by understanding what mutating and constraining tables are. A constraining table is one that a trigger needs to access directly for DML statement or indirectly when the SQL statement requires a foreign key check. A mutating table is one that is either being modified by a DML statement, or needs to be modified due to a DELETE CASCADE referential integrity action.
Tables mutate when an UPDATE, DELETE or INSERT is happening on them directly, and constrain when reference has to be made to them by SQL statements or referential integrity constraints in order to update some other table. A row trigger can read or write data from a table to which it is attached, only through OLD and NEW references. By rule, a trigger is bound by read consistencies and referential integrity. For instance, an INSERT trigger tries to insert a row with one foreign key value into a table. The trigger cannot insert a value into the referencing table to maintain the foreign key constraint.
329 © SQL Star International Ltd.
Triggers Vs Stored Procedures Database triggers, as you have seen are the same as any other PL/SQL block or stored procedure. They are created in the same way and the data dictionary contains the source code and pcode for both. But there are a few basic differences between the two. The following list explains the differences: •
Database triggers are invoked implicitly and stored procedures are explicitly invoked.
•
COMMIT, SAVEPOINT and ROLLBACK are not allowed in triggers but are allowed in stored procedures.
Enhancing Server Functionality Finally you need to know how trigger functionality enhances the working of the Oracle server. Database triggers are to be created to enhance functionality, which cannot otherwise be performed by the Oracle server. The features that can be enhanced by using triggers are: •
Security
•
Auditing
•
Non-declarative integrity
Data integrity
Referential integrity
•
Table replication
•
Derived data
330 © SQL Star International Ltd.
Implementing Security with Triggers The Oracle server implements security by creating schemas and roles and using the GRANT and REVOKE commands. Triggers can be used to enhance these security features by customizing some of them.
Some security issues are that: •
The server verifies the user name when a user connects to a database, but with a trigger you can set privileges not only on the user, but also on database values. These values could be the day of the week or time among others.
•
Oracle server can determine who can access tables, views, synonyms and sequences. A trigger can be created to specifically restrict access to tables only.
•
Oracle server specifies privileges for data manipulation and data definition changes. A trigger can be created to control data manipulations only.
Controlling security within the server GRANT SELECT, INSERT, UPDATE, DELETE ON Member TO rlMember; -- role created GRANT rlMember TO CLERK; -- role granted to user
Controlling security with a trigger CREATE OR REPLACE TRIGGER trigLibDBSecurity BEFORE INSERT ON Transaction BEGIN IF (TO_CHAR (SYSDATE, ‘DY’)=’SUN’ OR ‘HH24’)
(TO_CHAR (SYSDATE,
NOT BETWEEN ‘10’ AND ‘19’)) THEN RAISE_APPLICATION_ERROR (-20022, ‘Cannot record
book issue
transactions
on
Sundays
and
beyond
library hours’); END IF; END; /
331 © SQL Star International Ltd.
Trigger created.
Auditing Within the server, you can monitor and gather data regarding specific database activities. With triggers you can audit actual data values. The auditing activities performed by the Oracle server and triggers include: •
Oracle server audits data retrieval, manipulation and definition statements. Triggers audit data manipulation statements only.
•
Oracle server writes the audit trail to the centralized audit table (that is, a data dictionary table or an Operating System file), while a trigger can write it to a user-defined audit table.
•
Oracle server generates an audit report for every one session or access attempt, while a trigger can generate audit reports for every row.
•
Oracle server captures the successful and unsuccessful attempts, while a trigger captures only successful attempts.
Auditing performed by the server AUDIT INSERT, UPDATE, DELETE ON Transaction BY ACCESS WHENEVER SUCCESSFUL; Audit succeeded.
Auditing using a trigger CREATE OR REPLACE TRIGGER trigAuditTransactions AFTER INSERT OR UPDATE OR DELETE ON Transaction FOR EACH ROW BEGIN INSERT INTO TransactionAudit (DBUser, Dt, OldMemID, NewMemID, OldBkId,NewBkID, OldIssDt, NewIssDt, OldReturnDt, NewReturnDt, OldActualRetDt,NewActualRetDt, OldFine, NewFine) VALUES (USER, SYSDATE, :OLD.cMemberID, :NEW.cMemberID, cBookID,:NEW.cBookID, :OLD.dIssueDt, :NEW.dIssueDt, :OLD.dReturnDt, :NEW.dReturnDt,
332 © SQL Star International Ltd.
:OLD.dActualReturnDt, :NEW.dActualReturnDt, :OLD.nFine, :NEW.nFine); END; / Trigger created.
Enforcing Non-declarative Integrity The Oracle server enforces standard data integrity and referential integrity rules. The server implements data integrity by: •
Maintaining constant default values
•
Enforcing static constraints
•
Dynamically enabling and disabling
With a trigger, you can provide variable default values, enforce dynamic constraints, enable and disable dynamically, and incorporate declarative constraints within the definition of a table. Referential integrity is implemented to avoid data inconsistency. The important issues are: •
Restricting updates and deletes
•
Managing cascading and deletes
•
Enabling and disabling dynamically
When implementing referential integrity with triggers, some actions not possible by declarative constraints are taken care of: •
Cascading triggers
•
Setting default and NULL values during updates and inserts
•
Enforcing referential integrity in a distributed environment
•
Enabling and disabling dynamically
Data integrity enforced within the server ALTER TABLE Member ADD CONSTRAINT ChkFee CHECK (nFeeAmount>5); Table altered.
Data integrity enforced with a trigger
333 © SQL Star International Ltd.
CREATE OR REPLACE TRIGGER trigChkFee BEFORE UPDATE OF nFeeAmount ON MemberFee FOR EACH ROW WHEN (NEW.nFeeAmount< OLD.nFeeAmount) BEGIN RAISE_APPLICATION_ERROR (-20111, ‘cannot decrease the fee amount’); END; / Trigger created.
Testing trigger trigChkFee When you issue the following UPDATE statement, the trigger fires and fails the UPDATE: UPDATE MemberFee SET nFeeAmount = 10 WHERE cGrade = ‘B’; UPDATE MemberFee * ERROR at line 1: ORA-20111: cannot decrease the fee amount ORA-06512: at “SCOTT.TRIGCHKFEE”, line 2 ORA-04088: error during execution of trigger ‘SCOTT.TRIGCHKFEE’
Table Replication The Oracle server replicates a table by creating snapshots. A snapshot is a local copy of a table that originates from one or more remote master tables. You can read data from a snapshot but not perform DML statements on it. Data in the snapshot is periodically refreshed so that it shows the latest data as is in the master. Some activities you can perform with snapshots are: •
Copy tables asynchronously
•
Base the snapshots on multiple tables
334 © SQL Star International Ltd.
•
Improve the performance of data manipulation on the master, especially in case of a network failure
Replicating tables with triggers includes: •
Copying tables synchronously
•
Basing the tables on single master tables
•
Read and write from and to the replica tables
•
Destroys the performance of data manipulations on the master especially in case of a network failure
•
Copies of tables are maintained automatically with snapshots on remote nodes
Table replication within the server CREATE SNAPSHOT BookCopy AS SELECT * FROM Book;
Table replication with a trigger CREATE OR REPLACE TRIGGER trigBkCopy BEFORE INSERT OR UPDATE ON Book FOR EACH ROW WHEN (NEW.cBranchID=’04RANNJ’) BEGIN -- replicating the book data of branch 04RANNJ to table RandolphBook IF INSERTING THEN INSERT INTO RandolphBook VALUES(:NEW.cBookID,:NEW.cBookName,:NEW.cAuthorName, :NEW.cCategoryID,:NEW.cPublisherID, :NEW.dPublishedYr, :NEW.cBranchID,:NEW.nNoOfCopies,’n’); ELSE UPDATE RandolphBook SET cBookID = :NEW.cBookID WHERE cBookID = :NEW.cBookID; END IF;
335 © SQL Star International Ltd.
END;
Computing Derived Values
The Oracle server allows you to compute derived values in a batch by: •
Computing the derived columns asynchronously, at user defined intervals
•
Storing the values within the tables
•
Modifying the data in one step and calculating the derived value in the next
With triggers, you can continuously compute derived values by: •
Computing the derived columns synchronously
•
Storing the values within the tables or package variables
•
Modifying the data and performing the calculations are done in one step
Computing derived data within the server UPDATE MemberFee SET nCountAge = (SELECT COUNT (nAge) FROM Member WHERE Member.cGrade = MemberFee.cGrade);
Computing derived data with a trigger CREATE OR REPLACE TRIGGER trigIncCopies AFTER INSERT OR DELETE OR UPDATE OF nNoOfCopies ON Book FOR EACH ROW BEGIN IF DELETING THEN -- invoking a procedure prIncCopies PrIncCopies (:OLD.cBookID,1 * :OLD.nNoOfCopies);
336 © SQL Star International Ltd.
ELSIF UPDATING THEN PrIncCopies (:NEW.cBookID, :NEW.nNoOfCopies - :OLD.nNoOfCopies); ELSE PrIncCopies (:NEW.cBookID, :NEW.nNoOfCopies); END IF; END; / Trigger created.
Procedure code CREATE OR REPLACE PROCEDURE prIncCopies (vBookId IN Book.cBookID%TYPE, vCopies IN Book.nNoOfCopies%TYPE) IS BEGIN UPDATE Book SET nNoOfCopies = nNoOfCopies + vCopies WHERE cBookID = vBookId; END; / Procedure created.
Summary
337 © SQL Star International Ltd.
In this chapter, you have learnt that: DDL Event triggers fire due to DDL events. Database Event triggers fire on database events like Server Startup, Shutdown, Server Error etc. LOGON and LOGOFF triggers track the number of times a user logs on and off respectively from the database. A procedure can be called from the trigger using the CALL statement. Triggers enhance Security, Auditing, Data Integrity, Table Replication and Derived- data features.
338 © SQL Star International Ltd.
Lab Exercises 1.
Write a trigger on your schema to track when you have logged in and logged out.
2. What happens if a procedure that updates a column of table X is called in a database trigger of the same table ?
339 © SQL Star International Ltd.
340 © SQL Star International Ltd.