Learning Oracle Database Programming

From: xmy <xmy009_at_163.net>
Date: 1 Dec 2001 22:13:09 -0800
Message-ID: <440264b2.0112012213.17d9ba99_at_posting.google.com>


Learning Oracle Database Programming  

Chapter 6: Referential Integrity  

The next table you'll create is the account table. Each client has one or more accounts. Each

account has a unique 6 character account id which must be in the format nn-nnn ( n stands for

number). Like the client code, this may be expanded in the future. Once again you'll use a

system-assigned number as the primary key.  

Defining Integrity Relationships

Each account must belong to one client: an account can't exist without a client. Before an

account is set up, its client must be in the client table. Therefore, the client_no, which is

the primary key of the client table, will be repeated in the account table. The client_no

column in the account table references the client_no in the client_table. This is referential

integrity. It helps protect the database from invalid data. The client table is considered the

parent and the account table is its child. Referential integrity constraints are placed on the

child, not the parent.  

When you create a referential integrity constraint, you must decide what action should be taken

when the parent is deleted. The choices are  

  • delete the child or
  • prevent the parent's deletion as long as a child exists.

Orphaned children aren't allowed. If you want to permit orphans, you can't use a referential

integrity constraint to support this.  

Creating a Referential Integrity Constraint

For the account table, you'll tell Oracle to delete a client's accounts when the client is deleted. Create the account table by typing:

SQL> create table account (

2 accnt_no integer primary key,

3 accnt_id varchar2(6) not null unique,

4 client_no integer not null references

5 client(client_no) on delete cascade,

6 curr_balance number(11,2));

Table created.
The client_no column in the account table is a foreign key. A foreign key references a primary

key or a unique column in another table. The constraint could also be defined as "references

client" without explicitely stating the column because client_no is the primary key of the client

table.  

Declaring client_no to be not null in addition to the references constraint is necessary because

references means the value must exist in the client.client_no column or be null. The business

requirements state each account must belong to a client. Null values are inappropriate here.  

Check the constraints this create table statement created. Type:  

SQL> select * from user_constraints

2 where table_name = 'ACCOUNT';

OWNER                          CONSTRAINT_NAME                C

------------------------------ ------------------------------ -
TABLE_NAME

SEARCH_CONDITION
R_OWNER                        R_CONSTRAINT_NAME             
DELETE_RU
------------------------------ ------------------------------


STATUS
LOP                            SYS_C00377                     C
ACCOUNT
ACCNT_ID IS NOT NULL ENABLED
LOP                            SYS_C00378                     C
ACCOUNT
CLIENT_NO IS NOT NULL ENABLED
LOP                            SYS_C00379                     P
ACCOUNT ENABLED
LOP                            SYS_C00380                     U
ACCOUNT ENABLED
LOP                            SYS_C00381                     R
ACCOUNT
LOP                            SYS_C00369                     CASCADE
ENABLED Constraint SYS_C00381 is the foreign key constraint. It references constraint SYS_C00369.

Check that constraint. Type:  

SQL> select * from user_constraints

2 where constraint_name = 'SYS_C00369';

OWNER                          CONSTRAINT_NAME                C

------------------------------ ------------------------------ -
TABLE_NAME

SEARCH_CONDITION
R_OWNER                        R_CONSTRAINT_NAME             
DELETE_RU
------------------------------ ------------------------------


STATUS
LOP                            SYS_C00369                     P
CLIENT ENABLED Constraint SYS_C00369 is the client table's primary key constraint.  

Next you'll modify the lop_util package body adding account.accnt_no to lop_util.get_next_seqno.

Edit file utilb.sql (changes are in bold):  

/*

Creating package body for lop_util

File Name: utilb.sql

Purpose: To create the lop_util package body

Changes: 7/20/96 F.E. Added account.accnt_no to if condition.

*/  

create or replace package body lop_util is  

  • Use errors -20001 to -20029 when raising errors in this package.

function get_next_seqno(object_name in varchar2) return integer is

seqno integer;

err_msg varchar2(80);

begin

if (upper(object_name) = 'CLIENT.CLIENT_NO' or

upper(object_name) = 'ACCOUNT.ACCNT_NO') then

select lop_seq.nextval

into seqno

from dual;  

return seqno;

else

raise_application_error(-20002,'There is no source for ' ||

object_name);

end if;

exception

when others then

err_msg := sqlerrm;

raise_application_error(-20001,

'Error raised in get_next_seqno. ' || err_msg);

end get_next_seqno;

end lop_util;  

/  

Replace the package body:  

SQL> set document on  

SQL> _at_c:\lop\utilb  

DOC>Creating package body for lop_util

DOC>File Name: utilb.sql

DOC>Purpose: To create the lop_util package body

DOC>Changes: 7/20/96 F.E. Added account.accnt_no to if condition.

DOC>*/ Package body created.
Create a pre-insert for each row trigger similar to the one you created for the client table

to assign a number to the primary key, accnt_no. Type and then save or create a file,

acbri.sql (account before row insert), and then execute it:  

SQL> create or replace trigger account_bri

2 before insert on account

3 for each row

4 begin

5 /* Get a sequence number for the primary key */

6 :new.accnt_no := lop_util.get_next_seqno('account.accnt_no');

7 end;

8 /

Trigger created.
SQL> save c:\lop\acbri

Created file c:\lop\acbri
Also create a pre-update for each row trigger to prevent the updating of account.accnt_no.

Type and then save (or create and then execute a file):  

SQL> create or replace trigger account_bru

2 before update on account

3 for each row

4 begin

5 /* No updates allowed on primary key */

6 if (:new.accnt_no != :old.accnt_no) then

7 raise_application_error(-20000,

8 'Accnt_no is a primary key - not updatable.');

9 end if;

10 end;

11 /

Trigger created.
SQL> save c:\lop\acbru

Created file c:\lop\acbru
Before you test your triggers, you'll add several check constraints to the account table to

ensure an accnt_code is in the form nn-nnn. You'll also redefine the curr_balance column so

it will be given a default value of 0 if a record is inserted without any value for that column.

Type:  

SQL> alter table account add(

2 check(length(accnt_id) = 6),

3 check(ltrim(substr(accnt_id,1,2),'1234567890') is null),

4 check(ltrim(substr(accnt_id,4),'1234567890') is null),

5 check(substr(accnt_id,3,1) = '-'));

Table altered.
Notice the various usages of the substr function. Substr(accnt_id,1,2) returns the first two

characters of the accnt_id. Substr(accnt_id,4) returns the characters in accnt_id from the

fourth character to the end of the string.  

Next, use the modify form of the alter table command to add the default value option to the

column definition. This is not considered a constraint. (Remember, the only type of constraint

that can be created with the modify form of alter table is the not null constraint.) Type:  

SQL> alter table account modify (curr_balance default 0);

Table altered.  

Testing Constraints

Test your triggers and changes to lop_util by inserting some valid and some invalid records.

You'll need to know the valid values of client_no. Type:  

SQL> select * from client;

CLIENT_NO CLIEN SHORT_NAME
--------- ----- ----------

LONG_NAME


        1 12345 LASCO         2 03030 ROBSEC
Robin Securities

        3 34534 INSTRES
The Institute for Research
Now insert some records. You only have to include data for accnt_id and client_no because

your trigger will assign a value to the mandatory accnt_ no column, and you told Oracle to

assign a default value to the curr_balance column. Your values for client_no may be different.

Type (you can leave out the comments):  

SQL> insert into account(accnt_id,client_no)

2 values ('22-333',1) /* valid record */;

1 row created.
SQL> select * from account;

 ACCNT_NO ACCNT_ CLIENT_NO CURR_BALANCE
--------- ------ --------- ------------

       35 22-333 1 0
SQL> insert into account(accnt_id,client_no)

2 values('11-222',6) /* invalid client_no */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-02291: integrity constraint (LOP.SYS_C00381) violated - parent key not found
SQL> insert into account(accnt_id,client_no)

2 values('11-222',null) /* client_no is not null (fk only would allow nulls) */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-01400: mandatory (NOT NULL) column is missing or NULL during insert
SQL> insert into account(accnt_id,client_no)

2 values('22-333',2) /* Duplicate accnt_id */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-00001: unique constraint (LOP.SYS_C00380) violated SQL> insert into account(accnt_id,client_no)

2 values(null,1) /* accnt_id is a not null column */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-01400: mandatory (NOT NULL) column is missing or NULL during insert
SQL> insert into account(accnt_id,client_no)

2 values('2B-333',1) /* invalid accnt_id - B is not a number */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-02290: check constraint (LOP.SYS_C00386) violated SQL> insert into account(accnt_id,client_no)

2 values('22*555',1) /* invalid accnt_id - asterisk should be hyphen */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-02290: check constraint (LOP.SYS_C00388) violated SQL> insert into account(accnt_id,client_no)

2 values('22-5555',1) /* too long id is trapped by Oracle before constraint */;  

insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-01401: inserted value too large for column SQL> insert into account(accnt_id,client_no)

2 values('22-4',1) /* invalid accnt_id - less than 6 characters */;

 insert into account(accnt_id,client_no)

                   *

ERROR at line 1:
ORA-02290: check constraint (LOP.SYS_C00385) violated SQL> select * from account;

ACCNT_NO ACCNT_ CLIENT_NO CURR_BALANCE
-------- ------ --------- ------------

      35 22-333 1 0
Your trigger prevents updating accnt_no. Error ORA-20000 is produced by the trigger. The

other error messages were produced by Oracle. Type:  

SQL> update account set accnt_no = 6

2 where accnt_no = 35 /* trigger prevents update of pk */;

update account set accnt_no = 6

       *
ERROR at line 1:

ORA-20000: Accnt_no is a primary key - not updatable. 
ORA-06512: at line 4 
ORA-04088: error during execution of trigger 'LOP.ACCOUNT_BRU'
SQL> commit;

Commit complete.  

Cascading Deletes

Delete the client you used in the valid insert into account statement. Before you do this,

be sure you've committed your work since the following delete statement will be rolled back.

Type:  

SQL> delete from client where client_no = 1;

1 row deleted
Your account record should be gone because its foreign key constraint referencing

client.client_no was defined as delete cascade. Type:  

SQL> select * from account;

no rows selected
Rollback the delete so your client record and its account record remain in their tables. Type:  

SQL> rollback;

Rollback complete.
Actions since the last rollback are undone. Type:  

SQL> select * from account;

 ACCNT_NO ACCNT_ CLIENT_NO CURR_BALANCE
--------- ------ --------- ------------

       35 22-333 1 0
The account table now has a constraining relationship to the client table. The client

table may not be dropped until the foreign key (references) constraint in the account table

is dropped (not just disabled). The primary key index for the client table may not be dropped

until the foreign key constraint is either disabled or dropped. Because of this, you can't

create referential integrity constraints which point to a parent table in another schema

(userid) unless that userid has granted you references privilege on its table. If you want

to allow orphans when a parent is deleted, or to set the foreign key value to either null or

a default value when a parent is deleted, Oracle suggests you create the referential integrity

constraint, disable it, and then create triggers to support this. The constraint documents the

relationship and prevents dropping the parent table or the primary key or unique indexes.  

Dropping an Index with Dependencies

This relationship between the parent and child tables does complicate system maintenance.

For example, you could have a problem with the parent's primary key index. Many of the

constraints you created are supported by indexes (primary key or unique). When Oracle created

those indexes, it used the default storage parameters. Indexes have storage parameters

similar to those of tables (first extent, next extent, percent increase). If the defaults

turn out to be too small, the index can reach the maximum number of extents allowed. At

that point, no more records can be inserted into the table because the next entry for the

index can't be created. Before referential integrity, the index could simply be dropped

and recreated. Now all the foreign key constraints which reference this primary key must

be disabled before the parent's primary key can be dropped and recreated.  

You'll drop and recreate the client table's primary key index. First, find the client table's

primary key constraint. Type:  

SQL> select constraint_name

2 from user_constraints

3 where table_name = 'CLIENT'

4 and constraint_type = 'P';  

CONSTRAINT_NAME



SYS_C00369
The index enforcing this constraint will have the same name as the constraint. Try to drop it.

Type:  

SQL> drop index sys_c00369;

drop index sys_c00369
*
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key
The primary key constraint on the client table must be disabled. When a constraint is

disabled, the index enforcing it is dropped. Try to use the alter table disable command

to disable the primary key. Type:  

SQL> alter table client disable primary key;

alter table client disable primary key
*
ERROR at line 1:
ORA-02297: cannot disable constraint (LOP.SYS_C00369) - dependencies exist
The client primary key constraint can't be disabled until any dependencies caused by

referential integrity constraints are disabled. This can be done by adding the keyword

cascade to the alter table disable command. However, since there is no cascade option

for the alter table enable command, it's a good idea to identify the enabled dependent

constraints before you do this. If you don't, you won't know which of the dependent

constraints were enabled before this operation (some may have been disabled for other reasons).

Type:  

SQL> select table_name,constraint_name

2 from user_constraints

3 where r_constraint_name = 'SYS_C00369' and status = 'ENABLED';

TABLE_NAME                     CONSTRAINT_NAME

------------------------------ ------------------------------
ACCOUNT SYS_C00381
Now disable the client table's primary key. Type:  

SQL> alter table client disable primary key cascade;

Table altered.
The dependent constraint should be disabled. Type:  

SQL> select status from user_constraints

2 where constraint_name = 'SYS_C00381';  

STATUS



DISABLED
The index enforcing the client table's primary should be gone. Type:  

SQL> select * from user_indexes

2 where index_name = 'SYS_C00369';

no rows selected
When you enable the primary key constraint, you can tell Oracle the storage parameters to use

for the primary key index. This will be done through the using index clause of the alter

table...enable constraint command. (This also could have been part of the original create

table statement.) Type:  

SQL> alter table client

2 enable primary key

3 using index storage(initial 5000 next 1000 pctincrease 100);

Table altered.
The enable clause could have been written enable constraint sys_c00369. This command created

the new primary key index. Oracle gives it the same name as the constraint. Look at the

storage parameters for this index. Type:  

SQL> select uniqueness,initial_extent,next_extent,pct_increase

2 from user_indexes

3 where index_name = 'SYS_C00369';

UNIQUENES INITIAL_EXTENT NEXT_EXTENT PCT_INCREASE
--------- -------------- ----------- ------------
UNIQUE 6144 2048 100 Oracle created an initial extent of 6144 bytes. On this system (Windows95), the Oracle

blocksize is 2048 bytes. The request was for an initial extent of 5000 bytes which isn't

a multiple of the blocksize; so, Oracle rounded the initial extent to 3 blocks. It also

rounded up the size of the next extent to be 2048 since the requested size was less than

one block. The minimum number of blocks for a first extent is two. The minimum number

of blocks for a next extent is 1. (This applies to tables as well as indexes.)  

The foreign key constraint of the account table can be enabled now that the client table's

primary key has been restored. Type:  

SQL> alter table account enable constraint sys_c00381;

Table altered.
Check the account table's foreign key constraints. Type:  

SQL> select * from user_constraints

2 where table_name = 'ACCOUNT'

3 and constraint_type = 'R';  

OWNER                          CONSTRAINT_NAME                C

------------------------------ ------------------------------ -
TABLE_NAME

SEARCH_CONDITION
R_OWNER                        R_CONSTRAINT_NAME             
DELETE_RU
------------------------------ ------------------------------


STATUS
LOP                            SYS_C00381                     R
ACCOUNT
LOP                            SYS_C00442                     CASCADE
ENABLED Check the columns on which the constraints were placed. Type:  

SQL> select * from user_cons_columns

2 where constraint_name in ('SYS_C00369','SYS_C00381');

OWNER                          CONSTRAINT_NAME

------------------------------ ------------------------------
TABLE_NAME COLUMN_NAME
POSITION
------------------------------ ------------------------------

LOP                            SYS_C00369
CLIENT                         CLIENT_NO                             
1
LOP                            SYS_C00381
ACCOUNT                        CLIENT_NO                            
Constraint sys_c00381 is defined on the account.client_no column and it references constraint

sys_c00369, the primary key constraint defined on the client.client_no column. Received on Sun Dec 02 2001 - 07:13:09 CET

Original text of this message