PL/SQL
Packages:
A package is a collection of PL/SQL objects grouped together as one logical name, much like a class in C++ or Java and a Module in Visual Basic. However they are NOT object oriented and do not share all of the characteristics of objects such as inheritance. It does however have a form of encapsulation and polymorphism.
Encapsulation: In older, procedural languages data and code
were separate entities. With
object-oriented technologies, the data and code are packaged together in
entities known as objects. When you
create an object, it has its own set of data along with code that can operate on
its data. The only thing the developer
needs to worry about is the object's public interface. The public interface determines which code
and data belonging to that object can be accessed from the outside world. Encapsulation is one of the major advantages
of object-oriented programming.
By
providing a public interface and hiding the details of an object, developers can
change objects so that they perform completely differently without affecting any
other code. This works much like your
television set does. All that you, the
user, know is that you can turn the TV on or off, change the volume, and adjust
the color. How the TV actually shows a
picture is unknown to you. This allows
TV manufactures to change the way televisions work inside without having to
teach you a new interface.
Two
important principles about Encapsulation:
1.
Data
is almost always private. I.e. Not accessible to the outside
world.
2.
The
public interface is made up of two types of public methods (function)
A. Accessor:
Access to the data. Ex: getLname()
B. Mutator:
Change the data. Ex: setLname( Smith )
Polymorphism: With
object-oriented technology, you can define different objects with similar public
interfaces. It is known as polymorphism when two objects have identical interfaces
and potentially different implementations.
For instance, you might create an invoice object (form) and a purchase order object (form) such that each has a public "save
button" interface (click-event). With this in place, you can hand an object to
a generic save routine. When the object
is handed (clicked) to the save
routine, it can use the code associated with it's own implementation. The object-oriented language determines which
method (function) to call based on it s signature (the number and type or
arguments/parameters passed to it).
There are many benefits to
using packages:
1.
It allows you to group
related functions, procedures, shared variables, and
cursors.
2.
The entire package is loaded
when one of it s functions or procedures is executed, so that the first call is
expensive but calls to other functions or procedures in the package are
essentially free.
3.
Packages allow the creation
of private members that can only be accessed within the package. This allows for a form of
encapsulation.
Packages are made up of two
parts:
1.
The package header
section. This section is where you
declare all of your public functions, procedures, and shared variables. This is the public interface to the
package.
2.
The package body is where
all of your actual code for the functions/procedures declared in the header
section are implemented, also you may declare private functions, procedures,
variables (data-members) in this area.
The
following rules apply to Package bodies:
1.
There must be an exact match
between the cursor and module headers and their definitions in package
header.
2.
Do not repeat declaration of
variables, exceptions, type, or constants in the header again in the
body.
3.
Any element declared in the
header can be referenced in the body
Example:
AS
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN
NUMBER;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER;
END
manage_customers;
/
CREATE OR REPLACE PACKAGE
BODY manage_customers
AS
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN NUMBER
IS
IRetVal
Integer;
BEGIN
INSERT INTO customer
(customer_number, lname, fname)
VALUES (customer_seq.NextVal,
v_custlast, v_custfirst);
SELECT customer_seq.currval INTO
iRetVal FROM DUAL;
RETURN
iRetVal;
END;
v_customer_number IN
customer.customer_number%TYPE)
RETURN
NUMBER
IS
V_balance
customer.balance%TYPE;
BEGIN
SELECT NVL(balance,0) INTO
v_balance FROM customer
WHERE customer_number = v_customer_number;
RETURN v_balance;
END get_balance;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER
IS
BEGIN
IF (get_balance(v_cust_nr) <>
0) THEN
RETURN 0; -- Don t delete a customer with a
balance.
END IF;
DELETE FROM customer WHERE
customer_number = v_cust_nr;
RETURN 1; --
Success
END;
END
manage_customers;
/
Assume the following is in a
file called manage_customers.sql
In
SQL*Plus, compile as such:
SQL>
@manage_customers.sql
To
execute a function in a package do the following.
SQL> VARIABLE g_balance NUMBER;
SQL> EXECUTE :g_balance := manage_customers.get_balance
(4);
SQL> PRINT :g_balance;
USING NAMED ARGUMENTS:
SQL> exec :g_balance := manage_customers.get_balance(v_customer_number=>3);
SQL> VARIABLE g_num NUMBER;
SQL> EXECUTE :g_num :=
manage_customers.add_customer ('Smith', 'John');
SQL> PRINT :g_num ;
So
a function or procedure member of a function is called as such: package_name.member_name()
This is basically the same
as in C++ and Java.
The
above package is a very basic version of what you will have to do in your
project, of course you will have to have exception handling and you will need
Add, Update, and Delete function or procedures for each
table.
Creating Private
Objects:
Public members are declared in the Header and are accessible to the outside world. Members not declared in the Header but declared in the Body are NOT visible to the outside world, they are only visible to other members. This is very similar to public vs. private members of a class in C++ or Java.
For example, let s re-write
the above package as follows:
AS
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN
NUMBER;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER;
FUNCTION update_customer
(
v_cust_nr IN
customer.customer_number%TYPE,
v_custlast IN customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE,
v_street IN
customer.street%TYPE,
v_city IN
customer.city%TYPE,
v_state IN
customer.state%TYPE,
v_zip IN customer.zip_code%TYPE
) RETURN BOOLEAN;
-- Function get_balance is no-longer here, but
still below, that makes it private
END
manage_customers;
/
CREATE OR REPLACE PACKAGE
BODY manage_customers
AS
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN NUMBER
IS
IRetVal
Integer;
BEGIN
INSERT INTO customer
(customer_number, lname, fname)
VALUES (customer_seq.NextVal,
v_custlast, v_custfirst);
SELECT customer_seq.currval INTO
iRetVal FROM DUAL;
RETURN
iRetVal;
END;
v_customer_number IN
customer.customer_number%TYPE)
RETURN
NUMBER
IS
V_balance
customer.balance%TYPE;
BEGIN
SELECT balance INTO
v_balance FROM customer
WHERE customer_number = v_customer_number;
RETURN v_balance;
END get_balance;
FUNCTION
update_customer
(
v_cust_nr IN
customer.customer_number%TYPE,
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE,
v_street IN
customer.street%TYPE,
v_city IN
customer.city%TYPE,
v_state IN
customer.state%TYPE,
v_zip IN customer.zip_code%TYPE
) RETURN BOOLEAN IS
IRetVal Integer;
BEGIN
UPDATE
customer
SET lname =
v_custlast,
fname =
v_custfirst,
street =
v_street,
city =
v_city,
state =
v_state,
zip_code =
v_zip
WHERE customer_number = v_cust_nr;
COMMIT;
RETURN TRUE;
EXCEPTION
WHEN OTHERS
THEN
ROLLBACK;
RETURN FALSE;
END;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER
IS
BEGIN
IF (get_balance(v_cust_nr) <>
0) THEN
RETURN 0; -- Don t delete a customer with a
balance.
END IF;
DELETE FROM customer WHERE
customer_number = v_cust_nr;
RETURN 1; --
Success
END;
END
manage_customers;
/
We
can no longer call get_balance as we did in the last example, however member
function del_customer can still call get_balance, because they belong to the
same package. We can still call
del_customer from outside the packages as such:
SQL> VARIABLE g_retval
NUMBER;
SQL> EXECUTE :g_retval := manage_customers.get_balance
(124);
SQL> PRINT :g_retval;
SQL> VARIABLE g_num
NUMBER;
SQL> EXECUTE :g_num :=
manage_customers.del_customer (124);
SQL> PRINT
:g_num;
Creating Shared Variables
and Cursors:
As I said earlier you can create public package wide variables and cursors by placing them in the header section. Additionally you can have them initialized in the body the first time the package is loaded. To create private package wide variables just declare them in the body.
For
example
AS
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN
NUMBER;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER;
-- Public Interface to v_call_count, Accessor Method.
FUNCTION getCallCount RETURN NUMBER;
-- Public Interface to v_call_count, Mutator Method.
PROCEDURE setCallCount (p_num Integer);
p_call_count
Integer;
END
manage_customers;
/
CREATE OR REPLACE PACKAGE
BODY manage_customers
AS
v_call_count Integer;
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN NUMBER
IS
IRetVal
Integer;
BEGIN
p_call_count := p_call_count
+1;
v_call_count := v_call_count +1;
INSERT INTO customer
(customer_number, lname, fname)
VALUES (customer_seq.NextVal,
v_custlast, v_custfirst);
SELECT customer_seq.currval INTO
iRetVal FROM DUAL;
RETURN
iRetVal;
END;
v_customer_number IN
customer.customer_number%TYPE)
RETURN
NUMBER
IS
V_balance
customer.balance%TYPE;
BEGIN
SELECT balance INTO
v_balance FROM customer
WHERE customer_number = v_customer_number;
RETURN v_balance;
END get_balance;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER
IS
BEGIN
P_call_count := p_call_count +1;
v_call_count := v_call_count +1;
IF (get_balance(v_cust_nr) <>
0) THEN
RETURN 0; -- Don t delete a customer with a
balance.
END IF;
DELETE FROM customer WHERE
customer_number = v_cust_nr;
RETURN 1; --
Success
END;
-- This is an example of a accessor variable.
FUNCTION getCallCount RETURN NUMBER IS
BEGIN
return v_call_count;
END;
-- Public Interface to v_call_count, Mutator Method.
PROCEDURE setCallCount (p_num Integer) IS
BEGIN
IF (p_num >= 0) THEN
v_call_count :=
p_num;
ELSE
dbms_output.put_line('Invalid Amount for Call Count. Cant be
negative');
END IF;
END;
BEGIN
-- This
is an initialization, it only get's executed once.
p_call_count := 0;
v_call_count :=
0;
END
manage_customers;
/
SQL> VARIABLE g_count NUMBER;
SQL> EXECUTE :g_count := manage_customers.p_call_count;
SQL> PRINT :g_count;
SQL> EXECUTE :g_count := manage_customers.v_call_count;
SQL> PRINT :g_count;
SQL> EXECUTE :g_count := manage_customers.getCallCount();
SQL> PRINT :g_count;
SQL> exec manage_customers.setCallCount(10);
-- Notice for the procedure call we don't need a
return variable, that is because procedures don't return a value.
PL/SQL procedure successfully completed.
SQL> exec
:g_count := manage_customers.getCallCount();
PL/SQL procedure
successfully completed.
SQL> print
:g_count;
G_COUNT
---------
10
Function/Procedure Overloading (Polymorphism):
We mentioned earlier about polymorphism being one of the characteristics of object orientation. One way that it is implemented in object oriented languages such as C++ & Java is through something called Function Overloading. Basically what this mean is that you can have two or more functions/procedures with the same name so long as the parameters into the function differ enough from each other so that the compiler can determine which version of the function/procedure to call. For example I could create two versions of my del_customer function, one to delete by customer number the other to delete by customer name.
Example:
FUNCTION del_customer
(v_cust_nr IN customer.customer_number%TYPE)
FUNCTION del_customer
(v_last IN customer.lname%TYPE, v_first IN customer.fname%TYPE)
SQL> VARIABLE g_retval NUMBER;
SQL> EXECUTE :g_retval :=
manage_customers.del_customer (123); --
Will call the first version.
SQL> EXECUTE :g_retval :=
manage_customers.del_customer ( Smith , John ); -- Will call the second
version.
This is known as function overloading.
For
example
AS
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN
NUMBER;
-- Function del_customer is overloaded (Polymorphism)
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER;
-- Function del_customer is overloaded (Polymorphism)
FUNCTION
del_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN
NUMBER;
-- Public Interface to v_call_count, Accessor Method.
FUNCTION getCallCount RETURN NUMBER;
-- Public Interface to v_call_count, Mutator Method.
PROCEDURE setCallCount (p_num Integer);
p_call_count
Integer;
END
manage_customers;
/
CREATE OR REPLACE PACKAGE
BODY manage_customers
AS
v_call_count Integer;
FUNCTION add_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN NUMBER
IS
IRetVal
Integer;
BEGIN
p_call_count :=
p_call_count +1;
v_call_count := v_call_count +1;
INSERT INTO customer
(customer_number, lname, fname)
VALUES
(customer_seq.NextVal, v_custlast, v_custfirst);
SELECT
customer_seq.currval INTO iRetVal FROM DUAL;
RETURN
iRetVal;
END;
v_customer_number IN
customer.customer_number%TYPE)
RETURN
NUMBER
IS
V_balance
customer.balance%TYPE;
BEGIN
SELECT balance INTO
v_balance FROM customer
WHERE customer_number = v_customer_number;
RETURN v_balance;
END get_balance;
FUNCTION del_customer
(
v_cust_nr IN
customer.customer_number%TYPE
) RETURN
NUMBER
IS
BEGIN
P_call_count := p_call_count +1;
v_call_count := v_call_count +1;
IF
(get_balance(v_cust_nr) <> 0) THEN
RETURN 0; -- Don t delete a customer with a
balance.
END
IF;
DELETE FROM customer
WHERE customer_number = v_cust_nr;
RETURN 1; --
Success
END;
-- Function del_customer is overloaded (Polymorphism)
FUNCTION
del_customer
(
v_custlast IN
customer.lname%TYPE,
v_custfirst IN
customer.fname%TYPE
) RETURN NUMBER
IS
BEGIN
P_call_count := p_call_count +1;
/**
We don't have the v_cust_nr in this version of del_customer, so for now we will
comment it out.
IF
(get_balance(v_cust_nr) <> 0) THEN
RETURN 0; -- Don t delete a customer with a
balance.
END
IF;
DELETE FROM customer
WHERE lname = v_custlast AND fname = v_custfirst;
RETURN 1; --
Success
END;
-- This is an example of a accessor variable.
FUNCTION getCallCount RETURN NUMBER IS
BEGIN
return v_call_count;
END;
-- Public Interface to v_call_count, Mutator Method.
PROCEDURE setCallCount (p_num Integer) IS
BEGIN
v_call_count := p_num;
END;
BEGIN
-- This
is an initialization, it only get's executed once.
p_call_count := 0;
v_call_count :=
0;
END
manage_customers;
/
SQL> desc user_source;
Name Null
Type
----------------------------------------- --------
----------------------------
NAME VARCHAR2(30)
TYPE VARCHAR2(12)
LINE
NUMBER
TEXT VARCHAR2(4000)
SQL> select distinct name, type from
user_source;
NAME
TYPE
------------------------------
-
-----------
GET_BALANCE
FUNCTION
GET_BALANCE_PROC
PROCEDURE
MANAGE_CUSTOMERS
PACKAGE
MANAGE_CUSTOMERS
PACKAGE BODY
SQL> SET PAGESIZE 0;
SQL> SELECT text FROM
user_source WHERE name = 'MANAGE_CUSTOMERS' AND TYPE='PACKAGE';
Summary:
1.
Packages:
A Header: Public members and
interfaces
B. Body: Implementation of Public interfaces as
well as private members and methods.
2. Encapsulation: Private data and Public interfaces - (accessor/mutator)
3. Polymorphism: Method (function/procedure) overloading.