Migrating DBMS Management
from Microsoft Access Applications
to Oracle 7.1 RDBMS Server
White Paper
Oracle Corporation, Inc.
December, 1995
INTRODUCTION
ASSUMPTIONS
TOPICS COVERED
THE ACCESS/ORACLE CLIENTSERVER
ARCHITECTURE
ACCESS ARCHITECTURE
JET/ODBC/ORACLE ARCHITECTURE
MIGRATING ACCESS APPLICATIONS TO ORACLE RDBMS SERVER
OVERVIEW
MIGRATION IN DETAIL
MIGRATION, STEP BY STEP
EXTENDING YOUR APPLICATION
APPENDICES 9
APPENDIX 1 - ORACLE RESERVED WORDS
APPENDIX 2 - CODE AND QUERY SAMPLES
APPENDIX 3 - JET ARCHITECTURE DETAILS
APPENDIX 4 - ORACLE 7.1 RDBMS ARCHITECTURE DETAILS
OTHER ORACLE FEATURES
APPENDIX 5 - ODBC ARCHITECTURE DETAILS
APPENDIX 6 - JET/ODBC/ORACLE ARCHITECTURE DETAILS
APPENDIX 7 - ACCESS / ORACLE TUNING AND CUSTOMIZATION
APPENDIX 9 - QUESTIONS AND ANSWERS (PRELIM)
Introduction
Accurate and timely data is essential for business operations in
the 1990s. Faster computers along with easy-to-use database
software has led to the creation of personal and departmental
databases which manage important business data. Products such as
Microsoft Access allow developers and advanced users to build
complete business systems. Microsoft Access is based on file
sharing technology. Because of this, Access lacks the speed,
reliability and robustness provided by an independent RDBMS
server.
Using an RDBMS server with Access in a clientserver
architecture uses the strengths of both technologies. Access
provides excellent form and report systems, as well as a complete
programming language (Access Basic). An independent relation
database management system (RDBMS) provides reliable, robust, and
secure high speed data management.
Oracle 7.1 RDBMS Server, from Oracle Corporation, is a modern,
scaleable, high performance database server which can run on wide
range of computers from PCs to mainframes. Oracle 7.1 operates in
a networked, clientserver environment and can support tens,
hundreds or thousands of users depending on the server computer.
This paper, "Migrating DBMS Management from Microsoft Access
Applications to Oracle 7.1 RDBMS Server", explains how to
use Microsoft Access and Oracle 7.1 together in a
clientserver architecture. If you have an investment in
Access applications, you can retain this investment while adding
the advanced features of Oracle 7.1 RDBMS to you application
architecture.
After reading this paper, you will know how to move a working
Access application to an Access / Oracle architecture. In
addition, you will know how to extend your application using
Access or other client development tools such as Oracle Power
Object or Oracles Developer/2000 tool set.
The subjects covered in this paper also apply to Microsoft Visual
Basic (VB) applications which use the Jet DBMS engine. Visual
Basic applications can be moved to Oracle as easily as Access
Applications.
Assumptions
This paper assumes that you are familiar with Microsoft Access;
advanced topics assume that you are familiar with writing Access
Basic programs. This paper also assumes that you know how to
create triggers on Oracle 7.1.
If you have Access installed on your personal computer and you
have access to Oracle 7.1 from your personal computer you can
experiment with the concepts discussed in this paper. To write
stored procedures and triggers on Oracle you can use
Oracles SQL*Plus utility.
Topics Covered
This paper contains three sections and several appendices.
"The Access / Oracle ClientServer Architecture"
provides an overview of the major components involved in coupling
Access to Oracle.
"Migrating Access Applications to Oracle RDBMS Server"
reviews the steps required to move the data management portion of
an Access application to Oracle 7.1 RDBMS.
"Extending your Application" explores ways in which the
Access / Oracle application can be extended using other Oracle
development tools.
The appendices include detailed information about the Access /
Oracle architecture. The information in the appendices will be
helpful if you are interested in the details of how Access
operates with Oracle or if you want to tune your system for
maximum performance.
The Access / Oracle ClientServer Architecture
Access Architecture
Microsoft Access is based on a file server DBMS technology named
"Jet". As shown in Figure 1, Access Forms,
Reports and Basic code rely on Jet to manage data stored in a the
native "mdb" file format.
Figure 1 Access Architecture.
In a singleuser Access application, the mdb file, as well
as Access itself, are located on the same machine. In a
multiuser Access application, the mdb file is placed on a
file server and shared. Each client runs a copy of Access and the
Jet engine. In this configuration, Jet must move a large amount
of data across the network, including whole tables, to complete
its query processing.
Jet Recordsets
When Jet processes a query, it returns a Recordset (a cursor) for
the resulting set of records. Jet supports two types of
Recordsets, Snapshots and Dynasets.
A Snapshot is a picture of data, as it existed at the
time the query is run. When returning a snapshot, Jet runs the
query to completion, extracts all of the resulting rows and
columns into a virtual table and presents this virtual table to
the user. The user of a Snapshot is able to perform a full range
of operations on a snapshot, query the Snapshot, base Forms and
Reports on the Snapshot, etc. Changes cannot be made to Snapshot
data and changes made by others users after a Snapshot has been
opened are not reflected in the Snapshot.
A Dynaset, on the other hand, is a live view of the
data. When returning a Dynaset, Jet extracts the "key
values" from the data and stores these in memory. When a
user requests rows of data from the Dynaset, Jet fetches the rows
of interest by looking them up in the base tables via the
internally stored key values. Once a Dynaset is opened, the set
of key values cannot change. This means that while the data
pointed to by the "key value" may change and will be
reflected to the user, new rows added after the query is started
will not be a part of the set of key values and will not be made
visible to the user. Rows which are deleted after the keyset
query is run are still part of the set of key values, however
they are marked #DELETED# when presented to the user .
The Dynaset model is a powerful and flexible model which gives
the user of PC based information the opportunity to browse large
quantities of data and update the data at will. When used with
local data, Dynasets are fast and effective. However, the Dynaset
model presents one of the key performance challenges when Access
works with a RDBMS server such as Oracle.
Jet MultiUser Updates
Jet handles updates by multiple users using two methods -
optimistic and pessimistic locks .
Using pessimistic locking, Jet will place a hard lock on the data
page which contains the row being edited. Other users will not be
able to start editing the locked row until the lock is abandoned
or the changes are written to disk.
Jet employs an optimistic locking scheme when working with Oracle
7.1 RDBMS. An optimistic locking scheme does not place hard lock
on the source table(s). Instead, when a change is to be
committed, Jet checks to make sure that the data has not been
altered by another user before allowing the changes to be posted.
Jet Enforced Referential Integrity
Jet supports declarative referential integrity. This includes
Primary Key / Foreign Key relationships with onetoone
and onetomany cardinality with cascading updates and
deletes.
Jet / ODBC / Oracle Architecture
Using Oracle 7.1 RDBMS Server with Access can increase the
robustness and reliability of a multiuser system. Network
traffic is reduced because only the query requests and the
resulting data are sent over the network (instead of complete
tables). Jet technology is focused on singleuser
performance with adequate multiuser capabilities; Oracle
7.1 RDBMS on the other hand is mature central server technology
focused on multiuser performance, rollback and recovery,
and centralized query processing.
Obtaining adequate performance from the combination of Access and
Oracle requires an understanding of how Jet works with
centralized servers.
Figure 2 shows that Access requires ODBC to make its connection
to Oracle 7.1 RDBMS.
Figure 2 Access / ODBC / Oracle Architecture.
ODBC is an API that allows client applications to connect to
different RDBMS servers. Jet has been designed to make efficient
use of ODBC while requiring a level 1 ODBC driver . When Access
uses ODBC to connect to remote RDBMS servers, Jet continues to
function as the DBMS engine for Access. Access Forms, Reports and
Basic code continue to work with Jet as if they were working with
local or shared data in the mdb file format. Jet presents remote
Oracle tables as attached tables . These attached tables are
created at design time and appear to be local tables.
Because of the way Jet works with remote tables, Jet requires a
primary key on tables in Oracle in order to support Dynasets
against those tables. If a remote table does not have a primary
key, Jet will only open a nonupdateable Snapshot on the
table.
Migrating Access Applications to Oracle RDBMS Server
Overview
This section describes how to move the data management portion of
a working Access application to Oracle. By following the steps in
this section you can create an Access / Oracle clientserver
version of your application.
To move the data management portion of an Access application to
Oracle you must first separate the data and declarative
referential integrity portion of the application from the forms,
reports and code in the application. After this is done, the
database structure and data is moved to Oracle. Counter field
equivalents are created in Oracle and the referential integrity
support is added. Finally the remote tables must be attached to
Access and the application can then be tested.
After you are comfortable with the migration process, please
refer to the Appendices for more information including sample
Oracle trigger code, information on security, and performance
tuning suggestions.
Migration in Detail
Access applications can be contained in a single mdb file or
multiple mdb files. Multiple mdb files are used to separate the
data and application (form, report, code) portions of an
application. Generally the data mdb file is shared on a central
server and the application mdb file is located on each client
machine.
When an application is in a single mdb file, you will need to
take steps to separate the data from the rest of the application
(forms, reports, code, etc.).
Remember to make backup copies of your Access application and
data files before performing any of the following steps!
Preparing a
Single mdb File Application for Exporting
Follow these steps to create two mdb files (one containing data,
the other your application) from a single mdb file. These steps
assume that you are starting with a file called app.mdb; at the
end of these steps you will have app.mdb and data.mdb.
1] Make a backup of app.mdb.
2] Start Access and use the File
Compact Database menu to
compress app.mdb.
3] In File Manager, copy app.mdb to data.mdb.
4] Open data.mdb in Access, delete all forms, reports, modules
and macros.
5] In Access, open app.mdb and delete all the tables.
6] While still in app.mdb use File
Attach Table to make an
attachment in app.mdb to each table in data.mdb.
At this point your application should run as it did before you
split it into two mdb files . You are now ready to export the
data.mdb file to Oracle.
Exporting Access Data to Oracle
Figure 3 illustrates an Access application before and after
migration to Oracle. Both before and after migration, app.mdb
contains the forms, reports, macros and Basic modules which make
up the application.
Before migration, app.mdb contains an attached table from
data.mdb. (We will use <tablename> to refer to the name of
this table. Substitute the name of your actual tables, for
example, "Customers".)
After migration, app.mdb will have two attached tables for each
original table and a name mapping query. The original
<tablename> from data.mdb will be attached as
L_<tablename> (e.g., L_Customers). The original table will
be exported to Oracle and on Oracle will be called
O_<TABLENAME> (e.g. O_CUSTOMERS). An attachment will be
created to O_<TABLENAME> inside Jet and will be named
R_<tablename>.
Figure 3 Access Application before and after Migration
to Oracle.
Because the forms, reports and modules in app.mdb are expecting a
table called <tablename> a name mapping query called
<tablename> is added. This query will take the place of the
original table in the application. This query can refer to either
L_<tablename> or R_<tablename>; you can switch
between the local and remote table as you move your application
to Oracle. The name mapping query layer also helps resolve
reserved word conflicts between Access and Oracle. For example, a
column named "Sequence" must be renamed (e.g.,
O_Sequence) as this is an Oracle reserved word. The name mapping
query can re-map O_Sequence back to Sequence for use by the
Access application.
You will also need to move any referential integrity definitions
to Oracle and install triggers to support counter fields. In the
next section, a detailed set of steps are described to lead you
through this process.
Migration, Step
by Step
The following sequence of steps will guide you as your migrate to
Oracle. They assume that you are starting with separate
application and data "mdb" files named app.mdb and
data.mdb.
1] Make backup copies of app.mdb and data.mdb.
2] Insure that an ODBC connection exists to your Oracle database.
3] In app.mdb, rename each attached table to be prefixed with
"L_"
(e.g. rename <tablename> to L_<tablename>.)
4] Open data.mdb inside of Access and prepare each table for
migration:
a) Make a copy of the table, naming the new table
O_<TABLENAME>.
That is, "O_" prepended to the table name, in all
capital letters .
b) Make sure that the column names follow the requirements for
names in Oracle .
Change all column names to upper case.
c) Select the File
Export Menu in Access.
d) Follow the dialogs and these steps to export each table:
i Select export to <SQL Database>.
ii Select the table to export.
iii Name the Oracle table (use O_<TABLENAME>).
iv Select your ODBC DSN.
v Supply your logon information.
If you have checked for Oracle reserved words, there should be no
errors to this point. If your tables are large, it could take
many minutes to several hours to export each table. (Access
provides a percentage complete indicator while exporting each
table).
e) Delete "O_<TABLENAME> from the data.mdb file.
6] Close the data.mdb file.
7] Use File
Compact with data.mdb to recover the space used
for the table copies.
8] Open app.mdb file.
9] Attach each O_<TABLENAME> table from Oracle to app.mdb
using the following steps:
a) Select File
Attach Table menu.
b) Select <SQL Database>.
c) Select your ODBC DSN.
d) Supply your Oracle Logon information.
e) Select the table and press the Attach button.
10] Change the names of the attached tables from
O_<TABLENAME> to R_<tablename>.
11] Create a mapping query for each attached Oracle table. The
name of the query will be
<tablename>, the original name of the table, as seen by
your application. Make sure that
the name of each column is mapped back to the original name found
in the original tables .
12] Open the attached tables in datasheet view or open a form on
the tables to make sure that the
exporting and mapping steps have been successful. You will notice
that you are not able to
update the data in the tables. After you complete the migration
steps and build primary keys,
you will be able to modify your data.
13] For each column which was derived from an Access Counter
field (counter fields are mapped to
NUMBER(10,0)) perform the following steps :
a) Create a Sequence for each counter. You are free to choose any
starting number and
increment number, however you must insure that you do not overlap
numbers that have been
exported from Access. You might want to start the sequence at the
next major increment.
For example, if 258 is the largest value in a counter field, you
might want to start at 1000; this will make it clear which
records were inserted after the move to Oracle.
b) Create trigger code for the counter field which uses the
sequence .
c) Make the counter field column the Primary key or
at least create a unique index on the
column; you must insure that this index is the one selected by
Access as the key value
index, so you may want to prepend "aaaa" to the name of
the index.
15] Create primary key and foreign key definitions in Oracle to
match the Access structure.
Oracle supports declarative Cascade Delete relationships, but not
Cascade Updates. To support
Cascade Updates, you will need to write trigger code in Oracle .
16] Map default value definitions to Oracle .
17] Map row and table validation statements to Oracle CHECK
statements .
18] If you have any tables in Oracle which you need to update
from Access and which do not, at
this time, have a primary key, you must define one. As in step 13
above, you must make the
primary key index the first index in alphabetic order for the
table.
19] Finally, use the Access Attachment Manager to refresh the
attached table connections. This
insures that Jet caches the latest information about primary keys
and other table parameters.
After you complete this step you will be able to update your
tables.
20] Open the tables in app.mdb in a datasheet or form to insure
that the migration was successful.
Extending your
Application
After you move the data management portion of your Access
application to Oracle you can rely on Oracle 7.1 RDBMS to protect
your data, maintain all referential integrity and business rules
that you have encoded in PL/SQL.
With this foundation, you can extend your application with Access
or a wide range of other tools. Oracle offers several high
productivity tools such as Oracle Power Objects, the
Developer/2000 toolset, and Oracle Objects for OLE. Oracle
Objects for OLE (OO4O) is a high performance connectivity
solution for Visual Basic, Delphi and other client tools which
can control OLE Automation Servers.
In addition, if your application grows, you can move your Oracle
server to larger computers without changing your application.
Figure 4 - Extending the Application with a Mix of Client
Tools.
Appendices
Appendix 1 - Oracle Reserved Words
The following list of Oracle reserved words are from the Oracle
RDBMS Server Manual and the PL/SQL Users Manual. In forming
Oracle names the valid character set includes the upper and lower
case letters (A..Z, a..z) and $, _, # and numerals (0..9) after
the first character. Unlike Access, spaces are not allowed.
It is suggested that all Oracle identifiers be exclusively upper
case. While Oracle supports lower case identifiers, some client
tools required that identifiers be surrounded by quotes if an
identifier contains a lower case letter.
ABORT
ACCEPT
ACCESS
ADD
ALL
ALTER
AND
ANY
ARRAY
ARRAYLEN
AS
ASC
ASSERT
ASSIGN
AT
AUDIT
AUTHORIZATION
AVG
BASE_TABLE
BEGIN
BETWEEN
BINARY_INTEGER
BODY
BOOLEAN
BY
CASE
CHAR
CHAR_BASE
CHECK
CLOSE
CLUSTER
CLUSTERS
COLAUTH
COLUMN
COLUMNS
COMMENT
COMMIT
COMPRESS
CONNECT
CONSTANT
COUNT
CRASH
CREATE
CURRENT
CURRVAL
CURSOR
DATA_BASE
DATABASE
DATE
DBA
DEBUGOFF
DEBUGON
DECIMAL
DECLARE
DEFAULT
DEFINITION
DELAY
DELETE
DELTA
DESC
DIGITS
DISPOSE
DISTINCT
DO
DROP
DUAL
ELSE
ELSEIF
END
ENTRY
EXCEPTION
EXCEPTION_INIT
EXCLUSIVE
EXISTS
EXIT
FALSE
FETCH
FILE
FLOAT
FOR
FORM
FROM
FUNCTION
GENERIC
GOTO
GRANT
GROUP
HAVING
IDENTIFIED
IF
IMMEDIATE
IN
INCREMENT
INDEX
INDEXES
INDICATOR
INITIAL
INSERT
INTEGER
INTERSECT
INTO
IS
LEVEL
LIKE
LIMITED
LOCK
LONG
LOOP
MAX
MAXEXTENTS
MIN
MINUS
MLSLABEL
MOD
MODE
MODIFY
NATURAL
NEW
NEXTVAL
NOAUDIT
NOCOMPRESS
NOT
NOWAIT
NULL
NUMBER
NUMBER_BASE
OF
OFFLINE
ON
ONLINE
OPEN
OPTION
OR
ORDER
OTHERS
OUT
PACKAGE
PARTITION
PCTFREE
POSITIVE
PRAGMA
PRIOR
PRIVATE
PRIVILEGES
PROCEDURE
PUBLIC
RAISE
RANGE
RAW
REAL
RECORD
RELEASE
REM
RENAME
RESOURCE
RETURN
REVERSE
REVOKE
ROLLBACK
ROW
ROWID
ROWLABEL
ROWNUM
ROWS
ROWTYPE
RUN
SAVEPOINT
SCHEMA
SELECT
SEPARATE
SESSION
SET
SHARE
SIZE
SMALLINT
SPACE
SQL
SQLCODE
SQLERRM
START
STATEMENT
STDDEV
SUBTYPE
SUCCESSFUL
SUM
SYNONYM
SYSDATE
TABAUTH
TABLE
TABLES
TASK
TERMINATE
THEN
TO
TRIGGER
TRUE
TYPE
UID
UNION
UNIQUE
UPDATE
USE
USER
VALIDATE
VALUES
VARCHAR
VARCHAR2
VARIANCE
VIEW
VIEWS
WHEN
WHENEVER
WHERE
WHILE
WITH
WORK
XOR
Appendix 2 - Code and Query Samples
The following sections contains samples which support counter
field emulation, cascade update referential integrity, and name
mapping queries.
Counter Datatype Emulation
Access supports a counter datatype. A counter provides a
monotonically increasing sequence of Long Integers for a column
in a native Jet DBMS file. Oracle supports sequences. Sequences
generate a set of numbers which can be used in columns as unique
identifiers. An important difference between Access counters and
Oracle sequences is that trigger code is required in Oracle to
place a sequence number in a column when a new record is inserted
into a table.
When Jet has an attachment to an Oracle table and an Oracle
trigger changes or initializes the key values at the time of an
insert (not updates), Jet will perform a sequence of queries to
retrieve the new key value so that the inserted row can become a
member of the Dynaset. If Jet has trouble re-selecting the
inserted row the rows appears as #DELETED to the user.
The example below shows how to emulate a counter datatype in
Oracle. The Oracle table is defined as:
CREATE TABLE OTBLCOUNTERTEST(
PK NUMBER (10,0),
NAME VARCHAR2 (50),
CONSTRAINT PK_OTBLCOUNTERTEST PRIMARY KEY (PK))
An Oracle sequence is defined as:
CREATE SEQUENCE TEST INCREMENT BY 1 START WITH 1000
The trigger code is:
Create Trigger TRG_CNT_OTBLCOUNTERTEST
Before INSERT OR UPDATE on OTBLCOUNTERTEST
FOR EACH ROW
DECLARE
iCounter SCOTT.OTBLCOUNTERTEST.PRIMARYKEY%TYPE;
cannot_change_counter EXCEPTION;
BEGIN
IF INSERTING THEN
SELECT TEST.NEXTVAL into iCounter FROM dual;
:new.PRIMARYKEY := iCounter;
END IF; -- End of Inserting Code
IF UPDATING THEN
-- Do not allow the PK to be changed.
IF NOT(:new.PRIMARYKEY = :old.PRIMARYKEY) THEN
RAISE cannot_change_counter;
END IF;
END IF; -- End of Updating Code
EXCEPTION
WHEN cannot_change_counter THEN
raise_application_error(-20000,'Cannot Change Counter Value');
END;
This trigger emulates the counter datatype by trapping both
INSERT and UPDATE operations on a table. On any insert the
trigger will get the next value in the sequence "TEST"
for the PRIMARYKEY column. On updates, the trigger checks to see
if the user is trying to update the counter; if so, an exception
is raised and the error is passed back to Access.
It is not recommended to silently protect the counter on update.
For example, with the following code:
IF UPDATING THEN
-- Do not allow the PK to be changed.
IF NOT(:new.PRIMARYKEY = :old.PRIMARYKEY) THEN
:new.PRIMARYKEY := :old.PRIMARYKEY);
END IF;
END IF; -- End of Updating Code
Jet becomes confused in its management of the Dynaset and
produces strange results to the user.
As a possible enhancement to strict counter field emulation, one
could use the following code in the trigger to allow Access to
pass a value for the counter on a row insert:
IF INSERTING THEN
IF (:new.PRIMARYKEY IS NULL) THEN
SELECT test.NEXTVAL into iCounter FROM dual;
:new.PRIMARYKEY := iCounter;
END IF:
END IF; -- End of Inserting Code
This code will generate a new counter value only if the passed
value is NULL.
Name Mapping Query
It is easy to build a name mapping query in Access. Use either
the QBE or SQL window to define the query. In this example, the
original Access table is called SeqDateTable and is exported to
Oracle as O_SEQDATETABLE. After the export the table is attached
to Jet as R_SeqDateTable.
When the following query is saved as SeqDateTable, it will take
the place of the original table and complete the mapping to
Oracle. The query maps the column names PRIMARYKEY, O_SEQUENCE
and FIRSTDATE to PrimaryKey, Sequence and FirstDate for use by
Access.
SELECT NameMapper.PRIMARYKEY AS PrimaryKey,
NameMapper.O_SEQUENCE AS Sequence,
NameMapper.FIRSTDATE AS FirstDate
FROM R_SEQDATETABLE;
Default Values
Oracle supports declarative default values. However, when moving
an application from Access to Oracle, you may encounter
situations where you need an insert trigger to support defaults.
A reasonable design decision is to move all default processing to
triggers to centralize the code and reduce maintenance
complexity. The following code sample demonstrates supporting
default values in a trigger:
CREATE OR REPLACE TRIGGER BIU_M2
BEFORE INSERT OR UPDATE
ON M2
FOR EACH ROW
BEGIN
IF INSERTING THEN
/* Manage Default Values if a new value is NULL */
IF :new.Address IS NULL THEN
:new.Address := 'Default';
END IF;
END IF; -- End of Inserting Code
END; -- Trigger BI_M2
Column and Table Validation
Oracle supports CHECK statements which can be used to enforce
Table and Column constraints. However, when moving an application
from Access to Oracle, you may encounter situations where you
need an insert trigger to support validation. The following code
sample demonstrates supporting default values in a trigger.
Notice that <Access Validation Code > indicates where you
can insert the validation code from an Access Application .
CREATE OR REPLACE TRIGGER BIU_M2
BEFORE INSERT OR UPDATE
ON M2
FOR EACH ROW
BEGIN
-- Validation Code
IF NOT ( <Access Validation Code > ) THEN
raise_application_error (-20000, '<Access Error Message>');
END IF;
END; -- Trigger BI_M2
Cascade Update Trigger Code
Oracle does not provide direct support for Cascade Update
referential integrity constraints. Cascade Update support means
that when a Primary Key is changed, that change will be made to
all associated Foreign Keys in linked tables. It is not
surprising that Oracle does not support this capability, as it
not a common design feature in applications. Primary Keys are
supposed to be stable, usually for the life of an application.
The following code example is based on two tables:
create table M1 (
f1 number,
f2 number,
f3 number )
create table M2 (f1 number,
f2 number,
f3 number )
alter table M1 add primary key (f1)
alter table M2 add primary key (f1)
This definition will support onetomany cardinality.
To add support for onetoone cardinality add the
following:
alter table M1 add constraint uq_M1_001 unique (f2, f3)
alter table M2 add constraint uq_M2_001 unique (f2, f3)
The following code implements update cascade code for the two
tables, M1 and M2. Not that this example uses two columns in the
Primary / Foreign Key relationships. This is more complex than
most relationships and is used to fully illustrate the proper
code.
Please note that declarative and procedural support for RI cannot
coexist between two tables. To support Cascade Update between two
tables, all declarative Primary / Foreign Key relationships and
RI between the tables must be removed and supported instead with
procedural code.
CREATE OR REPLACE PACKAGE P_M1 AS
fire_trigger boolean := TRUE;
END P_M1;
CREATE OR REPLACE PACKAGE P_M2 AS
fire_trigger boolean := TRUE;
END P_M2;
CREATE OR REPLACE PACKAGE UQ_M1_M2 AS
PROCEDURE cascade_update (
o_F2 IN number,
o_F3 IN number,
n_F2 IN number,
n_F3 IN number,
bResult OUT boolean );
PROCEDURE cascade_delete (
F2 IN number,
F3 IN number,
bResult OUT boolean );
FUNCTION pk_exists (
F2 IN number,
F3 IN number) RETURN boolean;
FUNCTION fk_exists (
F2 IN number,
F3 IN number) RETURN boolean;
END UQ_M1_M2;
CREATE OR REPLACE PACKAGE BODY UQ_M1_M2 AS
/* Procedure cascade_update is called when field(s) */
/* F2 or */
/* F3 */
/* are changed in table M1. */
/* The changes are cascaded in table M2 */
PROCEDURE cascade_update (
o_F2 IN number,
o_F3 IN number,
n_F2 IN number,
n_F3 IN number,
bResult OUT boolean ) IS
CURSOR d_cur (n1 number, n2 number) IS
SELECT * FROM m2
WHERE f2 = n1 AND f3 = n2
FOR UPDATE of f2, f3;
BEGIN
FOR d_cur_rec IN d_cur ( o_F2, o_F3 )
LOOP
UPDATE M2 SET f2 = n_F2, f3 = n_F3
WHERE CURRENT OF d_cur;
END LOOP; -- Detail Record Loop
bResult := true;
END cascade_update;
/* Procedure cascade_delete is called when a record */
/* in M1 is being deleted and associated */
/* child records in M2 must also be deleted. */
PROCEDURE cascade_delete (
F2 IN number,
F3 IN number,
bResult OUT boolean ) IS
CURSOR d_cur (n1 number, n2 number) IS
SELECT * FROM m2
WHERE f2 = n1 AND f3 = n2
FOR UPDATE;
BEGIN
FOR d_cur_rec IN d_cur ( F2, F3 )
LOOP
DELETE FROM M2
WHERE CURRENT OF d_cur;
END LOOP; -- Detail Record Loop
bResult := true;
END cascade_delete;
/* Procedure pk_exists is called to determine is a given
primary key exists in table M1 */
FUNCTION pk_exists (
F2 IN number,
F3 IN number) RETURN boolean IS
l_F2 number;
l_F3 number;
bResult boolean;
CURSOR p_cur (n1 number, n2 number) IS
SELECT F2, F3 FROM m1
WHERE f2 = n1 AND f3 = n2;
BEGIN
OPEN p_cur( F2, F3 );
FETCH p_cur INTO l_F2, l_F3;
IF p_cur%NOTFOUND THEN
bResult := false;
ELSE
bResult := true;
END IF;
CLOSE p_cur;
RETURN( bResult );
END pk_exists;
/* Procedure pk_exists is called to determine is a given
primary key exists in table M1 */
FUNCTION fk_exists (
F2 IN number,
F3 IN number) RETURN boolean IS
l_F2 number;
l_F3 number;
bResult boolean;
CURSOR d_cur (n1 number, n2 number) IS
SELECT F2, F3 FROM m2
WHERE f2 = n1 AND f3 = n2;
BEGIN
OPEN d_cur( F2, F3 );
FETCH d_cur INTO l_F2, l_F3;
IF d_cur%NOTFOUND THEN
bResult := false;
ELSE
bResult := true;
END IF;
CLOSE d_cur;
RETURN( bResult );
END fk_exists;
END UQ_M1_M2;
CREATE OR REPLACE TRIGGER AUD_M1
AFTER UPDATE OR DELETE
ON M1
FOR EACH ROW
DECLARE
bResult_OK BOOLEAN;
bCascadeDeletes BOOLEAN := TRUE;
BEGIN
IF UPDATING THEN
IF (:old.F2 <> :new.F2) OR (:old.F3 <> :new.F3) THEN
P_M2.fire_trigger := FALSE;
UQ_M1_M2.cascade_update( :old.F2, :old.F3, :new.F2, :new.F3,
bResult_OK );
P_M2.fire_trigger := TRUE;
END IF;
END IF; -- End of Updating Code
IF DELETING THEN
IF bCascadeDeletes THEN
UQ_M1_M2.cascade_delete( :old.F2, :old.F3, bResult_OK );
ELSE
IF UQ_M1_M2.fk_exists( :old.F2, :old.F3 ) THEN
raise_application_error( -20000, 'Rows exist in child table');
END IF;
END IF;
END IF; -- End of Deleting Code
END; -- Trigger AUD_M1
CREATE OR REPLACE TRIGGER AIU_M2
AFTER INSERT OR UPDATE
ON M2
FOR EACH ROW
DECLARE
bResult_OK BOOLEAN;
BEGIN
IF INSERTING THEN
IF NOT( UQ_M1_M2.pk_exists( :new.F2, :new.F3 ) ) THEN
raise_application_error (-20000, 'No corresponding row in parent
table');
END IF;
END IF; -- End of Inserting Code
IF ( UPDATING AND P_M2.fire_trigger ) THEN
IF NOT( UQ_M1_M2.pk_exists( :new.F2, :new.F3 ) ) THEN
raise_application_error (-20000, 'No corresponding row in parent
table');
END IF;
END IF; -- End of Updating Code
END; -- Trigger AUD_M2
Appendix 3 - Jet Architecture Details
The Jet DBMS Engine is Microsofts client and file server
RDBMS technology. It is not sold separately, but is included in
all Access versions, Visual Basic version 4.0 and Visual C++ V
4.0 . Jet is implemented in a set of DLLs. Figure 5 shows how Jet
integrates with Access (and Microsoft Visual Basic).