CET341 OracleORTutorialv4.6 PDF

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 42

Object-Relational Oracle

Contents

1. Overview of Principal Object-Relational Features


2. User Defined Data Types and Object Tables
3. Methods
4. A New Way of Relating Tables
5. Inheritance
6. Arrays and Nested Tables
7. Triggers
8. Summary
A. PL/SQL
B. Class Diagram

© David Nelson – version 4.6 (March 2021) Page 1


1. Overview of Principal Object-Relational Features

Many relational systems, e.g. Oracle and Informix have been extended with object-
oriented functionality, therefore converting them to be classified as ‘object-
relational’ systems. Oracle first had object-relational features added in version 8
of its DBMS, and these facilities were extended in later versions. For example,
inheritance between object types was added in Oracle 9i.

In this tutorial, we will use a simple example of a hospital to demonstrate some of


the main object-relational features which have been added to Oracle.

You will, for example, learn how to:


- Create abstract data types
- Create sub-types
- Populate tables with complex data objects
- Perform queries over tables including sub-types
- Write methods using PL/SQL

We will also investigate triggers, which although not specifically an object-relational


feature, add a large amount of power to database systems. These allow for
complex integrity checking or data validation to be added to the database. Finally,
there is a brief reference to the language PL/SQL, in appendix A, which adds
programming language features to Oracle SQL for writing methods, triggers and
advanced queries. You used PL/SQL previously in the tutorial on Stored
Procedures and you may have used this previously when writing triggers using
Oracle APEX, for example in CET222. If not then you can refer to this appendix
which shows the main programming language constructs available.

In this tutorial you will develop a small database application in Oracle. An UML
class diagram is included in appendix B.

You should read through this tutorial and try all of the examples in either Oracle
SQL Developer, SQL Plus or Oracle APEX. Preferably you should use at least
Oracle 10g (we will be using Oracle 12g in this module). You will be given
instructions on how to access this in the tutorial.

Before you can run this tutorial, you need to log in to Oracle through either SQL
Developer, SQL P or Oracle APEX. Your user-name and password are the same
as the ones you previously used during this module for connecting to Oracle. If
you are using University of Sunderland computers then the database is

© David Nelson – version 4.6 (March 2021) Page 2


FCSDBPRD. Example output given is through the SQL*Plus application. You can
use Oracle APEX however there are some restrictions in functionality in APEX and
these will be pointed out as necessary.

© David Nelson – version 4.6 (March 2021) Page 3


2. User Defined Data Types and Object Tables

There are two forms of extended data type in Oracle: Object-types (equivalent to
abstract data types, or user defined types, UDTs, in SQL 3) and collection types
(i.e. arrays and nested tables). We will cover object types in this section. These
are similar to UDTs in SQL3, or can be thought of as being similar to a class in
object-oriented programming languages such as C#. We will look at collection
types in a later section.

An object type comprises of:


- an unique name;
- a collection of attributes, i.e. the data properties of the object;
- a collection of methods (optional), which can be written in PL/SQL.

2.1 Complex Attributes

Example 1: A simple object type definition

In Oracle (using SQL*Plus, APEX or SQL Developer), type, and run, the following:

CREATE TYPE AddressType as OBJECT


(
street VARCHAR2(30),
town VARCHAR2(20),
postcode VARCHAR2(8)
);
/

Important Points:
1. Note that you need the ‘/’ symbol at the end or the CREATE statement
because type definitions can include the ‘;’ symbol within their code, so you
need to highlight the end of your code entry using the ‘/’ symbol.
2. If your code contains syntax errors, you will get the message ‘Warning: Type
created with compilation errors.’, however the errors will most likely not be
displayed. To subsequently display the errors, you should type and run the
command:

show errors

© David Nelson – version 4.6 (March 2021) Page 4


which would then display your syntax errors. You MUST correct any
errors before you move on to the next step.

If you are using Oracle APEX then the show errors command does not work
and you should instead use the Object Browser as you have previously.

Next you need to create the Patient table. Type and run the following code.

Example 2: A table containing a user defined data type

CREATE TABLE patient


(
patient_no NUMBER(4) NOT NULL PRIMARY KEY,
patient_surname VARCHAR2(20) NOT NULL,
patient_forename VARCHAR2(20) NOT NULL,
dob DATE,
address AddressType
);

Use the describe command and you should see the following structure:

Name Null? Type


------------------------------ -------- -----------
PATIENT_NO NOT NULL NUMBER(4)
PATIENT_SURNAME NOT NULL VARCHAR2(20)
PATIENT_FORENAME NOT NULL VARCHAR2(20)
DOB DATE
ADDRESS ADDRESSTYPE

You can also use the describe command to display information about object types.
For example:

desc addresstype;
Name Null? Type
----------------------------- -------- -------------
STREET VARCHAR2(30)
TOWN VARCHAR2(20)

© David Nelson – version 4.6 (March 2021) Page 5


POSTCODE VARCHAR2(8)

Finally insert the following row of data into your new table:

INSERT INTO patient VALUES


(1, 'Smith', 'Fred', '01-JAN-1980',
AddressType('1 Fleet Road', 'Durham', 'DH5 9ZZ'));

Notice the extra parentheses around the column which contains the complex type
(which in itself contains four attributes), and the type name preceding it. This is
the required syntax when inserting complex attributes.

Now insert some more rows of data into your patient table (i.e. make up your own
data), and then list the contents of the table.

2.2 Object Tables

You could have created the patient table as an ‘object table’, by creating a patient
type. We could then, for example, create member functions (i.e. methods) or
nested tables as part of the type definition. We can even define sub-types (for
example we may want to be able to differentiate between in-patients and out-
patients), as we will see in a later section.

Firstly you will need to delete the existing patient table using the drop command,
i.e. type

DROP TABLE patient;

Then, input the following commands to create a patient type and an object table.

© David Nelson – version 4.6 (March 2021) Page 6


Example 3: Object Tables

CREATE TYPE PatientType as OBJECT


(
patient_no NUMBER(4),
patient_surname VARCHAR2(20),
patient_forename VARCHAR2(20),
dob DATE,
address AddressType,
MEMBER FUNCTION get_age RETURN NUMBER
) NOT FINAL;
/

Don’t worry about the ‘NOT FINAL’ bit for now – this simply signifies to Oracle
that we want the ability to be able to create sub-types (i.e. in-patients and out-
patients) which we will do later on. If you did not specify the type as ‘NOT
FINAL’, or alternatively used ‘FINAL’ then you would not be able to create new
sub-types.

You also do not need to worry just yet about the MEMBER FUNCTION, we will
look at these in the next section on methods.

Now re-create the patient table by typing the following.

CREATE TABLE patient OF PatientType


(patient_no PRIMARY KEY);

This would then give you the option of sharing this type across a number of tables
– as discussed later.

Use the describe command to inspect the structure of your Patient table.

Exercise: Insert the same data into this relation that you had inserted previously.
Then, insert two more rows of data into your tables. Run the following queries and
make sure that you understand the results:

1. SELECT * FROM patient;

© David Nelson – version 4.6 (March 2021) Page 7


Note, this command will not give a correct result in Oracle SQL Developer Web or
APEX. The display of complex objects is not supported in the version of Oracle
APEX that we are using, and you will get an unsupported data type message in
the result. The query does not run at all in SQL Developer Web.

This does not mean that your query is correct – it just means that the version of
the softwar we are using cannot support displaying the result. You will see
however that you can view parts of a complex object by the query below, which
does work in Oracle APEX and SQL Developer Web.

2. SELECT p.patient_no, p.address.street, p.address.town


FROM patient p;

© David Nelson – version 4.6 (March 2021) Page 8


3. Methods

3.1 Introduction

Types can contain methods as well as attributes. In Oracle they are known as
MEMBER FUNCTIONS.

These can then be used as if they were attributes within your SQL queries.
Methods can be written in any language, including PL/SQL (an extension of SQL),
Java and C++. Our examples in this section will be written in PL/SQL – a basic
tutorial on PL/SQL is included in appendix A, however you should understand this
language as you previously used it for developing stored procedures in a previous
tutorial.

The first example adds a new member function to the staff type.

Example 4: Object type with a method

CREATE TYPE StaffType as OBJECT


(
staff_id NUMBER(4),
staff_title VARCHAR2(4),
staff_surname VARCHAR2(20),
staff_address AddressType,
MEMBER FUNCTION get_staff_name RETURN VARCHAR2
);
/

This however simply informs Oracle that a function is going to be written, but the
code to perform this function has not yet been provided. You therefore have to
subsequently write the function body as follows:

CREATE OR REPLACE TYPE BODY StaffType AS


MEMBER FUNCTION get_staff_name RETURN VARCHAR2 IS
BEGIN
RETURN staff_title||' '||staff_surname;
END;
END;
/

© David Nelson – version 4.6 (March 2021) Page 9


Notice that it is not necessary to specify the size of the type in function definitions.
For example, the above function has the return type VARCHAR2 and not, for
example VARCHAR2(30). The same applies to type NUMBER.

Now, create a table called staff2 based on the staff type and insert the following
data:

staff_id title surname address


1 Dr Smith null
2 Mr Jones null

You should then run the following command:

SELECT s.staff_id, s.get_staff_name()


FROM staff2 s;

What does it do?

________________________________________________________________

________________________________________________________________

________________________________________________________________

Note that you need to use qualifiers (i.e. giving the table staff2 an alias s in the
query) throughout. Many parts of the extended versions of Oracle will not work
without using table aliases and qualifiers, so you should get used to using them
always from now on.

Exercise: In your definition of the Patient type you had defined a member function
get_age. However, you have not yet written the code for this function. Write the
code for the get_age function (HINT, use the months_between function that was
covered in the previous booklet on Oracle SQL) and test that it works by running
the following query:

© David Nelson – version 4.6 (March 2021) Page 10


SELECT p.patient_no, p.patient_surname, p.get_age()
FROM patient p;

3.2 Updates

If we wanted to update the address of a member of staff, then we could issue the
following update command:

UPDATE staff2 s
SET s.staff_address =
(
SELECT p.address
FROM patient p
WHERE p.patient_no = 1
)
WHERE s.staff_id = 1;

This would assign the address of the member of staff with staff_id 1 (i.e. Dr
Smith) to have the same address as the patient Smith. Note that we had to use
a nested select query to retrieve the patient address.

Now we have decided to update the address of the patient, if for example Fred
Smith moves to Lanchester, then we could issue the following update command:

UPDATE patient p
SET p.address.town = 'Lanchester'
WHERE p.patient_no = 1;

So you can see that we can use the ‘nested dot’ notation to update elements of a
composite attribute.

Now run the following two queries and note the result:

SELECT p.patient_no, p.address.town from patient p;

SELECT s.staff_id, s.staff_address.town from staff2 s;

© David Nelson – version 4.6 (March 2021) Page 11


Write below what you get as output.

________________________________________________________________

________________________________________________________________

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 12


4. A New Way of Relating Tables

Each row in a table has a unique identifier that is automatically generated by the
system, which is the object identity of that row. It is also commonly called the ‘row
identifier’.

Type the following command:

Example 5: Query including object identifier

SELECT ref(p), p.patient_no FROM patient p;

to output the object identifier. You’ll see that what is output is a long, meaningless
identifier. However, the use of object identity is very powerful.

Note that Oracle SQL Developer Web and Oracle APEX do not support the use
REF ref in some queries, so running the above query will unfortunately give you
an error in APEX. In SQL Developer web the query will run but display no data for
the REF column. However, the use of REF in associating tables will work, so all
the code below works fine in Oracle APEX and SQL Developer Web.

Create the following types and tables.

Example 6: Table using object identifier in place of joins

CREATE TYPE DiagnosisArrayType AS


VARRAY(3) OF VARCHAR2(20);
/

CREATE TYPE ConsultationType AS OBJECT


(
consult_id NUMBER(5),
doctor REF StaffType,
patient REF PatientType,
diagnosis DiagnosisArrayType
);
/

CREATE TABLE Consultations OF ConsultationType


(consult_id PRIMARY KEY);

© David Nelson – version 4.6 (March 2021) Page 13


INSERT INTO staff2 VALUES
(10, 'Dr', 'Watson', AddressType('113B Baker Street',
'London', 'SW1 2SH'));

Using row identifiers means we no longer need to use foreign keys to join tables.

Insert the following data into your new Consultations table, using the INSERT
command given in example 7, and then run the query given.

Example 7: Inserting data into tables using references to other rows

INSERT INTO Consultations VALUES


(1,
(SELECT ref(s) FROM staff2 s WHERE s.staff_id = 10),
(SELECT ref(p) FROM patient p WHERE p.patient_no =
1),
DiagnosisArrayType('Scurvy', 'Tuberculosis',
'Narcolepsy')
);

SELECT c.consult_id, DEREF(c.doctor) FROM


consultations c;

Unfortunately, as with REF, DEREF also does not work in Oracle APEX.
However the below query works and shows how you can follow references
between tables.

IN SQL Developer Web, the query will run but just displays [object Object], rather
than displaying the contents of the object.

Then run the following query:

SELECT c.consult_id, c.doctor.get_staff_name()


FROM consultations c;

© David Nelson – version 4.6 (March 2021) Page 14


Try and work out what these two queries are doing – it will give you a good
understanding of the object-oriented features of Oracle.

________________________________________________________________

________________________________________________________________

________________________________________________________________

This example also contains an example of a nested array. We will discuss this in
a later section.

© David Nelson – version 4.6 (March 2021) Page 15


5. Inheritance

Suppose we want to improve our database to allow us to store different types of


patients, for example we may have in-patients and out-patients.

In-patients have the previous properties of patient, plus an admission and


discharge date, whereas out-patients also have the previous properties of patient,
plus a visit date.

If Oracle did not support inheritance then there would be a number of alternative
methods with which we could model this.

ALTERNATIVE 1

We could create one table and store all of the values within that table:

TABLE Patient
Patient_ Surna Forena Addre Admit_ Discharge_ Visit_date
no me me ss date date
1 Smith Fred …. NULL NULL NULL

2 Jones Alan ….. 13/04/200 16/04/2005 NULL


5
3 King Helen …. NULL NULL 18/05/2005

Think what the problems with this would be:

________________________________________________________________

________________________________________________________________

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 16


ALTERNATIVE 2

We could create three tables, one to store patients, and then one each to store in-
patients and out-patients:

TABLE PATIENT
Patient_no Surname Forename Address
1 Smith Fred ….
2 Jones Alan …..
3 King Helen ….

TABLE IN_PATIENT
Patient_no Admit_date Discharge_date
2 13/04/2005 16/04/2005

TABLE OUT_PATIENT
Patient_no Visit_date
3 18/05/2005

Again, list the problems with this solution:

________________________________________________________________

________________________________________________________________

________________________________________________________________

ALTERNATIVE 3

We could create two tables, one to store in-patients and one to store out-patients:

TABLE IN_PATIENT

Patient_no Surname Forename Address Admit_date Discharge_da


te
2 Jones Alan ….. 13/04/2005 16/04/2005

© David Nelson – version 4.6 (March 2021) Page 17


TABLE OUT_PATIENT

Patient_no Surname Forename Address Visit_date


3 King Helen …. 18/05/2005

This is possibly the worst solution, why?

________________________________________________________________

________________________________________________________________

________________________________________________________________

Normally, solution one or two would be chosen (very much dependent on the
queries which you are most expecting to run), and then queries would have to be
written to retrieve the required information, e.g. list all in-patients, list all information
about all patients. However this requires a lot of work on behalf of the user.

A better solution is to have inheritance between the tables:

PATIENT

OUT_PATIENT IN_PATIENT

Database systems such as Oracle 11g allow type inheritance, by use of the
UNDER command. For example, to create both the in-patient and out-patient
types we would use the following commands:

© David Nelson – version 4.6 (March 2021) Page 18


Example 8: Types with Inheritance

CREATE TYPE InPatientType UNDER PatientType


(
admit_date DATE,
discharge_date DATE,
MEMBER FUNCTION days_in_hospital RETURN NUMBER
);
/

If we had not used the ‘NOT FINAL’ keyword earlier then we would be given an
error at this point because otherwise the system assumes the previously defined
Patient type is FINAL, which means that it is not allowed to have subtypes.

To insert some data into the patient table would then require the following syntax
(ensure that the patient number, i.e. 4, is the next available patient number in your
table – use a different number if patient 4 already exists):

INSERT INTO Patient VALUES


(InPatientType(4, 'Jones', 'Alan', '10-DEC-1985',
AddressType('17 Oracle Drive', 'Sunderland',
'SR1 2BC'),
'13-APR-2005', '16-APR-2005'));

The important point to note here is that we have not created an InPatient table, all
in-patients (and out-patients) can be stored within the patient table.

Exercise: Run the following query and see what data is returned. Is this what you
expected to see?

SELECT * FROM Patient;

Exercise: Create the OutPatient type and insert some data into the patient table.
Then, again run the query above to check that your data has been correctly
inserted into the Patient table.

© David Nelson – version 4.6 (March 2021) Page 19


As you’ll have seen from the previous exercise, when you type SELECT * FROM
Patient, you do not see all of the data as you may have expected. This is because,
as you should have seen earlier when you used the describe command, the patient
table is defined to only show the attributes from the patient type by default. So, to
display the extra information, there are a number of SQL queries which you can
run. Try the following queries to gain an understanding of how inheritance works,
and write below what each query does:

Important note: Oracle’s treatment of subtypes can sometimes cause problems if


you create and query a subtype in the same session. If you get errors stating that
the types do not exist when you run the below queries, commit your data, close
down the current session and start a new Oracle SQL*Plus/SQL Developer
session. If this does not resolve the problem, then it is likely that you have errors
in your type definitions.

1. SELECT VALUE(p) FROM Patient p;

________________________________________________________________

________________________________________________________________

________________________________________________________________

Unfortunately this query and query 2 below will not work in Oracle APEX, giving
an unsupported data type result. The remaining queries work fine.

In SQL Developer Web, rather than displaying the object contents, it just displays
‘[object Object]’.

2. SELECT TREAT(VALUE(p) AS InPatientType) FROM Patient p;

________________________________________________________________

________________________________________________________________

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 20


3. SELECT patient_no,
TREAT(VALUE(p) AS InPatientType).admit_date admit_date
FROM Patient p;

________________________________________________________________

________________________________________________________________

4. SELECT patient_no, TREAT(VALUE(p) AS


InPatientType).admit_date admit_date
FROM Patient p
WHERE VALUE(p) IS OF (InPatientType);

________________________________________________________________

________________________________________________________________

________________________________________________________________

5. SELECT p.patient_no FROM Patient p


WHERE VALUE(p) IS OF (ONLY PatientType);

________________________________________________________________

________________________________________________________________

________________________________________________________________

This is one implementation of inheritance. In the Postgres DBMS, inheritance is


implemented across tables, rather than across types. This means that we would
have three tables, but inheritance between the tables would be managed by the
system rather than having to rely on foreign keys.

Discuss which solution you prefer and why.

________________________________________________________________

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 21


________________________________________________________________

Exercise: Now, write the method, called days_in_hospital, for the InPatient type
which calculates how many days that the patient is in hospital for (for simplicity you
may assume that there are 31 days in any month). Test that your function works
for by running a query which calls this function for patient number 4.
6. Arrays and Nested Tables

6.1 Arrays

Try the following query:

Example 9: Retrieving an array in queries

SELECT c.consult_id, c.diagnosis FROM consultations c;

This in fact contains an array (of type diagnosis type) with a maximum of three
elements. It is called a VARRARY because the array can store a varying number
of elements, with a maximum upper bound (i.e. maximum number of elements)
which is specified when you declare the array type.

Unfortunately this query gives an unsupported data type error in Oracle APEX.
However you can run the following query instead to show all elements of the array:

SELECT c.consult_id, d.*


FROM consultations c, TABLE(c.diagnosis) d ;

To find out how many elements are stored in a particular array we need to write a
PL/SQL stored procedure (this is similar to a method but simpler in that it can exist
independently of a table definition and does not require to be embedded in an SQL
statement to be executed). The simple example below creates an array and
displays the number of elements in the array. If you type this directly into SQL*Plus
it will run the code, and display the array length. You can test this works correctly
by changing the number of elements in the array and checking that the result is
always correct.

CREATE OR REPLACE PROCEDURE check_array_size IS


TYPE MyArrayType is VARRAY(10) OF NUMBER(2);
x MyArrayType;
sze NUMBER(2);

© David Nelson – version 4.6 (March 2021) Page 22


BEGIN
x := MyArrayType(2,4,6,8);
sze := x.COUNT;
DBMS_OUTPUT.put_line('Array length is ' || sze);
END;
/

To run this procedure we firstly need to set an Oracle environment variable to


ensure that output from the procedure is output to the screen. To do this, type

SET SERVEROUTPUT ON;

Then, to run your procedure, type (in Oracle SQL*Plus or Oracle SQL Developer:

EXECUTE check_array_size();

If you are using Oracle APEX then the equivalent command is:

BEGIN
check_array_size();
END;

However, what we ideally want is a procedure which will count the number of
diagnoses for each consultation. Enter and run the following procedure and
ensure that you understand what it is doing. You will need to look in the
appendices at the section on Cursors to aid your understanding.

CREATE OR REPLACE PROCEDURE diagnoses_array_size IS


sze NUMBER(2);
BEGIN
FOR d_cursor IN (SELECT c.* FROM consultations c)
LOOP
sze := d_cursor.diagnosis.COUNT;
DBMS_OUTPUT.put_line('Array length for
diagnosis ' || d_cursor.consult_id || '
is ' || sze);
END LOOP;
END;
/

© David Nelson – version 4.6 (March 2021) Page 23


Exercise: Using the procedure as a starting point, write a function which can
be called from within an SQL query to return the size of a diagnosis array.
Call this function diagnosis_array_size_fn. Functions are covered in more
detail in the appendix, section A2. Test that your function works with the
following SQL query:

SELECT c.consult_id,
diagnoses_array_size_fn(c.diagnosis)
FROM consultations c;

Finally, to access a specific element of the array we could use the following query:

SELECT d.*
FROM consultations c, TABLE(c.diagnosis) d
WHERE d.COLUMN_VALUE = 'Scurvy';

6.2 Nested Tables

A nested table on the other hand does not have a fixed upper limit, i.e. it can
contain as many rows as you want.

The code to produce a table including a nested table is given below:

Example 10: Creating and using a nested table

CREATE TYPE NestedAddressType AS TABLE OF AddressType;


/

CREATE TYPE EmployeeType AS OBJECT


(
employee_id NUMBER(5),
employee_surname VARCHAR2(20),
employee_forename VARCHAR2(25),
employee_dob DATE,
employee_address NestedAddressType
);
/

© David Nelson – version 4.6 (March 2021) Page 24


CREATE TABLE Employees OF EmployeeType
(
employee_id PRIMARY KEY
)
NESTED TABLE employee_address STORE AS
NestedAddressTable;

We have a table employee as in the diagram below, where employee_address is


a column of table employee, and for each employee contains a nested table.

employee employee_ employee_ employee employee_address


_id surname forename _dob
1 Smith Fred 23-JUL-66 13 Scary Lane,
Sunderland, SR4 5EG
14 Mad Drive,
Gateshead, NE38 1AB

To insert data we would use the following commands:

Example 11: Inserting data into Nested Tables

INSERT INTO Employees VALUES


(1, 'Smith', 'Fred', NULL, NestedAddressType());

INSERT INTO TABLE


(
SELECT e.employee_address
FROM Employees e
WHERE e.employee_id = 1)
VALUES ('13 Scary Lane', 'Sunderland', 'SR4 5EG');

INSERT INTO TABLE


(
SELECT e.employee_address
FROM Employees e
WHERE e.employee_id = 1
)
VALUES ('14 Mad Drive', 'Gateshead', 'NE38 1AB');

© David Nelson – version 4.6 (March 2021) Page 25


And then run the following queries, writing below what each does:

Example 12: Queries with Nested Tables

1. SELECT * FROM Employees;

________________________________________________________________

________________________________________________________________

________________________________________________________________

2. SELECT e.employee_id, a.*


FROM Employees e, TABLE(e.employee_address) a
WHERE e.employee_id = 1;

________________________________________________________________

________________________________________________________________

________________________________________________________________

3. SELECT e.employee_id, a.street

FROM employees e, TABLE(e.employee_address) a;

________________________________________________________________

________________________________________________________________

________________________________________________________________

4. Arrays and Nested Tables break one of the levels of normalisation. Which

one do you think it breaks, and why?

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 26


________________________________________________________________

________________________________________________________________

5. Just as you did with arrays, it is also possible to display the length of nested
tables. Adapt the procedure you developed in the last section to display the
number of addresses for any employee.

© David Nelson – version 4.6 (March 2021) Page 27


7. Triggers

You have already seen triggers in the PL/SQL tutorial. You should run through
this section to ensure you understand triggers fully.

Triggers allow us to add complex data validation or data integrity checks to our
database. Although we can normally add data validation/integrity checks as
constraints on tables, the constraint system normally only allows very simple
checks to be performed. We use triggers when we want to manage more complex
constraints on our data.

The examples given in this section are very simple triggers, and you will be
expected to be writing much more complex triggers for your assignment, which will
include, for example SQL statements and other code examples given in the
appendix.

7.1 Defining Simple Triggers

For the next example, we need to create a new table, because we want to record
clocking on and clocking off times for staff who work in the hospital. We could
have created a type, but for simplicity we will simply create a table as follows:

CREATE TABLE StaffRota


(
staff_id NUMBER(4) NOT NULL,
work_date DATE NOT NULL,
time_in DATE NOT NULL,
time_out DATE NOT NULL,
PRIMARY KEY (staff_id, work_date)
);

Now we wish to add a constraint which says that staff are not allowed to leave
work before they have started. We could use the following trigger:

© David Nelson – version 4.6 (March 2021) Page 28


Example 12: Trigger example

CREATE OR REPLACE TRIGGER staff_trigger


BEFORE INSERT ON StaffRota
FOR EACH ROW
DECLARE
invalid_entry EXCEPTION;
BEGIN
IF :new.time_out <= :new.time_in THEN
RAISE
invalid_entry;
END IF;
EXCEPTION
WHEN invalid_entry THEN
RAISE_APPLICATION_ERROR(-20001, 'Staff cannot
leave work before their start time');
END;
/

Now, insert these two rows into the table:

INSERT INTO StaffRota


VALUES (1, '20-SEP-06', to_date('08:00', 'HH24:MI'),
to_date('15:00', 'HH24:MI'));

INSERT INTO StaffRota


VALUES (1, '21-SEP-06', to_date('08:00', 'HH24:MI'),
to_date('03:00', 'HH24:MI'));

Note the use of the to_date function. The DATE type also allows us to store time,
but we need to ‘cast’ the string so that the database can understand what we are
storing, based on the template (HH24:MI, i.e. 24 hour clock with 2 digits
representing the hour and two digits for the minutes, separated by a colon).

What is the result of running these two insert commands:

________________________________________________________________

________________________________________________________________

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 29


There are other types of triggers, e.g. AFTER INSERT, BEFORE UPDATE, etc.,
but their use should be fairly self-explanatory. There is also the INSTEAD OF
trigger, which can be used to update views but which we will not go into in this
booklet. We will look at these when we look at data warehousing.

You could have replaced the command:

WHEN invalid_entry THEN


RAISE_APPLICATION_ERROR(-20001, 'Discharge date
cannot be after admission date');

with

WHEN invalid_entry THEN


DBMS_OUTPUT.PUT_LINE('Discharge date cannot be
after admission date');

Make this change and write below the difference between the two statements:

________________________________________________________________

________________________________________________________________

________________________________________________________________

7.2 Row versus Statement level Triggers

Triggers can be written to fire either at row level or statement level. Row level
triggers fire once for every row affected by the statement, whereas statement level
triggers fire once for the whole statement.

For example, create the following table, and then the two very simple triggers
which will fire on an update to the table

© David Nelson – version 4.6 (March 2021) Page 30


CREATE TABLE trigger_example_table
(a NUMBER(2) NOT NULL);

CREATE OR REPLACE TRIGGER simple_row_level_trigger


BEFORE UPDATE ON trigger_example_table
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE('Row level trigger fired');
END;
/

CREATE OR REPLACE TRIGGER


simple_statement_level_trigger
BEFORE UPDATE ON trigger_example_table
BEGIN
DBMS_OUTPUT.PUT_LINE('Statement level trigger
fired');
END;
/

INSERT INTO trigger_example_table VALUES(1);


INSERT INTO trigger_example_table VALUES(2);

Then run the following update statement:

UPDATE trigger_example_table
SET a = a * 2;

Note below the effect of the two triggers, i.e. how many times does each trigger

fire. Why do you think this is?

________________________________________________________________

________________________________________________________________

________________________________________________________________

© David Nelson – version 4.6 (March 2021) Page 31


7.3 Before v After Triggers

You have a choice of firing the trigger either before the event happens, or after.
For example, if you are inserting a new row of data into a table, then you can either
fire the trigger before the new row of data is inserted, or after.

To decide which type of trigger you are using depends on the context of the trigger.
If, for example, you may need to change the data being inserted into the table then
you must use a BEFORE trigger. However, if you are not changing the data but
are for example performing data validation, then it is normally better to use an
AFTER trigger.

7.4 Triggers and Object-Relational Features

Some minor issues exist with Oracle’s support for object-relational features within
triggers. For example, it is not possible to use the REF feature within a trigger.
There are other issues, which you will discover when writing more complex triggers
for your assignment.

© David Nelson – version 4.6 (March 2021) Page 32


8. Summary

This short document has introduced you to some of the more advanced features
of the Oracle DBMS, which you can use in your assignment. Further coverage of
these features can be found in Connolly and Begg, which discusses the object-
relational features in Oracle. The following appendix is a short introduction to the
PL/SQL programming language, concentrating on syntax rather than being a
tutorial on PL/SQL. Further detailed information and examples on PL/SQL can be
found in Shah’s book ‘Database Systems Using Oracle’, as well as in Connolly and
Begg.

© David Nelson – version 4.6 (March 2021) Page 33


A. PL/SQL

The trigger example illustrated writing a procedure using a language which is


available in Oracle called PL/SQL. This language can be used for writing triggers,
methods, or just executing a set of commands once in the SQL*Plus environment.
PL/SQL is short for Procedural Language/SQL, and is a language for adding
computational complexity to SQL.

The structure of a procedure or trigger is therefore normally as follows:

DECLARE
<variable and constant declarations go here, this
section is optional>
BEGIN
<trigger or method code goes here>
EXCEPTION
<exception handling code goes here, this section
is optional>
END;
/

Within procedures it is not necessary to have the keyword DECLARE.

A.1. Variables and Constants

Variables and constants can be used within your procedure. They can use any
type in Oracle, e.g. NUMBER, VARCHAR2, DATE, BOOLEAN – the only
difference is that NUMBER and VARCHAR2 do not have to have a size specified.

These are all valid variables:

DECLARE
my_age NUMBER;
my_name VARCHAR := 'Fred' /* This is a variable
with a default value */
my_sex CONSTANT CHAR := 'M'; /* This is a
constant */

© David Nelson – version 4.6 (March 2021) Page 34


You can also declare variables to have the same type as an attribute within one of
your tables, e.g.

DECLARE
pat_surname patient.patient_surname%TYPE;

A.2. SQL

You can insert any SQL command, i.e. SELECT, INSERT, UPDATE, DELETE into
a procedure. Normally, you would use a SELECT query, and retrieve the results
from the query into a variable.

For example:

CREATE OR REPLACE PROCEDURE my_first_procedure IS


pat_surname patient.patient_surname%TYPE;
BEGIN
SELECT patient_surname
INTO pat_surname
FROM patient
WHERE patient.patient_no = 1;
DBMS_OUTPUT.PUT_LINE('Patient surname: '
||pat_surname);
END;
/

To run your procedure, type

EXECUTE my_first_procedure();

In Oracle APEX you would instead have to type

BEGIN
My_first_procecedure();
END;

And you should get the message:

PL/SQL procedure successfully completed.

© David Nelson – version 4.6 (March 2021) Page 35


However it would not have outputted the patient’s surname. To do this, you need
to turn the SERVEROUTPUT environment of Oracle on (this does not apply if you
are using Oracle APEX), using the command

SET SERVEROUTPUT ON

And then when you execute the procedure you will see the output from your
procedure.

The following example illustrates use of parameters which can be provided to a


procedure. The keyword IN specifies that the procedure is expecting a value on
invocation.

CREATE OR REPLACE PROCEDURE pr_name (pat_no IN number)


IS
pat_surname patient.patient_surname%TYPE;
BEGIN
SELECT patient_surname
INTO pat_surname
FROM patient
WHERE patient.patient_no = pat_no;
DBMS_OUTPUT.PUT_LINE('Patient surname: '
||pat_surname);
END;

Now run the following commands:

EXECUTE pr_name(3);
EXECUTE pr_name(10);

When you run the procedure the second time, you get an error message to say
‘no_data_found’. This is because the SQL SELECT command has not returned
any data, i.e. there is no patient with patient number 10. To get round this, we
could add an exception block to the procedure, to deal with a NO_DATA_FOUND
exception. This is a type of pre-defined exception, the other one you will commonly
see is TOO_MANY_ROWS, which will fire if your query returns more than one row
when you are only expecting one. We can therefore add some more code to the
above procedure:

© David Nelson – version 4.6 (March 2021) Page 36


CREATE OR REPLACE PROCEDURE pr_name (pat_no IN number)
IS
pat_surname patient.patient_surname%TYPE;
BEGIN
SELECT patient_surname
INTO pat_surname
FROM patient
WHERE patient.patient_no = pat_no;
DBMS_OUTPUT.PUT_LINE('Patient surname: '
||pat_surname);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('This patient does not
exist');
END;

In the trigger which you created earlier, there was an example of the other type of
exception, which is a user-defined exception. You can write user-defined
exceptions to deal with special cases in your code.

We could change the above procedure to a function which would produce a


complete patient’s name.

CREATE OR REPLACE FUNCTION full_name


(pat_no IN number)
RETURN VARCHAR2 IS
pat_surname patient.patient_surname%TYPE;
pat_forename patient.patient_forename%TYPE;
BEGIN
SELECT patient_surname, patient_forename
INTO pat_surname, pat_forename
FROM patient
WHERE patient.patient_no = pat_no;
RETURN (pat_forename||' '||pat_surname);
END;

Then you could use this function in a query such as the one below:

SELECT p.patient_no, full_name(p.patient_no)


FROM patient p;

© David Nelson – version 4.6 (March 2021) Page 37


A.3. PL/SQL Statements

The statements that you most commonly use in PL/SQL will be:

- selection
- loops

Selection

There are two types of selection statement:

IF and CASE

The syntax of IF is:

IF condition THEN
<action statements 1>
ELSIF condition2 THEN
<action statements 2>
ELSIF condition3 THEN
<action statements 3>
ELSE
<action statement 4>
END IF;

An example of an IF statement was given in example 12.

CASE has two options, either you can test the value of a variable, or you can check
specific conditions. The syntax for both options is given below:

CASE Syntax 1:

CASE <variable>
WHEN <value1> THEN <statement set 1>
WHEN <value2> THEN <statement set 2>

ELSE <statement set n>
END CASE;

© David Nelson – version 4.6 (March 2021) Page 38


CASE Syntax 2

CASE
WHEN <condition1> THEN <statement set 1>
WHEN <condition2> THEN <statement set 2>

ELSE <statement set n>
END CASE;

Loops

There are three types of looping statement:

1. Basic loop
2. WHILE loop
3. FOR loop

Basic loop

Basic loops are indefinite, and therefore need a condition to exit. The syntax for a
basic loop is:

LOOP
<statement 1>
<statement 2>

<statement n>
EXIT WHEN <condition>;
END LOOP;

An example of a LOOP statement is given in section A.4.

The WHILE loop is an alternative to the basic loop and performs as long as the
condition is TRUE. The syntax is:

WHILE <condition> LOOP


<statement 1>
<statement 2>

<statement n>
END LOOP;

© David Nelson – version 4.6 (March 2021) Page 39


Finally, the FOR loop will iterate a set number of times. The syntax is:

FOR <counter_variable> IN <lower>..<upper> LOOP


<statement 1>
<statement 2>

<statement n>
END LOOP;

Where <counter_variable> is a previously defined variable. The loop can also be


made to count downwards by inserting the keyword REVERSE after IN in the loop
header.

A.4. Cursors

Cursors allow us to execute a statement and then process the results of the
statement on a line by line basis. A cursor is declared within the DECLARE section
in the PL/SQL block.

DECLARE
pat_no patient.patient_no%TYPE;
pat_surname patient.patient_surname%TYPE;
pat_forename patient.patient_forename%TYPE;
CURSOR pat_cursor IS
SELECT patient_no, patient_surname,
patient_forename
FROM patient;
BEGIN
IF NOT pat_cursor%ISOPEN THEN
OPEN pat_cursor;
END IF;
LOOP
FETCH pat_cursor INTO pat_no, pat_surname,
pat_forename;
EXIT WHEN pat_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Patient number: '
||pat_no);
DBMS_OUTPUT.PUT_LINE('Patient name: '
||pat_forename||' '||pat_surname);

© David Nelson – version 4.6 (March 2021) Page 40


END LOOP;
DBMS_OUTPUT.PUT_LINE(pat_cursor%ROWCOUNT ||'
patients found');
CLOSE pat_cursor;
END;
/

A simpler way of executing this would be the following:

DECLARE
BEGIN
FOR pat_cursor IN
(SELECT patient_no, patient_surname,
patient_forename
FROM patient)
LOOP
DBMS_OUTPUT.PUT_LINE(pat_cursor.patient_fore
name||' '||pat_cursor.patient_surname);
END LOOP;
END;
/

It would depend on how you intend to use the cursor as to which is the best
method. For simple operations then the second example would suffice, but if you
need more complex manipulation of the cursor then you would need to explicitly
create the cursor as in the first example.

© David Nelson – version 4.6 (March 2021) Page 41


B. UML Class Diagram

© David Nelson – version 4.6 (March 2021) Page 42

You might also like