Professional Documents
Culture Documents
Chapter 01:
Introduction to PL/SQL:
Procedure maintain_company(
action_in IN Varchar2,
id_in IN NUMBER,
name_in IN VARCHAR2 := NULL)
IS
BEGIN
....
EXCEPTION
END;
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.
* 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.
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.
*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 ...
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.
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;
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;
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;
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.
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.
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.
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;
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.
create your own subtypes of native datatypes. improve maintainability and readability of code.
*****************************************************************************************************
**************
27/03/08
*****************************************************************************************************
**************
1.4.5 PL/SQL Release 2.2:
PL/SQL Wrapper: Standalone utility that transforms PL/SQL code to portable binary/object code.
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.
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.
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.
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.
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;
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.
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:
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.
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.
==========================================================================================
========================================
CHAPTER 2: PL/SQL Language Fundamentals
==========================================================================================
========================================
*****************************************************************************************************
*****************************
31/03/08
*****************************************************************************************************
*****************************
2.1 The PLSQL character set:
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
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.3 Literals:
*****************************************************************************************************
***************************
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).
'''''' = ''
double quote character doesn't have any significance inside a string 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.
TRUE and FALSE. Never place single quotes around boolean literals.
2.4: Semicolon Delimiter:
2.5: Comments:
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.
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.
example:
DECLARE
no_such_sequence EXCEPTION;
PRAGMA EXCEPTION_INIT (no_such_sequence, -2289);
BEGIN ...
END;
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.
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;
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.
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
==========================================================================================
=====================================
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;
Package Specification:
PACKAGE package_name
/*
|| Author:
||
|| Overview:
||
|| Major Modifications:
||
*/
IS
...
END package_name;
Package Body:
PACKAGE BODY package_name
IS
/*-------------------- Package Variables ------------------------*/
... declarations ...
FUNCTION ...
PROCEDURE ...
FUNCTION ...
PROCEDURE ...
END package_name;
==========================================================================================
===============
Chapter 4: Variables and Program Data
==========================================================================================
===============
4.1: Identifiers:
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.
Scalar datatypes fall into one of the four categories: number, character, boolean and date-time.
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
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.
No scale: no rounding.
Examples:
big_whole_number NUMBER;
contains the full range of supported values, as precision: 38 and scale: 0.
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.
Store text and are manipulated by character functions. Character strings are free-form.
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.
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.
store variable length character strings. must specify max length for string when declaring a variable length string. range 1 to
32767 bytes.
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.
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.
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:
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.
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'.
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.
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.
*****************************************************************************************************
*************************************
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.
4.2.6.2: NVARCHAR2:
store variable length NLS character data. specify length when declaring NVARCHAR2. max length of NVARCHAR2 is
32767.
Large Object Datatypes. LOB can store upto 4GB of raw data, binary data (Images...), or character text data.
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.
ex:
DECLARE
fam_five japanese NCLOB;
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;
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;
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.
as far as possible avoid implicit conversions. use explicit functions for better understanding.
Rules:
max_salary := 0;
IF max_salary = NULL ... -- This will never be true.
my_string := 'Fun'
y_string := NULL;
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.
perform special case checking with IS NULL and IS NOT NULL operators.
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.
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.
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.
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]
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;
hire_date DATE;
enough_data BOOLEAN;
total_revenue NUMBER (15, 2);
long_para VARCHAR2 (2000);
next_date CONSTANT DATE := '15-APR-96';
when assigning a default value, can also specify that var must be NOT NULL.
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.
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.
company_id company.company_id%TYPE;
4.5.2: Anchoring at compile time: make sure that all affected modules are re-compiled after data structure changes.
DECLARE
-- base variable
unlimited_revenue NUMBER;
total_rev unlimited_revenue%TYPE;
total_rev_94 total_rev%TYPE;
total_rev_95 total_rev%TYPE;
BEGIN ...
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;
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.
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.
DECLARE
val_1 POSITIVE;
BEGIN
val_1 := -10000000;
PLSQL 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.
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.1:
IF
THEN
END IF;
Ex:
IF report_requested
THEN
print_report(report_id);
END IF;
5.1.2:
IF THEN
ELSE
END IF;
IF caller_type = 'VIP'
THEN
generate_response('EXPRESS');
ELSE
generate_response('NORMAL');
5.1.3:
IF THEN
ELSIF THEN
ELSE
END IF;
Make sure that IF-ELSIF conditions are mutually exclusive. No overlapping conditions.
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.
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;
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.
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 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;
Syntax: NULL;
NULL statement does nothing except pass control to the next executable statement.
IF status = 'GOOD'
THEN
process_order;
ELSE
NULL;
END IF;
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;
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".
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.
<<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.
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.
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.
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.
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.
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.
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.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.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;
Syntax:
CURSOR cursor_name [([parameter, [parameter])]]
[RETURN return_specification]
IS SELECT statement;
CURSOR company_cur
IS
SELECT company_id FROM company;
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
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].
Package Specification:
PACKAGE order_pkg
IS
CURSOR order_cur IS RETURN oe_order_headers_all%ROWTYPE;
END order_pkg;
Package Body:
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.
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.
IF NOT order_cur%ISOPEN
THEN
OPEN order_cur;
ELSE
NULL;
END IF;
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 row of a PLSQL table row, a variable and oracle forms bind variable:
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.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.
Explicit Cursors:
%FOUND
%NOTFOUND
%ROWCOUNT
%ISOPEN
Example:
Cursor order_cur
IS
SELECT order_number, order_header_id
FROM oe_order_headers_all;
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;
OPEN order_cur;
LOOP
FETCH order_cur INTO order_rec;
EXIT WHEN NOT order_cur%FOUND
OPEN order_cur;
LOOP
FETCH order_cur INTO order_rec;
EXIT WHEN order_cur%NOTFOUND;
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;
can access info about most recently executed SQL statements through SQL cursor attributes.
SQL%FOUND;
SQL%NOTFOUND;
SQL%ROWCOUNT;
SQL%ISOPEN;
* 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.
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;
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;
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.
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;
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;
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.
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;
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;
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;
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
CLOSE order_cur_var;
END;
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.
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.
We assign a value (cursor object) to the cursor object when we OPEN the cursor.
DECLARE
BEGIN
END;
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.
Example:
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);
WHEN ROWTYPE_MISMATCH
THEN
FETCH building_curvar INTO commercial_rec;
show_commercial_site(commercial_rec);
END;
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.
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.
* 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.
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;
CLOSE curvar1;
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;
can pass cursor variable as an argument to a function or procedure. need to specify the mode of the param and the datatype.
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);
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;
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.
/*
Variables which determine whether the function should continue to
loop through cursor's records.
*/
found_unassigned_task BOOLEAN := false;
more_tasks BOOLEAN := false;
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
==========================================================================================
=================================================
FOR Loop:
rank for the fixed range of values. from one to maximum number:
In many situations, the number of times a loop must execute varies and so FOR loop cannot be used.
LOOP
<executable statement (s)>
END LOOP;
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;
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;
LOOP
balance_remaining := account_balance(account_id);
apply_balance(account_id, balance_remaining);
END LOOP;
LOOP
... body
EXIT WHEN boolean_condition;
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.
FOR loop_counter IN 1 .. 10
LOOP
executable statements ...
END LOOP;
cannot specify loop index increment. to obtain increment index, other than one, have to write some cute code.
or
FOR loop_index IN 1 .. 50
LOOP
calc_values(loop_index*2);
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.
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;
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.
WHILE TRUE
LOOP
<executable_statement(s)>
END LOOP;
<<year_loop>>
WHILE year = 'LEAP_YEAR'
LOOP
<<month_loop>>
FOR month_number IN 1 .. 12
LOOP
...
END LOOP month_loop;
END LOOP year_loop;
<<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;
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.
<<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;
7.7.3: Avoiding the phony loop: use loops only where necessary.
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;
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.
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.
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.
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;
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.
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.
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.
* 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.
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.
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;
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
...
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.
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.
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
*****************************************************************************************************
*************************************
8.5.1.1: PL/SQL runtime engine raised a named system exception: auto raised by PLSQL, use exception handler to handle
these.
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;
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;
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.
EXCEPTION
WHEN OTHERS
THEN
send_error_to_pipe (SQLCODE);
RAISE;
END;
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.
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.
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.
Cannot use AND, since only one exception can be raised at a time.
Best way to handle un-handled exceptions: Make sure that the outermost PL/SQL block contains a WHEN OTHERS clause in
the exception section.
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;
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.
PROCEDURE RAISE_APPLICATION_ERROR
(error_number IN NUMBER, error_msg IN VARCHAR2)
Example:
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.
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:
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.
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;
==========================================================================================
================================================
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.
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.
*** 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.
** 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.
** 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.
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;
DECLARE
comp_name company.name%TYPE;
comp_rec company%ROWTYPE;
BEGIN
SELECT * FROM company
INTO comp_rec
WHERE company_id = 1024;
...
END;
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;
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;
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.
Syntax:
cannot declare a field to be an exception or a cursor [with PLSQL 2.3, can declare a field to be a cursor variable.]
<record_name> <record_type>;
prev_customer_sales_rec customer_sales_rectype;
top_customer_rec customer_sales_rectype;
* 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.
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;
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;
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;
...
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:
cust_sales_roundup_rec cust_sales_roundup%ROWTYPE;
CURSOR cust_sales_cur IS
SELECT * FROM cust_sales_roundup;
cust_sales_rec cust_sales_cur%ROWTYPE;
* 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.
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.
** 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.
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.
** Record Initialization:
* Can initialize a table or cursor based record at the time of declaration only with another record of same type and source.
* 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;
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.
DECLARE
TYPE phone_rectype IS RECORD
(intl_prefix VARCHAR2(2),
area_code VARCHAR2(3),
exchange VARCHAR2(3),
phn_number VARCHAR2(4),
extension VARCHAR2(4)
);
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),
auth_rep_info_rec.fax_phone#.area_code := auth_rep_info_rec.home_phone#.area_code;
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;
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
);
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:
==========================================================================================
================================================
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;
**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.
Definition of PLSQL table: One-dimensional, unbounded, sparse collection of homogeneous elements, indexed by integers.
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.
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 $.
<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;
<table_name> (<primary_key_value>)
primary_key_value is compatible with the BINARY_INTEGER data type. Assign values to a row using ':='.
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;
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;
PACKAGE company_pkg
IS
END company_pkg;
* you must declare a PLSQL table based on the type before you can use any of the programs.
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;
* Iterative
Eg:
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;
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;
* 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;
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 ...
* 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.
* 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.
Eg 01:
TYPE emp_tabtype IS TABLE OF employee%ROWTYPE
INDEX BY BINARY_INTEGER;
Eg 02:
CURSOR emp_cur
IS
SELECT *
FROM employee;
Eg 03:
Eg01:
emp_tab(375) := 'RAHULMN';
Eg02:
IF names_table (old_name_row + 1).last_name = 'SMITH'
THEN ...
can assign the whole record fetched from the db directly into the row of a PLSQL table.
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;
company_keys_tab company_keys_tabtype;
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;
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;
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.
Data-Smart/Sparse Storage:
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:
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:
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
/* 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'
);
END array;
/*------------------Private Modules--------------------*/
/*-----------------Public Modules----------------------*/
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.
*/
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;
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.
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
*****************************************************************************************************
*************************************
* 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.
Header Section
...
Declaration Section
...
Execution Section
...
Exception Section
...
PROCEDURE get_happy
IS
BEGIN
DECLARE
...
BEGIN
...
END;
END;
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.
DECLARE
...
BEGIN
...
DECLARE
...
BEGIN
...
END;
END;
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;
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;
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.
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.
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 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.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.
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.
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);
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;
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:
DECLARE
sales_for_2008 NUMBER DEFAULT tot_sales(1504, 'C')
BEGIN
...
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;
** 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:
DECLARE
company_name VARCHAR2(60);
BEGIN
...
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;
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.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.
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);
DECLARE
PROCEDURE show_data (data_in IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Data is ' || data_in);
END;
BEGIN
...
EXCEPTION
...
END;
/*--------------LOCAL MODULES-----------------*/
PROCEDURE assign_next_case
(employee_id_in IN NUMBER, case_out OUT 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!
* Overloaded programs which differ only by parameter names, must be called using the named notation.
* 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.
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.
==========================================================================================
================================================
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.2.2: The Body: Contains all the code behind the package specification: variables, cursors and other objects.
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.
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 pets_inc
IS
max_pets_in_facility CONSTANT INTEGER := 120;
pet_is_sick EXCEPTION;
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.
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;
16.2.6.3: Observations:
PACKAGE exchdlr
IS
en_general_error NUMBER := -20000;
exc_general_error EXCEPTION;
PRAGMA EXCEPTION_INIT (exc_general_error, en_general_error);
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';
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.
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.
* 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.
PACKAGE cursor_sampler
IS
CURSOR caller_tab (id_in IN NUMBER) RETURN caller%ROWTYPE;
END cursor_sampler;
* Cursors are best used, when they are included in the package specification.
* 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;
==========================================================================================
================================================
Calling PLSQL functions in SQL
==========================================================================================
================================================
17.1: Looking at the problem:
* 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.
* 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.
[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
* As a package-based function
* 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.
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:
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.
====
* 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.
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;
* 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.
Ex:
Syntax:
PACKAGE configure
IS
PRAGMA RESTRICT_REFERENCES(configure, WNDS, WNPS);
user_name VARCHAR2(100);
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.
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.
The select statement referring to salary will always refer to the column and not the function.
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.
Cascading pragmas lead to more restrictions, thus drastically changing and sometimes crippling existing code.
SQL queries with functions in them can violate the read-consistency model of Oracle RDBMS.
tot_rec tot_cur%ROWTYPE;
BEGIN
OPEN tot_cur;
FETCH tot_cur INTO tot_rec;
RETURN tot_rec.total;
END total_sales;
* 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.
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
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:
2:
* 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 ...?
* For above implementation, we need to create customized view everytime we need this kind of an implementation.
Ex:
* 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;
Now the total_salary and max_salary functions are similar. Consolidating all variations into one function:
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;
* 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
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;
* 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:
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:
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.
-- 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;
-- show the number of times urgent occurs in notes for each day. count of urgent should be atleast 1
Below package contains function which prints amounts on cheques in written version
PACKAGE checks
IS
FUNCTION num2words(amount_in IN NUMBER)
RETURN VARCHAR2;
END checks;
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;
BEGIN
-- populating multiples of 10
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: