You are on page 1of 131

Book: Oracle PL/SQL Programming - Oreilly

Chapter 01:

Introduction to PL/SQL:

1.1 What is PL/SQL

Procedure maintain_company(
action_in IN Varchar2,
id_in IN NUMBER,
name_in IN VARCHAR2 := NULL)
IS
BEGIN
....
EXCEPTION
END;

1.2: The concept of programming in Oracle Applications:

1.3: The origin of PL/SQL:


1.3.1: Improved Application Portability with PL/SQL.
1.3.2: Improved Execution Authority and Transaction Integrity with PL/SQL:
*** Transaction Integrity: Commit and Rollback.
*** No direct access to update tables, only access to tables is thru procedures. This will reduce user induced errors and
Ensure Security.

1.4: PL/SQL Versions


...1.4.3.1: Integration with SQL:
* PL/SQL Originally designed to provide procedural extensions to the SQL language.
* From with-in PL/SQL you can execute any DML statement(SELECT, UPDATE, INSERT and DELETE).
* Cannot execute a DDL statement such as CREATE TABLE.
* from with-in the native PL/SQL language, can make full use of all SQL operators, predicates and functions. Outside the SQL
statement, we can use functions like TO_CHAR, LIKE etc..
* Can commit and rollback transactions and create SAVEPOINTS.
* Can make use of PL/SQL constructs in the SQL statements:

Select employee.last_name
INTO new_hire_name
FROM EMPLOYEE
WHERE hire_date = last_hire_date

Distributed SQL:

UPDATE employee@NEW_YORK
SET salary = (SELECT MAX(salary) FROM employee@TORONTO)

NEW_YORK and TORONTO are database links to database tables in different database instances.

1.4.3.2: Expanded set of datatypes for variables and constants:

* can declare local variables and constants and then use those identifiers in the PLSQL program. can declare variables and
constants to be a datatype known to RDBMS such as VARCHAR2 or NUMBER.
* PL/SQL specific data structures:
**BOOLEAN: TRUE, FALSE or NULL

**BINARY_INTEGER: similar to NUMBER, BINARY_INTEGER datatype represents values as signed binary integers of
virtually any size. As signed binary is the internal format for numeric values, you can perform calculations with this datatype
that do not require any conversions.

**PL/SQL record: contains one or more fields and is similar to a row in a database table. Can assign values to variables and
SELECT information from the database into these variables.

ROWID, RAW, LONG RAW and MLSLABEL

Also defines a set of subtypes, which define constraints to the base type:
NATURAL: All integers greater than zero, a subtype of BINARY_INTEGER.
REAL: A Subtype of numbers.

1.4.3.3: Programmer-defined records:

*can create records using the %ROWTYPE attribute, these records only reflect the structure of a table or structure.
*can create records with what ever structure we decide upon, completely independent of any table or cursor. [PLSQL 2.0]
*programmer defined record may have another record as a field in it's record, thereby allowing nested records.
example:
DECLARE
/* Create a record to hold 4 quarters of sales data */
TYPE sales_quarters_rectype IS RECORD
(
q1_sales NUMBER,
q2_sales NUMBER,
q3_sales NUMBER,
q4_sales NUMBER
);
/*
Create a record to hold sales information for a customer.
Notice the nested record.
*/
TYPE customer_sales_rectype IS RECORD
(
customer_id NUMBER (5),
customer_name customer.name%TYPE,
total_sales NUMBER (15, 2)
);
sales_by_quarter sales_quarters_rectype;
/* Create a record for use in this program */
customer_sales_rec customer_sales_rectype;
BEGIN ...

1.4.3.4: PLSQL Tables:

PLSQL table is a memory resident object that gives array like access to rows of information. similar to but not same as a
database table.
Currently a PLSQL table may contain only one column [with datatype of our choice] and one primary key with mandatory
type of BINARY_INTEGER.

Example:

DECLARE
/* Table of strings to hold company names */
TYPE company_names_tabtype IS TABLE OF
VARCHAR2(100) INDEX BY BINARY_INTEGER;

company_names company_names_tabtype;
BEGIN ...
1.4.3.5: Built-In functions:

1. Character functions.
2. Date functions.
3. Numeric functions.
4. Conversion functions.

1.4.3.6: Built-In packages:

DBMS_OUTPUT, DBMS_PIPE, DBMS_LOCK,

1.4.3.7 Control Structures:

Conditional Control: via IF Statements.


Iterative Control: loops [FOR, WHILE and simple loops]
Sequential control: GOTO and NULL statements.

PLSQL does not support a CASE structure.


*****************************************************************************************************
*********
26 March 2008
*****************************************************************************************************
*********
IF-ELSIF-ELSE-END IF

WHILE still_searching
LOOP
FOR month_index IN 1 .. 12
LOOP
calculate_profits(month_index);
END LOOP;
ENDLOOP;

GOTO transfers control from one executable statement to any other statement in the current program body. Specify NULL
statement to do nothing.

IF rooms_available = 0
THEN
GOTO no_rooms;
ELSE
reserve_a_rooms;
END IF;

<<no_rooms>>
NULL;

1.4.3.8 Cursor-based access to the database

DECLARE
CURSOR extinction_cur IS
SELECT species_name, last_sighting
FROM rainforest
WHERE year = 1994
AND number_species_left = 0;

extinction_rec extinction_cur%ROWTYPE;
expedition_leader VARCHAR2(100);
BEGIN
/* only open the cursor if it's not open */
IF NOT extinction_cur%ISOPEN
THEN
OPEN extinction_cur;
END IF;

/* Fetch the next record. */


FETCH extinction_cur INTO extinction_rec;

/* Execute statements based on record contents. */


IF extinction_rec.last_sighting = 'BRAZIL'
THEN
expedition_leader := 'RAMOS';
ELSIF extinction_rec.last_sighting = 'BACKYARD'
THEN
expedition_leader := 'VIJAY';
END IF;
/* Close the cursor */
CLOSE extinction_cur;
END;

1.4.3.9 Error Handling

When an error occurrs, an exception is raised. The normal processing in the program halts and control is transferred to a
seperate exception handling block. Exception handling provides an event driven model.

DECLARE
soc_sec_number NUMBER;
BEGIN
select social_security#
INTO soc_sec_number
FROM employee
WHERE last_name = 'FEUERSTEIN';
EXCEPTION
WHEN NO_DATA_FOUND
THEN
INSERT INTO EMPLOYEE
(last_name, first_name, social_security#, hire_date, department_id)
VALUES ('FEUERSTEIN', 'STEVEN' , 123456789, SYSDATE, 10);
END;

1.4.3.10 Modular construction

Code is divided into blocks.


Two types of blocks: Named and Unnamed.

Named: Procedure and Function.

Procedure: Sequence of executable statements that performs a particular action.


Function: Function is a block that returns a value.

PROCEDURE display_emp_status(status_code_in IN VARCHAR2)


IS
/* display a message based on the status code supplied as a parameter */
BEGIN
IF status_code_in = 'O'
THEN
DBMS_OUTPUT.PUT_LINE('Status is Open');
ELSE
DBMS_OUTPUT.PUT_LINE('Status is closed');
END;

FUNCTION total_compensation
(salary_in IN NUMBER, commission_in IN NUMBER) RETURN NUMBER
/* Calculate return total compensation, add 0 to salary if the commission is null */
IS
BEGIN
RETURN salary_in + nvl(commission_in, 0);
END;

With Packages decide which code is publicly available to programmers and which code needs to be hidden, can implement
global variables, data structures and values, which persist for the entire duration of the user session.

1.4.3.11 Stored Procedures, functions and packages:

Oracle database repository not only for the data, but also the program code. Shared Global Area (SGA), caches compiled
PL/SQL programs and supplies those objects to the PLSQL runtime engine when needed by the oracle session. Stored
packages offer improved performance.

1.4.4 PL/SQL Release 2.1

1.4.4.1 Stored functions in SQL

Can call stored functions (written in PLSQL) from with in a SQL statement. Can call stored functions any where an expression
is allowed, eg: SELECT, WHERE, START WITH, GROUP BY, HAVING, ORDER BY, SET and VALUES. Just use one of
your own functions as you would a built in SQL function, such as TO_DATE, SUBSTR or LENGTH.

Stored procedures are in and of themselves PL/SQL statements; they cannot be embedded in a SQL statement.

select last_name, total_compensation(salary, commission)


FROM employee
ORDER BY total_compensation(salary, commission) DESC;

1.4.4.2 Support for DDL and dynamic SQL

DBMS_SQL package: allows you to execute dynamic SQL DDL and DML statements. SQL statement is dynamic when it's
not parsed and bound at compile time. Instead the statement itself is passed through runtime and then is passed to the SQL
engine for processing.

PROCEDURE drop_object
(object_type_in IN VARCHAR2, object_name_in IN VARCHAR2)
IS
cursor_id INTEGER;
BEGIN
/*
Open a cursor which will handle the dynamic SQL statement
The function returns the pointer to that cursor.
*/
cursor_id := DBMS_SQL.OPEN_CURSOR;

/*
Parse and execute the drop command which is formed through concatenation of the arguments.
*/
DBMS_SQL.parse(cursor_id, 'DROP ' || object_type_in || ' ' || object_name_in, DBMS_SQL.NATIVE);
/*
Close the Cursor
*/
DBMS_SQL.close_cursor(cursor_id);
EXCEPTION
WHEN OTHERS
THEN
DBMS_SQL.CLOSE_CURSOR(cursor_id);
END;

eg: drop_object('table', 'employee');


drop_object('function', 'myFunc');

1.4.4.3 Entry Level ANSI SQL92 Support

DECLARE
CURSOR profit_cur IS
SELECT company_id, SUM(revenue) - SUM(cost) net_profit
FROM fin_performance
ORDER BY net_profit DESC;
BEGIN ...

In the past we would have repeated the difference between the Sums or have user ORDER BY 2 DESC.

1.4.4.4: Programmer defined sub-types:

create your own subtypes of native datatypes. improve maintainability and readability of code.

SUBTYPE primary_key_type IS NATURAL

now a variable of primary_key_type should be a non zero, positive integer.

*****************************************************************************************************
**************
27/03/08
*****************************************************************************************************
**************
1.4.5 PL/SQL Release 2.2:

1.4.5.1: The PL/SQL Wrapper:

PL/SQL Wrapper: Standalone utility that transforms PL/SQL code to portable binary/object code.

1.4.5.2: Cursor Variables:

Before, we could only declare and manipulate static cursors, cursors that are bound at design time to a specific query and a
specific cursor name. now we can declare a cursor variable and open it for any compatible query. Can receive and pass cursor
variables as arguments to modules.

1.4.5.3: Job scheduling with DBMS_JOB: allows to schedule jobs with-in the db. oracle uses DBMS_JOB to manage it's
snapshot facility. job can be any valid PLSQL code.

1.4.6: PLSQL Release 2.3:

1.4.6.1: File IO with UTL_FILE package:

allows to read and write to operating system files from within PL/SQL.
1.4.6.2: Cursor variables for all PLSQL environments:

open, fetch from and close cursors using standard PL/SQL syntax.
Weak cursor type: allows you to declare a cursor without specifying it's record structure.

1.4.6.3: Expanded PL/SQL table capabilities:

offers a set of operators or built-ins, which provide additional information about the PL/SQL table:
COUNT: returns the number of rows defined in the PL/SQL table.
LAST: returns the number of the highest row defined in the table.
DELETE: deletes the row from the table.

1.4.6.4: Improved remote dependency model:

1.4.7: PL/SQL Version 8.0:


1.4.7.1: Support for an object oriented model.
*****************************************************************************************************
*******************
28/03/08
*****************************************************************************************************
*******************

1.4.7.2: Oracle AQ/ Advanced Queuing facility


implements deferred execution of work. foundation technology for workflow management systems.
available in PLSQL through the DBMS_AQ built-in package.

1.4.7.3: Variable arrays and nested tables:


Collection structures. nested tables and variable sized arrays [VARRAYS].
can store a table with-in a table using a collection.

1.4.7.4: Object Views:


Object views and conventional views can have their own triggers. "INSTEAD_OF" triggers allow us to write PL/SQL code to
support insert/ delete thru any view.

1.4.7.5: External Procedures:

1.4.7.6: Large Object Support:

Support several variations of LOB or Large Object datatypes. LOB can store upto 4GB of raw data, binary data (images) or
character text data.

BFILE: declare variables which hold a file locator pointing to large binary object in an operating system file outside the
database.
BLOB: declare vars which hold a LOB locator pointing to a large binary object.'
CLOB: declare vars which hold a LOB locator pointing to a large block of single-byte, fixed width character data.
NCLOB: declares vars which hold a LOB locator pointing to a large block of single-byte or fixed width multibyte character
data.

2 types of LOBs: internal and external. Internal LOBs(BLOBs, CLOBs and NLOBs) are stored in the database and can
participate in transactions. external LOBs (BFILEs) are large binary data stored outside the database table spaces. External
lobs cannot participate in transactions. cannot commit or rollback changes to a BFILE. rely on underlying filesystem for data
integrity.

1.5 Advice for oracle programmers:


*****************************************************************************************************
*******************
30/03/08
*****************************************************************************************************
*******************
1.5.1: Take a creative, even radical approach.
1.5.2: Get ready to establish new habits.
1.5.3: Assume PL/SQL has what you need:
**use DBMS_PIPE to communicate information between different oracle sessions.
1.5.4:Share your ideas.

1.6: Few of PL/SQL features:

1.6.1: Anchored declarations:


use %TYPE and %ROWTYPE declaration attributes to anchor the datatype of one variable to that of a previously existing
data structure or variable. anchoring data structure can be a column in a database table, the entire table itself, a programmer
defined record or a local PLSQL variable.
ex:
my_company company.name%TYPE;

1.6.2: Built-in functions.


LENGTH: returns length of a string.
SUBSTR: returns a sub-portion of a string. By passing a negative starting location, SUBSTR will count from the end of a
string.
INSTR: returns position in a string, where a substring is found. INSTR will actually scan in reverse through the string for the
nth occurance of the substring.

1.6.3: Built-In Packages: eg: TO_CHAR, ROUND etc.

1.6.4: The CURSOR for loop:

1.6.5: Scoping with Nested blocks:

have placed BEGIN and END keywords around a sequence of DELETE statements. This way if any DELETE statement fails,
we trap the exception, ignore the problem and move on to the next delete.

PROCEDURE delete_details
IS
BEGIN
BEGIN
DELETE from child1 WHERE ...;
EXCEPTION
WHEN OTHERS THEN NULL;
END;

BEGIN
DELETE FROM child2 WHERE ...;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END;

1.6.6: Module overloading:

With-in a package and with-in the declaration section of a PL/SQL block, you can define more than one module with the same
name.

PACKAGE BODY check


IS

/* First version takes a DATE parameter */


FUNCTION value_ok(date_in IN DATE) RETURN BOOLEAN
IS
BEGIN
RETURN date_in <= SYSDATE;
END;

/* Second version takes a NUMBER Parameter */

FUNCTION value_ok(number_in IN NUMBER) RETURN BOOLEAN


IS
BEGIN
RETURN NUMBER > 0;
END;

1.6.7 Local Modules:

local module is a procedure defined in the declaration of a PL/SQL block (anonymous/unnamed). this module is considered
local because it is only defined with-in the parent PL/SQL block. it cannot be called by any other PL/SQL blocks defined
outside of that enclosing block.

1.6.8: Packages:

1.7: Best practices for PL/SQL excellence:

1.7.1: Write as little code as possible.


Some Specs:
1. Use the cursor FOR loop. whenever you read through every record fetched by a cursor, the cursor FOR loop will save lots
of typing over the manual approach of explicitly opening, fetching from and closing the cursor.
2. Work with records. whenever you fetch data from a cursor, you should fetch into a record declared against that cursor with a
%ROWTYPE attribute. if declaring multiple variables which are related, declare your own record TYPE.
3. Use local modules to avoid redundancy and improve readability. create function to perform any repeated calculations.

1.7.2: Synchronize Program and Data Structures:

Write code so that it will adapt to changes in underlying data structures and relationships:

1. Anchor declarations of variables back to database columns and tables they represent. use %TYPE or %ROWTYPE. if those
database elements change, compiled code is discarded. when re-compiled, changes are automatically applied to the code.

2. Always fetch from an explicit cursor into a record declared with %ROWTYPE, as opposed to individual variables.

3. Encapsulate access to your data structures with in packages. all SQL statements should be hidden behind a package
interface. simply call the appropriate package procedure or function or open appropriate package cursor.

4. learn about package generators.

1.7.3: Center all development around packages:

some best practices for working with packages:

1. don't declare data in package specification. hide it in package body. build get and set programs to retrieve data and change
it.
2. build toggles into your packages, such as "local" debug mechanisms, which you can turn easily on and off. this way, a user
of your package can modify the behavior of programs inside the package with out having to change her code.
3. avoid writing repetitive code inside your package bodies.
4. spend as much time as possible in package specifications.
5. be prepared to work in and enhance multiple packages simultaeneously.
6. always keep package specifications in seperate files from the package bodies.
7. compile all of the package specification for your application before any of your bodies.

1.7.4: Standardize PLSQL development emvironment:

1.7.5: Structured code and other best practices:

1. never exit from a FOR/WHILE loop with an EXIT or RETURN statement.


2. ensure that a function has a single successful RETURN as the last line of the executable section. normally, each exception
handler in a function would also return a value.
3. don't let functions have OUT, IN OUT parameters, the function should only return value through the RETURN clause.
4. make sure that the name of the function depicts the value being returned and the name of the procedure depicts the action
being performed.
5. never declare the FOR loop index (either an integer or a record). this is done implicitly by the PLSQL runtime engine.
6. do not use exceptions to perform branching logic. exceptions should describe error situations only.
7. when using the ELSIF statement, make sure that the conditions are mutually exclusive.
8. remove all hardcoded values from programs. replace them with named constants or functions defined in packages.
9. do not use "SELECT COUNT(*)" from a table unless you need to know about the total number of hits. if we only need to
know if there's more than one match, simply fetch twice with an explicit cursor.
10. do not use names of tables or columns for variable names.this can cause compile errors.can cause un-predictable behavior.

==========================================================================================
========================================
CHAPTER 2: PL/SQL Language Fundamentals
==========================================================================================
========================================
*****************************************************************************************************
*****************************
31/03/08
*****************************************************************************************************
*****************************
2.1 The PLSQL character set:

Letters: A-Z, a-z


digits: 0-9
symbols: ~!@#$%&*()-_+=:;<>.,?/
Whitespace: tab, space, carriage return

PLSQL is case-insensntive.

; statement terminator
% attribute indicator (cursor attributes like %ISOPEN and indirect declaration attributes like %ROWTYPE). Also used as a
multibyte wildcard symbol, as in SQL.
_ single underscore: single byte wildcard symbol, as in SQL.
: host variable indicator, such as :block.item in Oracle Forms
** exponentiation operator
<> and != not equals
|| concatenation operator
<< and >> label delimiters
<= and >= relational operators
:= assignment operator
=> association operator for named notation
-- single line comment indicator
/**/ double line comment indicator

2.2 Identifiers:

Properties of an identifier:
upto 30 characters in length.
must start with a letter
can include $ _ and # sign
cannot contain spaces

2.2.1 Reserved words:

PLSQL has reserved words. identifier is a reserved word if it has a special meaning in PLSQL, should not/cannot be re-
defined by programmers for own uses.

One reserved word is END. compile error generated when trying to declare a variable named END.

2.2.2 White spaces and keywords:

2.3 Literals:

is a value which is not represented by an identifier. simply a value.

NUMBER: 415, 21.6, NULL

STRING: 'Rahul', '31-JAN-2008' or NULL

BOOLEAN: TRUE, FALSE or NULL

*****************************************************************************************************
***************************
02/APR/2008
*****************************************************************************************************
***************************

No way to indicate a true date literal. '31-JAN-08' is a string literal. PL/SQL and SQL convert such a string automatically into
a date, by calling TO_DATE function. Date only has an internal representation.

String literal can be composed of 0 or more characters from the PL/SQL character set. '' empty string literal and is a NULL
string. This literal has a datatype of CHAR (Fixed length string).

PLSQL is case sensitive with in string literals.

2.3.1 Embedding single quotes inside a string:

Inside a literal two single quotes = 1 single quote.

'NLS_LANG = ''ENGLISH''' = NLS_LANG = 'ENGLISH'\

'''''' = ''

double quote character doesn't have any significance inside a string literal.

2.3.2: Numeric Literal:

Can be integers or real numbers(a number that contains a fractional component). PLSQL considers 154.00 to be a real number,
even though the fractional component is 0 and the number is actually an integer. 3.05E19 or 12e-5 are valid.

2.3.3 Boolean literal:

TRUE and FALSE. Never place single quotes around boolean literals.
2.4: Semicolon Delimiter:

2.5: Comments:

2.5.1: Single Line Comment Syntax


--
2.5.2: Multiline comment syntax:
/**/

2.6: The PRAGMA keyword:

The PRAGMA keyword is used to signify that the remainder of the PLSQL statement is a pragma, or directive, to the
compiler. Pragmas are processed at compile time; they do not execute at runtime.

A pragma is a special instruction to the compiler. Also called a pseudo-instruction, a pragma doesn't change the meaning of a
program. It simply passes information to the compiler. very similar to the tuning hints we can embed in a SQL statement
inside block comments.

PLSQL offers the following Pragmas:

EXCEPTION_INIT: tells the compiler to associate a particular error number with an identifier we have declared as an
exception in the program.

RESTRICT_REFERENCES: Tells the compiler the purity level (freedom from side effects) of a packaged program.

SERIALLY_REUSABLE: Tells the PLSQL runtime engine that package-level data should not persist between references to
that data.

AUTONOMOUS_TRANSACTION: Tells the PLSQL runtime engine that the transaction is independent of the parent
transaction.

Syntax: PRAGMA <instruction>

example:

DECLARE
no_such_sequence EXCEPTION;
PRAGMA EXCEPTION_INIT (no_such_sequence, -2289);
BEGIN ...
END;

2.7: Block Structure:

core concepts:

Modularization: The PL/SQL block is the basic unit of work from which modules such as procedures and functions are
built.ability to modularize is important.
Scope: The block provides a scope or context for logically related objects. In the block, we can group together declarations of
variables and executable statements that belong together.
can create anonymous blocks and named blocks which are functions and procedures. packages group together multiple
procedures and functions.

2.7.1: Sections of PL/SQL block:

Each PL/SQL block has upto 4 sections:

Header:
Relevant for named blocks only. Header determines the way a named block or program must be called.
Declaration section:
The part of block that declares variables, cursors and sub-blocks that are referenced in the execution and exception sections.

Execution Section:
The part of the PL/SQL block containing the executable statements, the code is executed by the PLSQL runtime engine.

Exception Section:
The section that handles exceptions to normal processing (warnings and errors).

Block Header
IS
Declaration Section
BEGIN
Execution Section
EXCEPTION
Exception Section
END;

2.7.2: Scope of a block:

In the declaration section of a block, can define variables, modules and other structures. These declarations are local to that
block. When the execution of that block finishes, these structures no longer exist. if a cursor is opened in a block, that cursor is
automatically closed at the end of the block.

The block is the scope of any objects declared in that block. The block also provides scope for exceptions that are declared and
raised. one reason to create a block is to take advantage of the exception section that comes with that block. gives us finer
control over exception handling.

2.7.3: Nested blocks:

PROCEDURE calc_totals
IS
year_total NUMBER;
BEGIN
year_total := 0;
/* Nested anonymous block */
DECLARE
month_total NUMBER;
BEGIN
month_total := year_total/12;
END;
END;

any element declared in the outer block is global to all the blocks nested within it.

==========================================================================================
=====================================
Chapter 3: Effective Coding Style
==========================================================================================
=====================================

3.1: Fundamentals of effective layout:


3.2: Formatting SQL statements:
We can place blank lines inside a SQL statement when we are coding that SQL from with-in a PLSQL block. We may not
embed white spaces in SQL statements when executing from the SQL*Plus command line.
3.3: Formatting Control:
3.4: Formatting PLSQL blocks:
FUNCTION company_name(company_id_in IN company.company_id%TYPE)
RETURN VARCHAR2
IS
c_name company.company_name%TYPE;
BEGIN
SELECT name INTO c_name FROM company
WHERE company_id = company_id_in;
RETURN c_name;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN NULL;
WHEN OTHERS
THEN
RETURN NULL;
END;

3.5: Formatting Packages:


Package has both specification and body. The package specification contains the declarations or definitions of all those objects
that are visible outside of the package -- the public objects. This means that the objects can be accessed by any account that
has been granted EXECUTE authority on that package.

PACKAGE rg_select
IS
list_name VARCHAR2(60);

PROCEDURE init_list
(item_name_in IN VARCHAR2,
fill_action_in IN VARCHAR2 := 'IMMEDIATE');
PROCEDURE delete_list;
PROCEDURE clear_list;

END rg_select;

3.6: Using comments effectively:

3.7: Documenting the entire package:

Package Specification:

PACKAGE package_name
/*
|| Author:
||
|| Overview:
||
|| Major Modifications:
||
*/
IS

...

END package_name;

Package Body:
PACKAGE BODY package_name
IS
/*-------------------- Package Variables ------------------------*/
... declarations ...

/*-------------------- Private modules --------------------------*/

FUNCTION ...
PROCEDURE ...

/*-------------------- Public Modules ---------------------------*/

FUNCTION ...
PROCEDURE ...

END package_name;

++++++++++++++++++++++++++++++++++++++++++++++ END OF PART 1 ++++++++++++++++++++++++++++++


++++++++++++++

==========================================================================================
===============
Chapter 4: Variables and Program Data
==========================================================================================
===============

Attributes of a variable: name, datatype, value(s)

4.1: Identifiers:

Upto 30 chars in length; Must start with a letter; can have $, # or _

named constant: special kind of variable. Named constant has a name, datatype and value, the value of a named constant must
be set when declaring the named constant and may not change thereafter.

4.1.1: Choosing the right name:

4.1.2: Select Readable names:

4.2: Scalar Data types:

Scalar datatype is atomic, not made up of other variable components.


Composite datatype has internal structure or components. 2 composite types currently supported by PL/SQL are the record and
table.

Scalar datatypes fall into one of the four categories: number, character, boolean and date-time.

4.2.1: Numeric Datatypes:

4.2.1.1: Binary Integer Datatypes:


BINARY_INTEGER, INT, INTEGER, NATURAL, SMALL_INT, POSITIVE

BINARY_INTEGER: allows to store signed integers. range: -2pow31 + 1 to 2pow31 -1. Are represented in the Pl/SQL
compiler as signed binary numbers and do not need to be converted before PL/SQL performs numeric calculations. Variables
of type NUMBER do need to be converted.
If performing intensive calculations with integer values, performance improvement can be achieved by declaring variables as
BINARY_INTEGER.
NATURAL and POSITIVE are subtypes of BINARY_INTEGER.

* A subtype uses the storage format and restrictions on how the variable of this type can be used, but it allows only a subset of
the values allowed by the full datatype.

NATURAL: 0 to 2pow31
POSITIVE: 1 to 2pow31

4.2.1.2: Decimal Numeric Datatypes:

DEC, DECIMAL, DOUBLE_PRECISION, FLOAT, NUMBER, NUMERIC, REAL

NUMBER datatype used to store fixed or floating point type. maximum precision is 38 digits. When declaring var type
NUMBER, can optionally specify variable's precision and scale.

NUMBER (precision, scale): precision of a number is total number of digits. scale dictates the number of digits to the right or
left of the decimal point at which rounding occurs. Both precision and scale values must be literals (and integers). Cannot use
constants or variables. Legal values of scale range from -84 to 127.

*****************************************************************************************************
**********************************
07/APR/2008
*****************************************************************************************************
**********************************

If scale is positive, then the scale determines the point at which rounding occurs to the right of the decimal point.

If the scale is negative, the scale determines the point at which rounding occurs to the left of the decimal point.

Is scale is zero, then rounding occurs to the nearest whole number.

No scale: no rounding.

Examples:

bean_counter NUMBER (10, 3);


can hold upto 10 digits, 3 of which are on the right of the decimal point.
12345.6784 rounded to 12345.678

big_whole_number NUMBER;
contains the full range of supported values, as precision: 38 and scale: 0.

rounded_million NUMBER (10, -6);


-ve scale, causes to the left of the decimal point.
Scale || Rounding
-1 nearest 10
-2 nearest 100
...
-6 nearest million

assign 56.45 to rounded million, will be rounded to 0.


assign 1,567,988 : rounded to 2,000,000

small_value NUMBER (3, 5);


scale larger than precision. Here precision means max number of digits allowed to the right of the decimal point.
assign .003566 to small_int, will be rounded off to .00357. since scale is 2 greater than precision, any value assigned to
small_value must have 2 0s directly to the right of the decimal point, followed by three non-zero digits.
4.2.1.3: PLS_INTEGER Datatype:

Vars declared as PLS_INTEGER store signed integers. magnitude range of this data-type is -2147483647 to 2147483647. Use
PLS_INTEGER for all integer calculations which do not fall outside it's range. PLS_INTEGER require less storage space than
NUMBER values. operations on PLS_INTEGER use machine arithmetic, makes them more efficient.

Vars declared as PLS_INTEGER and BINARY_NUMBER have the same range, but are treated differently. When a
calculation involving PLS_INTEGER overflows, PLSQL raises an exception. overflow involving BINARY_INTEGER will
not raise an exception, if the result is being assigned to a NUMBER variable.

4.2.2: Numeric Subtypes:


Remiander of datatypes are all subtypes of NUMBER. They are provided in oracle's SQL and PLSQL to provide compatibility
with ANSI SQL, SQL/DS and DB2 datatypes. have the same range of legal values as their base type. The NUMERIC,
DECIMAL and DEC datatypes declare only fixed point numbers. FLOAT, DOUBLE PRECISION and REAL allow floating
decimal points with binary precisions that range from 63 to 126.

Subtype Compatibility Corres Oracle Datatype

DEC(prec, scale) ANSI NUMBER(prec, scale)


DECIMAL(prec, scale) IBM NUMBER(prec, scale)
DOUBLE PRECISION ANSI NUMBER
FLOAT(binary) ANSI, IBM NUMBER
INT ANSI NUMBER(38)
INTEGER ANSI, IBM NUMBER(38)
NUMERIC(prec, scale) ANSI NUMBER(prec, scale)
REAL ANSI NUMBER
SMALLINT ANSI, IBM NUMBER(38)

4.2.3: Character Datatypes:

Store text and are manipulated by character functions. Character strings are free-form.

4.2.3.1: The CHAR datatype:

specifies that a character has fixed length. if you do not specify length of a string, PLSQL declares a string of 1 byte. This is
opposite to the situation of the NUMBER datatype.

fit_almost_anything NUMBER;
results in a numeric variable of upto 38 digits in precision.don't declare all vars a NUMBER, even if range of legal values is
much less than default.

line_of_text CHAR;
results in a character variable of only 1 char. if we assign a string of more than one character to the above variable, PLSQL
will raise an exception. always specify length when using CHAR datatype.

examples: yes_or_no CHAR(1) DEFAULT 'Y';


line_of_text CHAR(80);
whole_paragraph CHAR(10000);

even if we declare a CHAR var of 10000 chars, cannot stuff var's value into Oracle db with column type CHAR, as it's
capacity only 255 chars. so if var value's length > 255, use the SUBSTR function.

4.2.3.2: The VARCHAR and VARCHAR2 datatypes:

store variable length character strings. must specify max length for string when declaring a variable length string. range 1 to
32767 bytes.

<variable_name> VARCHAR2 (<max_length>);


ex: DECLARE
small_string VARCHAR2(4);
line_of_text(2000);

maximum length allowed for PLSQL VARCHAR2 variables is much higher than that of VARCHAR2 datatype in the Oracle
RDBMS, which is only 2000. before storing PLSQL VARCHAR2 value into a VARCHAR2 db column, remember that only
the first 2000 can be inserted [use SUBSTR]. because the length of a LONG column is 2 GB, can insert PLSQL VARCHAR2
values into a LONG db column without any worries of overflow.

VARCHAR: subtype of VARCHAR2. aviod the usage of VARCHAR and CHAR.

Comparing VARCHAR2 and CHAR datatypes is a bit tricky:

Ex:

DECLARE
company_name CHAR(30) DEFAULT 'PC HEAVEN';
parent_company_name VARCHAR2(25) DEFAULT 'PC HEAVEN';
BEGIN
IF company_name = parent_company_name
THEN
-- This code will never be executed.
END IF;
END;

The conditional test will never return TRUE since the value company_name has been padded to length of 30 with 21 spaces.
Use RTRIM to CHAR values when involved in comparison or database modification.

4.2.3.3: The LONG datatype:

Variable declared long can store 32760 bytes of data. The LONG datatype for PL/SQL variables is different from the LONG
datatype for columns in the oracle server [can store upto 2 GB data]. this makes the LONG column possible repository for
graphic images etc.

can always insert a PLSQL LONG var into a LONG db column, but cannot store data in LONG db column of size more than
32760 bytes into a LONG PLSQL column.

In oracle DB, there are many restrictions on how to use LONG column in SQL:

1. Table may not contain more than 1 single LONG column.


2. may not use LONG column in a GROUP BY, ORDER BY, WHERE or CONNECT BY clause.
3. may not apply character funcs like SUBSTR, INSTR or LENGTH on the LONG column.

PLSQL LONG vars are free of these restrictions.

4.2.3.4: The RAW Datatype:

used to store binary data or other kinds of data such as digitized picture or image. RAW var has the same length as the
VARCHAR2 (32767 bytes), which must also be specified when the datatype is declared. difference between RAW and
VARCHAR2: PLSQL will not try to interpret RAW data. In Oracle RDBMS, oracle will not perform character set conversions
on RAW data when it's moved from one system to another system.

PLSQL RAW Size: 32767, Oracle RDBMS RAW Size: 255. Cannot insert full size PLSQL RAW data into Oracle RDBMS
RAW, but can insert into Oracle RDBMS LONG RAW [2 GB storage].

4.2.3.5: The LONG RAW Datatype: stores upto 32760 bytes. just like LONG datatype, PLSQL does not interpret LONG
RAW.
4.2.3.6: The ROWID datatype:

In Oracle RDBMS, ROWID is a pseudocolumn that is a part of every table we create. The rowid is an internally generated and
maintained binary value which ids a row of data in a table. It is called a pseudo column since SQL uses in places where we use
columns. RDBMS generates the row id for each row, as it is inserted into the database. The information in the rowid provides
the exact physical location of the row in the database. we cannot change the value of a rowid.

use the ROWID dtype to store rowids from the database. use dbms_rowid package to manipulate ROWIDS.
access by ROWID is typically the fastest way to locate or retrieve a particular row from the database, faster even than a search
by primary key.

ex:

PROCEDURE remove_internal_competitors IS
BEGIN
FOR emp_rec IN
(SELECT connections, rowid
FROM employee
WHERE salary > 50000)
LOOP
IF emp_rec.connections IN ('President', 'CEO')
THEN
send_holiday_greetings;
ELSE
DELETE FROM employee
WHERE rowid = emp_rec.rowid;
END IF;
END LOOP;
END;

ROWID is oracle rdbms specific. for multi-platform compatible applications, ROWID is not advisable.

4.2.4: Boolean Datatype:

TRUE, FALSE, NULL. Boolean is a Logical datatype. Oracle RDBMS does not support Boolean Datatype. create a table with
column with dtype of CHAR(1) and put values of 'Y' or 'N'.

4.2.5: The Date-Time Datatype:

RDBMS provides a true DATE datatype which stores both date and time information. can enter a data in many formats,
RDBMS stores dates in a standard internal format. It is a fixed length value which uses seven bytes. cannot actually specify
this internal or literal value with an assignment. Instead, rely on implicit conversion of character and numeric values to an
actual date or explicit conversion with the TO_DATE function. PLSQL provides a DATE datatype which corresponds directly
with the RDBMS DATE.

Oracle DATE stores the following information:


century, year, month, day, hour, minute, second

PLSQL validates and stores dates between January 1, 4712 BC to December 31, 4712 AD. Time component is stored as the
number of seconds past midnight. enter a data without a time, the time portion of the database value defaults to
midnight(12:00:00 AM).

Neither Oracle RDBMS DATE or the PLSQL Date datatypes stores times in increments of less than single seconds. not very
useful to track real-time activities which happen in sub-second intervals. here, we can store this info in NUMBER datatype.
Can get sub-second timings using the DBMS_UTILITY package's GET_TIME function.

can perform arithmetic on date variables, such as subtraction of one date from other or addition/subtraction of numbers from a
date. can make use of date functions. SYSDATE: returns current time and date. TO_CHAR: To convert a date to a character
string or to a number.

Julian Date: number of days since the first valid date, January 1, 4712 BC. Use Julian Dates if you need to perform
calculations or display date information with a single point of reference and continuous dating.

4.2.6: NLS Character Datatypes:

NLS: National Language Support.

*****************************************************************************************************
*************************************
08/APR/08
*****************************************************************************************************
*************************************
8-bit ASCII character set is simply not able to represent all the available characters in langs like Japanese etc. Such languages
require 16 bits (2 bytes) to represent each character. NLS features allow to convert between character sets.

4.2.6.1: NCHAR:

store fixed length NLS character data. need to specify length when declaring NCHAR. default of 1 is used.
max length is 32767

length context:
if national character is a fixed-width character set, then length indicates number of characters. If variable width character set,
length is length in bytes.

ex: ssn NCHAR (10);


yes_no NCHAR;

4.2.6.2: NVARCHAR2:

store variable length NLS character data. specify length when declaring NVARCHAR2. max length of NVARCHAR2 is
32767.

ex: any_name NVARCHAR2 (200);

4.2.7: LOB Datatypes:

Large Object Datatypes. LOB can store upto 4GB of raw data, binary data (Images...), or character text data.

Two types of LOBs in Oracle: Internal and External


Internal LOBS: BLOB, CLOB and NCLOB are stored in the database and can participate in a transaction in the db server.
External LOBS: BFILE are large binary data stored in operating system files outside the db tablespaces. cannot participate in
db transactions. cannot commit or rollback changes to a BFILE. rely on underlying filesystem for data integrity.

DTypes:

4.2.7.1: BFILE: Store large binary objects upto 4 GB in files outside the db. var gives readonly, byte-stream IO access to the
files.
When declaring a BFILE var, we allocate memory to store the file locator of the BFILE, not the BFILE contents itself. file
locator contains directory alias and file name.

ex:

DECLARE
book_one BFILE;

4.2.7.2: BLOB: store large binary objects "out of line" inside the db. when a table has a BLOB column, a row of data for that
table contains a pointer or locator to the actual location of the BLOB data. Hence it is not "in line" with other column values
on that row.

BLOB var contains locator, locator points to large binary object. BLOB upto 4GB in size, participate fully in transactions. any
changes made to BLOB can be committed or rolled back along with other outstanding changes in the transaction. BLOB
locators cannot span transactions or sessions.

ex:
DECLARE
my_foto BLOB;

4.2.7.3: CLOB: store large blocks of single byte character data "out of line" inside the db. when a table has a CLOB column, a
row of data for that table contains a pointer or locator to the actual location of the CLOB data. Hence it is not "in line" with
other column values on that row.

CLOB var contains locator, locator points to single byte character data. CLOB upto 4GB in size, participate fully in
transactions. any changes made to CLOB can be committed or rolled back along with other outstanding changes in the
transaction. CLOB locators cannot span transactions or sessions.

ex:
DECLARE
famous_five_novel CLOB;

NCLOB: store large blocks of single byte or fixed width multi-byte character data "out of line" inside the db. when a table has
a NCLOB column, a row of data for that table contains a pointer or locator to the actual location of the NCLOB data. Hence it
is not "in line" with other column values on that row.

NCLOB var contains locator, locator points to single byte/fixed width multi-byte character data. NCLOB upto 4GB in size,
participate fully in transactions. any changes made to NCLOB can be committed or rolled back along with other outstanding
changes in the transaction. NCLOB locators cannot span transactions or sessions.

variable width character sets are not supported in NCLOBs.

ex:
DECLARE
fam_five japanese NCLOB;

4.2.7.5: LOBs and LONGs:

LOBs are different and preferrable to LONGs.


LONG: 2 GB ; LOB: 4 GB.
DBMS_LOB package can be used to manipulate LOBs in many ways. Oracle offers Random Access to LOB objects, but only
sequential access to LONG objects.
Can perform SUBSTR and INSTR operations against a LOB. not possible with LONG data.
LOB can be an attribute of an object type. LONG cannot.

4.2.7.6: Working with LOBS:

CREATE TABLE favorite_books


(isbn VARCHAR2(50), title VARCHAR2(100), contents_loc CLOB);

CREATE OR REPLACE PROCEDURE how_big (title_in IN VARCHAR2)


IS
CURSOR book_cur
IS
SELECT contents_loc
FROM favorite_books
WHERE title = UPPER(title_in);
book_loc CLOB;
BEGIN
OPEN book_cur;
FETCH book_cur INTO book_loc;
IF book_cur%NOTFOUND
THEN
DBMS_OUTPUT.PUT_LINE('Remember you don''t like "' || INITCAP(title_in) || '".');
ELSE
DBMS_OUTPUT.PUT_LINE(title_in || ' contains ' || TO_CHAR(DBMS_LOB.GET_LENGTH(book_loc)) || '
characters.');
END IF;
CLOSE book_cur;
END;
/

INSERT INTO favorite_books (isbn, title, contents_loc)


SELECT isbn, title || ', Second Edition', contents_loc
FROM favorite_books
WHERE title = 'Oracle PL/SQL Programming';

assigned a new LOB locator for the second edition. copied the LOB value to this new row, not merely created another locator
or pointer back to the same text. DML operations such as INSERT or UPDATE always affect an entire LOB. To change or
delete a portion of a LOB, call appropriate functions in the DBMS_LOB package.

cannot copy values between a character LOB and a VARCHAR2 var. use DBMS_LOB to extract some or all of the CLOB
value and place it in a VARCHAR2 variable.

DECLARE
big_kahuna CLOB;
little_kahuna VARCHAR2(2000);
BEGIN
SELECT contents_loc
INTO big_kahuna
WHERE title = 'WAR AND PEACE';
little_kahuna := DBMS_LOB.SUBSTR(big_kahuna, 2000, 1);
END;

4.2.2.7: Working with BFILEs:

When working with BFILEs in PL/SQL, we work with a LOB locator. Here, the locator simply points to the file stored on the
server. Two different rows in a db table can have a BFILE column which point to the same file.

A BFILE locator is composed of a directory alias and a filename. use BFILENAME function to return a locator based on those
two pieces of info.

DECLARE
all_of_us BFILE;
BEGIN
all_of_us := BFILENAME('photos', 'family.jpg');
END;

Here 'photos' is a db object called DIRECTORY.

CREATE DIRECTORY photos AS 'c:\photos';

need the CREATE DIRECTORY or CREATE ANY DIRECTORY privs to create a directory. to be able to reference this
directory, must be granted the READ priv.
GRANT READ ON DIRECTORY photos TO SCOTT;

max number of BFILEs that can be opened in a session is established by the db initialization parameter
SESSION_MAX_OPEN_FILES. This parameter defines an upper limit on the number of files opened simultaeneously in a
session. Not just BFILEs but all kinds of files, including those opened using the UTL_FILE package.

4.2.8: Conversion between dtypes:


4.2.8.1: Explicit Data conversions: take place when we use a built-in conversion function to force conversion of a value from
one dtype to another.
4.2.8.2: Implicit data conversions: when PLSQL detects that a conversion is necessary, it will attempt to change values as
necessary to perform the operation.
4.2.8.3: Drawbacks of implicit conversions:

as far as possible avoid implicit conversions. use explicit functions for better understanding.

4.3 NULLs in PL/SQL:

Rules:

1. A NULL is never equal to anything else.

my_string := ' ';


IF my_string = NULL ... -- This will never be true.

max_salary := 0;
IF max_salary = NULL ... -- This will never be true.

IF NULL = NULL ... -- Never True

2. A NULL is never not equal to anything.

my_string := 'Fun'
y_string := NULL;

if my_string != y_string THEN ... -- Never True.

3. When we apply a NULL value to a function, we generally receive a NULL value in return.
A NULL value cannot be found with INSTR function in a string.

my_string := NULL
IF LENGTH(my_string) = 0 THEN ... -- Will not work.

new_value := POWER(NULL, 10); -- New value is set to NULL.

4.3.1: NULL values in comparisons:


When PL/SQL executes a program, it initializes all locally declared variables to NULL. can override this with default value.
make sure that var has been assigned to a value before use in an operation.

perform special case checking with IS NULL and IS NOT NULL operators.

IS NULL : true when a var is NULL else FALSE


IS NOT NULL: true when a var is not NULL else FALSE

4.3.2: Checking for NULL values:

IF hire_date >=SYSDATE OR hire_date IS NULL


THEN
DBMS_OUTPUT.PUT_LINE('Date Req, Cannot be in future');
END IF;

IF :employee.commission >= comp_plan.target_commission


THEN
send_THANK_YOU(:employee_id);
ELSIF :employee.commission < comp_plan.target_commission
THEN
send_WORK_HARDER(:employee_id);
ELSIF :employee.commission IS NULL
THEN
non_sales_BONUS(:employee_id);
END IF;

PL/SQL treats a string of 0 length as NULL.


my_string = ''; and my_string = NULL; are the same.

4.3.3: Function results with NULL arguments:

1. Concatenation: 2 ways to concatenate: CONCAT function and the concatenation operator [||]. In both cases, concatenation
ignores NULL values and simply concatenates "around" the NULL.

CONCAT('junk', NULL) ==> junk


'junk' || NULL ||' mail ' || NULL ==> junk mail

2. NVL function: used for translating a NULL value into a non NULL value. takes 2 args, if 1st arg's NULL, 2nd arg is
returned.

new_description := NVL(old_description, 'Not Applicable');

3. REPLACE Function: returns a string in which all occurrances of a specified match string are replaced with a replacement
string. If the match String is NULL, REPLACE does not try to match and replace any characters in the original string. If the
replace string is NULL, the replace removes from the original string any characters found in the match string.

4.4 Variable Declarations:

declare a var b4 referencing it. exception to this rule is for the index VARS of FOR loops. declarations should be made in the
declaration section of the anonymous block, procedure, function or package.
<variable_name> <dtype> [optional default assignment]

4.4.1: Constrained Declarations:

dtype in a declaration can be constrained or un-constrained. is constrained when we specify a number which constrains or
restricts the magnitude of the value which can be assigned to that variable. un-constrained when they are no restrictions.

itty_bitty_# NUMBER(1);
large_but_constrained_# NUMBER (20, 5);

un-constrained:

no_limits NUMBER;

4.4.2: Declaration Examples:

hire_date DATE;
enough_data BOOLEAN;
total_revenue NUMBER (15, 2);
long_para VARCHAR2 (2000);
next_date CONSTANT DATE := '15-APR-96';

4.4.3: Default values:


<var_name> <dtype> := <default_value>;
<var_name> <dtype> DEFAULT <default_value>;

default value can be literal, declared var or an expression.


term_limit NUMBER DEFAULT 3;
national_debt NUMBER DEFAULT POWER (10, 10);
order_overdue CONSTANT BOOLEAN := ship_date > ADD_MONTHS(3);

4.4.4: NOT NULL Clause:

when assigning a default value, can also specify that var must be NOT NULL.

company_name VARCHAR2 (60) NOT NULL DEFAULT 'PCS R US';

now in code company_name := NULL will cause a VALUE_ERROR exception.

company_name VARCHAR2 (60) NOT NULL; -- This will raise exception. since NOT NULL constraint will conflict with
the NULL assigned to company_name during instantiation.

4.5 Anchored Declarations:

use the %TYPE declaration attribute to anchore dtype of one variable to a datastructure, such as a PLSQL Variable or a
column in a table.

<var_name> <type attribute>%TYPE [optional default value assignment];

ex: total_sales NUMBER (20, 2);


monthly_sales total_sales%TYPE;

company_id company.company_id%TYPE;

4.5.1: Benefits of anchored declarations:


* Synchronization with database columns.
* Normalization of local variables.

4.5.2: Anchoring at compile time: make sure that all affected modules are re-compiled after data structure changes.

4.5.3: Nesting usages of the %TYPE attribute:

DECLARE
-- base variable
unlimited_revenue NUMBER;

total_rev unlimited_revenue%TYPE;

total_rev_94 total_rev%TYPE;
total_rev_95 total_rev%TYPE;
BEGIN ...

4.5.4: Anchoring to vars in other PLSQL blocks:

To anchor, the var should simply be visible in that section.

PROCEDURE calc_reven
IS
unlimited_rev NUMBER;
total_rev unlimited_rev%TYPE;
BEGIN
IF ...
THEN
DECLARE
tot_rev_94 total_rev%TYPE;
BEGIN
...
END;
END IF;
END calc_reven;

4.5.5 Anchoring to NOT NULL dtypes:

When declaring a var, can also specify the var to be NOT NULL. This NOT NULL declaration constraint is transferred to
variables declared with the %TYPE attribute.

This is not the case with db columns.

4.6: Programmer Defined SubTypes:

a subtype of a datatype is a variation that sets the same set of rules as the original dtype, but might allow only a subset of the
dtype's values.

* constrained subtype: SUBTYPE POSITIVE IS BINARY_INTEGER RANGE 1 .. 2147483647

DECLARE
val_1 POSITIVE;
BEGIN
val_1 := -10000000;

above code throws a VALUE_ERROR exception.

* un-constrained subtype: SUBTYPE FLOAT IS NUMBER

provides an alias or an alternate name for the original dtype.

4.6.1: Declaring sub-types:


declare subtype in the declaration section.
SUBTYPE <subtype_name> IS <base_type>;

PLSQL Subtypes:

Subtype category Description


PL/SQL Datatype Pre-defined PL/SQL datatype, including pre-defined subtypes, such as POSITIVE
Programmer Defined Subtype Previously created with a subtype declaration
Variable_name%TYPE The subtype is inferred from the dtype of the variable
table_name.table_col%TYPE Determined from the dtype of the column in the table
table_name%ROWTYPE Contains the same structure as the table
cursor_name%ROWTYPE Contains the same structure as the virtual table created by the cursor
PL/SQL table Contains the same structure as the PLSQL table previously declared with the TYPE
statement

4.6.2: Examples of subtype declarations:

SUBTYPE hire_date_type is DATE;


SUBTYPE soc_sec_number_type is POSITIVE;

TYPE room_tab is TABLE OF NUMBER(3) INDEX BY BINARY_INTEGER


SUBTYPE hotel_room_type IS room_tab;

SUBTYPE primary_key_number IS NUMBER;


SUBTYPE company_key_type IS primary_key_number;

SUBTYPE last_name_type IS employee.last_name%TYPE;

SUBTYPE first_name_type IS emp_rec.first_name%TYPE;

SUBTYPE three_value_logic IS VARCHAR2 IN ('YES', 'NO', 'MAYBE'); -- Invalid.

SUBTYPE prefix IS CHAR(3); -- Invalid.

4.6.3: Emulating Constrained Subtypes:

When creating subtype based on an existing variable or a db column, that supertype inherits the length from the original dtype.
This constraint takes effect when you declare variables based on the subtype, but only as a default. We can always override
that constraint.

Finally an anchored subtype does not carry over the NOT NULL constraint to the variables it defines. Nor does it transfer a
default value that was included in the original declaration of the variable or column.

4.7 Tips for creating and using Variables:

4.7.1: Establish Clear Variable Naming Conventions


4.7.2: Name Subtypes to self-document code
4.7.3: Avoid Recycling Variables
4.7.4: Use named constants to avoid hard-coding values.
4.7.5: Convert Variables to named constant
4.7.6: Remove unused variables from programs
4.7.7: Use %TYPE when a variable represents a constant
4.7.8: Use %TYPE to standardize non-database declarations
4.7.9: Use variavbles to hide complex logic

DECLARE
/* Hide Business Rules Behind Variables. */
order_overdue CONSTANT BOOLEAN
DEFAULT (shipdate < ADD_MONTHS (SYSDATE + 3) OR
order_date >= ADD_MONTHS(SYSDATE-2)) AND
order_status = 'O';
high_priority CONSTANT BOOLEAN DEFAULT cust_priority_type = 'HIGH';

BEGIN
IF order_overdue and high_priority
THEN
ship_order('EXPRESS');
ELSE
ship_order('GROUND');
END IF;
END;

==========================================================================================
========================================
CHAPTER: 05 Conditional and Sequential control.
==========================================================================================
========================================
*****************************************************************************************************
*****************************
09/APR/08
*****************************************************************************************************
*****************************

5.1 Conditional Control Statements:

5.1.1:

IF
THEN
END IF;

Ex:

IF report_requested
THEN
print_report(report_id);
END IF;

5.1.2:

IF THEN
ELSE
END IF;

Statements in ELSE executed when the IF Condition executes to FALSE or NULL.

IF caller_type = 'VIP'
THEN
generate_response('EXPRESS');
ELSE
generate_response('NORMAL');

5.1.3:

IF THEN
ELSIF THEN
ELSE
END IF;

*ELSE Clause optional.

IF new_caller AND caller_id IS NULL


THEN
confirm_caller;
ELSIF new_company AND company_id IS NULL
THEN
confirm_company;
ELSIF new_call_topic AND call_id IS NULL
THEN
confirm_call_topic;
ELSE
continue_call;
END IF;
5.1.3.2: Mutually Exclusive IF-ELSIF conditions:

Make sure that IF-ELSIF conditions are mutually exclusive. No overlapping conditions.

5.1.4: Nested IF Statements:

IF <condition1>
THEN
IF <condition2>
THEN
...
ELSE
IF <condition3>
THEN
...
ELSE
...
END IF;
...
END IF;
END IF;

If the evaluation of a condition is ver expensive [in CPU terms/memory], can defer that processing to an inner IF statement, so
that the condition is evaluated only when absolutely necessary.

Avoiding Syntax Gotchas:

Always Match-up an IF with an END IF.


Must have a space between an END and IF, ELSIF keyword does not have an E, before IF. Place semicolon only after the
END IF keyword.
*****************************************************************************************************
********************************
10/APR/2008
*****************************************************************************************************
********************************
5.2: Sequential Control Statements:

GOTO and NULL.

5.2.1: The GOTO Statement:

performs un-conditional branching to a named label.


Syntax: GOTO label_name.
label definition: <<label_name>>
When GOTO statement is encountered, control is immediately shifted to the first executable statement following the label.

Restrictions reg the GOTO statement:


*Atleast one executable statement must follow a label.
a label itself is not an executable statement, so it cannot take the place of one. The following examples show illegal uses of
labels:

IF status = 'COMPLETED'
THEN
<<all_done>> /* Illegal! */
ELSE
schedule_activity;
END IF;
DECLARE
CURSOR company_cur IS ...;
BEGIN
FOR company_rec IN company_cur
LOOP
apply_bonuses(company_rec.company_id);
<<loop_termination>> /* Illegal */
END LOOP;
END;

FUNCTION new_formula(molecule_in IN VARCHAR2) RETURN NUMBER


IS
BEGIN
...
RETURN formula_number;
<<all_done>> /* Illegal! */
END;

* Target labels and scope of GOTO:

target label should be in the same scope as the GOTO statement.


Scope Error: PLS-00375: illegal GOTO statement; this GOTO cannot branch to label.

IF Condition: Only way to enter an IF block is through an evaluation of the IF condition as TRUE. hence, below code is
erratic:

GOTO label_inside_IF;
IF status='NEW'
THEN
<<label_inside_IF>> /* Out of Scope! */
show_new;
END IF;

BEGIN Statements: only way to enter a block with in a block is through the sub block's begin statement. PLSQL insists on
orderly entrances and exits. This code produces error becuz it doesn't comply with the structure:

GOTO label_inside_block;
BEGIN
<<label_inside_block>> /* Crosses block boundary */
NULL;
END;

Scope of IF statements: GOTO should not transfer from one IF clause to another:

IF status = 'NEW'
THEN
<<new_status>>
GOTO old_status; /* Crosses IF clause Boundary! */
ELSIF status = 'OLD'
THEN
<<old_status>>
GOTO new_status; /* Crosses IF clause Boundary! */
END IF;

Don't jump into the middle of a loop: cannot jump into the middle of a loop with GOTO.

FOR month_num IN 1..12


LOOP
<<do_a_month>>
schedule_activity(month_num);
END LOOP;
GOTO do_a_month; /* Can't go back to a loop! */

Don't GOTO a local module. Also cannot issue a GOTO from a local module into main body.

DECLARE
FUNCTION local_null IS
BEGIN
<<case_statement>>
NULL;
END;
BEGIN
GOTO case_statement; /* label not visible here */
END;

Target labels and PL/SQL blocks:

Target label must be in the same part of the PLSQL block as the GOTO statement. A GOTO in the execution section may not
go to the exception section, vice versa. A GOTO in an exception handler may reference a label in the same handler.

BEGIN
/*
The label and GOTO should be in the same section.
*/
GOTO out_of_here;
EXCEPTION
WHEN OTHERS
THEN
<<out_of_here>> /* Out of Scope */
NULL;
END;

5.2: The NULL statement:

Syntax: NULL;
NULL statement does nothing except pass control to the next executable statement.

Reasons for using a NULL statement:

Improve readability of the program:

IF status = 'GOOD'
THEN
process_order;
ELSE
NULL;
END IF;

Nullifying the effect of a raised exception:

Structure and flow of the exception block:

EXCEPTION
WHEN <exception_name1>
THEN
...
WHEN <exception_nameN>
THEN
...

WHEN OTHERS
THEN
...
END;

PROCEDURE calc_avg_sales
BEGIN
:sales.avg = :sales.month1/:sales_total;
EXCEPTION
WHEN ZERO_DIVIDE
THEN
:sales_avg = 0;
RAISE FORM_TRIGGER_FAILURE;

WHEN OTHERS THEN NULL;


END;

use the NULL statement to make sure that a raised exception halts execution of the current PL/SQL block, but does not
propogate any exceptions to the enclosing blocks. "End processing in the current PLSQL block and move to the enclosing
block, but otherwise take no action".

Supporting top-down design of modules:

Top Down Design:

* General description of the system.


* Step-by-step refinements.
* Modules which implement that system.
* Code that implements the modules.

Can implement top-down design by creating stubs.


Stub: name and parameters which we need, but no internal implementation.
In order for a PL/SQL program to compile, it must have atleast one executable statement.
Sample Stubs:

PROCEDURE revise_timetable (year_in IN NUMBER) IS


BEGIN
NULL;
END;

PROCEDURE company_name (company_id_in IN NUMBER) IS


BEGIN
NULL;
END;

Using NULL with GOTO to avoid additional statement execution:

use a GOTO statement to quickly move to the end of a program if the state of the data indicates that no further processing is
required.

PROCEDURE process_date (data_in IN order%ROWTYPE,


data_action IN VARCHAR2) IS
BEGIN
IF data_in.ship_date IS NOT NULL
THEN
status := validate_shipdate(data_in.ship_date);
IF status != 0
THEN
GOTO end_of_procedure;
IF data_in.order_date IS NOT NULL
THEN
status := validate_orderdate(data_in.order_date);
IF status != 0 THEN GOTO end_of_procedure;
....

<<end_of_procedure>>
NULL;
END;

==========================================================================================
================================================
Chapter 6: Database Interactions and Cursors
==========================================================================================
================================================
*****************************************************************************************************
*************************************
12/APR/2008
*****************************************************************************************************
************************************* 6.1 Transaction Management:

Transaction begins implicitly with the first SQL statement issued since the last COMMIT or ROLLBACK or with the start of
a session.

6.1.1: COMMIT: Saves all outstanding changes since the last COMMIT or ROLLBACK and releases all locks.

make permanent any changes made by your session to the database in the current transaction. once you commit, ur changes
will be visible to other oracle users or sessions.
syntax: COMMIT [WORK] [COMMENT text];
WORK keyword - optional. COMMENT specifies a comment which is then associated with the current transaction. Text is a
quoted literal and not more than 50 chars in length. COMMENT text is usu employed in distributed trasactions. text can be
handy for examining and resolving in-doubt transactions within a two phase commit framework. stored in data dictionary
along with transaction id.

COMMIT removes any row or table locks issued in the session. erases any save points issued since the last COMMIT or
ROLLBACK. cannot ROLLBACK once COMMIT happens.

Examples:

COMMIT;
COMMIT WORK;
COMMIT COMMENT 'maintaining account balance';

6.1.2: ROLLBACK: Erases all outstanding changes since the last COMMIT or ROLLBACK and releases all locks.

undo some or all changes made by the session to the database in the current transaction.
syntax: ROLLBACK [WORK] [TO [SAVEPOINT] savepoint_name];
2 ways to use ROLLBACK statement: 1. Without parameters 2. with a TO clause to indicate a savepoint at which the
ROLLBACK should stop.
Parameterless ROLLBACK undos all outstanding changes in the transaction. ROLLBACK TO statement allows you to undo
all changes and release all locks which were issued since the savepoint identified by savepoint_name was marked.
Savepoint is an undeclared oracle identifier. cannot be a literal (enclosed in quotes) or variable name.
Examples:
ROLLBACK;
ROLLBACK WORK;
ROLLBACK TO begin_cleanup;

When rolling back to a savepoint, all save points issued after the specified savepoint_name are erased. The savepoint to which
you rollback is however not erased. can rollback to the same savepoint again. Immediately before you execute an INSERT,
UPDATE, or DELETE PL/SQL implicitly generates a SAVEPOINT. In this way only the last DML statement is undone.

6.1.3: SAVEPOINT: Establishes a savepoint, which then allows to perform partial ROLLBACK.

SAVEPOINT gives a name to and marks a point in the processing of your transaction. This marker allows you to
ROLLBACK to that point, erasing any changes or releasing any locks issued after that savepoint. preserves any changes and
locks issued before this savepoint was declared.
Syntax: SAVEPOINT savepoint_name;
savepoint_name is an un-declared oracle identifier. can be upto 30 chars in length and can have the characters #,$ and _.
savepoints are scoped to PLSQL blocks. if we re-use a savepoint name within the current transaction, that savepoint is
"moved" from it's original position to the current point in the transaction, regardless of the procedure, function, or anonymous
block within the SAVEPOINT statements are executed. if we issue a savepoint in a recursive program, a new SAVEPOINT is
executed at each level of recursion, but we can only rollback to the most recent version of the savepoint.

SET TRANSACTION: allows to begin a read-only or read-write session, establish an isolation level, or assign the current
transaction to a specified rollback segment. This statement must be the first statement to be processed in a transaction and can
only appear once. Comes in 4 flavors:

SET TRANSACTION READ ONLY; defines the current transaction as read only. all subsequent queries only see those
changes which were committed before the transaction began.

SET TRANSACTION READ WRITE; sets the current transaction as read write.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE|READ COMMITTED; defines how transactions that modify a
db should be handled. when we specify 'SERIALIZABLE', a data manipulation statement (update, insert, delete) which
attempts to modify a table already modified in a uncommitted transaction will fail. if we specify READ COMMITTED, a
DML which requires row level locks held by another transaction will wait until those row locks are released.

SET TRANSACTION USE ROLLBACK SEGMENT rollback_segname:

This version assigns the current transaction to the specified rollback segment and establishes the transaction as read-write. This
statement cannot be used in conjunction with SET TRANSACTION READ ONLY.

LOCK TABLE: Allows to lock an entire db table in the specified mode. This over-rides default row level locking usually
applied to a table.
by doing this you can share or deny access to the table while you perform operations against it.

LOCK TABLE table_reference_list IN lock_mode MODE [NOWAIT];

where table_reference_list is a list of one or more table references (identifying a table/view or a remote entity through a db
link).
lock_mode is the mode of the lock, which can be one of the following:
ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE
If you specify the NOWAIT keyword, oracle will not wait for the lock if the table has been locked by another user. If you
leave out the NOWAIT keyword, oracle waits until the table is available. locking a table will not stop users from querying or
reading the table.

LOCK TABLE emp IN ROW EXCLUSIVE MODE;


LOCK TABLE emp, dept IN SHARE MODE NOWAIT;
LOCK TABLE scott.emp@newyork IN SHARE UPDATE MODE;
6.2: Cursors in PLSQL:
*****************************************************************************************************
************************************
13/APR/2008
*****************************************************************************************************
************************************

When you execute an SQL statement from PL/SQL, oracle RDBMS assigns a private work area for that statement. This work
area contains info about the SQL statement and the set of data returned or affected by that statement. PL/SQL cursor is a
mechanism by which you can name that work area and manipulate information with-in it. can think of a cursor as a pointer
into a table in the db.

Gen Syntax: CURSOR employee_cur IS SELECT * FROM employee;


once cursor is declared: OPEN employee_cur;
fetch rows from cursor: FETCH employee_cur INTO employee_rec;
close cursor: CLOSE employee_cur;

DECLARE
CURSOR order_cur
IS
SELECT ooh.order_number, ool.item_number
FROM oe_order_headers_all ooh
oe_order_lines_all ool
WHERE ooh.header_id = ool.header_id
AND ooh.order_number = '71000080';
BEGIN
...
END;

in above example, cursor acts as a pointer to the virtual table generated by the SQL in cursor declaration.

6.2.1: Types of Cursors:

Types of SQL which can be executed in PL/SQL:


Static: Content of the SQL statement is determined at compile time.
Dynamic: Constructed at run-time and executed. [Using DBMS_SQL package]

Two types of Cursor objects:

1. Static cursor objects: static cursors of PL/SQL. cursor always refers to one SQL statement, SQL determined at compile
time.
comes in 2 flavors: implicit and explicit.
PL/SQL declares and manages implicit cursors everytime we execute a SQL DML statement, such as an INSERT or SELECT
which returns a single row. We define our own explicit cursors. must use explicit cursor when we need to retrieve more than
one row of data using SELECT. Can then use the cursor to fetch rows one by one. the set of rows returned by a cursor are
called the active set or the result set of the cursor. The row to which the cursor points to is called the current row.

2. Cursor Variables: Can declare a variable which references a cursor object in the db. variable may refer different SQL at
different times. act as references to cursor objects. can also pass a cursor variable as a parameter to a procedure or function.

6.2.2: Cursor Operations:

Regardless of the type of cursor, steps performed by PLSQL to execute a statement in our program:

PARSE: parse SQL to check validity and determine execution plan (either rule-based/cost-based optimizer.)
BIND: associates values from the program to place holders in SQL statement. for static SQL, the SQL engine performs these
tasks. But when using dynamic SQL, we need to explicitly request a binding of variable values.
OPEN: when opening a cursor, bind variables are used to determine the result set of the SQL statement. The pointer to the
active or current row is set to the first row. sometimes we won't explicitly open a cursor, PL/SQL will perform this for us.
EXECUTE: the statement is run with-in the SQL engine.
FETCH: when performing query, FETCH returns the next row from the cursor's result set. each time we fetch, PL/SQL moves
the pointer forward in the result set. when working with explicit cursors, FETCH does not do anything if there are no rows to
FETCH (does not raise an error).
CLOSE: closes cursor and releases all memory used by the cursor. once closed, cursor no longer has a result set.

6.3: Implicit and Explicit Cursors:

6.3.1: Implicit Cursors: PLSQL issues an implicit cursor when we execute a SQL statement directly from code. oracle
performs open, fetch and close automatically. outside of our programmatic control. PLSQL employs implicit cursor for each
UPDATE, DELETE or INSERT, cannot use explicit cursors for these statements. have choice between using an implicit or
explicit cursors only for a SELECT statement which retrieves one row, for SELECT statements which retrieve more than one
row, we must use explicit cursors.

6.3.2: Drawbacks of implicit cursors:

6.3.2.1: less efficient than explicit cursors: implicit cursor executes as an SQL statement. Oracle's SQL is ANSI standard.
ANSI dictates that single row fetch must not only fetch the first record, but also perform a second fetch to check if too many
rows are retrieved by the query [TOO_MANY_ROWS PL/SQL Exception]. hence implicit query performs minimum 2 fetches
and explicit query only one fetch.
6.3.2.2: vulnerable to data errors: with implicit cursors, we cannot handle different conditions. with explicit cursors, program
is protected against changes in data and will continue to fetch rows without raising exceptions.
6.3.2.3: gives us less programmatic control: can't get inside the seperate operations of a cursor. such as open and close stages.
can't examine the attributes of a cursor, to see whether a row was found or the cursor is already open. can't apply programming
constructs such as IF ELSE, to data access.
Always Use EXPLICIT CURSORS.

6.3.3: Explicit Cursors: have complete control over how to access info from the db. info about the state of the cursor is
available through the examination of cursor attributes.

DECLARE
/* Declare the cursor */
CURSOR order_cur
IS
SELECT ooh.order_number, ool.item_id
FROM oe_order_headers_all ooh,
oe_order_lines_all ool
WHERE ooh.header_id = ool.header_id
AND ooh.order_number = '71880056';

BEGIN
/* Open Cursor */
IF NOT order_cur%ISOPEN
THEN
OPEN order_cur;
END IF;
/* Fetch one or more rows from the cursor */
FETCH order_cur INTO order_rec;
/* Close Cursor */
CLOSE order_cur;

END;

6.4: Declaring Cursors:

Syntax:
CURSOR cursor_name [([parameter, [parameter])]]
[RETURN return_specification]
IS SELECT statement;

cursor without parameters:

CURSOR company_cur
IS
SELECT company_id FROM company;

cursor with parameters:

CURSOR company_cur (company_id_in IN NUMBER)


IS
SELECT company_id FROM company
WHERE company_id = company_id_in;

cursor with return type:

CURSOR emp_cur RETURN employee%ROWTYPE


IS
SELECT * FROM employee
WHERE department_id = 10;

6.4.1: The Cursor Name: follows identifier rules in PLSQL, an undeclared identifier. cannot assign values to it.
6.4.2: PLSQL variables in a cursor: can reference local PL/SQL program data [vars and constants], bind variables in WHERE,
GROUP, HAVING, and the SELECT list of a SELECT statement.

DECLARE

projected_bonus NUMBER := 1000;

CURSOR emp_cur
IS
SELECT employee_id,
salary + projected_bonus new_salary,
:review.evaluation
FROM employee
WHERE hiredate < ADD_MONTHS(SYSDATE, -36);
BEGIN
...
END;

6.4.3: Identifier precedence in a CURSOR: If local variables conflict with db column or table namesin the cursor, then rename
the loc vars.
6.4.4: The cursor RETURN clause: RETURN clause of a cursor allows us to create a specification of the cursor [can be placed
in package specification] which is seperate from the body [SELECT statement].

CURSOR order_cur RETURN oe_order_headers_all%ROWTYPE


IS
SELECT * FROM oe_order_headers_all;

Specification: CURSOR order_cur RETURN oe_order_headers_all%ROWTYPE

Body: SELECT * FROM oe_order_headers_all;

RETURN clause may be made up of any of the following datatype structures:


record defined from a database table, using the %ROWTYPE attribute.
record defined from a programmer-defined record.

Package Specification:

PACKAGE order_pkg
IS
CURSOR order_cur IS RETURN oe_order_headers_all%ROWTYPE;
END order_pkg;

Package Body:

PACKAGE BODY order_pkg


IS
CURSOR order_cur IS RETURN oe_order_headers_all%ROWTYPE
IS
SELECT * FROM oe_order_headers_all;
...
END order_pkg;

The number of expressions in the cursor's select list must match the number of columns in the record identified by the
table_name%ROWTYPE or PLSQL_Record%ROWTYPE.

6.5 Opening Cursors:

Syntax:
OPEN <cursor_name> [(argument [,argument])];
arguments are values to be passed, if the cursor was declared with parameter list. when opening a cursor, PLSQL executes the
query for that cursor. it also ids the active set of data, rows from all involved tables that meet the criteria in the WHERE and
join conditions. does not actually retrieve the rows, that is performed by FETCH statement.
regardless of when we perform the first FETCH, read consistency model in oracle RDBMS guanrantees that all fetches will
reflect data as it existed when the cursor was opened. from the moment the cursor is opened, until we close the cursor, all data
fetched through the cursor will ignore any inserts, updates or deletes performed after the cursor was opened.
If the SELECT statement uses a FOR UPDATE clause, then when the cursor is opened, all the queries identified by the query
are locked.
try to open a cursor which is already opened, will get the following error:
PL/SQL: cursor already open. Can only open closed or never opened cursors.

check %ISOPEN attribute of cursor b4 opening:

IF NOT order_cur%ISOPEN
THEN
OPEN order_cur;
ELSE
NULL;
END IF;

6.6 Fetching from cursors:

A cursor represents a virtual table within your PL/SQL program. The point of declaring/opening a cursor is to return/fetch
rows of data from the cursor and manipulate the info retrieved.
Syntax:
FETCH <cursor_name> INTO <record_or_variable_list>;
can fetch into a record structure [%ROWTYPE attr or TYPE declaration] or can fetch into a list of one or more variables.

Fetch into a PL/SQL record:


FETCH company_cur INTO company_rec;
Fetch into a variable:
FETCH new_balance_cur INTO new_balance_dollars;

Fetch into a row of a PLSQL table row, a variable and oracle forms bind variable:

FETCH emp_name_cur INTO emp_name (1), hiredate, :dept.min_salary;

6.6.1: Matchinf Column list with INTO clause:


When we fetch into a list of variables, the number of variables must match the number of expressions in the SELECT list of
the cursor. same applies when we fetch into a record, no of cols in the record must match.
6.6.2: Fetching past the last row: after opening cursor, can fetch from it until there are no records left in the active set. at this
point the %NOTFOUND cursor attribute is set to true.
can even fetch after the last record. PLSQL will not raise any exceptions. fetch will not set the values of the INTO list to
NULL.
must check the value of the %NOTFOUND attr.

6.7: Column Aliases in Cursors:


In an explicit cursor, column aliases are required for calculated columns when:

1. We fetch into a record with a %ROWTYPE declaration against a cursor.


2. To reference the calculated column in our program.

DECLARE
CURSOR company_cur
IS
SELECT company_name, SUM(inv_amt) total_sales
FROM company c, invoice i
WHERE c.company_id = i.invoice_id
AND i.invoice_date BETWEEN '01-JAN-1994' AND '31-DEC-1994';
TYPE company_rec_type IS RECORD (
company_name company.company_name%TYPE;
total_sales invoice.inv_amt%TYPE;
);
company_rec company_rec_type%ROWTYPE;
BEGIN
OPEN company_cur;
FETCH company_cur INTO company_rec;
IF company_rec.total_sales > 5000
THEN
...
END IF;
END;

6.8: Closing Cursors:


Syntax: CLOSE <cursor_name>;
open cursor uses a certain amount of memory. exact amount depends on the active set of the cursor. cursors can also cause db
to issue row level locks when the FOR UPDATE clause is used in the select statement.

6.8.1: Maximum number of cursors: db init parameter when starting a db instance: OPEN_CURSORS. specifies a max no of
cursors a user can have open at once. if exceeded, then "maximum open cursors exceeded" error encountered.
When we close a cursor, we disable it. cannot fetch after cursor close. close a cursor only if open. use %ISOPEN to check.

6.8.2: Closing local cursors:


when we declare a cursor in a PL/SQL block (anonymous block, procedure, or function), the cursor is only defined within (is
local to) that block. when execution of that block terminates, PLSQL will close cursor automatically w/o raising any
exceptions.
If cursor is defined in a package, then it's scope is not limited to any PLSQL block, it will stay OPEN until you CLOSE it
explicitly or disconnect oracle session.
Always include CLOSE statements.

6.9: Cursor Attributes:


Access cursor attributes to get information about current status of the cursor or the result of the last fetch from the cursor.

Explicit Cursors:

%FOUND
%NOTFOUND
%ROWCOUNT
%ISOPEN

Example:

Cursor order_cur
IS
SELECT order_number, order_header_id
FROM oe_order_headers_all;

order_cur%FOUND / order_cur%NOTFOUND / order_cur%ROWCOUNT / order_cur%ISOPEN

DECLARE
Cursor order_cur
IS
SELECT order_number, order_header_id
FROM oe_order_headers_all;
order_rec order_cur%ROWTYPE;
BEGIN
IF NOT order_cur%ISOPEN
THEN
OPEN order_cur;
ELSE
NULL;
END IF;
FETCH order_cur INTO order_rec;

WHILE order_cur%FOUND
LOOP
DBMS_OUTPUT.PUT_LINE('Just Fetched Row # ' || TO_CHAR(order_cur%ROWCOUNT));
FETCH order_cur INTO order_rec;
END LOOP;
CLOSE order_cur;
END;

6.9.1: The %FOUND attribute:


reports the status of your most recent FETCH against the cursor. evaluates to TRUE if a row was returned, FALSE if no row
was returned.
reference to %FOUND attr results in INVALID_CURSOR exception, when cursor is not yet opened.

OPEN order_cur;
LOOP
FETCH order_cur INTO order_rec;
EXIT WHEN NOT order_cur%FOUND

DBMS_OUTPUT.PUT_LINE('Order Number: ' || order_rec.order_number || ' order_header_id: ' || order_rec.header_id || '


Row Number: ' || order_cur%ROWCOUNT);
END LOOP;
CLOSE order_cur;
6.9.2: The %NOTFOUND attribute:
opposite of %FOUND. returns TRUE when cursor is unable to fetch rows as the last row has been fetched and FALSE when a
row has been fetched. referencing %NOTFOUND when a cursor has not yet been opened will result in INVALID_CURSOR
exception.
<cursor_name>%FOUND = NOT <cursor_name>%NOTFOUND
<cursor_name>%NOTFOUND = NOT <cursor_name>%FOUND

OPEN order_cur;
LOOP
FETCH order_cur INTO order_rec;
EXIT WHEN order_cur%NOTFOUND;

DBMS_OUTPUT.PUT_LINE('Order Number: ' || order_rec.order_number || ' order_header_id: ' || order_rec.header_id || '


Row Number: ' || order_cur%ROWCOUNT);
END LOOP;
CLOSE order_cur;

6.9.3: The %ROWCOUNT attribute:

returns the number of records fetched from a cursor at the time this attribute was queried. referencing the %ROWCOUNT
attribute before opening a cursor will result to INVALID_CURSOR exception. can use %ROWCOUNT attr to limit the
number of records fetched from a cursor.

DECLARE
CURSOR order_cur
IS
SELECT order_number, header_id
FROM oe_order_headers_all;
order_rec order_cur%ROWTYPE;
BEGIN
IF NOT order_cur%ISOPEN
THEN
OPEN order_cur;
ELSE
NULL;
END IF;

LOOP
FETCH order_cur INTO order_rec;
EXIT WHEN order_cur%NOTFOUND
OR order_cur%ROWCOUNT > 10;

DBMS_OUTPUT.PUT_LINE(order_rec.order_number || ' ' || order_rec.header_id || ' ' || order_cur%ROWCOUNT);


END LOOP;
CLOSE order_cur;
END;

6.9.4: The %ISOPEN attribute:


returns TRUE when cursor is open, otherwise returns FALSE. will encounter error when trying to open a cursor which is
already open.
"cursor already open".

6.9.5: Implicit SQL cursor attributes:

can access info about most recently executed SQL statements through SQL cursor attributes.

SQL%FOUND;
SQL%NOTFOUND;
SQL%ROWCOUNT;
SQL%ISOPEN;

6.9.6: Differences between implicit and explicit cursor attributes:

* If RDBMS has not opened an implicit cursor for the session, then %ROWCOUNT will return NULL. %ISOPEN, %FOUND
and %NOTFOUND will return FALSE. INVALID_CURSOR exception will not be thrown.
* %ISOPEN will always return false before and after the execution of the SQL statement. After statement (SELECT,
UPDATE, INSERT or DELETE) has executed, the implicit cursor will have opened and closed implicitly. Implicit cursor can
never be open outside of the statement itself.
* If you plan to make use of an SQL cursor attr, make sure that you reference the attribute immediately after execution of the
SQL statement.
* %FOUND attr returns TRUE if a SELECT, UPDATE or DELETE affected atleast one record. returns FALSE if no records
were affected. Will return true if a SELECT retrieves atleast one row.
* Behavior of %NOTFOUND is opposite of %FOUND behavior for SELECT, INSERT and UPDATE. when using SELECT,
never rely on %FOUND or %NOTFOUND attributes.
* When an implicit SELECT statement does not return any rows, PLSQL raises the NO_DATA_FOUND exception. When
implicit SELECT returns more than one row, then PLSQL throws TOO_MANY_ROWS exception. When exception is raised,
control shifts to exception block.

6.10: Cursor Parameters:

* Parameters make cursors more reusable.


* Parameter avoid scoping problems: When passing parameters instead of hard-coding values, the result set for that cursor is
not tied to a specific variable in a program or block. If the program has nested blocks, can define the cursor at a higher level
and use it in any of the sub-blocks with variables of those local blocks.

6.10.1: Generalizing cursors with parameters:

DECLARE
CURSOR order_cur (order_type_in VARCHAR2)
IS
SELECT order_number, order_header_id
WHERE UPPER(order_type) = UPPER(order_type_in);
BEGIN
...
END;

6.10.2: Opening cursors with parameters:

DECLARE
CURSOR order_cur (order_type_in VARCHAR2)
IS
SELECT order_number, order_header_id
WHERE UPPER(order_type) = UPPER(order_type_in);
BEGIN
OPEN order_cur('DP PROJECT ORDER');
END;

6.10.3: Scope of Cursor Parameters:

Scope of cursor param is confined to that cursor. cannot refer to the cursor parameter outside of the SELECT statement
associated with that cursor.

6.10.4: Cursor Parameter Modes: Only Supports the IN parameter type. OUT and IN OUT are not supported.

6.10.5: Default values for parameters:


DECLARE
CURSOR order_cur (order_type_in VARCHAR2 := 'DP Project Order')
IS
SELECT order_number, header_id
FROM oe_order_headers_all
WHERE UPPER(order_type) = UPPER(order_type_in);

order_rec order_cur%ROWTYPE;

my_order_number oe_order_headers_all.order_number%TYPE;
my_header_id oe_order_headers_all.header_id%TYPE;
BEGIN
OPEN order_cur('Getronics Order');
--OPEN order_cur;
/* Both Legal */
--FETCH order_cur INTO order_rec;
FETCH order_cur INTO my_order_number, my_header_id;
END;

6.11: SELECT FOR UPDATE in cursors:


*****************************************************************************************************
*************************************
14/APR/08
*****************************************************************************************************
*************************************
When you issue a SELECT...FOR UPDATE statement, RDBMS automatically obtains row level locks for all rows identified
by the SELECT statement. No one else will be able to change any of these records until you perform a COMMIT or
ROLLBACK.

CURSOR order_cur
IS
SELECT order_number, header_id
FROM oe_order_headers_all
WHERE order_number = '71800065'
FOR UPDATE;

CURSOR instance_cur
IS
SELECT *
FROM csi_item_instances
WHERE instance_number = '123546'
FOR UPDATE OF install_location_id;

Can use the FOR UPDATE clause in a SELECT against multiple tables. Here, rows in a table are locked only if the FOR
UPDATE clause references a column in that table.

CURSOR cust_num_cur
IS
SELECT hp.party_id, hp.party_name, hca.customer_number
FROM hz_parties hp, hz_cust_accounts hca
WHERE hp.party_id = hca.party_id
AND hp.party_id = '268746'
FOR UPDATE OF hca.customer_number;

Here no rows of the hz_parties table are locked.

OF list of the FOR UPDATE clause does not restrict us from changing only those columns listed. Locks are still placed on all
rows. Can append keyword NOWAIT to the FOR UPDATE clause to tell not to wait if the table has been locked by another
user. In this case control will immediately returned to the program, so that we can proceed with other work or wait until we
have a lock on the table. Without NOWAIT your process will block until the table is available. No limit to the wait time unless
the table is remote.

6.11.1: Releasing Locks with COMMIT:


When we open a cursor with FOR UPDATE clause, all rows identified by the cursor are locked. When a COMMIT or
ROLLBACK occurs, the locks are released. Now, we cannot execute another FETCH against a FOR UPDATE clause, after
the COMMIT or ROLLBACK occurs. The position in the cursor is lost.

DECLARE
CURSOR order_type_cur
IS
SELECT header_id, order_number, order_type, order_date
FROM oe_order_headers_all
WHERE UPPER(order_type) = 'GETRONICS_ORDER';
FOR UPDATE OF order_type;
BEGIN
FOR order_type_rec IN order_type_cur /* Cannot make another FETCH after COMMIT */
LOOP
IF order_date >= SYSDATE
THEN
UPDATE oe_order_headers_all
SET order_type = 'DP_GETRONICS_ORDER'
WHERE UPPER(order_type) = 'GETRONICS_ORDER'
AND order_date >= SYSDATE
AND header_id = order_type_rec.header_id;
COMMIT;
END IF;
END LOOP;
END;

Error Encountered: 'Fetch Out Of Sequence'.

6.11.2: WHERE CURRENT OF Clause:

PLSQL provides WHERE CURRENT OF clause for both the UPDATE and DELETE statements inside a cursor in order to
change the most recently fetched row of data.

UPDATE table_name
SET clauses
WHERE CURRENT OF cursor_name;

DELETE
FROM table_name
WHERE CURRENT OF cursor_name;

Use WHERE CURRENT OF Clause:


Delete the record i just fetched.
Update these columns in that row i just fetched.

DECLARE
CURSOR order_type_cur
IS
SELECT header_id, order_number, order_type, order_date
FROM oe_order_headers_all
WHERE UPPER(order_type) = 'GETRONICS_ORDER';
BEGIN
FOR order_rec in order_cur
LOOP
IF order_date >= SYSDATE
THEN
UPDATE oe_order_headers_all
SET order_type = 'DP_GETRONICS_ORDER'
WHERE CURRENT OF order_type_cur;
COMMIT;
END IF;
END LOOP;

END;

6.12: Cursor Variables:

Cursor variable can be opened for any query, even different queries with-in a single program execution. provides a mechanism
for passing results of queries between different PLSQL programs, even between client and server PLSQL programs.

DECLARE
TYPE order_cur_type IS REF CURSOR RETURN oe_order_headers_all%ROWTYPE;

order_cur_var order_cur_type;

order_rec oe_order_headers_all%ROWTYPE;
BEGIN

OPEN order_cur_var FOR Select * FROM oe_order_headers_all;

FETCH order_cur_var INTO order_rec;

CLOSE order_cur_var;
END;

6.12.1: Features of Cursor Variables:

Cursor Vars let you:


* Associate a cursor variable with different queries at different times in your program execution.
* Pass a cursor variable as an argument to a procedure or function.
* Can OPEN/CLOSE/FETCH from cursors. reference standard attrs: %ISOPEN, %FOUND, %ROWCOUNT,
%NOTFOUND.
* Assign contents of one cursor variable [and it's result set] to another cursor variable.

6.12.2: Similarities to Static Cursors:

* The CLOSE statement:

DECLARE
TYPE my_sample_cur_type IS REF CURSOR;
my_sample_cur my_sample_cur_type;
emp_rec employee%ROWTYPE;
BEGIN
OPEN my_sample_cur FOR SELECT * FROM employee;
FETCH my_sample_cur INTO emp_rec;
CLOSE my_sample_cur;
END;

* Cursor Attributes:
my_sample_cur%ISOPEN;
my_sample_cur%FOUND;
my_sample_cur%NOTFOUND;
my_sample_cur%ROWTYPE;

* Fetching from cursor var: Syntax is same, but PLSQL applies additional rules to make sure that the data structure of the
cursor variable's row match that of the data structures to the right of the INTO keyword.

6.12.3: Declaring REF CURSOR Types and Cursor Variables:

Steps to create cursor variables:

Create a referenced cursor TYPE.


Declare the actual cursor variable based on that type.

TYPE <cursor_type_name> IS REF CURSOR [RETURN <return_type>];

Strong Type Cursor:


TYPE order_cur_type IS REF CURSOR RETURN oe_order_headers_all%ROWTYPE;
attaches a record type to the cursor variable at the time of the cursor declaration. can only be used with SQL statement and
FETCH INTO whose data structures match the specified record type.

Weak Type Cursor:


TYPE order_cur_type IS REF CURSOR;
not associated with any record structure. can be used much more flexibly than strong type cursors. can be used to query
multiple SQL statements within scope of a single program.

Cursor variable points to a cursor object. Cursor object has the result set which is identified by the SQL statement attached to
it. Declaration of a cursor variable does not create an object.

6.12.4: Opening Cursor Variables:

We assign a value (cursor object) to the cursor object when we OPEN the cursor.

OPEN <cursor_name> FOR <select_statement>;

Strong Type REF CURSORS:


The structure of the select statement must match the return type specified in the TYPE declaration statement.

DECLARE

TYPE emp_cur_type IS REF CURSOR RETURN employee%ROWTYPE;


emp_cur emp_cur_type;
emp_rec employees%ROWTYPE;

BEGIN

OPEN emp_cur FOR SELECT * FROM emp;


FETCH emp_cur INTO emp_rec;
CLOSE emp_cur;

END;

Weak Type REF CURSORS:

can OPEN it for any query.

DECLARE
TYPE util_cur_type IS REF CURSOR;
util_cur_var util_cur_type;
BEGIN
OPEN util_cur_var FOR SELECT * FROM employees;
OPEN util_cur_var FOR SELECT * FROM oe_order_headers_all;
OPEN util_cur_var FOR SELECT SYSDATE FROM dual;
END;

If cursor variable has not yet been assigned to any cursor object, OPEN FOR statement implicitly creates an object for the
variable.
If cursor variable already points to some object, then OPEN FOR reuses the variable and simply points it to another SQL
query.

6.12.5: Fetching from Cursor Variables:

FETCH <cursor variable name> INTO <record type>;


FETCH <cursor variable name> INTO <variable1, variable2, variable3>;

6.12.5.1: Strong and Weak REF CURSOR Types:


For Strong REF CURSOR types, the data structure compatibility check happens at compile time. But for Weak REF CURSOR
types, the data structure compatibility check must happen at run time, when the FETCH is executed. If the query and the INTO
clause do not structurally match [even after using implicit conversions], PLSQL engine will raise pre-defined
ROWTYPE_MISMATCH exception.

6.12.5.2: Handling the ROWTYPE_MISMATCH exception:


Before PL/SQL performs the FETCH, it checks for datastructure compatibility. can trap the ROWTYPE_MISMATCH and try
to FETCH the cursor variable using another INTO clause, without skipping any rows in the result set.

Example:

1. Define Weak REF CURSOR:

TYPE building_curtype IS REF CURSOR;

2. Create Procedure, mode of cursor variable param is IN OUT.

PROCEDURE open_site_list
(address_in IN VARCHAR2,
site_cur_inout IN OUT building_curtype)
IS
home_type CONSTANT INTEGER := 1;
commercial_type CONSTANT INTEGER := 2;

CURSOR site_type_cur
IS
SELECT site_type
FROM property_master
WHERE address = address_in;
site_type_rec site_type_cur%ROWTYPE;

BEGIN
OPEN site_type_cur;
FETCH site_type_cur INTO site_type_rec;
CLOSe site_type_cur;

IF site_type_rec.site_type = home_type
THEN
OPEN site_cur_inout
FOR SELECT *
FROM home_properties
WHERE address LIKE '%' || address_in || '%';
ELSIF site_type_rec.site_type = commercial_type
THEN
OPEN site_cur_inout
FOR SELECT *
FROM commercial_properties
WHERE address LIKE '%' || address_in || '%';
END IF;
END open_site_list;

3. Code which uses ROWTYPE_MISMATCH to read the cursor obtained from open_site_list:

DECLARE
building_curvar building_curtype;

home_rec home_properties%ROWTYPE;
commercial_rec commercial_properties%ROWTYPE;
BEGIN
prompt_for_address(address_string);
open_site_list(address_string, building_curvar);

FETCH building_curvar INTO home_rec;


show_home_site(home_rec);

WHEN ROWTYPE_MISMATCH
THEN
FETCH building_curvar INTO commercial_rec;
show_commercial_site(commercial_rec);
END;

6.12.6: Rules for cursor variables:

A cursor variable is said to "refer to a given query" if either of the following is true:
1. An OPEN statement FOR that query was executed for the cursor variable.
2. A cursor variable was assigned a value, from another cursor variable that refers to that query.

6.12.6.1: Compile Time Row Matching Rules:

Two cursor variables(incl procedure params) are compatible for assignment and argument passing if any of the following are
true:

* Both Variables (or params) are strong REF CURSOR type with the same <rowtype_name>.
* Both variables (or params) are of weak REF CURSOR type, regardless of the rowtype.
* One variable (parameter) is any Strong REF CURSOR type, other is of any weak REF CURSOR type.
* Weak REF CURSOR type var may OPEN FOR a query of any rowtype, no matter what it referred to earlier.
* Strong REF CURSOR type may OPEN FOR a query whose rowtype matches structurally to the rowtype specified in the
TYPE specification.

6.12.6.2: Run-time row matching rules:

* Weak REF CURSOR type var may refer to a query of any rowtype, no matter what it referred to earlier.
* Strong REF CURSOR type may refer to a query whose rowtype matches structurally to the rowtype specified in the TYPE
specification.
* Two records (or LOVs) are considered structurally matching [with implicit conversions] if:
* The no of fields is same in both the records.
* For each field in one record, a corresponding field in the second list has the same PLSQL datatype, which can be converted
by PLSQL to match the first.
* For a cursor variable (parameter) used in a FETCH statement, the query associated with the cursor variable must structurally
match with implicit conversions, the record or list of variables of the INTO clause of the FETCH statement. Same rule used
for static cursors.

6.12.6.3: Cursor Variable Aliases:

If we assign one cursor variable to another, the two cursor variables become aliases for the same cursor object. share the
reference to the cursor object. action taken against the cursor object using one variable is available to and reflected by the other
cursor variable.

DECLARE
TYPE curvar_type IS REF CURSOR;
curvar1 curvar_type;
curvar2 curvar_type;

emp_rec employees%ROWTYPE;
BEGIN
OPEN curvar1 FOR SELECT * FROM employees;

curvar2 := curvar1;

FETCH curvar1 INTO emp_rec; -- record 1

FETCH curvar2 INTO emp_rec; -- record 2

CLOSE curvar1;

FETCH curvar2 INTO emp_rec; -- INVALID_CURSOR Exception


END;

6.12.6.4: Scope Of a cursor object:

same as that of a static cursor, the PLSQL block in which the variable is declared [if declared in a package, it's globally
accessible].

Cursor object: Once an OPEN FOR creates a cursor object, the cursor object remains accessible as long as atleast one cursor
variable refers to that object. create a cursor object in one PLSQL Block [scope] and assign it to another cursor variable. assign
that cursor variable to a cursor variable in another scope. the cursor object remains accessible even if the original cursor
variable has gone out of scope.

DECLARE
TYPE curvar_type IS REF CURSOR;
curvar1 curvar_type;
order_number_var oe_order_headers_all.order_number%TYPE;
BEGIN
DECLARE
curvar2 curvar_type;
BEGIN
OPEN curvar2 FOR SELECT order_number
FROM oe_order_headers_all
WHERE header_id = 235457;
curvar1 := curvar2;
END;

FETCH curvar1 INTO order_number_var; -- data still accessible


END;
6.12.7: Passing Cursor Variables as arguments:

can pass cursor variable as an argument to a function or procedure. need to specify the mode of the param and the datatype.

6.12.7.1: Identifying REF CURSOR type

DECLARE
TYPE order_cur_type IS REF CURSOR RETURN oe_order_headers_all.order_number%TYPE;

PROCEDURE get_order_num_cur (
order_header_id IN oe_order_headers_all.header_id%TYPE
, order_num_cur OUT order_cur_type)
IS
local_order_num_cur order_cur_type;
BEGIN
OPEN local_order_num_cur FOR SELECT order_number
FROM oe_order_headers_all
WHERE header_id = order_header_id;
order_num_cur := local_order_num_cur;
END;

my_order_num_cur order_cur_type;
my_order_num oe_order_headers_all.order_number%TYPE;

BEGIN
get_order_num_cur(435257, my_order_num_cur);

FETCH my_order_num_cur INTO my_order_num;

CLOSE my_order_num_cur;
END;

If creating a stand-alone procedure or function, the only way you can reference a pre-existing REF CURSOR is by placing the
TYPE statement in a package. All variables declared in the package specification act as global variables with-in a session.

PACKAGE company IS
TYPE curvar_type IS REF CURSOR RETURN company%ROWTYPE;
END package;

PROCEDURE open_company(curvar_out OUT company.curvar_type)


IS
BEGIN
END;

6.12.7.2: Setting the parameter mode:

IN : Can only be read by the program.


OUT : Can only be written to by the program.
IN OUT: Read/Write in a program.

6.12.8: Cursor Variable Restrictions:

* Cannot be declared in a package, since they do not have a persistent state.


* Cannot use RPCs (Remote Procedure Calls) to pass a cursor from one server to another.
* If we pass a cursor variable as a bind or host variable to PLSQL, cannot be able to fetch from it within the server, unless you
also open it within the same call.
* The query you associate with a cursor in OPEN FOR, cannot contain FOR UPDATE.
* cannot test for cursor variable equality, in-equality or nullity using comparison operators.
* cannot assign NULLs to a cursor variable.
* Database columns cannot store cursor variable values. will not be able to use REF CURSOR types to specify column types
in statements to CREATE TABLEs or CREATE VIEWs.
* Cursor variables cannot be used with dynamic SQL [DBMS_SQL package].

6.13: Working with CURSORS:

6.13.1: Validating Foreign key entry with cursors:


6.13.1.1: In-efficiency of Group functions in cursors.
6.13.1.2: Using multiple fetches more efficiently
6.13.2: Managing a work queue with SELECT FOR UPDATE

Task: To find an unassigned task, hand it to the user and obtain a lock on that record, so that no one else can modify that
record while the user is at work on that un-assigned task.

FUNCTION next_task RETURN task.task_id%TYPE


IS
/* Cursor of all tasks, assigned and un-assigned */
CURSOR task_cur IS
SELECT task_id
FROM task
WHERE task_status = 'OPEN'
ORDER BY task_priority, date_entered DESC;

/* Record for the above cursor */


task_rec task_cur%ROWTYPE;

/* An Exception for error ORA-0054


"resource busy and acquire with NOWAIT specified"
*/
record_locked EXCEPTION;
PRAGMA EXCEPTION_INIT(record_locked, -54);

/*
Variables which determine whether the function should continue to
loop through cursor's records.
*/
found_unassigned_task BOOLEAN := false;
more_tasks BOOLEAN := false;

/* Primary key of the un-assigned task to be returned */


return_value task.task_id%TYPE;

BEGIN
/* Open cursor and start lookup through it's records. */
WHILE NOT found_unassigned_task AND more_tasks
LOOP
/* Fetch the next record, if nothing's found, we're done */
FETCH task_cur INTO task_rec;
more_tasks = task_cur%FOUND;
IF more_tasks
THEN
/* A record was fetched. Create an anonymous block within the function so that we can trap the record_locked exception
and still stay in the loop.
*/
BEGIN
/* Try to get a lock on the current Task */
SELECT task_id INTO return_value
FROM task
WHERE task_id = task_rec.task_id
FOR UPDATE OF task_id NOWAIT;
/* If we get to this line, then we were able to get a lock onto this particular task. Notice that SELECT INTO has
therefore already set the function's return value. Now Set the boolean to stop the loop
*/
found_unassigned_tasks := TRUE;
EXCEPTION
WHEN record_locked
THEN
/* Record was already locked, so keep going. */
NULL;
END;
END IF;
END LOOP;
/*
Return the Task Id. Notice that if an un-assigned task was NOT found, I will simply return NULL per declaration default.
*/
CLOSE task_cur;
RETURN return_value;
EXCEPTION
WHEN OTHERS
THEN
CLOSE task_cur;
NULL;
END;

==========================================================================================
=================================================
Chapter 7: LOOPS
==========================================================================================
=================================================

PLSQL provides 3 kinds of loops:

* Simple or infinite loops.


* The FOR loop (numeric or cursor)
* The WHILE loop

7.1: LOOP BASICS:


7.1.1: Examples Of Different Loops:
Procedure executed in each of the following loops:
set_rank(ranking_level): Accepts the maximum ranking level as an argument and sets the ranking until the level exceeds the
maximum.

The Simple Loop:

PROCEDURE set_all_ranks(max_rank_in IN INTEGER)


IS
ranking_level NUMBER (3) := 1;
BEGIN
IF max_rank_in >= 1
THEN
LOOP
set_rank(ranking_level);
ranking_level := ranking_level + 1;
EXIT WHEN ranking_level > max_rank_in;
END LOOP;
END IF;
END;

FOR Loop:

rank for the fixed range of values. from one to maximum number:

PROCEDURE set_all_ranks(max_rank_in IN INTEGER)


IS
BEGIN
FOR ranking_level IN 1 .. max_rank_in
LOOP
set_rank(ranking_level);
END LOOP
END;

The WHILE loop:

PROCEDURE set_all_ranks(max_rank_in IN INTEGER)


IS
ranking_level NUMBER (3) := 1;
BEGIN
WHILE ranking_level<=max_rank_in
LOOP
set_rank(ranking_level);
ranking_level := ranking_level + 1;
END LOOP;
END;

In many situations, the number of times a loop must execute varies and so FOR loop cannot be used.

7.1.2: Structure for PL/SQL loops:


Code outside the loop should not have to know about the inner working of the loop.

7.2: The Simple Loop:

LOOP
<executable statement (s)>
END LOOP;

body must contain atleast one executable statement.

Loop Termination: When EXIT statement is executed in the body of the loop. If exit statement is not executed, then the simple
loop becomes an in-finite loop.
Test for termination: takes place inside the body of a loop, only then is an EXIT or EXIT WHEN statement executed. body of
simple loop will always execute atleast once.
Use this loop when: not sure how many times the loop has to execute. you want the loop to execute atleast once.

Infinite Loop: keeps checking for messages from a particular pipe, so that it can respond immediately and display information
in the pipe.

DECLARE
pipe_status INTEGER;
message_text VARCHAR2;
BEGIN
LOOP
pipe_status := DBMS_PIPE.RECEIVE_MESSAGE('execution_trace');
IF pipe_status = 0
THEN
DBMS_PIPE.UNPACK_MESSAGE(message_text);
DBMS_OUTPUT.PUT_LINE(message_text);
END IF;
END LOOP;
END;

7.2.1: Terminating a Simple Loop: EXIT and EXIT WHEN:

EXIT;
EXIT WHEN condition; condition - boolean condition.

LOOP
balance_remaining := account_balance(account_id);
IF balance_remaining < 1000
THEN
EXIT;
ELSE
apply_balance(account_id, balance_remaining);
END IF;
END LOOP;

Can use an EXIT statement only with-in a LOOP.

LOOP
balance_remaining := account_balance(account_id);

EXIT WHEN balance_remaining < 1000;

apply_balance(account_id, balance_remaining);
END LOOP;

7.2.2: Emulating a REPEAT UNTIL loop:

LOOP
... body
EXIT WHEN boolean_condition;
END LOOP;

7.3: The Numeric FOR LOOP:


*****************************************************************************************************
**************************************
15/APR/2008
*****************************************************************************************************
**************************************

FOR <loop_index> IN [REVERSE] <lowest_number> .. <highest_number>


LOOP
<executable_statement(s)>
END LOOP;

have atleast one statement between LOOP and END LOOP.

Loop termination: terminates when the number of times specified in the range scheme has been satisfied. can also terminate
using a EXIT statement, not recommended.
Test for termination: after each execution of the loop body, PLSQL checks the value of the loop index. If the lower bound is
greater than the upper bound of the range scheme, the loop never executes it's body.
Reason to use this loop: Use when you want to execute a body of code a fixed number of times.
7.3.1: Rules for Numeric For Loops:

* Don't declare loop index. PLSQL implicitly declares it as a local variable of datatype INTEGER. scope of this index is the
loop itself, cannot reference outside of the loop.
* Expressions with-in the range scheme are evaluated once when the loop starts. The range is not re-evaluated during the
execution of the loop. make changes within the loop to the variables which you used to determine the FOR loop range, those
changes will have no effect.
* never change values of either loop index or range boundary with-in the loop. bad practice.
* don't use EXIT statement with-in a FOR loop.
* Use REVERSE keyword to make the FOR loop decrement from upper bound to lower bound.

7.3.2: Examples of Numeric FOR LOOPS:

FOR loop_counter IN 1 .. 10
LOOP
executable statements ...
END LOOP;

FOR loop_counter IN REVERSE 1 .. 10


LOOP
executable statements ...
END LOOP;

FOR calc_index IN start_period_number .. LEAST(end_period_number, current_period)


LOOP
executable statements ...
END LOOP;

7.3.3: Handling Non-Trivial Increments:

cannot specify loop index increment. to obtain increment index, other than one, have to write some cute code.

FOR loop_index IN 1 .. 100


LOOP
IF MOD (loop_index, 2) = 0
THEN
calc_values(loop_index);
END IF;
END LOOP;

or

FOR loop_index IN 1 .. 50
LOOP
calc_values(loop_index*2);
END LOOP;

7.4: The Cursor FOR Loop:

FOR record_index IN cursor_name


LOOP
executable_statements ...
END LOOP;

record_index is declared implicitly by PLSQL with the %ROWTYPE attribute against the cursor specified by cursor_name.

Loop Termination: terminates un-conditionally when all of the records in the associated cursor have been fetched. can
terminate with the EXIT statement, not recommended.
Test for termination: after each execution of the loop body, PLSQL performs another fetch. If the %NOTFOUND attribute of
the cursor evaluates to TRUE, then the loop terminates.
Reason for use: Use this loop when u want to fetch and process every record in the cursor.

7.4.1: Example of Cursor FOR Loops:

DECLARE
CURSOR occupancy_cur IS
SELECT pet_id, room_number
FROM occupancy
WHERE occupancy_date = SYSDATE;
BEGIN
FOR occupancy_rec IN occupancy_cur
LOOP
update_bill(occupancy_rec.pet_id, occupancy_rec.room_number);
END LOOP;
END;

7.4.2: The cursor FOR loop record:


cannot reference the loop_record outside the FOR loop.
7.4.3: When to use Cursor FOR loop: Use it to scan through all the items in a set. Termination in between is not
recommended.

7.5: The WHILE loop:

WHILE <condition>
LOOP
<execution_statements>
END LOOP;

condition is boolean, evaluates to TRUE, FALSE, NULL. loop executes as long as the condition executes to TRUE.
Loop Termination: When boolean expression [condition] evaluates to FALSE or NULL.
Test For Termination: Evaluation of boolean condition occurs prior to the first and each subsequent execution of the body.
Reason For use: When not sure how many times you must execute loop body.
To conditionally terminate the loop.
Don't want to execute the loop atleast one time, if the condition results in FALSE or NULL.

7.5.1: Infinite WHILE loop:

WHILE TRUE
LOOP
<executable_statement(s)>
END LOOP;

7.6: Managing Loop Execution:

7.6.1: Loop Labels:


Can associate a label with a loop and use that label to increase your control over loop execution.
Syntax: <<loop_label>>
loop label must appear just before the LOOP statement.
EX:
<<all_emps>>
FOR emp_rec IN emp_cur
LOOP
...
END LOOP;
labels can also appear optionally after the END LOOP keyword.

<<year_loop>>
WHILE year = 'LEAP_YEAR'
LOOP
<<month_loop>>
FOR month_number IN 1 .. 12
LOOP
...
END LOOP month_loop;
END LOOP year_loop;

use dot notation to refer to loop related variables.

<<year_loop>>
WHILE year_number >= 1995
LOOP
<<month_loop>>
FOR month_number IN 1 .. 12
LOOP
IF year_loop.year_number = 1996
THEN
...
END IF;
END LOOP month_loop;
END LOOP year_loop;

7.6.1.1: Benefits of Loop Labels:


7.6.1.2: Loop Termination using labels:

EXIT loop_label;
EXIT loop_label WHEN condition;

Example:

<<year_loop>>
WHILE year_number >= 1995
LOOP
...
<<month_loop>>
FOR month_number IN 1 .. 12
LOOP
...
EXIT year_loop WHEN termination_condition -- Both the Loops are terminated.
END LOOP month_loop;
END LOOP year_loop;

7.6 Loop Scope: loop boundary creates a scope similar to that of a PL/SQL block.

loop index always takes precedence over a variable of same name declared outside the scope of the loop.

PROCEDURE calc_revenue (year_in IN NUMBER)


IS
month_number NUMBER (2) := 6;
BEGIN
FOR month_number IN 1 .. 12
LOOP
IF calc_revenue.month_number < 6 -- will retrieve the value 6, defined outside of the loop scope.
THEN
...
END IF;
calc_revenue_for_month(month_number); -- will use the loop index.
END LOOP;
END;

7.6.2.2: Scope With Labels:

<<year_loop>>
FOR date_number IN 1994 .. 1999
LOOP
<<month_loop>>
FOR date_number IN 1 .. 12
LOOP
IF year_loop.date_number = 1994 AND date_number = 1 -- date_number belongs to month_loop and year loop's
date_number is qualified by --it's loop label.
THEN
...
END IF;
END LOOP;
END LOOP year_loop;

Tips for Pl/SQL loops:

7.7.1: Naming loop indexes.


use meaningful and self-documenting loop indexes.
7.7.2: The proper way to say good bye.
Termination: only one way to enter a loop, number of ways to exit it.
7.7.2.1: Premature FOR loop termination: not advisable.
7.7.2.2: EXIT and EXIT WHEN statements: Should not be used in FOR and WHILE loops.
7.7.2.3: RETURN statement: never use a RETURN statement inside a loop.
7.7.2.4: GOTO statement: should not be used inside of a loop.

7.7.3: Avoiding the phony loop: use loops only where necessary.

7.7.4: PL/SQL loops versus SQL processing.

DECLARE
CURSOR checked_out_cur
IS
SELECT pet_id, name, checkout_date
FROM occupancy
WHERE checkout_date IS NOT NULL;
BEGIN
FOR checked_out_rec IN checked_out_cur
LOOP
INSERT INTO occupancy_history(pet_id, pet_name, checkout_date)
VALUES (checked_out_rec.pet_id, checked_out_rec.pet_name, checked_out_rec.checkout_date);
DELETE FROM occupancy WHERE pet_id = checked_out_rec.pet_id;
COMMIT;
END LOOP;
END;

The above process can be done using 2 SQL statements.

INSERT INTO occupation_history (pet_id, pet_name, checkout_date)


SELECT pet_id, pet_name, checkout_date
FROM occupancy
WHERE checkout_date IS NOT NULL;
DELETE FROM occupation WHERE checkout_date IS NOT NULL;

Use a cursor when we need to modify each record or selected records in a record set using different processes.

==========================================================================================
================================================
Chapter 8: Exception Handlers
==========================================================================================
================================================

Errors of any kind are treated as exceptions -- situations that should not occur. Can be one of the problem:

* An error generated by the system (such as "out of memory" or "duplicate value in index").
* An error caused by a user action.
* A warning issued by the application to the user.

Exception handling process cleanly seperates error handling process from executable statements. provides an event driven
model for processing errors. When exception occurs, current PL/SQL block's execution halts and control is transferred to the
seperate exception section of your program, to handle exceptions. Cannot return to that block after you finish handling the
exception. Instead, control is passed to the enclosing block.

8.1: Why Exception Handling:

Advantages of exception handling:

* Event driven handling of errors.


* Clean Processing of error processing blocks.
* Improved reliability of error handling

8.2: The Exception Section:

DECLARE
... declarations ...
BEGIN
... execution section ...
EXCEPTION
... exception handlers ...
END;

Syntax:

EXCEPTION
WHEN exception_name [or exception_name]
THEN
<executable statements>
END;

Example:

EXCEPTION
WHEN NO_DATA_FOUND
THEN
...
WHEN INVALID_CURSOR
THEN
...
WHEN payment_overdue
THEN
...
WHEN OTHERS
THEN
...
END;

WHEN OTHERS is optional. When it's not present, the exception is immediately raised to the enclosing block. If the
exception is not handled, then the user is presented directly with the error code and description.

8.3: Types of exceptions:

Four kinds of exceptions in SQL:

8.3.1: Named System Exceptions:


Exceptions that have been given names in PL/SQL and raised as a result of an error in PL/SQL or RDBMS processing.
declared in the STANDARD package of PL/SQL.
SQLCODE is a PL/SQL built-in function, which returns the status code of the last executed statement. SQLCODE returns 0 if
the last statement executed without any errors. In most, but not all cases, the SQLCODE valud is the same as the oracle error
code.

Should not declare standard exceptions in our programs. If we declared, we will have declared our own local exception. It will
not be raised when an internal error with that name occurs.

EXCEPTION
WHEN CURSOR_ALREADY_OPEN
THEN
CLOSE my_cursor;
END;

Exception List:

CURSOR_ALREADY_OPEN: Tried to OPEN a cursor that was already OPEN. Must CLOSE cursor before trying to OPEN
or ReOPEN it.
DUP_VAL_ON_INDEX: INSERT or UPDATE statement tried to store duplicate values in a column or columns in a row
which is restricted by a unique index.
INVALID_CURSOR: made a reference to a cursor that did not exist. FETCH or CLOSE a cursor before it is opened.
INVALID_NUMBER: PL/SQL executes an SQL statement which cannot convert a character string successfully into a
number.
LOGIN_DENIED: Program tried to login to Oracle RDBMS using invalid username/password combination.
NO_DATA_FOUND: Execute a SELECT_INTO statement that returned no rows. referenced an un-initialized row in a
PLSQL table. You read past the EOF using the UTL_FILE package.
NOT_LOGGED_ON: Program tried to execute a call to the database before it has logged into the db.
PROGRAM_ERROR: PL/SQL encountered an internal error.
STORAGE_ERROR: Program ran out of memory or somehow memory got corrupted.
TIMEOUT_ON_RESOURCE: Timeout has occurred in RDBMS, while waiting for resource.
TOO_MANY_ROWS: A SELECT INTO statement returned more than one row. SELECT INTO statement can return only
one row.
TRANSACTION_BACKED_OUT: remote part of a transaction is rolled back. either with an explicit ROLLBACK command
or as the result of some other action.
VALUE_ERROR: PL/SQL raises the VALUE_ERROR whenever it encounters an error having to do with the conversion,
truncation, or invalid constraining of numeric or character data.
ZERO_DIVIDE: Your program tried to divide by zero.

8.3.2: Named Programmer Defined Exceptions:


Exceptions that are raised as a result of errors in ur application code. You give these exceptions names by declaring them in
the declaration section. You then raise the exceptions explicitly in the program. PL/SQL exception handling model does not
make any distinction between internal errors and application specific errors.

Syntax for declaring custom exceptions:

exception_name EXCEPTION;

PROCEDURE calc_annual_sales
(company_id_in IN company.company_id%TYPE)
IS
invalid_company_id EXCEPTION;
no_sales_for_company EXCEPTION;
negative_balance EXCEPTION;

duplicate_company
BEGIN

...
RAISE invalid_company_id;
...
RAISE no_sales_for_company;
...
RAISE negative_balance;
EXCEPTION

WHEN invalid_company_id
THEN
...
WHEN no_sales_for_company
THEN
...
WHEN negative_balance
THEN
...
END;

8.3.3: Un-named System Exceptions:

Exceptions that are raised as a result of an error in PL/SQL or RDBMS processing, but have not been given names by
PL/SQL. Only the most common errors are named, the rest have numbers and can be assigned names using the PRAGMA
EXCEPTION_INIT syntax.

assign your own name to the Oracle or PL/SQL error raised in your program and then handle that error.

8.3.3.1: The EXCEPTION_INIT pragma:

use PRAGMA to associate a name with an internal code. EXCEPTION_INIT instructs the compiler to associate or initialize a
programmer defined exception with an oracle error code. EXCEPTION_INIT must appear in the declaration section of a
block, after declaration, the exception name is used in the PRAGMA.

DECLARE
exception_name EXCEPTION;
EXCEPTION_INIT(exception_name, error_code_literal);
BEGIN
...
error_code_literal: oracle error code, including the minus, if the error code is literal, which it is most of the time.

PROCEDURE delete_company (company_id_in IN NUMBER)


IS
child_records_exist EXCEPTION
EXCEPTION_INIT (child_records_exist, -2292);
BEGIN
DELETE FROM company
WHERE company_id = company_id_in;
EXCEPTION
WHEN child_records_exist
THEN
DBMS_OUTPUT.PUT_LINE ('Delete Child records first!');
END;

8.3.4: Un-named programmer defined exceptions:

Exceptions that are defined and raised in the server by the programmer. Here, programmer provides both the error number
(between -20000 and -20999) and an error message and raises the exception using RAISE_APPLICATION_ERROR. That
error along with that message is passed back to the client side application.

PROCEDURE RAISE_APPLICATION_ERROR (error_number_in IN NUMBER, error_msg_in IN VARCHAR2);

* System Exceptions (both named and un-named) are raised by PL/SQL when ever a program violates a rule in RDBMS (such
as "duplicate value in index") or causes a resource limit to be exceeded (ex: "maximum open cursors exceeded"). each
RDBMS error has a number associated with it.

8.4: Determining Exception Handling Behaviour:

8.4.1: Scope of an Exception:

Portion of code which is "covered" by that exception.

Named System Exceptions: Exceptions are globally available because they are not declared in or confined to any particular
block of code. can raise and handle a named system exception in any block.

Named Programmer Defined Exceptions: can only be raised in the execution and exception sections of the block in which they
are declared(and all nested blocks).

Unnamed system exceptions: can be handled in any PLSQL block using the WHEN OTHERS section. If they are assigned a
name, the scope of that name is the same as that of named programmer defined exceptions.

Unnamed programmer defined exceptions: only defined in the clause to RAISE_APPLICATION_ERROR and then is passed
to the calling program.

8.4.1.1: Scope of a programmer-defined exception:

PROCEDURE check_account(company_id_in IN NUMBER)


IS
overdue_balance EXCEPTION;
BEGIN
... executable statements ...
LOOP
...
IF ... THEN
RAISE overdue_balance
END IF;
END LOOP;
EXCEPTION
WHEN overdue_balance
THEN
...
END;

8.4.1.2: Raising exceptions in nested blocks:

PROCEDURE check_account(company_id_in IN NUMBER)


IS
overdue_balance EXCEPTION;
BEGIN
BEGIN
... executable statements ...
RAISE overdue_balance;
END;
... executable statements ...
LOOP
...
IF ... THEN
RAISE overdue_balance
END IF;
END LOOP;
EXCEPTION
WHEN overdue_balance
THEN
...
END;

8.4.1.3: Overlapping exception names:

locally declared exception will take precedence over system exception of the same name.

DECLARE
no_data_found EXCEPTION;
BEGIN
...
EXCEPTION
WHEN no_data_found -- locally declared exception. does not trap standard PLSQL generated [query that returns no rows].
THEN
...
END;

8.4.1.4: Qualifying exception names:

Using the locally defined exception and the standard exception of the same name at the same time. use dot notation on the pre-
defined exception. notation distinguishes between the two and finds handler for the pre-defined exception.

DECLARE
no_data_found EXCEPTION;
BEGIN
...
EXCEPTION
WHEN STANDARD.NO_DATA_FOUND
THEN
...
WHEN NO_DATA_FOUND
THEN
...
END;

or

EXCEPTION

WHEN NO_DATA_FOUND OR STANDARD.NO_DATA_FOUND


THEN
...
END;

8.4.1.5: Referencing duplicate exceptions in nested blocks:


Cannot declare a the same exception more than once in the same block. but can declare the same exception in nested blocks.
declaration local to the nested block takes precedence over the global exception. Both exceptions will be completely different.

PROCEDURE check_account (company_id_in IN NUMBER)


IS
overdue_balance EXCEPTION;
BEGIN
...
DECLARE
overdue_balance EXCEPTION;
BEGIN
...
RAISE overdue_exception;
END;

...
RAISE overdue_exception;
EXCEPTION
WHEN overdue_exception
THEN
...

END;

Above Procedure compiles, but gives a run-time error, "unhandled user-defined exception". Since the nested block has it's own
declaration section and has an exception declared, the flow first searches for an exception handler in the nested block, not
finding one, flow goes to the enclosing block, but the exception in the nested block is not recognized in the main block [even
though the names are the same]. Hence error is generated.

Ways To prevent this:


1. raise procedure level overdue_balance exception, with in subblock. use dot notation to qualify exception names.

RAISE check_account.overdue_balance;

2. make sure that locally raised overdue_balance exception is handled by the WHEN OTHERS exception in the enclosing
block.

8.4.2: Propogation of an exception:

When no exception handler found, PLSQL propogates the exception into enclosing block of that current block. PLSQL then
attempts to handle the exception by raising that exception once more in the enclosed block. continues to do so until there are
no more enclosing blocks. Ultimately, if no handler is found, then it's an un-handled exception.
8.5 Raising an Exception:

*****************************************************************************************************
*************************************
16/APR/2008
*****************************************************************************************************
*************************************

Four ways of raising exception:

* Programmer raised a unnamed programmer defined exception: raised using RAISE_APPLICATION_ERROR

8.5.1.1: PL/SQL runtime engine raised a named system exception: auto raised by PLSQL, use exception handler to handle
these.

PL/SQL raises ZERO_DIVIDE exception:

DECLARE
total_sales NUMBER := 0;
cust_sales NUMBER := 0;
sales_dominaiton EXCEPTION;
BEGIN
SELECT SUM (sales) INTO cust_sales
FROM invoice WHERE customer_id = 1001;
IF cust_sales/total_sales > .5
THEN
RAISE sales_domination;
END IF;
EXCEPTION
WHEN ZERO_DIVIDE
THEN
DBMS_OUTPUT.PUT_LINE('Divide by zero occurred');
WHEN sales_domination
THEN
DBMS_OUTPUT.PUT_LINE('Customer 1001 accounts for more than half of all sales!');
END;

8.5.1.2: Programmer raised a named exception: explicit call to RAISE statement:

Programmer raises ZERO_DIVIDE exception.

DECLARE
total_sales NUMBER := 0;
cust_sales NUMBER;
sales_domination EXCEPTION;
BEGIN
SELECT SUM (sales) INTO cust_sales
FROM invoice WHERE customer_id = 1001;
IF total_sales = 0
THEN
RAISE ZERO_DIVIDE;
ELSIF cust_sales/total_sales > .5
THEN
RAISE sales_domination;
END IF;
EXCEPTION
...
END;

8.5.1.3: Impact of un-handled exceptions:

If the program contains IN OUT or OUT params, then PL/SQL engine does not assign values to those params. Any changes
made to those params during program execution are rolled back.
* Runtime engine does not ROLLBACK any changes made to the db during program execution. Have to issue an explicit
ROLLBACK statement to do this.

8.5.2: Re-raising an exception:

Programmer re-raises the current exception.

RAISE; -- This statement re-raises the exception most recently occurred.

EXCEPTION
WHEN OTHERS
THEN
send_error_to_pipe (SQLCODE);
RAISE;
END;

8.5.3: Exceptions raised in a declaration:

When an exception is raised in the declaration section of a PL/SQL block, then control is passed to the enclosing block, if any.
Not to the exception section of the current PL/SQL block.

8.5.4: Exception raised in an exception handler.

Can raise another exception from with-in an exception handler. this exception can only be handled in the exception section of
the enclosing block, never in the current block.

Using exceptions raised inside the exception block:

DECLARE -- block 1
BEGIN
DECLARE -- block 2
skip_sub-block EXCEPTION;
BEGIN
...
DECLARE -- block 3
BEGIN
...
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RAISE skip_sub_block;
END;
EXCEPTION
WHEN skip_sub_block
THEN
NULL;
END;
...
EXCEPTION
END;
8.6: Handling Exceptions:

Once an exception is raised, the execution section is terminated. Cannot return to that body of the code. WHEN OTHERS
clause must be the last exception handler in the exception section.

8.6.1: Combining Multiple Exceptions in a single handler:

WHEN balance_too_low OR ZERO_DIVIDE


THEN

Cannot use AND, since only one exception can be raised at a time.

8.6.2: Unhandled Exceptions:

Best way to handle un-handled exceptions: Make sure that the outermost PL/SQL block contains a WHEN OTHERS clause in
the exception section.

8.6.3: Using SQLCODE and SQLERRM in WHEN OTHERS clause:

SQLCODE: returns ERROR code; SQLERRM: returns ERROR message.

PROCEDURE delete_company (company_id_in IN NUMBER)


IS
BEGIN
DELETE FROM company
WHERE company_id = company_id_in;
EXCEPTION
WHEN OTHERS
THEN
DECLARE
error_code := SQLCODE;
error_message := SQLERRM;
BEGIN
IF error_code = -2292
THEN
...
ELSIF error_code = -2291
THEN
...
ELSE
...
END IF;
END;
END delete_company;

8.6.4: Continuing Past Exceptions:

PROCEDURE change_data IS
BEGIN
BEGIN
DELETE FROM employees WHERE ...;
EXCEPTION
WHEN OTHERS THEN NULL;
END;

BEGIN
UPDATE company SET ...;
EXCEPTION
WHEN OTHERS THEN NULL;
END;

BEGIN
INSERT INTO company_history ...;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END;

8.7: Client Server Communication:

RAISE_APPLICATION_ERROR feature provided by Oracle to communicate application specific errors from the server side
(usu a database trigger) to the client side application.

8.7.1: Using RAISE_APPLICATION_ERROR:

PROCEDURE RAISE_APPLICATION_ERROR
(error_number IN NUMBER, error_msg IN VARCHAR2)

Example:

RAISE_APPLICATION(-20001, 'Sample Exception');

When calling RAISE_APPLICATION_ERROR, it's as though an exception has been raised with the RAISE statement.
Execution of the PLSQL block halts immediately and any changes made to OUT or IN OUT arguments (if present) will be
reversed. Changes made to db will not be ROLLBACK, will have to issue an explicit ROLLBACK to counter this. The built-
in returns a programmer defined error number and message back to the client component of the application. Can then use
EXCEPTION_INIT pragma and exception handlers to handle the error in graceful and user-friendly manner.

Error number you specify must be between -20000 and -20999. Error message cannot be more than 2K bytes in length, if it's
longer the procedure will simply truncate anything beyond 2K.

8.7.2: RAISE_APPLICATION_ERROR in a database trigger:

CREATE OR REPLACE TRIGGER minimum_age_check


BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
IF ADD_MONTHS(:month_val, 18*12) > SYSDATE
THEN
RAISE_APPLICATION_ERROR (-20001, 'Atleast 18 Years Age!!!');
END IF;
END;

On Client Side:

DECLARE
no_babies_allowed EXCEPTION;
PRAGMA EXCEPTION_INIT(no_babies_allowed, -20001);
BEGIN
INSERT INTO employee ...;
EXCEPTION
WHEN no_babies_allowed
THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
8.8: NO_DATA_FOUND Multi-purpose exception:

NO_DATA_FOUND raised under 3 circumstances:

1. An Implicit Query returns no data.


2. Attempt to reference a row in PLSQL table which has not yet been defined.
3. Attempt to read past the end of an operating system file.

Overlapping use of the same exception could cause some confusion. Suppose in a single PL/SQL block, we query from an
implicit table and also make references to a PL/SQL table's row. NO_DATA_FOUND could be raised in both scopes, how to
know which one raised it? Use nested PLSQL blocks to resolve this issue.

FUNCTION company_name (id_in IN NUMBER, access_type_in IN VARCHAR2)


RETURN VARCHAR2
IS
return_value VARCHAR2(60);
bad_data_in_select EXCEPTION;

BEGIN
IF access_type_in = 'DBMS'
THEN
BEGIN
SELECT name INTO return_value
FROM company
WHERE company_id = id_in;
RETURN return_value;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RAISE bad_data_in_select;
END;
ELSIF access_type_in = 'MEMORY'
THEN
RETURN company_name_table(id_in);
END IF;
EXCEPTION
WHEN bad_data_in_select
THEN
DBMS_OUTPUT.PUT_LINE('Unable to locate company in Database.');

WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE('Unable to locate company in memorized table.');
END;

8.9: Exception Handler as an IF statement:

FUNCTION convert_date (value_in IN VARCHAR2)


RETURN DATE
IS
DECLARE
return_value DATE;
BEGIN
IF value_in IS NULL THEN return_value := NULL;
ELSE
return_value := TO_DATE(value_in, 'MM/DD/YY');
EXCEPTION
WHEN OTHERS
THEN
BEGIN
return_value := TO_DATE(value_in, 'DD-MON-YY');
EXCEPTION
BEGIN
return_value := TO_DATE(value_in, 'MM/YY');
EXCEPTION
WHEN OTHERS
THEN
return_value = NULL;
END;
END;
END IF;
RETURN return_value;
END;

8.10: Raise Nothing But Exceptions.

==========================================================================================
================================================
Chapter 09: Records in PL/SQL
==========================================================================================
================================================
*****************************************************************************************************
*************************************
24/JUL/2008
*****************************************************************************************************
*************************************
Records very similar in concept and structure to the rows of a database tables. Composite data structure: composed of more
than one element, each with it's own value. record as a whole does not have a value, instead each component or field has a
value.

9.1 Record Basics:

9.1.1: Different types of Records:

Table Based: A record based on a table's column structure. Each field corresponds to -- and has the same name as -- a column
in a table.
Cursor Based: A record based on a cursor's select statement. Each field corresponds to a column or expression in the cursor's
select statement.
Programmer Defined: A record whose structure the programmer gets to define with a declaration statement. Each field is
defined explicitly (its name and datatype) in the TYPE statement of that record. a field in a programmer based record can also
be another record.

9.1.2: Accessing record based data: Using the dot notation.


<record_name>.<field_name>
Ex: employee_rec.emp_full_name

9.1.3: Benefits of using records:

*** Data abstraction: Instead of working with individual attributes of an entity or an object, think of and manipulate that entity
as a "thing in itself". When you create a record, you abstract all the different attributes or fields of the subject of that record.
you establish a relationship between all those different attributes and give that relationship a name by defining that record.

*** Aggregate operations: can perform operations which apply to all columns in a record.

*** Leaner / Cleaner code.


9.1.4: Guidelines for using records:

** Create corresponding cursors and records: Whenever we create a cursor, also create the corresponding record [except for
cursor FOR loops]. Always fetch into that record, instead of individual variables.

** Create table based records: using %ROWTYPE attribute.

** Pass records as parameters, rather than individual variables, when possible. Drawback: If a record is passed as an OUT or
IN OUT parameter, it's field values are saved by the PLSQL program in case of the need for a rollback. This can use up
memory and consume un-necessary CPU cycles.

9.1.5: Referencing a Record and it's fields:

9.1.6: Comparing 2 records:

To compare records, must always do so through comparison of record's individual fields. No such thing as a "read-only"
PLSQL record structure.

Comparison:

IF old_company_rec.name = new_company_rec.name
AND old_company_rec.start_date = new_company_rec.start_date
AND old_company_rec.address1 = new_company_rec.address1
THEN
....
END IF;

Overwriting:

DECLARE
CURSOR company_cur IS ...;
company_rec company_cur%ROWTYPE;
BEGIN
OPEN company_cur;
FETCH company_cur INTO company_rec;
company_rec.name = 'New Name';
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;

9.2: Table Based Records:

9.2.1: Declaring records with the %ROWTYPE attribute.


<record_name> <table_name>%ROWTYPE

DECLARE
comp_name company.name%TYPE;
comp_rec company%ROWTYPE;
BEGIN
SELECT * FROM company
INTO comp_rec
WHERE company_id = 1024;
...
END;

9.3 Cursor Based Records


A record whose structure is drawn from the select list of a cursor. each field in the record corresponds to and has the same
name as the column or aliased expression in the cursor's query.

DECLARE
CURSOR comp_summary_cur IS
SELECT C.company_id, c.name, c.city
FROM company c, sales s
WHERE c.company_id = s.company_id

comp_summary_rec comp_summary_cur%ROWTYPE;
BEGIN
...
END;

<record_name> <cursor_name>%ROWTYPE;

9.3.1: Choosing columns for a cursor record.

DECLARE
CURSOR high_losses_cur IS
SELECT country_code dying_country_cd,
size_in_acres shrinking_plot,
species_lost above_avg_loss
FROM rain_forest_history
WHERE species_lost >
(SELECT AVG(species_lost)
FROM rain_forest_history
WHERE TO_CHAR(analysis_date, 'YYYY') = '1994');

high_losses_rec high_losses_cur%ROWTYPE;
BEGIN
OPEN high_losses_cur;
LOOP
FETCH high_losses_cur INTO high_losses_rec;
EXIT WHEN high_losses_cur%NOTFOUND;

publicize_loss(high_losses_rec.dying_country_cd);
project_further_damage(high_losses_rec.shrinking_plot);
END LOOP;
CLOSE high_losses_cur;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;

9.3.2: Setting the Record's Column Names:

A cursor's query can also include calculated values or expressions. Here, we must provide an alias for that calculated value, if
we want to access that through a record.

CURSOR comp_performance_cur(id_in IN NUMBER)


IS
SELECT name, SUM(order_amount) tot_sales
FROM company
WHERE company_id = id_in;
comp_performance_rec comp_performance_cur%ROWTYPE;

IF comp_performance_rec.tot_sales > 10000 THEN ...

9.4: Programmer-Defined Records:

Perform 2 steps to declare programmer defined records:


*****************************************************************************************************
*************************************
14/AUG/2008
*****************************************************************************************************
*************************************
1. Declare or define a record TYPE containing the structure you want in your record.
2. Use this record TYPE as the basis for declarations for your own actual records having that structure.

9.4.1: Declaring Programmer defined Record TYPEs:

Syntax:

TYPE <type_name> IS RECORD


(<field_name1> <datatype1>,
<field_name2> <datatype2>,
...
<field_namen> <datatypen>
);

Datatype can be any of the following:

Pre-defined datatype (VARCHAR2, NUMBER etc)


Programmer defined subtype
Declarations using %TYPE attribute
Declarations using %ROWTYPE attribute
PLSQL record type
PLSQL table type

cannot declare a field to be an exception or a cursor [with PLSQL 2.3, can declare a field to be a cursor variable.]

TYPE company_rectype IS RECORD


(comp# company.company_id%TYPE,
name company.name%TYPE
);

9.4.2: Declaring the Record:

<record_name> <record_type>;

TYPE customer_sales_rectype IS RECORD


(customer_id NUMBER(5),
customer_name customer.name%TYPE,
total_sales NUMBER(15,2)
);

prev_customer_sales_rec customer_sales_rectype;
top_customer_rec customer_sales_rectype;

No %ROWTYPE here, it is only needed for table and cursor records.

* Can also specify default values using DEFAULT keyword or := in record definition.
* Can apply constraints like NOT NULL (here we also need to assign a default value).
* Each field name within a record must be unique.

9.4.3: Examples of programmer defined record declarations:

SUBTYPE long_line_type IS VARCHAR2;

CURSOR company_sales_cur IS
SELECT name, SUM(order_amount) total_sales
FROM company c, order o
WHERE c.company_id = o.company_id;

TYPE employee_ids_tabletype IS
TABLE OF employee.employee_id
INDEX BY BINARY_INTEGER;

TYPE company_rectype IS RECORD


(company_id company.company_id%TYPE,
company_name company.name%TYPE,
new_hires_tab employee_ids_tabletype
);

TYPE mish_mash_rectype IS RECORD


(emp_number NUMBER(10) NOT NULL := 100,
paragraph_text long_line_type,
company_nm company.name%TYPE,
total_sales company_sales_cur.total_sales%TYPE := 0,
new_hires_tab employee_ids_tabletype,
prefers_nonsmoking_fl BOOLEAN := FALSE,
new_company_rec company_rectype
);

9.5: Assigning Values To and From Records:

* Direct Field Assignment using assignment operator:

top_customer_rec.total_sales := 0;
report_rec.output_generated := check_report_status(report_rec.report_id);

DECLARE
rain_forest_rec rain_forest_history%ROWTYPE;
BEGIN
rain_forest_rec.country_code := 1005;
rain_forest_rec.analysis_date := SYSDATE;
rain_forest_rec.size_in_acres := 32;
rain_forest_rec.species_lost := 425;

INSERT INTO rain_forest_history VALUES


( rain_forest_rec.country_code,
rain_forest_rec.analysis_date,
rain_forest_rec.size_in_acres,
rain_forest_rec.species_lost
);
....
END;

* SELECT INTO from an implicit cursor:


DECLARE
TYPE customer_sales_rectype IS RECORD
(customer_id NUMBER(5),
customer_name customer_name%TYPE,
total_sales NUMBER(15,2)
);
top_customer_rec customer_sales_rectype;
BEGIN
SELECT customer_id, name, SUM (sales)
INTO top_customer_rec
FROM customer
WHERE sold_on BETWEEN < ADD_MONTHS(SYSDATE, -3);

SELECT customer_id, name, SUM(sales)


INTO top_customer_rec.customer_id, top_customer_rec.customer_name,
top_customer_rec.total_sales
FROM customer
WHERE sold_on BETWEEN < ADD_MONTHS(SYSDATE, -3);
....

* FETCH INTO from an explicit cursor:

DECLARE

CURSOR cust_sales_cur IS
SELECT customer_id, name, SUM(sales) tot_sales
FROM customer
WHERE sold_on BETWEEN < ADD_MONTHS(SYSDATE, -3);
cust_sales_rec cust_sales_cur%ROWTYPE;
BEGIN

OPEN cust_sales_cur;
FETCH cust_sales_cur INTO cust_sales_rec;

/* or */

OPEN cust_sales_cur;
FETCH cust_sales_cur INTO
cust_sales_rec.customer_id,
cust_sales_rec.name
cust_sales_rec.tot_sales;
....

* Aggregate assignment:

DECLARE
prev_rain_forest_rec rain_forest_history%ROWTYPE;
curr_rain_forest_rec rain_forest_history%ROWTYPE;
BEGIN
...
curr_rain_forest_rec := prev_rain_forest_rec;
...

9.6: Record Types and Record Compatibility:

Three types of records: Table-based, cursor-based and programmer defined. Record defined by it's name, type and structure.
Two records can have the same structure but be of a different type.
Table structure against which all different records will be declared:

CREATE TABLE cust_sales_roundup


(customer_id NUMBER (5),
customer_name VARCHAR2(100),
total_sales NUMBER (15, 2)
);

* table based record:

cust_sales_roundup_rec cust_sales_roundup%ROWTYPE;

* cursor based record:

CURSOR cust_sales_cur IS
SELECT * FROM cust_sales_roundup;

cust_sales_rec cust_sales_cur%ROWTYPE;

programmer defined record:

TYPE customer_sales_rectype IS RECORD


(customer_id NUMBER (5),
customer_name customer_name%TYPE,
total_sales NUMBER (15, 2)
);

* manual record: collection of individual variables which the programmer can treat as a group by always making changes to
and referring to all variables together.

v_customer_id NUMBER (5);


v_customer_name customer_name%TYPE;
v_total_sales NUMBER (15, 2);

Above records have the same structure but each is of a different type. Records of different types are incompatible with each
other at the record level.

9.6.1: Assignment Restrictions:

** Manual Records: Cannot assign a manual record to a real record of any type and vice versa. Must execute a seperate
assignment for each field in the record.

top_customer_rec.customer_id = v_customer_id;
top_customer_rec.customer_name = v_customer_name;
top_customer_rec.total_sales = v_total_sales;

** Records of the same type: can perform aggregate assignments only between records of the same type and the same source.

cust_sales_roundup_rec := top_customer_rec; /*Incompatible*/


cust_sales_rec := cust_sales_roundup_rec; /*Incompatible*/

Even when records in an aggregate assignment are of the same type and structure, the following rules must hold:

* Both cursor based records in an aggregate assignment must be based on the same cursor.
* Both table based records in an aggregate assignment must be based on the same table.
* Both programmer defined records in an aggregate assignment must be based on the same TYPE ... RECORD statement.

** Setting records to NULL:


assignment of NULL to a record is allowed. will set each of the fields in the record to default value of NULL.

** Record Initialization:
* Can initialize a table or cursor based record at the time of declaration only with another record of same type and source.

PROCEDURE create_companies(prev_company_rec IN company%ROWTYPE)


IS
curr_company_rec company%ROWTYPE := prev_company_rec;
BEGIN
...
END;

* Create a new record type and record. then create a second record type using the first record as it's single column. Initialize
this new record with the previously defined record.
DECLARE
TYPE first_rectype IS RECORD (var1 VARCHAR2(100));
first_rec first_rectype;

TYPE second_rectype IS RECORD (nested_rec first_rectype := first_rec);


BEGIN
...
END;

9.7: NESTED Records:

Can include a record as a field within another record. This is called a nested record. The record that contains the nested record
as a field is called the enclosing record.

** Example of a nested record:

DECLARE
TYPE phone_rectype IS RECORD
(intl_prefix VARCHAR2(2),
area_code VARCHAR2(3),
exchange VARCHAR2(3),
phn_number VARCHAR2(4),
extension VARCHAR2(4)
);

TYPE contact_set_rectype IS RECORD


(day_phone# phone_rectype,
eve_phone# phone_rectype,
fax_phone# phone_rectype,
cell_phone# phone_rectype
);
...

without nested records:

DECLARE
TYPE phone_rectype IS RECORD
(day_intl_prefix VARCHAR2(2), day_area_code VARCHAR2(3),
day_exchange VARCHAR2(3), day_phn_number VARCHAR2(4),
day_extension VARCHAR2(4),

eve_intl_prefix VARCHAR2(2), eve_area_code VARCHAR2(3),


eve_exchange VARCHAR2(3), eve_phn_number VARCHAR2(4),
eve_extension VARCHAR2(4),

fax_intl_prefix VARCHAR2(2), fax_area_code VARCHAR2(3),


fax_exchange VARCHAR2(3), fax_phn_number VARCHAR2(4),
fax_extension VARCHAR2(4),

cell_intl_prefix VARCHAR2(2), cell_area_code VARCHAR2(3),


cell_exchange VARCHAR2(3), cell_phn_number VARCHAR2(4),
cell_extension VARCHAR2(4)
);
auth_rep_info_rec contact_set_rectype;
BEGIN
...

* Unnested records are difficult to read.


* Unnested records are difficula to maintain.

** Dot Notation with records:


DECLARE
TYPE level3_rectype IS RECORD (textline VARCHAR2(1000));
TYPE level2_rectype IS RECORD (paragraph level3_rectype);
TYPE level1_rectype IS RECORD (story level2_rectype);
level1_rec level1_rectype;

Fully Qualified name for the textline variable:


level1_rec.story.paragraph.textline

auth_rep_info_rec.fax_phone#.area_code := auth_rep_info_rec.home_phone#.area_code;

** Aggregate Assignments of nested records:

auth_rep_info_rec.fax_phone# := auth_rep_info_rec.home_phone#;

possible becuz both the fax_phone# and home_phone# have the same record type, phone_rectype.

DECLARE
TYPE contact_set_rectype IS RECORD
(day_phone# phone_rectype,
eve_phone# phone_rectype,
fax_phone# phone_rectype,
cell_phone# phone_rectype
);
TYPE emerg_contacts_rectype IS RECORD
(emerg1_phone# phone_rectype,
emerg2_phone# phone_rectype
);

auth_rep_info_rec contact_set_rectype;
in_an_emergency_rec emerg_contacts_rectype;
BEGIN
in_an_emergency_rec.emerg1_phone# := auth_rep_info_rec.day_phone#;
END;

in_an_emergency_rec := auth_rep_info_rec [Incompatible]

** DeNormalizing program data in nested records:

DECLARE
TYPE comp_address_rectype IS RECORD
(addr1 company.addr1%TYPE,
addr2 company.addr2%TYPE,
addr3 company.addr3%TYPE,
addr4 company.addr4%TYPE,
city company.city%TYPE,
state company.state_abbrev%TYPE,
zipcode company.zipcode%TYPE
);

TYPE company_rectype IS RECORD


(comp_id company.company_id%TYPE,
comp_name company.name%TYPE,
recv_address_rec comp_address_rectype,
ship_address_rec comp_address_rectype,
ap_address_rec comp_address_rectype,
cr_address_rec comp_address_rectype
);
* Nested Records hide data complexity:

PROCEDURE display_company(company_rec_in IN company_rectype)

above is better than

PROCEDURE display_company
(company_rec_in IN company_rectype,
recv_address_in IN comp_address_rectype,
ship_address_in IN comp_address_rectype,
ap_address_in IN comp_address_rectype,
cr_address_in IN comp_address_rectype)

here even if the company doesn't have an address, i still have to pass it as a paramter for the procedure to compile.

==========================================================================================
================================================
Chapter 10: PLSQL Tables:
==========================================================================================
================================================

10.1: PLSQL Tables and Other Collections:

PROCEDURE
IS
TYPE string_tabletype IS
TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER;

company_name_table string_tabletype;
BEGIN
company_name_table (row_in) := name_in;
EXCEPTION
WHEN OTHERS
THEN NULL;
END;

* PLSQL table: one type of collection structure.

**PL/SQL Tables
* Reside in the private PLSQL area of the Oracle Server database instance, not available to client side structures at this time.
cannot declare and manipulate PL/SQL tables in oracle developer/2000 environment.
* Can build stored procedures and packages which use PLSQL tables. can call this stored procedures from oracle developer /
2000 env.

**Nested Tables and VARRAYs:


* can be used as datatypes of fields in conventional tables and attributes of objects.

10.2: Characteristics of PL/SQL tables:

Definition of PLSQL table: One-dimensional, unbounded, sparse collection of homogeneous elements, indexed by integers.

* One dimensional: PLSQL table can have only one column.


* Unbounded or Unconstrained: No predefined limit to the number of rows in a PL/SQL table, grows dynamically as we add
more rows to the table. No rows are allocated for this structure, when it is defined.
* Sparse: In a PLSQL table, a row exists in the table only when a value is assigned to that row. Rows do not have to be
assigned sequentially. Row 15 can have a value of 'Fox' and Row 15446 can have a value of 'Eagle'.
* Homogeneous elements: Because a plsql table contains a single column, all rows must contain values of the same datatype.
Can have a plsql table of records. All rows will have the same set of columns.
* Indexed by integers: Indexed by BINARY_INTEGER. Range of Binary Integer: -2pow31 -1 to 2pow31 -1. Since row
number does not have to be used sequentially and has an enormous range, can use this integer index in interesting ways. eg:
row number of PLSQL table can be the primary key of the table.

10.3: PLSQL tables and DML statements.


** Tables are PLSQL constructs and are not a part of the SQL language and standards.
* No concept of transaction integrity with PL/SQL tables. cannot commit information or roll back changes from the table.
* Cannot select from a PLSQL table. Instead, we use loops to move through the table row by row.
* Cannot issue DML statements like INSERT, UPDATE or DELETE. [PLSQL 2.3 offers a delete operator.]

10.4: Declaring a PLSQL table:

2 steps:
* Define a particular PLSQL table structure, using the table TYPE statement. Result of this statement is a datatype which can
be used in declaration statements.
* Declare the actual table based on that table type.

** Defining the table TYPE:

TYPE <table_type_name> IS TABLE OF <datatype> [NOT NULL]


INDEX BY BINARY_INTEGER;

BINARY_INTEGER allows the fastest retrieval of data.

Rules for table type name are the same as any PLSQL identifier. 30 chars length, must start with a letter, can include special
characters such as _ or $.

Datatype can be any of the following:

1. Scalar datatype or 2. Anchored datatype. 3. Records [For PLSQL 2.3].

** Declaring the PLSQL table:

<table_name> <table_type>

PACKAGE company_pkg
IS
TYPE primary_keys_tabtype IS TABLE OF NUMBER NOT NULL
INDEX BY BINARY_INTEGER;
company_keys_tab primary_keys_tabtype;
emp_keys_tab primaryh_keys_tab_type;
...
END company_pkg;

10.5: Referencing and modifying PL/SQL table rows:

Row in a PL/SQL table is a scalar value. Reference using following syntax:

<table_name> (<primary_key_value>)

primary_key_value is compatible with the BINARY_INTEGER data type. Assign values to a row using ':='.

company_name_tab (15) := 'MSIT OU';


company_keys_tab (-2000) := new_company_id;
header_string := 'Sales Of' || company_name_tab (15);

** Automatic conversions of row number expressions:

* Store the string in row 16:


requests_table(15.556) := 'Totals By Category';

* Store the string in row 255:


company_keys_table('25'||'5') := 1000;

* Expression will be evaluated and used as a row number:


key_wordlist_table (last_row + 15) := 'Bow Wow';

** Referencing an undefined row:

A row in a PLSQL table does not exist until you assign a value to that row. PLSQL does not create the memory structure for
the rows in the table until we need them. Whenever we assign a value to a row in the PLSQL table, PLSQL creates a row.

PLSQL raises the NO_DATA_FOUND exception if we reference a row which does not exist. NO_DATA_FOUND is also
raised when an implicit CURSOR SELECT statement does not return any rows or when you attempt to read past the end of the
file with the UTL_FILE package.

DECLARE
new_request NUMBER;
TYPE request_tabtype IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
request_table request_tabtype;
BEGIN
new_request := request_table(15);
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
END;

** Nonsequential use of PLSQL table:


Can place a value in any row of the table you wish, regardless of the primary key value of the last row you filled.

DECLARE
TYPE test_tabtype IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
test_table test_tabtype;
BEGIN
test_table (1) := 'value1';
test_table (20) := 'value2';
table_table (255) := 'value3';
END;

** Passing PLSQL tables as parameters:


In a single call, pass all the values in a table to the module. The send_promos procedure sends a promotional mailing to all the
companies in my table. The company_overdues procedure returns a table with the names of the companies which have
overdue bills.

PACKAGE company_pkg
IS

TYPE primary_keys_tabtype IS TABLE OF company.company_id%TYPE NOT NULL


INDEX BY BINARY_INTEGER;
company_keys_tab primary_keys_tabtype;
emp_keys_tab primary_keys_tabtype;

TYPE company_names_tabtype IS TABLE OF company.name%TYPE


INDEX BY BINARY_INTEGER;
company_names_tab company_names_tabtype;

PROCEDURE send_promos (company_table_in IN primary_keys_tabtype);

FUNCTION companies_overdue (overdue_date_in IN DATE)


RETURN company_names_tabtype;

FUNCTION id(name IN company.name%TYPE)


RETURN company.company_id%TYPE;

END company_pkg;

* you must declare a PLSQL table based on the type before you can use any of the programs.

CREATE OR REPLACE PROCEDURE send_promos_for_overdue_companies


(date_in IN DATE := SYSDATE)
IS
v_row PLS_INTEGER;
cnames company_pkg.company_name_tabtype;
BEGIN
cnames := company_pkg.companies_overdue(date_in);
v_row := cnames.FIRST;
LOOP
EXIT WHEN v_row IS NULL;
DBMS_OUTPUT.PUT_LINE(cnames(v_row));
v_row := cnames.NEXT (v_row);
END LOOP;
EXCEPTION
WHEN OTHERS
THEN NULL;
END;

Ex02:

DECLARE
v_row PLS_INTEGER;
company_ids company_pkg.primary_keys_tabtype;
date_in := SYSDATE;
BEGIN
company_pkg.company_names_tab := company_pkg.companies_overdue(date_in);
v_row := company_pkg.company_names_tab.FIRST;
LOOP
EXIT WHEN v_row IS NULL;
company_ids(NVL(company_ids.LAST, 0) +1) := company_pkg.id(company_pkg.company_names_tab(v_row));
v_row := company_pkg.company_names_tab.NEXT (v_row);
END LOOP;

company_pkg.send_promos(company_ids);
company_pkg.company_names_tab.DELETE;
EXCEPTION
WHEN OTHERS
THEN NULL;
END company_pkg;

10.6 Filling Rows of a PLSQL table:


* Direct Assignment
Eg:
countdown_test_list (43) := 'Internal Pressure';
company_names_table (last_name_row) := 'Johnstone Clingers';

* Iterative
Eg:

CREATE OR REPLACE PROCEDURE show_bizdays(start_date_in IN DATE := SYSDATE, ndays_in IN NUMBER := 30)


IS
TYPE bizdays_tabtype IS TABLE OF DATE INDEX BY BINARY_INTEGER;
bizdays_norm bizdays_tabtype;

nth_day BINARY_INTEGER := 1;
v_date DATE := start_date_in;
BEGIN
WHILE nth_day < ndays_in
LOOP
IF TO_CHAR(v_date, 'DY') NOT IN ('SAT', 'SUN')
THEN
bizdays_norm (nth_day) := v_date;
DBMS_OUTPUT.PUT_LINE(v_date);
nth_day := nth_day + 1;
END IF;
v_date := v_date + 1;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN NULL;
END show_bizdays;

* Aggregate:
Eg:

DECLARE
TYPE name_tabtype IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
old_names name_tabtype;
new_names name_tabtype;
BEGIN
old_names(1) := 'Smith';
old_names(2) := 'Raul';
new_names(1) := 'Smita';
new_names(2) := 'Rahul';

old_names := new_names;

-- DBMS_OUTPUT.PUT_LINE(old_names(1)); -- This statement will raise a no data found exception.


EXCEPTION
WHEN OTHERS
THEN NULL;
END;

10.7: Clearing the PLSQL table:

* Can set a single row to NULL : company_names_table (num_rows) := NULL.


But this assignment doesn't actually remove the row or make it undefined, it just sets the value of the row to NULL.

One way to empty a PLSQL table of all rows is to perform an aggregate assignment with a table that is empty.

DECLARE
TYPE company_names_tabtype IS TABLE OF company.name%TYPE INDEX BY BINARY_INTEGER;
company_names_tab company_names_tabtype;
empty_company_names_tab company_names_tabtype;
BEGIN
...
company_names_tab := empty_company_names_tab;
EXCEPTION
WHEN OTHERS
THEN NULL;
END;

10.8: PLSQL table enhancements:

* Records are supported as elements of PLSQL tables.

** Built-in functions of PLSQL tables:


General Syntax for calling PLSQL table built-ins:
* An operation which takes no arguments:
<table_name>.<operation>
* An operation which takes a row index as an argument:
<table_name>.<operation>(row_index1 [, row_index2])

* COUNT: Returns the number of elements currently contained in the PLSQL table.
Spec: FUNCTION COUNT RETURN INTEGER;
Eg: total_rows := emp_table.COUNT;
if emp_table is defined in emp_pkg, then total_rows := emp_pkg.emp_table.COUNT;

* DELETE: Deletes one or more elements from the PLSQL table.


Spec:
1. PROCEDURE DELETE; -- Deletes all rows in the PLSQL table.
2. PROCEDURE DELETE (index_in IN INTEGER); -- Deletes this particular row in the PLSQL table.
3. PROCEDURE DELETE (start_index_in IN INTEGER, end_index_in IN INTEGER); -- Deletes rows between start index
and end index in the table.

Info:
-- If any one of the arguments is NULL, then DELETE will not delete any rows at all.
-- If end_index is less than start_index, then no rows will be deleted.

Eg:
01. names_tab.DELETE; -- Deletes all rows in names_tab.
02. names_tab.DELETE (15); -- Deletes 15th row in names_tab.
03. names_tab.DELETE (-15000, 0); -- Deletes all rows between -15000 and 0 inclusive.

* EXISTS: Returns FALSE if a reference to an element at the specified index would raise the NO_DATA_FOUND exception.
Returns TRUE if an element is defined at the specified index of the table.
Spec: FUNCTION EXISTS (index_in IN INTEGER) RETURN BOOLEAN;
Eg: IF names_tab.EXISTS (1) THEN ...

Emulation of EXISTS function:

FUNCTION row_exists (table_in IN <table_type>, index_in IN INTEGER)


RETURN BOOLEAN
IS
mrvar VARCHAR2(20);
BEGIN
myvar := table_in(index_in);
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN FALSE;
END;

* FIRST: Returns the smallest index of the PLSQL table for which an element is defined.
SPEC: FUNCTION FIRST RETURN INTEGER;
Eg:
first_entry_row := names_tab.FIRST;
Info: If the PLSQL table does not contain any elements, then FIRST returns NULL.

* LAST: Returns the greatest index of the PLSQL table, for which an element is defined.
Spec: FUNCTION LAST RETURN INTEGER;
Eg: last_entry_row := names_tab.LAST;
Info: If PLSQL table does not contain any rows, then LAST returns NULL.
If you plan to fill the PLSQL table sequentially with values, then we need to use NVL function to convert NULL into 0.

FOR company_rec IN company_cur


LOOP
next_row := NVL(company_table.LAST, 0) + 1;
company_table(next_row) := company_rec;
END LOOP;

* NEXT: Returns the smallest index of the PLSQL table greater than the specified index.
Spec: FUNCTION NEXT (index_in IN INTEGER) RETURN INTEGER;
Eg: next_row := names_tab.NEXT(curr_row);
Info: NEXT will return NULL, if there are no rows defined after the specified index.

* PRIOR: Returns the greatest index of the PLSQL table smaller than the specified index.
Spec: FUNCTION PRIOR (index_in IN INTEGER) RETURN INTEGER;
Eg: prior_row := names_tab.PRIOR(curr_row);
Info: PRIOR will return NULL if there are no rows defined before the specified index.

** PLSQL table of Records:

TYPE <type_name> IS TABLE OF <data_type>


INDEX BY BINARY_INTEGER;

Eg 01:
TYPE emp_tabtype IS TABLE OF employee%ROWTYPE
INDEX BY BINARY_INTEGER;

Eg 02:

CURSOR emp_cur
IS
SELECT *
FROM employee;

TYPE emp_tabtype IS TABLE OF emp_cur%ROWTYPE


INDEX BY BINARY_INTEGER;

Eg 03:

TYPE emp_rectype IS RECORD (employee_id INTEGER, employee_name VARCHAR2(30));

TYPE emp_tabtype IS TABLE OF emp_rectype


INDEX BY BINARY_INTEGER;

* Referencing fields of record elements in PLSQL tables:

<table name>(<index expression>).<field name>

Eg01:
emp_tab(375) := 'RAHULMN';

Eg02:
IF names_table (old_name_row + 1).last_name = 'SMITH'
THEN ...

Eg03: defining functions which return PLSQL tables.


<function_name>(<argument_list>)(<index_expression>).<field_name>

FUNCTION best_company (year_in IN INTEGER)


RETURN company_tabtype;

DBMS_OUTPUT.PUT_LINE(best_company(1995)(10).company_name || ' Was 10th Best.');

Assigning Records in PLSQL tables:

can assign the whole record fetched from the db directly into the row of a PLSQL table.

FOR company_rec IN company_cur


LOOP
next_row := NVL(company_table.LAST, 0) + 1;
company_table(next_row) := company_rec;
END LOOP;

10.9: Working with PLSQL tables:

* Transferring DB information into PLSQL tables:

Cannot use a SQL SELECT statement to transfer data directly from a database table to a PLSQL table. Using Cursor FOR
loop to transfer data:

1. Define a PLSQL table TYPE for each data type found in the columns of the db table.
2. Declare PLSQL table which will each receive the contents of a single column.
3. Declare cursor against the db table.
4. Execute the FOR loop.

DECLARE
CURSOR company_cur
IS
SELECT company_id, incorp_date, fill_date
FROM company;

TYPE company_keys_tabtype IS TABLE OF company.company_id%TYPE


INDEX BY BINARY_INTEGER;

company_keys_tab company_keys_tabtype;

TYPE date_tabtype IS TABLE OF company.incorp_date%TYPE


INDEX BY BINARY_INTEGER;

incorp_date_tab date_tabtype;
fill_date_tab date_tabtype;

next_row INTEGER := 0;
BEGIN
FOR company_rec IN company_cur
LOOP
next_row := NVL(company_keys_tab.LAST, 0) + 1;
company_keys_tab (next_row) := company_rec.company_id;
incorp_date_tab (next_row) := company_rec.incorp_date;
fill_date_tab (next_row) := company_rec.fill_date;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
NULL;
END;

DECLARE

CURSOR company_cur
IS
SELECT company_id, incorp_date, fill_date
FROM company;

TYPE company_rec_tabtype IS TABLE OF company_cur%ROWTYPE


INDEX BY BINARY_INTEGER;

company_rec_tab company_rec_tabtype;

next_row INTEGER := 0;
BEGIN
FOR company_rec IN company_cur
LOOP
next_row := NVL(company_rec_tab.LAST, 0) + 1;
company_rec_tab(next_row) := company_rec;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
NULL;
END;

** Data-smart row numbers in PLSQL tables.

Sequential/Parallel storage:

Approach: Create two tables: one that holds error codes and another that holds messages. When an error is encountered,
procedure scans sequentiallu through the PLSQL table of codes until it finds a match. The row in which the code is found is
also the row in which the message is present.

PROCEDURE display_error (errcode_in IN NUMBER)


IS
matching_row BINARY_INTEGER := 1;
keep_searching BOOLEAN := error_pkg.last_row > 0;
BEGIN
WHILE keep_searching
LOOP
IF error_pkg.error_code_tab (matching_row) = errcode_in
THEN
keep_searching := FALSE;
DBMS_OUTPUT.PUT_LINE(error_pkg.error_msg_tab(matching_row));
ELSE
keep_searching := matching_row <= error_pkg.last_row;
END IF;
matching_row := matching_row + 1;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE('No Match Found!');
WHEN OTHERS
THEN
NULL;
END;

Data-Smart/Sparse Storage:

PROCEDURE display_error(errcode_in IN NUMBER)


IS
BEGIN
DBMS_OUTPUT.PUT_LINE(error_pkg.error_details_tab(errcode_in));
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE('No Match Found For ' || errcode_in);
WHEN OTHERS
THEN
NULL;
END;

* Displaying a PLSQL table:


PROCEDURE display_table (table_in IN <the_table_type>,
number_of_rows_in IN INTEGER)
IS
BEGIN
FOR table_row IN 1 .. number_of_rows_in
LOOP
DBMS_OUTPUT.PUT_LINE(table_in (table_row));
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;

WHEN OTHERS
THEN
NULL;
END;

Assumptions:

01. The first defined row of the table is row one. The FOR loop always starts at one.
02. All rows between one and number of rows are defined.
03. The number_of_rows_in is a positive number, the loop does not even consider the possibility of a negative number.

Flexible display_table_procedure:

PROCEDURE table_in (table_in <the_table_type>,


end_row_in IN INTEGER,
start_row_in IN INTEGER := 1,
failure_threshold_in IN INTEGER := 0,
increment_in IN INTEGER := +1)

table_in: The PLSQL table to be displayed.


end_row_in: Last row of the PLSQL table, which has a value defined.
start_row_in: The first row of the PLSQL table which has a value defined.
failure_threshold_in: The number of times the program can encounter and handle the no data found error. Default is 0, the first
time the program encounters the NO_DATA_FOUND error, it will stop.
increment_in: The amount by which the row counter is incremented, as the procedure scans through the table.

Implementation of display table:

PROCEDURE display_table (table_in <the_table_type>,


end_row_in IN INTEGER,
start_row_in IN INTEGER := 1,
failure_threshold_in IN INTEGER := 0,
increment_in IN INTEGER := +1)
IS
current_row INTEGER := start_row_in;
count_misses INTEGER := 0;
within_threshold BOOLEAN := TRUE;

FUNCTION in_range (row_in IN INTEGER) RETURN BOOLEAN IS


BEGIN
IF increment_in < 0
THEN
RETURN row_in >= end_row_in;
ELSE
RETURN row_in <= end_row_in;
END IF;
END;
BEGIN
IF increment_in = 0
THEN
DBMS_OUTPUT.PUT_LINE('Increment for table display must be non-zero!');
ELSE
WHILE in_range(current_row) AND within_threshold
LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE(table_in (current_row));
EXCEPTION
WHEN NO_DATA_FOUND
THEN
within_threshold := count_misses < failure_threshold;
IF within_threshold
THEN
count_misses := count_misses + 1;
current_row := current_row + increment_in;
END IF;
WHEN OTHERS
THEN
NULL;
END;
END LOOP;
END IF;
IF NOT within_threshold
THEN
DBMS_OUTPUT.PUT_LINE('Exceeded threshold on undefined rows in table.');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
NULL;
END;

** Building Traditional Arrays with PLSQL tables:

Because the PLSQL table has no (practical) size limitation, we can use which ever rows in the table we desire. Can spread n
different columns of n different rows across the expanse of the PLSQL table. So, can partition the single PLSQL table so that
it contains all the cells of a traditional array.

General Formula which converts the row and column of an array cell into a corresponding PLSQL table is:

table_row := (cell_column - 1) * number_of_rows_in_array + cell_row;

Features of array package:

array.make
Make or declare an array with specified numbers of rows and columns. Can optionally specify the default value to be placed
in each cell.

array.cell
Obtain the value of any cell in the array.
array.change
Change the value of any cell in the array.

array.erase
Change the value of any cell in the array.

array.display
Display the contents of the array. Can display the table contents in array style or inline style.

PACKAGE array
IS

/* Returns the number of rows in the array */


FUNCTION row_count RETURN INTEGER;

/* Returns the number of columns in the array */


FUNCTION column_count RETURN INTEGER;

/* Create an array */
PROCEDURE make
( num_rows_in IN INTEGER := 10,
num_columns_in IN INTEGER := 1,
initial_value_in IN NUMBER := NULL,
conflict_action_in IN VARCHAR2 := 'OVERWRITE'
);

/* Return the value in a cell. */


FUNCTION cell (row_in IN INTEGER, col_in IN INTEGER)
RETURN NUMBER;

/* Change the value in a cell */


PROCEDURE change (row_in IN INTEGER, col_in IN INTEGER, value_in IN NUMBER);

/* Erase the array */


PROCEDURE erase;

/* Display the array */


PROCEDURE display
(start_row_in IN INTEGER := 1,
end_row_in IN INTEGER := row_count,
start_col_in IN INTEGER := 1,
end_col_in IN INTEGER := column_count,
display_style_in IN VARCHAR2 := 'ARRAY'
);

END array;

PACKAGE BODY array


IS

/* The Number of Rows in the Array */


number_of_rows INTEGER := NULL;

/* The number of columns in the array */


number_of_columns INTEGER := NULL;

/* The generic table structure for a numeric table */


TYPE number_array_type IS TABLE OF NUMBER
INDEX BY BINARY_INTEGER;

/* The actual table which will hold the array */


number_array number_array_type;

/* An empty table used to erase the array */


empty_array number_array_type;

/*------------------Private Modules--------------------*/

FUNCTION row_for_cell (row_in IN INTEGER, col_in IN INTEGER)


RETURN INTEGER
IS
BEGIN
RETURN (col_in-1) * number_of_rows + row_in
END;

/*-----------------Public Modules----------------------*/

FUNCTION row_count RETURN INTEGER


IS
BEGIN
RETURN number_of_rows;
END;

FUNCTION column_count RETURN INTEGER


IS
BEGIN
RETURN number_of_columns;
END;

PROCEDURE make
(num_rows_in INTEGER := 10,
num_columns_in IN INTEGER := 1,
initial_value_in IN NUMBER := NULL,
conflict_action_in IN VARCHAR2 := 'OVERWRITE'
)
IS
/*
Create an array of the specified size with the initial value. If the
table is already in use, then it will be erased and remade only if the
conflict action is OVERWRITE or default.
*/
BEGIN
/*
If the number of rows is NOT NULL, then the array is already in use. If the
conflict action is default or OVERWRITE, then erase the existing array.
*/
IF number_of_rows IS NOT NULL AND
UPPER(conflict_action_in) = 'OVERWRITE'
THEN
erase;
END IF;
/*
Only continue now if the number of rows is NULL.
If it has a value, then the table is in use and the user did not want to overwrite it.
*/
IF number_of_rows IS NULL
THEN
/* Set the global variables storing the size of the array */
number_of_rows := num_rows_in;
number_of_columns := num_columns_in;
/*
A PLSQL table's row is defined only if a value
is assigned to that row, even if that is only a
NULL value. So to create the array, I will simply
make the needed assignments. We use a single table, but segragate
distinct areas of the table for each column of the data. We use
row_for_cell function to space out the different cells of the array across the table.
*/

FOR col_index IN 1 .. number_of_columns


LOOP
FOR row_index IN 1 .. number_of_rows
LOOP
number_array(row_for_cell(row_in, col_in)) := initial_value_in;
END LOOP;
END LOOP;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
NULL;
END make;

FUNCTION cell (row_in INTEGER, col_in INTEGER)


RETURN NUMBER
IS
BEGIN
RETURN number_array(row_for_cell(row_in, col_in));
END;

PROCEDURE change(row_in INTEGER, col_in INTEGER, value_in NUMBER)


IS
BEGIN
number_array(row_for_cell(row_in, col_in)) := value_in;
END;

PROCEDURE erase
IS
BEGIN
number_array := empty_array;
number_of_rows := NULL;
number_of_columns := NULL;
END;

PROCEDURE display
(
start_row_in IN INTEGER := 1,
end_row_in IN INTEGER := row_count,
start_col_in IN INTEGER := 1,
end_col_in IN INTEGER := col_count,
display_style_in IN VARCHAR2 := 'ARRAY'
)
IS
BEGIN
...
END display;

END array;

* Optimizing Foreign Key Lookups with PLSQL tables:

DECLARE
CURSOR company_cur IS
SELECT name FROM company
WHERE company_id = :employee.company_id;
BEGIN
OPEN company_cur;
FETCH company_cur INTO :employee.company_name;
IF company_cur%NOTFOUND
THEN
DBMS_OUTPUT.PUT_LINE(...);
END IF;
CLOSE company_cur;
END;

Above code is in-efficient since, for every company_id, the name is queried from the db.

Blending Database and PLSQL access.


We can store data in PLSQL tables, as and when the data is queried from the db. So the next time data is required, we can first
query the PLSQL table for data and then query the db for data, if the data is not found in the PLSQL table.

FUNCTION company_name
(id_in IN company.company_id%TYPE)
RETURN VARCHAR2
IS
BEGIN
get-data-from-table;
return-company_name;
EXCEPTION
get-data-from-db;
store-in-plsql-table;
return-company-name;
END;
Performance impact of blended access.
==========================================================================================
================================================
Procedures and Functions
==========================================================================================
================================================

*****************************************************************************************************
*************************************
17/APR/2008
*****************************************************************************************************
*************************************

15.1: Modular Code:


* More Re-usable, More Manageable, More Readable, More Reliable.

* Procedure: A named PL/SQL block that performs one or more actions and is called as an executable PL/SQL statement. can
pass information into and out of a procedure through it's parameter list.

* Function:A named PL/SQL block that returns a single value and is used like a PL/SQL expression. Can pass information
into a function through it's parameter list.

* Anonymous Block: Un-named PL/SQL block which performs one or more actions. gives developer more control over scope
of identifiers and exception handling.

* Package: Named collection of procedures, types, functions and variables.

15.2: Review of the PL/SQL block structure:

** Common Block Structure:

Header Section
...
Declaration Section
...
Execution Section
...
Exception Section
...

15.2.1: Sequence of Section construction:


* Must always have one executable section in the block.

15.2.2: PL/SQL block structure examples:

PROCEDURE get_happy(ename IN VARCHAR2)


IS
hire_date DATE;
BEGIN
hire_date := SYSDATE -2;
INSERT INTO employee
(emp_name, hiredate)
VALUES (ename_in, hire_date);
EXCEPTION
WHEN DUP_VAL_IN_INDEX
THEN
DBMS_OUTPUT.PUT_LINE('Cannot Insert');
END;

* Function without an exception section:

FUNCTION soft_pillow (type_in IN VARCHAR2)


RETURN VARCHAR2
IS
CURSOR pillow_cur
IS
SELECT softness FROM pillow
WHERE pillow_type = type_in;
pillow_rec pillow_cur%ROWTYPE;
BEGIN
OPEN pillow_cur;
FETCH pillow_cur INTO pillow_rec;
CLOSE pillow_cur;
RETURN pillow_rec.softness;
END;

15.3: The anonymous PL/SQL block:


BEGIN
DBMS_OUTPUT.PUT_LINE('Hello World');
END;

PROCEDURE get_happy
IS
BEGIN
DECLARE
...
BEGIN
...
END;
END;

15.3.1: The structure of an anonymous block:


DECLARE
... optional declaration statements ...
BEGIN
... executable statements ...
EXCEPTION
... optional exception handlers ...
END;
15.3.2: Examples of anonymous blocks:

01:

BEGIN
hiredate := SYSDATE;
END;

02:

DECLARE
right_now DATE := SYSDATE;
BEGIN
hiredate := right_now;
END;

03:

DECLARE
right_now DATE := SYSDATE;
too_late EXCEPTION;
BEGIN
IF :employee.hiredate < ADD_MONTHS(right_now, 6)
THEN
RAISE too_late;
ELSE
:employee.hiredate := right_now;
END IF;
EXCEPTION
WHEN too_late
THEN
DBMS_OUTPUT.PUT_LINE(...);
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE('Error Encountered ' || SQLCODE);
END;
15.3.3: Anonymous blocks in the oracle tools:
* Database Trigger on a record or column of a table. The body of the trigger is coded in PLSQL. While the trigger has a name,
the PLSQL code itself is un-named, hence anonymous.

15.3.4: Nested blocks:

DECLARE
...
BEGIN
...
DECLARE
...
BEGIN
...
END;
END;

15.3.4.1: Nested block terminology:


* Nested/Enclosed/Child/Sub Block
* Blocks Calling Nested Blocks: Enclosing/Parent Blocks.
15.3.4.2: Nested blocks provide scope:
* Use this scope to improve control over activity in the program.

PROCEDURE update_management
(company_id_in IN NUMBER, avgsal_in IN NUMBER)
IS
BEGIN
-- The vice-president shows his generosity...
BEGIN
SELECT salary INTO v_sal
FROM employee
WHERE company_id = company_id_in
AND title = 'VICE-PRESIDENT';
IF v_sal > avgsal_in * 10
THEN
UPDATE employee SET salary := salary * .50
WHERE company_id = company_id_in
AND title = 'VICE-PRESIDENT';
ELSE
DBMS_OUTPUT.PUT_LINE ('The VP is OK!');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
END;

-- The president shows her generosity...


BEGIN
SELECT salary INTO v_sal
FROM employee
WHERE company_id = company_id_in
AND title = 'PRESIDENT';
IF v_sal > avgsal_in * 10
THEN
UPDATE employee SET salary := salary * .50
WHERE company_id = company_id_in
AND title = `PRESIDENT';
ELSE
DBMS_OUTPUT.PUT_LINE ('The Prez is a pal!');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
END;
END;
15.3.4.3: Named modules offer scoping effect of nested block:

PROCEDURE update_management
(company_id_in IN NUMBER, avgsal_in IN NUMBER, decr_in IN NUMBER)
IS
CURSOR salcur (title_in IN VARCHAR2)
IS
SELECT salary
FROM employee
WHERE company_id = company_id_in
AND title = title_in
AND salary >= avgsal_in * 10;

PROCEDURE update_exec (title_in IN VARCHAR2)


IS
salrec salcur%ROWTYPE;
BEGIN
OPEN salcur(title_in);
FETCH salcur INTO sal_rec;
IF salcur%NOTFOUND
THEN
DBMS_OUTPUT.PUT_LINE('The ' || title_in || ' is OK!');
ELSE
UPDATE employee SET salary := salary * decr_in
WHERE company_id = company_id_in
AND title = title_in;
END IF;
CLOSE salcur;
END;
BEGIN
update_exec('VICE_PRESIDENT');
update_exec('PRESIDENT');
END;

15.3.5: Scope and Visibility:


* Scope and Visibility of identifiers:
Identifier is the name of a PL/SQL object. Anything from a variable to a program name. An identifier is visible in a program
when it
can be referenced by an un-qualified name.
15.3.5.1: Qualified Identifiers:
* Qualifier for an identifier can be a package, module name (procedure/function) or loop label. Qualify the name of an
identifier
with dot notation.
Eg:
:GLOBAL.company_id : Global variable in oracle forms.
std_types.dollar_amount : Subtype declared in a package.
* Scope of an indentifier is generally the block in which the identifier is declared.
DECLARE
first_date DATE;
last_date DATE;
BEGIN
first_date := SYSDATE;
last_date := ADD_MONTHS(SYSDATE, 6);
END;
Both first_date and last_date are visible in this block. The scope of the two variables is precisely this anonymous block. cannot
make reference to either of these variables in a second anonymous block or procedure.

If an identifier is declared or defined outside of the current PL/SQL block, it is visible (can be referenced without a qualifier)
only under the following conditions:

* Identifier is the name of the standalone procedure or function on which you have the EXECUTE privilege. Can call that
module in the
block.
* Identifier is declared in a block which encloses the current block.

15.3.5.2: Scope of nested blocks:


When we declare a variable in a PLSQL block, then that variable is local to that block, but is visible or global to any block
which are defined within the enclosing block.
DECLARE
last_date DATE;
BEGIN
last_date := LAST_DAY(SYSDATE);
BEGIN
IF last_date > :employee.hire_date
THEN
...
END IF;
END;
END;

15.3.5.3: Qualifying identifier names with module name:


PackageName.VariableName [available globally], ProcedureName.VariableName

PROCEDURE calc_totals
IS
salary NUMBER;
BEGIN
...
DECLARE
salary NUMBER;
BEGIN
salary := calc_totals.salary;
END;
END;

Only salary always resolves to the local variable in the Inner block. calc_totals.salary resolves to the procedure wide salary
variable.

15.3.5.4: Cursor Scope:


The rules for cursor scope are same as those for all other identifiers.

PROCEDURE get_employees
IS
CURSOR emp_cur IS
SELECT employee_id, sal + bonus FROM employees;
BEGIN
OPEN emp_cur;
DECLARE
empid NUMBER;
total_comp NUMBER;
BEGIN
FETCH emp_cur INTO empid, total_comp;
IF total_comp < 5000
THEN
MESSAGE ('I need a raise');
END IF;
END;
EXCEPTION
WHEN OTHERS
THEN NULL;
END;

* emp_cur can be referenced within get_employees and any nested blocks in get_employees.

PROC ... IS
empid NUMBER;
total_comp NUMBER;
BEGIN
DECLARE
CURSOR emp_cur IS
SELECT employee_id, sal + bonus FROM employees;
BEGIN
OPEN emp_cur;
END;
-- This won't work.
FETCH emp_cur INTO empid, total_comp;
...
END;
15.3.6: Block Labels:
* Give a name to the PLSQL block for the duration of the execution. Is a PLSQL label which is placed directly in front of the
first line of the block.
Format: <<label>>
Example:

<<calc_dependencies>>
DECLARE
v_senator VARCHAR2(100) := 'THURMOND, JESSE';
BEGIN
IF total_contributions (v_senator, 'TOBACCO') > 25000
THEN
DBMS_OUTPUT.PUT_LINE('We''re Smokin!');
END IF;
END;

<<tobacco_dependency>>
DECLARE
v_senator VARCHAR2(100) := 'THURMOND, JESSE';
BEGIN
IF total_contributions (v_senator, 'TOBACCO') > 25000
THEN
<<alochol_dependency>>
DECLARE
v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES';
BEGIN
IF tobacco_dependency.v_senator =
alcohol_dependency.v_senator
THEN
DBMS_OUTPUT.PUT_LINE('Maximizing profits - the American way of life!');
END IF;
END;
END IF;
END;

15.4 Procedures:
Is a module performing one or more actions. since a procedure is a standalone executive statement in PLSQL, a PLSQL block
could consist of nothing more than a single call to a procedure.

Syntax:

PROCEDURE name [( parameter [, parameter...])]


IS
[declaration statements]
BEGIN
executable statements
[EXCEPTION
exception handler statements]
END [name];

PROCEDURE apply_discount
(company_id_in IN company.company_id%TYPE, discount_in IN NUMBER)
IS
min_discount CONSTANT NUMBER := .05;
max_discount CONSTANT NUMBER := .25;
invalid_discount EXCEPTION;
BEGIN
IF discount_in BETWEEN min_discount AND max_discount
THEN
UPDATE item
SET item_amount = item_amount * (1 - discount_in)
WHERE EXISTS (SELECT 'X' from order
WHERE order.order_id = item.order_id
AND order.company_id = company_id_in);
IF SQL%ROWCOUNT = 0 THEN RAISE NO_DATA_FOUND; END IF;
ELSE
RAISE invalid_discount;
END IF;
EXCEPTION
WHEN invalid_discount
THEN
DBMS_OUTPUT.PUT_LINE('The specified discount is invalid.');

WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE('No Orders in the system for the company ' || TO_CHAR(company_id_in));
END apply_discount;

15.4.1: Calling a Procedure:


A Procedure called is an executable PLSQL statement.
Eg:
apply_discount(new_company_id, 0.15);
display_store_summary;

15.4.2: Procedure Header: Consists of Module Type, Name and a list of parameters [if any].
15.4.3: Procedure Body:
Is the code required to implement the procedure. consists of declaration, execution and exception sections of the procedure.
Everything after the IS keyword in the procedure makes up the procedure body. Declaration and Exception sections are
optional.

Smallest PLSQL Procedure:

PROCEDURE do_nothing IS
BEGIN
NULL;
END do_nothing;

15.4.4: The END label: Append the name of the procedure directly after the END keyword when the procedure is completed.
Name serves as a label that explicitly links up the end of the program with the beginning.

15.5: Functions:
Is a module that returns a value. Call to a function can only be a part of an executable statement. Cannot be a stand-alone
executable statement.

15.5.1: Structure of a function:

FUNCTION name [(parameter1 [, parameter2 ...])]


RETURN return_datatype
IS
[declaration_section]
BEGIN
[executable_section]
EXCEPTION
[exception handler statements]
END;

Eg:

FUNCTION tot_sales
( company_id_in IN company.company_id%TYPE,
status_in IN order.status_code%TYPE := NULL)
RETURN NUMBER
IS
/* Internal Upper cased version of status code */
status_int order.status_code%TYPE := UPPER (status_in);

CURSOR sales_cur (status_in IN order.status_code%TYPE) IS


SELECT SUM (amount * discount)
FROM item
WHERE EXISTS (SELECT 'X' FROM order
WHERE order.order_id = item.order_id
AND company_id = company_id_in
AND status_code LIKE status_in);

return_value NUMBER;
BEGIN
OPEN sales_cur(status_int);
FETCH sales_cur INTO return_value;
IF sales_cur%NOTFOUND
THEN
CLOSE sales_cur;
RETURN NULL;
ELSE
CLOSE sales_cur;
RETURN return_value;
END tot_sales;

15.5.2: The RETURN datatype: Standard PL/SQL datatype or Composite Datatype.

Datatype cannot be an exception or cursor name.

Datatype can be scalars like:

VARCHAR2, NUMBER, BINARY_INTEGER, BOOLEAN etc

or complex or composite datatypes like:

PLSQL Table, Nested Table or VARRAY, PLSQL Record, Object Type or LOB/CLOB/BFILE/BLOB

15.5.3: The END label: Append the end label directly after the END keyword when the function is completed.
15.5.4: Calling a function:

sales_for_1995 := tot_sales(1504, 'C');

DECLARE
sales_for_2008 NUMBER DEFAULT tot_sales(1504, 'C')
BEGIN
...

IF tot_sales(1504, 'C') > 10000


THEN
...

15.5.4.1: Functions without Parameters:


IF tot_sales THEN ...
15.5.5: Function Header: FUNCTION <function_name> [(parameter1 [, parameter2...])] RETURN <return_data_type> IS
15.5.6: Function Body: Everything after the IS keyword.
15.5.7: A Tiny Function:

FUNCTION does_nothing RETURN BOOLEAN


IS
BEGIN
RETURN TRUE;
END;

IF does_nothing
THEN
NULL;
END IF;

15.5.8: The RETURN statement: Must have atleast one return statement. Can have multiple RETURN statements. Only one
RETURN statement is processed during the function's execution.
When a RETURN statement is processed, the function terminates immediately and returns control to the calling PLSQL block.
15.5.8.1: Multiple RETURNS

IF sales_cur%NOTFOUND
THEN
CLOSE sales_cur;
RETURN NULL;
ELSE
CLOSE sales_cur;
RETURN return_value;
END IF;

15.5.8.2: Return any valid expression:


* RETURN 'String';
* RETURN POWER(max_salary, 5);
* RETURN (100-pct_of_total_salary(employee_id));
* RETURN TO_DATE(01 || earliest_month || initial_year, 'DDMMYY');

** An Expression in the RETURN statement is evaluated when the RETURN statement is executed.

15.5.8.3: No RETURN is executed: PLSQL raises an error: "Function Returned Without Value."
15.5.8.4: RETURN as last executable statement:
FUNCTION company_type (type_code_in IN VARCHAR2)
RETURN VARCHAR2
IS
return_value VARCHAR2(25) := NULL;
BEGIN
IF type_code_in = 'S'
THEN
return_value := 'SUBSIDIARY';
ELSIF type_code_in = 'P'
THEN
return_value := 'PARTNER';
END IF;

RETURN return_value;
END;
15.5.8.5: RETURN statement in a procedure:

RETURN statements can also be used in procedures. procedure version of RETURN cannot take an expression. RETURN
simply halts the procedure and returns control to the calling program. Avoid using both RETURN and GOTO to bypass proper
control structures.

15.6: Parameters:

Consideration regarding parameters:


* number of parameters
* types of parameters
* names of parameters
* default values of parameters

15.6.1: Defining Parameters:


Parameter declaration must be un-constrained.
Constrained Declaration: Constrains or limits the kind of values that can be assigned to a variable declared with that datatype.
Unconstrained declaration does not limit values in this way.

DECLARE
company_name VARCHAR2(60);
BEGIN
...

PROCEDURE display_company (company_name IN VARCHAR2) IS ...


15.6.1.1: %TYPE and %ROWTYPE:
PACKAGE pet_hotel
IS

TYPE breed_type IS
TABLE OF breed.breed_name%TYPE INDEX BY BINARY_INTEGER;
TYPE avail_rooms_type IS
TABLE OF room.room_number%TYPE INDEX BY BINARY_INTEGER;

FUNCTION room_by_breeds (breed_table_in IN breeds_type)


RETURN avail_rooms_type;

END;

%TYPE and %ROWTYPE are both un-constrained declarations. Actual sizes of the parameters depend on the structures of
tables and columns to which the attributes point. This size is resolved only at compile time.

15.6.2: Parameter Modes: IN, IN OUT and OUT

15.6.2.1: IN Mode:
* Default mode when nothing is specified. cannot assign or modify values of IN parameters.
* IN parameters function like constants and value of the formal IN parameter cannot be changed within the program.

15.6.2.2: OUT Mode:


* During the execution of the program, any assignments to an OUT parameter are actually made to an internal copy of the
OUT parameter.
When the program terminates successfully and returns control to the calling block, the value in the local copy is traneferred
to the
actual OUT parameter.
* OUT parameter can only be found at the left side of an assignment operation.
* Cannot provide a default value to the OUT parameter.
* Actual OUT param must be a variable, not a constant or literal, because at the end of the program a value has to be assigned
to an OUT parameter. A constant or a literal cannot be a receptacle of this value.
* Any assignments made to an OUT parameter are rolled back when an exception is raised in the program.
15.6.2.3: The IN OUT Mode:
* Can pass values into a program and return values from a program
* Cannot have a default value. must be a variable.
* Can be used on both sides of the assignment.

PROCEDURE combine_and_format_names
(first_name_inout IN OUT VARCHAR2,
last_name_inout IN OUT VARCHAR2,
full_name_out OUT VARCHAR2,
name_format_in IN VARCHAR2 := 'LAST, FIRST'
)
IS
BEGIN
first_name_inout := UPPER(first_name_inout);
last_name_inout := UPPER(last_name_inout);

IF name_format_in = 'LAST, FIRST'


THEN
full_name_out := last_name_inout || ', ' || first_name_inout;
ELSIF name_format_in = 'FIRST LAST'
THEN
full_name_out = first_name_inout || ' ' || last_name_inout;
END IF;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;

15.6.3: Actual and Formal Params:


Formal: Names that are declared in the parameter list of the header in a module.
Actual: Values or expressions placed in the parameter list to the actual call to the module.
15.6.4: Matching Actual and Formal Parameters in PL/SQL:
15.6.4.1: Positional Notation: Associate the actual parameter implicitly [by position] with the formal parameter.
15.6.4.2: Named Notation: Explicitly Associate the formal parameter with the actual parameter using "=>".
formal_param_name => actual_value
Ex: tot_sales(company_id_in => 5675 , status_in => 'N')
Can also mix named and positional notation in the same program call:
tot_sales (company_id_in, status_in => 'N')
If we mix, then must list all our positional parameters before the named parameters. Positional notation has to have a starting
point from
which to keep track of positions and the only starting point is the first parameter.

15.6.4.3: Benefits of named notation:


* Named notation is self-documenting.
* Named notation gives us complete flexibility over parameter specification.

15.6.5: Default Values:


* Can provide default values for IN parameters.
* Can use either the DEFAULT keyword or the := for giving default values.
PROCEDURE sample_proc IS
(var1 IN NUMBER DEFAULT 5,
var2 IN VARCHAR2 := 'HELLO')
IS...
Switch to named notation to leave leading default parameters.
sample_proc(var2 => 'BYE');
sample_proc(5);
sample_proc(5, 'HILDA');

15.7: Local Modules:


* Is a procedure or function which is defined inside the declaration section of a Pl/SQL block.
* Must be located after all declaration statements.
* Cannot be called by any other PLSQL blocks defined outside of that enclosing block. Only defined in the parent block.

DECLARE
PROCEDURE show_data (data_in IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Data is ' || data_in);
END;
BEGIN
...
EXCEPTION
...
END;

15.7.1: Benefits of Local Modularization:


* Reduce the size of the module by stripping it of repetitive code.
* Improve code Readability.
15.7.2: Reducing Code volume:

PROCEDURE calc_percentages (tot_sales_in IN NUMBER)


IS
FUNCTION char_format (val_in IN NUMBER) RETURN VARCHAR2
IS
BEGIN
RETURN TO_CHAR((val_in/tot_sales)*100, $999,999);
END;
BEGIN
my_package.food_sales := char_format(sales_pkg.food_sales);
my_package.liquor_sales := char_format(sales_pkg.liquor_sales);
END;

15.7.3: Improving Readability:

PROCEDURE assign_workload (department_in IN NUMBER)


IS
/* Declare local variables first */
avg_workload NUMBER;
case_number NUMBER;

/*--------------LOCAL MODULES-----------------*/
PROCEDURE assign_next_case
(employee_id_in IN NUMBER, case_out OUT NUMBER)
IS ...
BEGIN ... END;

FUNCTION average_cases (department_id_in IN NUMBER) RETURN NUMBER


IS BEGIN ... END;

FUNCTION caseload (employee_id_in IN NUMBER) RETURN NUMBER


IS BEGIN ... END;

FUNCTION next_appointment (case_id_in IN NUMBER) RETURN NUMBER


IS BEGIN ... END;

PROCEDURE schedule_case
(employee_id IN NUMBER, case_in IN NUMBER, date_in IN DATE)
IS ... BEGIN ... END;

BEGIN
avg_workload := average_cases (department_in);
FOR emp_rec IN (SELECT employee_id
FROM employee
WHERE department_id = department_in)
LOOP
IF caseload (emp_rec.employee_id) < avg_workload
THEN
assign_next_open_case(emp_rec.employee_id, case_number);
schedule_case(emp_rec.employee_id, case_number, next_appointment(case_number));
END IF;
END LOOP;
END assign_workload;

15.7.4: Bottom Up Reading: Execution section of the program is pushed to the bottom. To try and understand local modules,
first go to the
execution section and understand the big-picture.
15.7.5: Scope of local modules: Only with-in the current module.
15.7.6: Spruce up your code with local modules!

15.8: Module Overloading:


DECLARE
FUNCTION value_ok (date_in IN DATE) RETURN BOOLEAN
IS
BEGIN
RETURN date_in < SYSDATE;
END;

FUNCTION value_ok (number_in IN NUMBER) RETURN BOOLEAN


IS
BEGIN
RETURN number_in < 1000;
END;
BEGIN
IF value_ok (SYSDATE-4) || value_ok (1500)
THEN
...
END IF;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
15.8.1: Overloading in PLSQL builtins: TO_CHAR
15.8.2: Benefits of overloading:
15.8.3: Where to Overload modules:
* Inside Declaration of a PL/SQL block or inside a package.
* Not with stand-alone procedures or functions.
15.8.4: Restrictions on overloading:
* Datatype family of atleast one param of the overloaded program must differ.
INTEGER, REAL, DECIMAL, FLOAT, POSITIVE, BINARY_INTEGER are NUMBER subtypes.
CHAR, VARCHAR2, LONG are character subtypes.
** Creation of subtypes:
SUBTYPE line_text_type IS CHAR;
SUBTYPE atomic_type IS VARCHAR2;
SUBTYPE word_seperator_type IS VARCHAR2;

* Overloaded programs which differ only by parameter names, must be called using the named notation.

FUNCTION calculate_net_profit (revenue_in IN NUMBER)


RETURN NUMBER IS
BEGIN
...
END calculate_net_profit;

FUNCTION calculate_net_profit (total_revenue_in IN NUMBER)


RETURN NUMBER IS
BEGIN
...
END calculate_net_profit;

calculate_net_profit (total_revenue_in => 28090);


calculate_net_profit (revenue_in => 28090);

* The parameter list of overloaded programs must differ by more than parameter mode. Overloaded functions must differ by
more than their return type. At the time the overloaded function is called, the compiler does not know what type of data the
functions will return. The compiler cannot therefore, determine which version of the function to use, if all the params are
same.
* All of the overloaded programs must be defined within the same PLSQL scope or block (anonymous block, module or
package).
Cannot define one version in one block and the other version in a different block.

PROCEDURE develop_analysis (quarter_end_in IN DATE, sales_in IN NUMBER)


IS
PROCEDURE chg_estimate (date_in IN DATE) IS BEGIN ... END;
BEGIN
DECLARE
PROCEDURE chg_estimate (number_in IN NUMBER) IS BEGIN ... END;
BEGIN
chg_estimate (quarter_end_in);
chg_estimate (number_in);
END;
END develop_analysis;

On compilation, we get the following error: Wrong number or type of arguments in call to 'CHG_ESTIMATE'
The above two chg_estimate procedures are not overloaded. because they are declared in different PLSQL blocks, they have a
different scope and visibility. The scope of date chg_estimate is the entire body of develop_analysis. the scope of number
chg_estimate is only for the anonymous block. Therefore number chg_estimate takes precedence over date chg_estimate.

15.9: Forward Declarations:


* Declaring a module ahead of actual definition of the module.
* Forward declaration consists simply of program header followed by Semicolon.
* Cannot make forward declarations to variable or cursor. works with only procedures and functions.
* Definition for a forwardly declared program must be contained in the declaration section of the same PL/SQL block
(anonymous block, procedure, function or package)

PROCEDURE perform_calcs (year_in IN NUMBER)


IS
/* Header only for total_cost function */
FUNCTION total_cost (...) RETURN NUMBER;

FUNCTION net_profit (...) RETURN NUMBER


IS
BEGIN
RETURN tot_sales(...) - tot_cost(...);
END;

FUNCTION total_cost(...) RETURN NUMBER


IS
BEGIN
IF net_profit(...) < 0
THEN
RETURN 0;
ELSE
RETURN ...;
END IF;
END;
BEGIN
...
END perform_calcs;

15.10: Go Forth and Modularize

==========================================================================================
================================================
Packages
==========================================================================================
================================================

* Package is collection of PL/SQL objects that are packaged and grouped together within a special BEGIN-END syntax.
* Most basic operators of PLSQL language such as + and LIKE operators and INSTR function are all defined in a special
package called STANDARD.
16.1: The benefits of packages:
16.1.1: Enforced Information Hiding.
* Decide which elements are public and which are private.
* Can restrict access to the package to only the specification.

16.1.2: Object Oriented Design

16.1.3: Top-Down Design


* Package specification can be written before the body.
* Top Down design: Move from high-level requirements to functional decompositions to module calls.
* Package specification can compile without it's body.
* Programs that call packaged modules will compile, as long as the specification compiles.

16.1.4: Object Persistence


* Offer ability to implement global data in your application environment. Global Data: Info that persists across application
components, isn't local to the current module.
* Objects defined in a package specification (visible to anyone with EXECUTE authority on that package) act as global data
for all PLSQL objects in the application. If you have access to the package, can modify package variables in one module and
reference the changed variables in another module. Data persists for the duration of the user session (db connection).
* If a packaged procedure opens a cursor, that cursor remains open and is available to other packaged routines throughout the
session. Do not have to explicitly define the cursor in each program. Can open it in one module and fetch in another module.
* Package variables can carry data across the boundaries of transactions. They are tied to the session itself and not to a
transaction.

16.1.5: Performance Improvement.


* When an object in a package is referenced for the first time, the entire package (already compiled and validated) is loaded
into memory. All other package elements are thereby made immediately available for future calls to the package. PLSQL does
not have to keep retrieving program elemnts or data from disk each time a new object is referenced.
* Oracle RDBMS automatically tracks the validity of all program objects (procedures, functions and packages) stored in the
database. determines what other objects a program is dependent on, such as tables. If a dependent object such as a table's
structure changes, then all programs that rely on that object are flagged invalid. DB automatically re-compiles these invalid
programs before thay are used.
* Can limit automatic re-compiles by placing functions and procedures inside packages. If program A calls packaged program
B, it does so through the package's specification. As long as spec of a package does not change, any program that calls the
module is not flagged as in-valid and will not have to be re-compiled.

16.2: Overview of the package structure:


16.2.1: The Package Specification:
* Contains the definition or specification of all elements in the package that my be referenced outside the package.
Eg:
PACKAGE sp_timer
IS
PROCEDURE capture(context_in IN VARCHAR2);
PROCEDURE show_elapsed;
END sp_timer;

16.2.2: The Body: Contains all the code behind the package specification: variables, cursors and other objects.

PACKAGE BODY sp_timer


IS
last_timing NUMBER := NULL;

PROCEDURE capture (context_in IN VARCHAR2)


IS BEGIN
last_timing := DBMS_UTILITY.GET_TIME;
END;

PROCEDURE show_elapsed IS
BEGIN
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.GET_TIME - last_timing);
END;
END sp_timer;
* body may also contain elements which do not appear in the package spec. These are called private elements of the package
and cannot be referenced outside of the package.
* Package body may also contain an execution section, which is called the initialization section. It is run only once to initialize
the package.

16.2.3: Package Syntax:

Package Specification:

PACKAGE package_name
IS
[Declarations of Variables and Type ...]
[Specification of Cursors ...]
[Specification of modules ...]
END [package_name];
* Must have atleast one declaration or specification statement in the package specification.

Package Body:

PACKAGE BODY package_name


IS
[declaration of variable and types]
[specification and SELECT statement of cursors]
[specification and body of modules]
BEGIN
[executable statements]
EXCEPTION
[exeption handlers]
END [package_name];

16.2.4: Public and Private Package Elements:


** Public: Defined in the specification.
** Private: Defined only in the body of the package, does not appear in the specification. Cannot be accessed from outside the
package. Any other element of the package may reference and use a private element.
* Private elements in a package must be defined before they can be referenced by other elements in the package. If a public
procedure calls a private function, that function must be defined above the public procedure in the package body.
16.2.5: How to reference package elements:
Package owns it's objects. Use dot notation to provide a fully qualified specification for a package's object.

PACKAGE pets_inc
IS
max_pets_in_facility CONSTANT INTEGER := 120;
pet_is_sick EXCEPTION;

CURSOR pet_cur RETURN pet%ROWTYPE;

FUNCTION next_pet_shots (pet_id_in IN NUMBER) RETURN DATE;


PROCEDURE set_schedule (pet_id_in IN NUMBER);
END pets_inc;
in an outside procedure:

BEGIN
IF pets_inc.max_pets_in_facility > 100
THEN
...
END IF;
EXCEPTION
WHEN pets_inc.pet_is_sick
THEN
...;
END;

* Inside the package, we do not need to qualify references to other elements of that package.

PROCEDURE set_schedule (pet_id_in IN NUMBER)


IS
BEGIN
IF max_pets_in_facility > 100
THEN
...
END IF;
END set_schedule;

16.2.6: Quick Tour of a package:


PACKAGE pets_inc
IS
SUBTYPE petid_type IS pet.pet_id%TYPE;
pet_id_nu petid_type;
CURSOR pet_cur (pet_name_in IN VARCHAR2) RETURN pet%ROWTYPE;
FUNCTION next_pet_shots (pet_id_in IN petid_type) RETURN DATE;
PROCEDURE set_schedule (pet_id_in IN petid_type);
END pets_inc;

Calling package elements from an external procedure:

PROCEDURE show_next_visit (pet_in IN VARCHAR2)


IS
next_visit DATE;
pet_rec pets_inc.pet_cur%TYPE;

BEGIN
OPEN pets_inc.pet_cur(pet_in);
FETCH pets_inc.pet_cur INTO pet_rec;

IF pets_inc.pet_cur%FOUND
THEN
next_visit := pets_inc.next_pet_shots (pet_rec.pet_id);
DBMS_OUTPUT.PUT_LINE('Schedule next visit for ' || pet_in || ' on ' || TO_CHAR(next_visit));
END IF;

CLOSE pets_inc.pet_cur;
END show_next_visit;

* pets_inc package body:

16.2.6.2: The Package Body:


PACKAGE BODY pets_inc
IS
max_date CONSTANT DATE := SYSDATE + 10;

CURSOR pet_cur (pet_name_in IN VARCHAR2) RETURN pet%ROWTYPE IS


SELECT * FROM pet WHERE name LIKE '%' || pet_name_in || '%';

FUNCTION pet_status (pet_id_in IN petid_type) RETURN VARCHAR2


IS
BEGIN
...
END;

FUNCTION next_pet_shots (pet_id_in IN petid_type) RETURN DATE


IS
BEGIN
...
END;

PROCEDURE set_schedule (pet_id_in IN petid_type)


IS
BEGIN
...
END;
END pets_inc;

16.2.6.3: Observations:

16.3: Package Specification:


* Specification of a package lists all objects in that package that are available for use in applications.
* May contain the following: Variable Declarations, TYPE declarations, Exception declaration, cursor specification [specify
cursor name and RETURN statement], Module Specification.
* Of the above declarations, only the cursors and modules need to be defined in the body.
16.3.1: Packages without bodies:
**Package only requires body if one of the following is true:
* Want to define private package elements.
* Have included a cursor or module in your specifications.

16.3.1.1: A package of exceptions:

PACKAGE exchdlr
IS
en_general_error NUMBER := -20000;
exc_general_error EXCEPTION;
PRAGMA EXCEPTION_INIT (exc_general_error, en_general_error);

en_must_be_eighteen NUMBER := -20001;


exc_must_be_eighteen EXCEPTION;
PRAGMA EXCEPTION_INIT (exc_must_be_eighteen, en_must_be_eighteen);

max_error_number_used NUMBER := -20001;

TYPE error_msg_type IS TABLE OF VARCHAR2(240) INDEX BY BINARY_INTEGER;


error_msg_table error_msg_tabtype;

END exchdlr;
16.3.1.2: A package of magic values:

PACKAGE config_pkg
IS
closed_status CONSTANT VARCHAR2(1) := 'C';
open_status CONSTANT VARCHAR2(1) := 'O';
active_status CONSTANT VARCHAR2(1) := 'A';
inactive_status CONSTANT VARCHAR2(1) := 'I';

min_difference CONSTANT NUMBER := 1;


max_difference CONSTANT NUMBER := 100;

earliest_date CONSTANT DATE := SYSDATE;


latest_date CONSTANT_DATE := ADD_MONTHS(SYSDATE, 120);

END config_pkg;

Magic values are literals which have special significance in an application. These values should never be hard-coded into an
application.
A seperate package should be maintained, so that, these values can be changed easily when required.

16.3.1.3: Top-down design with bodiless packages.


Top-Down Design: Start with the general description of an application and gradually decompose into seperate functional
areas, programs or individual statements.

PACKAGE support
IS
PROCEDURE add_call (call_id IN INTEGER);
PROCEDURE remove_call (call_id IN INTEGER);
END support;

PACKAGE workload
IS
FUNCTION average (dept_id IN INTEGER)
RETURN NUMBER;
FUNCTION operator (operator_id IN INTEGER)
RETURN NUMBER;
END workload;

* Above 2 are only package specifications, below procedure calls the methods in the above 2 specifications. It compiles,
because, PLSQL only need to know the signature of the method when it's called. Since a package specification contains the
complete signature of the method, this is possible.

PROCEDURE assign_call(call_id IN INTEGER,


operator_id IN INTEGER,
dept_id IN INTEGER)
IS
BEGIN
IF workload.operator(operator_id) < workload.average(dept_id)
THEN
support.add_call(call_id);
END IF;
END assign_call;

16.3.2: Declaring Package Cursors:

* When we include a cursor in a package specification, we must use the RETURN clause of the cursor. It is an optional part of
the cursor definition when it is defined in the declaration section of a PLSQL block. In a package spec, it is mandatory.
* RETURN clause indicates data elements that are returned by a fetch from the cursor. The SELECT statement of the cursor
appears only in the package body, not the spec.

* RETURN clause made up of either of the following datatype structures:


* A record defined from a database table, using the %ROWTYPE attribute.
* A record defined from a programmer defined record.

PACKAGE cursor_sampler
IS
CURSOR caller_tab (id_in IN NUMBER) RETURN caller%ROWTYPE;

TYPE caller_rec IS RECORD (caller_id caller.caller_id%TYPE,


company_id company.company_id%TYPE);
CURSOR caller_cur RETURN caller_rec;
END cursor_sampler;

PACKAGE BODY cursor_sampler


IS
CURSOR hiredate_cur RETURN date_variable%TYPE
IS
SELECT hire_date FROM employee;

CURSOR caller_key RETURN caller.caller_id%TYPE


IS
SELECT called_id FROM caller;

CURSOR caller_tab (id_in NUMBER) RETURN caller%ROWTYPE


IS
SELECT * FROM caller WHERE caller_id = id_in;

CURSOR caller_cur RETURN caller_rec


IS
SELECT caller_id, company_id FROM caller;

END cursor_sampler;

* Cursors are best used, when they are included in the package specification.

16.4: The package body:


* Contains all the code required to implement the package specification.
* Package body is required when any of the following conditions is true:
* The Package specification contains a cursor declaration: Need to specify the SELECT statement in the body.
* The package specification contains a procedure or function declaration. Need to define the module in the function body.
* We wish to execute code in the initialization section of the package body.
* Package body has declaration, execution and exception sections. Declaration section consists of any public or private
objects.
* Execution section is the "initialization section" of the package. Executed when the package is instantiated. Exception section
handles any exceptions raised by the execution section.
* Package body can have empty declaration section, but can include an initialization section. Package body can have a
declaration section without an execution section [common format].

16.4.1: Declare in Specification or Body:


* If we declare a variable in the package spec, then it's available in the package body. No need to declare again.
* Variables which exist in package body and not in the spec, are global within the package and invisible outside the package.
These are called private objects.
16.4.2: Synchronize body with package:
* Package spec and body must be kept synchronized. Changes in package spec should be implemented in package body and
vice versa.
* If spec and body don't match, then they won't compile.
* When PLSQL compiles a package, it verifies that everything defined in the package spec has a body. If there's an error in the
match, then an exception is raised.

16.5: Package Data:


* Package data is any data-structure declared in a package body or specification.
** With package data we can:
* Create global variables that are accessible from any program in the current session.
* Create persistent data structures -- data that remains throughout the oracle session, even when no package programs are
running.
16.5.1: Architechture of package data:
* The first time we reference a package element, the entire compiled package is loaded into the SGA. That code is shared by
all sessions having EXECUTE authority on the package.
* Data that is declared by the package elements is also instantiated in the SGA, but is not shared across all sessions. Each
oracle session is assigned it's own PLSQL area, which contains a copy of the package data. This private PLSQL area is
maintained in the SGA, for as long as the oracle session is running. The values assigned to packaged data structures persist for
the session duration.
16.5.3: Global Public Data:
* Data structure declared in the package spec is global public DS.
* Can change global public data-structures, unless they are declared as CONSTANTs in the declaration statement.
* Specification of a module should give us all info we need to understand how to call and use that module. If a program reads
or writes global data-structures, then we won't know that by seeing the specification. cannot be sure of what's happening in the
application.
* Always advisable to pass data as parameters in and out of modules. This leads to better implmentations.

16.5.4: Global Private Data:


* Also called package-level data is declared inside the package body. It is global with-in the package.
* This data cannot be referenced from outside the package, as it does not appear from the package specification.

PACKAGE BODY sp_timer


IS
last_timing NUMBER := NULL;

PROCEDURE capture (context_in IN VARCHAR2 := NULL)


IS
BEGIN
last_timing := DBMS_UTILITY.GET_TIME;
END;

FUNCTION elapsed RETURN NUMBER IS


BEGIN
IF last_timing IS NULL
THEN
RETURN NULL;
ELSE
RETURN DBMS_UTILITY.GET_TIME - last_timing;
END IF;
END;
END sp_timer;
* Here last_timing is global to the package, but not accessible outside the package.

16.5.5: Providing an interface to global data:

Reasons to build programmatic interface around global data:

* Loss Of Control
* Maintenance Headaches
16.6: Package Initialization:
* The first time an application makes reference to a package element, the whole package is loaded into the SGA of the DB
instance, making all objects immediately available in the memory. We can supplement this automatic instantiation of package
code with automatic execution of initialization code for the package.
* The optional initialization section consists of all statements following the BEGIN statement to the END statement for the
entire package body. Statements are executed only once, the first time an object in the package is referenced.
16.6.1: Drawbacks of package initialization:
* Can be dangerous to have the tool perform actions for you that are not explicitly triggered by user or developer action.
* harder to trace actions triggered automatically by the tool.
* Executable statements buried in initialization section are much harder to maintain.
16.6.2: Use Initialization section for complex logic:
* Use initialization section only when you need to set the initial values of package elements using rules and complex logic that
cannot be handled by the default value syntax for variables.
16.6.3: Side Effects:
* Avoid setting values of package global data from other packages within the initialization section.
PACKAGE BODY company_pkg
IS
/*
Initialization section of company_pkg setting value of global data variable
in employee_pkg.
BIG NO-NO
*/
SELECT count(employee_id)
INTO employee_pkg.emp_count
FROM employees
WHERE effective_start_date > SYSDATE;
END company_pkg;
16.6.4: Load Session Data in Initialization Section:
* Perfectly legitimate use of the initialization section.
* Below package contains information about the current user session.
* All the package variables are set the very first time any of the variables are accessed by the application.

PACKAGE session_pkg
IS
user_name VARCHAR2(80);
user_id VARCHAR2(10) := USER;
show_lov VARCHAR2(1);
show_toolbar VARCHAR2(1);
printer VARCHAR2(30);
END session_pkg;

PACKAGE BODY session_pkg


IS
BEGIN
SELECT first_name || ' ' || last_name,
user_id,
show_lov_flag,
show_toolbar_flag,
default_printer
INTO user_name, user_id, show_lov, show_toolbar, printer
FROM user_config
WHERE user_id = USER;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
user_name = 'NOT REGISTERED';
show_lov = 'Y';
show_toolbar = 'Y';
printer = 'lpt1';
WHEN OTHERS
THEN
RAISE_APPLICATION_ERROR
(-20000, 'Problem obtaining user profile for ' || USER);
END session_pkg;

==========================================================================================
================================================
Calling PLSQL functions in SQL
==========================================================================================
================================================
17.1: Looking at the problem:

Calling a function in PLSQL:

SELECT line_number, ps_parse.number_of_atomics(line_text) AS num_words


FROM notes
ORDER BY num_words;

* In the above case, column alias is assigned to function using 'AS'. can use the alias in ORDER BY without a second call to
the function.

Advantages of calling functions in SQL:

* Consolidate business logic to a smaller number of well tuned and easily maintained functions.
* Improve performance of your SQL statements.
* Simplify SQL statements.
* Perform actions in SQL which are otherwise impossible.

* Can call functions in SELECT, WHERE, START WITH, GROUP BY, HAVING, ORDER BY, SET and VALUES clauses.

INSERT INTO notes


(call_id, line_text, line_number)
VALUES
(:call.call_id, :note.text, next_line_number(:call.call_id))

UPDATE employee SET salary = max_compensation(department_id)


WHERE employee_id = 1005;

SELECT job_category(job_title_id) AS title, SUM(salary)


FROM employee
GROUP BY title

17.2: Syntax for calling stored functions in SQL:

[schema_name.][package_name.][func_name[@db_link_name][parameter_list]]

Function Definition:

FUNCTION calc_sales
(company_id_in IN company.company_id%TYPE,
status_in IN order.status_code%TYPE := NULL)
RETURN NUMBER;

Calling Examples:
* As a stand-alone function

SELECT calc_sales(1001, 'O')


FROM orders;

* As a package-based function

SELECT sales_pkg.calc_sales(1001, 'O')


FROM orders;

* As a remote package-based function:

SELECT sales_pkg.calc_sales@NEW_DELHI(1001, 'O')


FROM orders;

* As a stand-alone function in a specific schema:

SELECT scott.calc_sales(1001, 'O')


FROM orders;

* Should always avoid hard-coding the module's schema and database link directly in SQL statements. Should create
synonyms that hide this information.
* If you ever need to change the owner of the function or move it into a different database, can change only the synonym,
rather than changing all the SQL statements that call that function.
* When using a stored function in an SQL statement, must use positional notation. named and mixed notations are not
allowed.

17.3: Requirements for Stored Functions in SQL:

Requirements programmer defined PLSQL functions should meet, to be callable from an SQL statement:

* Function must be stored in the database: A function in an individual form cannot be called by SQL. No way for SQL to
resolve reference to that function.
* Function must be row specific function, not a column or a group function. can apply to only a row of data. not an entire
column of data that crosses rows.
* All function's parameters should be IN parameters.
* The datatypes of the function's parameters, as well as the datatype of the RETURN clause of the function must be
recognized by the oracle server. PLSQL datatypes which are not yet supported in the database: BOOLEAN,
BINARY_INTEGER, PLSQL Tables, PLSQL records and programmer defined subtypes.
* Functions defined in packages must have a RESTRICT_REFERENCES pragma. If you want to call from SQL, a function
defined in a package, you will need to add a pragma to the package spec.

Invalid Functions:

-- SQL doesn't recognize PLSQL tables.


TYPE string_tabtype IS TABLE OF VARCHAR2(1000) INDEX BY BINARY_INTEGER;
FUNCTION temp_table RETURN string_tabtype;

-- SQL doesn't recognize BOOLEAN


FUNCTION call_is_open (call_id_in call.call_id%TYPE) RETURN BOOLEAN;

FUNCTION calc_sales(company_id NUMBER, use_closed_orders_in BOOLEAN)


RETURN NUMBER;

17.4: Restrictions on PL/SQL functions in SQL:

Ex:
Invalid use of a function:
FUNCTION total_comp
(salary_in IN employee.salary%TYPE, bonus_in IN employee.bonus%TYPE)
RETURN NUMBER
IS
BEGIN
UPDATE employee SET salary = salary_in/2;
RETURN salary_in + NVL(bonus_in, 0);
END total_comp;

Don'ts:

* Modification of database tables: Can affect the result of the query from which the function might originate. may affect any
other SQL statement in the session.

* Modification of package variables in Stored functions in SQL: Since package variables act global variables in a session, this
could have an impact on other stored function or procedure, which in turn could affect a SQL statement using that stored
function.

* In WHERE clause: Query optimizer can re-order the evaluation of predicates in the WHERE clause to minimize the number
of rows processed. A function executing in this clause could subvert the query optimization process.

====

A Function should concentrate on computing and returning a value.

Oracle server makes it impossible to do any of the following:

* Stored function cannot modify database tables. No INSERT, DELETE or UPDATE.


* A stored function called remotely or through a parallelized action may not read or write package variables. Oracle server
does not support side-effects that cross user sessions.
* A stored function can update values of a package only if that function is called in SELECT, VALUES and SET clauses.
Cannot update package variables in WHERE or GROUP BY
* We can call DBMS_OUTPUT.PUT_LINE or DBMS_PIPE. Not sure about DBMS_SQL
* Cannot apply PLSQL table methods [COUNT, FIRST, LAST, NEXT, PRIOR etc] in a stored function used in SQL.
* The stored function may not call another module (stored procedure and function) that breaks any of the above rules.

17.5: Calling Packaged Functions in SQL:

* Spec and body of a package are distinct. Spec must exist b4 the body has been defined. When a select statement calls a
packaged function, only info available to it is the package spec. But, contents of package body determine whether that function
is valid for execution in SQL.
* Hence, we have to explicitly assert the purity level (the extent to which a function is free of side-effects) of a stored function,
in a package spec. Oracle server can then determine whether the function violates that purity level when the package body is
compiled.
* Can assert the purity level of a function with the RESTRICT_REFERENCES pragma.

17.5.1: The RESTRICT_REFERENCES pragma:

* Pragma is a special directive in PLSQL.


* With RESTRICT_REFERENCES telling the compiler the purity level the function meets or exceeds. Seperate pragma
statement for each function we wish to use in an SQL statement. must come after the function declaration in the package
specification.

PRAGMA RESTRICT_REFERENCES (function_name, WNDS [,RNDS] [,WNPS] [,RNPS])

WNDS: Writes no database state. Asserts that the function does not modify any database tables.
RNDS: Reads no database state. Asserts that the function does not read any database tables.
WNPS: Writes no package state. Asserts that the function does not modify any package variables.
RNPS: Reads no package state. Asserts that the function does not read any package variables.

* Only the function_name and WNDS is mandatory in the pragma, the other arguments are optional and can appear in any
order.
* No one argument implies the other. I can write from the database without reading from it and can read from a package
variable without writing into one.

PACKAGE company_financials
IS
FUNCTION company_type (type_code_in IN VARCHAR2)
RETURN VARCHAR2;

FUNCTION company_name (company_id_in IN company.company_id%TYPE)


RETURN VARCHAR2;

PRAGMA RESTRICT_REFERENCES (company_type, WNDS, RNDS, WNPS, RNPS);


PRAGMA RESTRICT_REFERENCES (company_name, WNDS, WNPS, RNPS);
END company_financials;

* If a function we want to call in SQL calls a procedure in a package, must also provide the RESTRICT_REFERENCES
pragma for that procedure. Can't call that procedure directly in SQL, but even if it's indirectly called, it should follow the rules.

17.5.1.1: Pragma violation errors:

If your function violates it's pragma, will receive PLS-00452 Error.

Ex:

CREATE OR REPLACE PACKAGE BODY company_financials


IS
FUNCTION company_type (type_code_in IN VARCHAR2)
RETURN VARCHAR2
IS
v_sal employee.sal%TYPE := NULL;
BEGIN
SELECT sal INTO v_sal FROM employee WHERE employee_id = 1;
RETURN 'something';
END;

FUNCTION company_name (company_id_in IN company.company_id%TYPE)


RETURN VARCHAR2
IS
BEGIN
UPDATE emp SET sal = 100000;
RETURN 'something';
END company_name;
END company_financials;

On Compilation: PLS-00452 -- Sub program COMPANY_TYPE violates its associated pragma.


PLS-00452 -- Sub program COMPANY_NAME violates its associated pragma.

17.5.2: Asserting purity level with the package initialization section.

Syntax:

PRAGMA RESTRICT_REFERENCES (package_name, WNDS [, RNDS] [, WNPS] [, RNPS]);


Here, we include the name of the package itself.

PACKAGE configure
IS
PRAGMA RESTRICT_REFERENCES(configure, WNDS, WNPS);
user_name VARCHAR2(100);
END configure;

PACKAGE BODY configure


IS
BEGIN
SELECT lname || ', ' || fname INTO user_name
FROM user_table
WHERE user_id = session_pkg.user_id;
END configure;

Here, eventhough we write the user_name package variable we specify WNPS because, user_name belongs to the same
package and modifying the variables of the same package is not considered a side effect.

17.6: Column/Function name precedence:

If a function has the same name as a table column in the SELECT statement and it has no parameters, then the column always
takes precedence over the function.

CREATE TABLE employee (employee_id, ...., salary, ....);

FUNCTION salary RETURN NUMBER;

The select statement referring to salary will always refer to the column and not the function.

SELECT salary FROM employee;

If you want to override the column precedence, must qualify the name of the function with the name of the schema that owns
the function.

SELECT scott.salary FROM employee;

17.7: Realities: Calling PLSQL functions in SQL:

Some disadvantages of calling functions in SQL:

* manually apply RESTRICT_REFERENCES to the code.


* functions execute outside the read-consistency model of oracle.
* Overhead of calling a function from SQL remains high.
* Tuning mechanisms such as EXPLAIN PLAN do not take into account the SQL that may be called inside functions called in
SQL statements.
* Some built-in PLSQL packages cannot be called in functions called from SQL.

17.7.1: Manual Application of PRAGMAS:

Cascading pragmas lead to more restrictions, thus drastically changing and sometimes crippling existing code.

17.7.2: Read-Consistency Model complications:

SQL queries with functions in them can violate the read-consistency model of Oracle RDBMS.

SELECT name, total_sales (account_id)


FROM account
WHERE status = 'ACTIVE';

FUNCTION total_sales (id_in account.account_id%TYPE)


RETURN NUMBER
IS
CURSOR tot_cur
IS
SELECT SUM(sales) total
FROM orders
WHERE account_id = id_in
AND year = TO_NUMBER(TO_CHAR(SYSDATE, 'YYYY'));

tot_rec tot_cur%ROWTYPE;
BEGIN
OPEN tot_cur;
FETCH tot_cur INTO tot_rec;
RETURN tot_rec.total;
END total_sales;

* Suppose: The account table has 5 million rows in it.


Order table: 20 million rows.
* If i start the query at 11, it takes 1 hr and ends at 12.
Now, at 11:45, somebody with proper authorization comes and deletes all the rows in the ORDERs table and commits the
transaction.
* According to read-consistency model of oracle, session running the query should see all those deleted rows until the query
completes, but the next time the total_sales function executes from with-in the query, it finds no order rows and will return
NULL. it will do the same for the rest of the time.
* If these functions are called for long running queries or transactions, we may need to probably issue the following command
to enforce read consistency between SQL statements in the current transactions.

SET TRANSACTION READ ONLY

17.8 Examples of embedded PL/SQL:

17.8.1: Encapsulating calculations:

* In applications, may need to perform the same calculations again and again. Code redundancy occurs. Will be a problem if
significant alteration in business logic is needed.
* Hide/encapsulate all of your formulas and calculations into stored functions.
Ex:

SELECT
DECODE(payment_date,
LAST_DAY(payment_date),
LEAST(ADD_MONTHS(payment_date, 1),
TO_DATE(TO_CHAR(ADD_MONTHS(payment_date, 1), 'MMYYYY') ||
TO_CHAR(payment_date, 'DD'), 'MMYYYYDD' ))
, ADD_MONTHS(payment_date, 1))
FROM premium_payments;

Explanation: If the last payment date falls on the last day of the month, then return as the next payment date the earliest of
either the result of adding one month to payment date (using ADD_MONTHS) or the same day in the new month as the day in
the month of the last payment date. If the last payment date is not on the last day of the month, then get the next payment date,
by simply using ADD_MONTHS to get the next payment date.

The above query can be implemented using the following function:

FUNCTION new_add_months (date_in IN DATE, months_shift IN NUMBER)


RETURN DATE
IS
return_value DATE;
BEGIN
return_value := ADD_MONTHS(date_in, months_shift);
IF date_in = LAST_DAY(date_in)
THEN
return_value := LEAST(return_value, TO_DATE(TO_CHAR(return_value, 'MMYYYY') || TO_CHAR(date_in, 'DD'),
'MMYYYYDD'));
END IF;
RETURN return_value;
END new_add_months;

Now, we can call this new stored function to obtain the next payment date easily.

SELECT new_add_months(payment_date, 1)
FROM premium_payments

17.8.2: Combining Scalar and aggregate values:

Question: Show me the name and the salary of the employee, who has the highest salary in each department, as well as
total salary for that person's department.

Two parts:

1:

SELECT department_id, last_name, salary


FROM employee E1
WHERE salary = (SELECT MAX(salary)
FROM employee E2
WHERE E1.department_id = E2.department_id)

2:

SELECT department_id, SUM(salary)


FROM employee
ORDER BY department_id;

* Cannot very easily combine them, since that would require listing and obtaining both scalar and aggragate values from the
same table.
SELECT department_id, last_name, salary, SUM (salary)
FROM ...?
WHERE ...?
GROUP BY ...?

Alternate solution: Create a view.

CREATE VIEW dept_salary


AS
SELECT department_id, SUM(salary) total_salary
FROM employee
GROUP BY department_id;

Using the view:

SELECT department_id, last_name, salary, total_salary


FROM employee e, dept_salary DS
where e.department_id = DS.department_id
AND salary = (SELECT MAX(salary)
FROM employee E1
WHERE e.department_id = E1.department_id)

* For above implementation, we need to create customized view everytime we need this kind of an implementation.

Instead of the view, we could use a function:

FUNCTION total_salary (dept_id employee.department_id%TYPE)


RETURN employee.salary%TYPE
IS
CURSOR grp_cur
IS
SELECT SUM(salary)
INTO return_value
FROM employee
WHERE department_id = dept_id;
return_value employee.salary%TYPE;
BEGIN
OPEN grp_cur;
FETCH grp_cur INTO return_value;
CLOSE grp_cur;
RETURN return_value;
END;

now this can be called as

SELECT department_id, lasty_name, salary, total_salary(department_id)


FROM employee
WHERE salary = (SELECT MAX(salary)
FROM employee e1
WHERE e1.department_id = e.department_id);

17.8.3: Replacing Correlated sub-queries:

* Can use stored functions in SQL statements to replace correlated subqueries.


* Correlated Sub-queries: Is a SELECT statement inside the WHERE clause of a SQL statement [SELECT, INSERT or
DELETE] which is correlated or makes reference to one or more columns of the SQL statement.

Ex:

SELECT E.department_id, last_name, salary, total_salary(E.department_id)


FROM employee E
WHERE salary = (SELECT MAX(Salary)
FROM employee E2
WHERE E.department_id = E2.department_id)

* The Inner query is executed once for every row of the outer query.
* Correlated sub-query is a very powerful feature of SQL, since it offers the equivalent of a procedure language's nested loop
capability.
LOOP
LOOP
END LOOP;
END LOOP;

* 2 drawbacks of correlated sub-query:


* Logic can become fairly complicated.
* The resulting SQL statement can be difficult to understand and follow.

* Can use stored functions in-place of the correlated sub-query:

FUNCTION max_salary (dept_id_in IN department.department_id%TYPE)


RETURN NUMBER
IS
CURSOR grp_cur
IS
SELECT MAX(salary)
FROM employee
WHERE department_id = dept_id_in;
return_value NUMBER;
BEGIN
OPEN grp_cur;
FETCH grp_cur INTO return_value;
CLOSE grp_cur;
RETURN return_value;
EXCEPTION
WHEN OTHERS
THEN
RETURN NULL;
END;

Usage of above function:

SELECT E.department_id, last_name, salary, total_salary(department_id)


FROM employee E
where salary = max_salary(department_id);

Now the total_salary and max_salary functions are similar. Consolidating all variations into one function:

FUNCTION salary_stat (dept_id_in department.department_id%TYPE,


stat_type_in IN VARCHAR2 )
RETURN NUMBER
IS
v_stat_type VARCHAR2(20) := UPPER(stat_type_in);

CURSOR grp_cur
IS
SELECT SUM(salary) sumsal,
MAX(salary) maxsal,
MIN(salary) minsal,
AVG(salary) avgsal,
COUNT(DISTINCT salary) countsal
FROM employee
WHERE department_id = dept_id_in;
grp_rec grp_cur%ROWTYPE;
ret_val NUMBER;
BEGIN
OPEN grp_cur;
FETCH grp_cur INTO grp_rec;
CLOSE grp_cur;

IF v_stat_type = 'SUM'
THEN
ret_val := grp_rec.sumsal;
ELSIF v_stat_type = 'MAX'
THEN
ret_val := grp_rec.maxsal;
ELSIF v_stat_type = 'MIN'
THEN
ret_val := grp_rec.minsal;
ELSIF v_stat_type = 'AVG'
THEN
ret_val := grp_rec.avgsal;
ELSIF v_stat_type = 'COUNT'
THEN
ret_val := grp_rec.countsal;
ELSE
ret_val := NULL;
END IF;

RETURN ret_val;
EXCEPTION
WHEN OTHERS
THEN
RETURN NULL;
END salary_stat;

using above function:

SELECT department_id, salary, last_name, salary_stat(department_id, 'sum')


FROM employee
where salary = salary_stat(department_id, 'max');

17.8.4: Replacing DECODES with IF statements:

* DECODE function offers IF-like capabilities in the nonprocedural SQL environment provided by oracle.
* Can use DECODE to perform complex IF THEN ELSE logic within a query.
* Downside: Difficult to write and very difficult to maintain.

* Below Query uses DECODE to determine whether a date is within the prescribed range and if it is, adds to the count of rows
which fulfill this requirement:

SELECT FC.year_number,
SUM(DECODE(GREATEST(ship_date, FC.q1_sdate),
ship_date,
DECODE(LEAST(ship_date, FC.q1_edate),
ship_date,
0,
1),
0)) Q1_Results,
SUM(DECODE(GREATEST(ship_date, FC.q2_sdate),
ship_date,
DECODE(LEAST(ship_date, FC.q2_edate),
ship_date,
1,
0)
0)) Q2_Results,
SUM(DECODE(GREATEST(ship_date, FC.q3_sdate),
ship_date,
DECODE(LEAST(ship_date, FC.q3_edate),
ship_date,
1,
0)
0)) Q3_Results,
SUM(DECODE(GREATEST(ship_date, FC.q4_sdate),
ship_date,
DECODE(LEAST(ship_date, FC.q4_edate),
ship_date,
1,
0)
0)) Q4_Results
FROM Orders O,
fiscal_calendar FC
Order By FC.year_number

* Repetition of content in the above query needs modularization.

FUNCTION incr_in_range (ship_date DATE, sdate_in DATE, edate_in DATE)


RETURN NUMBER
IS
BEGIN
IF ship_date BETWEEN sdate_in AND edate_in
THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
EXCEPTION
WHEN OTHERS
THEN
RETURN NULL;
END;

Query using the above function:

SELECT FC.year_number,
SUM(incr_in_range(ship_date, q1_sdate, q1_edate)),
SUM(incr_in_range(ship_date, q2_sdate, q2_edate)),
SUM(incr_in_range(ship_date, q3_sdate, q3_edate)),
SUM(incr_in_range(ship_date, q4_sdate, q4_edate))
FROM Orders O,
Fiscal_calendar FC
Group By
FC.year_number;

17.8.5: GROUP BY partial column values:

* GROUP BY clause of a SELECT statement allows us to collect data across multiple records and group them by one or more
columns.
* Following query calculates the total amount paid to all workers with the same job description:

SELECT job_title_desc, SUM(salary)


FROM employee E, jo_title T
WHERE E.job_title_id = T.job_title_id
Group BY job_title_desc;

Depicting the following things using a query would be difficult:

* Amount Of Salary for each type or category of Job title


* Earnings of various kinds of clerks in the company.
* Earnings of different types of vice-presidents or managers.
Using Stored Functions to deliver the above easily:

FUNCTION job_category (title_id_in IN job_title.job_title_id%TYPE)


RETURN VARCHAR2
IS
CURSOR title_cur
IS
SELECT job_title_desc FROM job_title
WHERE job_title_id = title_id_in;
title_rec title_cur%ROWTYPE;

BEGIN
OPEN title_cur; FETCH title_cur INTO title_rec;
IF title_cur%NOTFOUND
THEN
CLOSE title_cur;
RETURN NULL;
ELSE
CLOSE title_cur;
IF title_rec.job_title_desc LIKE 'CLERK'
THEN
RETURN 'CLERK';
ELSIF title_rec.job_title_desc LIKE 'MANAGER'
RETURN 'MANAGER';
ELSIF title_rec.job_title_desc LIKE 'VICE PRESIDENT'
RETURN 'VICE PRESIDENT';
END IF;
END IF;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;

Using the above function, the SQL query can be written as:

SELECT job_category(title_id_in) title, SUM(salary)


FROM employee
GROUP BY title;

17.8.6: Sequential processing against a column's value:

In SQL:

* Easy to find out if a particular word or set of characters appears in a string with the INSTR function.
* Easy to determine which rows have a column that contains a certain word.
* Very difficult to find out the number of times a particular word or set of characters appear in a string in a single row.

Some SQL examples:

-- shows all lines of text containing 'the'


SELECT text
FROM notes
WHERE INSTR(text, 'the') > 0;

-- using INSTR to find all lines of text containing the word 'the' atleast 3 times
SELECT text
FROM notes
WHERE INSTR(text, 'the', 1, 3) > 0

-- using INSTR to find all lines of text containing the word 'the' exactly 3 times

SELECT text
FROM notes
WHERE INSTR(text, 'the', 1, 3) !=0
AND INSTR(text, 'the', 1, 4) = 0;

Using PLSQL functions:

-- show the number of occurances of the in the text.

SELECT text, ps_parse.number_of_atomics(text, 'the')


FROM notes;

-- show the number of times urgent occurs in notes for each day. count of urgent should be atleast 1

SELECT TO_CHAR(note_date, 'DAY'), ps_parse.number_of_atomics(notes, 'urgent') urgent_count


FROM notes
WHERE urgent_count > 0;

17.8.7: Recursive processing in a SQL statement:

* SQL does not support recursion

Below package contains function which prints amounts on cheques in written version

PACKAGE checks
IS
FUNCTION num2words(amount_in IN NUMBER)
RETURN VARCHAR2;

PRAGMA RESTRICT_REFERENCES(num2words, WNDS);

END checks;

PACKAGE BODY checks


IS

v_date DATE := NULL;

TYPE text_table_type IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;


text_table text_table_type;

FUNCTION num2words(amount_in IN NUMBER)


RETURN VARCHAR2
IS
l_amount NUMBER := 0;
BEGIN
l_amount := FLOOR(amount_in); -- Not considering decimals

IF l_amount >=1000
RETURN num2words(l_amount/1000) || ' Thousand ' || num2words(MOD(l_amount/1000));
END IF;
IF l_amount >= 100
RETURN num2words(l_amount/100) || ' Hundred ' || num2words(MOD(1_amount/100));
END IF;

IF l_amount >= 20
THEN
RETURN text_table(FLOOR(l_amount/10)) || ' ' || num2words(MOD(l_amount/10));
END IF;

RETURN text_table(l_amount + 10);


END num2words;

BEGIN
-- populating multiples of 10

text_table (1) := 'Ten';


text_table (2) := 'Twenty';
text_table (3) := 'Thirty';
text_table (4) := 'Forty';
text_table (5) := 'Fifty';
text_table (6) := 'Sixty';
text_table (7) := 'Seventy';
text_table (8) := 'Eighty';
text_table (9) := 'Ninety';

text_table (10) := NULL;

-- populating values from 1 to 19

FOR i IN 1 .. 19
LOOP
v_date := TO_DATE(TO_CHAR(i)||'-JAN-2008', DD-MM-YYYY);
text_table (i+10) := INITCAP(TO_CHAR (v_date, 'DDSP'));
END LOOP;
END;
END checks;

Examples:

checks.num2words(99) := Ninety Nine


checks.num2words(12345) := Twelve Thousand Three Hundred Forty Five
checks.num2words(5) := Five

Using the function in a SQL query:

SELECT TO_CHAR(SYSDATE, 'Month DD, YYYY'),


payee,
amount,
checks.num2words(amount),
comment
FROM bill;
WHERE bill_status = 'UNPAID';

You might also like