-- 14 --

Defining Table and Column Constraints

Every information system must maintain data and referential integrity. In addition, an information system must also enforce the business rules of the organization that it serves. In the past many information systems were monolithic--;an in-house developed software package accessing a single database residing on a single mainframe computer. Occasionally, third-party utilities were used to access the database.

Today, systems commonly employ a variety of off-the-shelf client tools to access and modify a database. As a result, protecting information at the server level, as well as the client level, is critical. Enforcing security at the client application through menu options and other mechanisms is not sufficient. This chapter explores the use of table and column constraints to preserve data and referential integrity and to enforce business rules.

The discussion begins with a review of the basic steps for defining primary and foreign keys.

Using a Primary Key

You should be sure to define a primary key for every table in your database for the following reasons.

Defining a Primary Key During Table Creation

You can define a table's primary key in the CREATE TABLE statement with the following syntax:

CREATE TABLE table-name

(column-definition1,

...

column-definitionN,

[CONSTRAINT constraint-name] PRIMARY KEY (column1,...,columnN))

The variables include the following: table-name is a valid Oracle table name.

column-definition1 through column-definitionN are legal column declarations.

constraint-name is a constraint name that is assigned to the primary key constraint.

column1
through columnN are the columns that compose the primary key.

Here is how you define a primary key on the Product table:

SQL> r

  1  CREATE TABLE Product

  2        (Product_ID               VARCHAR2(12) NOT NULL,

  3         Manufacturer_ID          VARCHAR2(6) NOT NULL,

  4         Description              VARCHAR2(2000),

  5         Date_of_Manufacture      DATE,

  6         Initial_Retail_Value     NUMBER(7,2),

  7         Current_Used_Value       NUMBER(7,2),

  8         Replacement_Product      VARCHAR2(30),

  9         Created_Date             DATE,

 10         Created_By               VARCHAR2(30),

 11         Modified_Date            DATE,

 12         Modified_By              VARCHAR2(30),

 13         Constraint Product_PK Primary Key (Product_ID, Manufacturer_ID)

 14* );

Table created.

The following SQL statements illustrate how the primary key prevents duplicate rows from being inserted into the table. A duplicate row is defined as a row whose primary key columns have the same values as those of another row. The following code reveals that the primary key is enforced, even though the first INSERT transaction hasn't been committed.

SQL> insert into Product

  2  (Product_ID, Manufacturer_ID, Initial_Retail_Value)

  3  values

  4  ('B101', KANIN', 500);

1 row created.

SQL> insert into Product

  2  (Product_ID, Manufacturer_ID, Initial_Retail_Value)

  3  values

  4  ('B101','KANIN',600);

insert into Product

            *

ERROR at line 1:

ORA-00001: unique constraint (FRAYED_WIRES.PRODUCT_PK) violated

You also have the option of defining the primary key after the table has been created.



Tip

If you don't provide a constraint name, Oracle automatically creates constraint names for each of the table and column constraints that you define. The Oracle-generated names are fairly cryptic, and you won't know what they are unless you query the Oracle data dictionary view named USER_CONSTRAINTS. In several situations, you need to know a constraint name. For example, when you want to drop a foreign key, you'll need to drop the constraint associated with the foreign key. Unless you've supplied the constraint name for the foreign key, you'll have to look up the constraint name. Therefore, providing Oracle with constraint names for primary and foreign keys is always a good idea. Consider naming primary key constraints as tablename_PK and foreign key constraints as tablename_column_FK, staying within the 30-character limit for Oracle object names. You should also specify names for check and unique constraints (for example, tablename_column_CK or tablename_column_UN).


Defining a Primary Key After Table Creation

To define a primary key constraint for an existing table, use the ALTER TABLE statement.

SQL> CREATE TABLE Product

  2        (Product_ID               VARCHAR2(12) NOT NULL,

  3         Manufacturer_ID          VARCHAR2(6) NOT NULL,

  4         Description              VARCHAR2(2000),

  5         Date_of_Manufacture      DATE,

  6         Initial_Retail_Value     NUMBER(7,2),

  7         Current_Used_Value       NUMBER(7,2),

  8         Replacement_Product      VARCHAR2(30),

  9         Created_Date             DATE,

 10         Created_By               VARCHAR2(30),

 11         Modified_Date            DATE,

 12         Modified_By              VARCHAR2(30));

Table created.

SQL> alter table Product add

  2  Constraint Product_PK Primary Key (Product_ID, Manufacturer_ID);

Table altered.

If the existing table contains duplicate rows, Oracle returns an error message when you attempt to define the primary key.

SQL> insert into Product

  2  (Product_ID, Manufacturer_ID, Initial_Retail_Value)

  3  values

  4  ('B101','KANIN', 500);

1 row created.

SQL> insert into Product

  2  (Product_ID, Manufacturer_ID, Initial_Retail_Value)

  3  values

  4  ('B101','KANIN', 500);

1 row created.

SQL> alter table Product add

  2  Constraint Product_PK Primary Key (Product_ID, Manufacturer_ID);

alter table Product add

*

ERROR at line 1:

ORA-02299: cannot add or enable constraint (FRAYED_WIRES.PRODUCT_PK)-

duplicate keys found

The Foreign Key and Referential Integrity

Primary and foreign keys work together to enforce referential integrity. A foreign key in a table is a column, or set of columns, whose values are restricted to those of the primary key in another table. You should define foreign keys whenever possible. For client/server applications, the first line of defense for referential integrity is the client application software. The last line of defense for referential integrity is the primary and foreign keys that have been defined for the database.

As with a primary key, a foreign key can be declared when a table is first created.

Declaring a Foreign Key During Table Creation

To illustrate the definition of a foreign key, refer to the Product and Manufacturer tables in the Frayed Wires sample database. Each manufacturer is assigned a unique ID that resides in the Manufacturer_ID column. To address the possibility that two manufacturers could use the same product ID, you can store Manufacturer_ID in the Product table. To uniquely identify rows in the Product table, declare the primary key to be Product_ID and Manufacturer_ID.

SQL> CREATE TABLE Manufacturer

  2        (Manufacturer_ID          VARCHAR2(6) NOT NULL,

  3         Manufacturer_Name        VARCHAR2(30) NOT NULL,

  4         Street_Address           VARCHAR2(60),

  5         City                     VARCHAR2(30),

  6         State                    VARCHAR2(2),

  7         Zipcode                  VARCHAR2(9),

  8         Telephone_Number         VARCHAR2(10),

  9         Fax_Number               VARCHAR2(10),

 10         Created_Date             DATE NOT NULL,

 11         Created_By               VARCHAR2(30) NOT NULL,

 12         Modified_Date            DATE,

 13         Modified_By              VARCHAR2(30),

 14         Constraint Manufacturer_PK Primary Key (Manufacturer_ID));

Table created.

SQL> CREATE TABLE Product

  2        (Product_ID               VARCHAR2(12) NOT NULL,

  3         Manufacturer_ID          VARCHAR2(6) NOT NULL

  4         Constraint Manufacturer_ID_FK

           References Manufacturer(Manufacturer_ID),

  5         Description              VARCHAR2(2000),

  6         Date_of_Manufacture      DATE,

  7         Initial_Retail_Value     NUMBER(7,2),

  8         Current_Used_Value       NUMBER(7,2),

  9         Replacement_Product      VARCHAR2(30),

 10         Created_Date             DATE,

 11         Created_By               VARCHAR2(30),

 12         Modified_Date            DATE,

 13         Modified_By              VARCHAR2(30),

 14         Constraint Product_PK Primary Key (Product_ID, Manufacturer_ID));

Table created.

After the foreign key has been defined, referential integrity is enforced.

SQL> insert into Product

  2  (Product_ID, Manufacturer_ID, Description)

  3  values

  4  ('X1000','SCH100','VCR, Stereo-HiFi, 4-Head');

insert into Product

            *

ERROR at line 1:

ORA-02291: integrity constraint (FRAYED_WIRES.MANUFACTURER_ID_FK)

violated - parent key not found

As an alternative, you can opt not to specify the datatype for a column that is a foreign key. The next example doesn't include VARCHAR2(6) in the declaration of Manufacturer_ID. Instead, Oracle looks up the datatype and width for the Manufacturer_ID column in the Manufacturer table and uses those definitions when creating the Product table.

SQL> CREATE TABLE Product

  2        (Product_ID               VARCHAR2(12) NOT NULL,

  3         Manufacturer_ID          NOT NULL

  4         Constraint Manufacturer_ID_FK

           References Manufacturer(Manufacturer_ID),

  5         Description              VARCHAR2(2000),

  6         Date_of_Manufacture      DATE,

  7         Initial_Retail_Value     NUMBER(7,2),

  8         Current_Used_Value       NUMBER(7,2),

  9         Replacement_Product      VARCHAR2(30),

 10         Created_Date             DATE,

 11         Created_By               VARCHAR2(30),

 12         Modified_Date            DATE,

 13         Modified_By              VARCHAR2(30),

 14         Constraint Product_PK Primary Key (Product_ID, Manufacturer_ID));

Table created.

SQL> desc Product

 Name                            Null?    Type

 ------------------------------- -------- ----

 PRODUCT_ID                      NOT NULL VARCHAR2(12)

 MANUFACTURER_ID                 NOT NULL VARCHAR2(6)

 DESCRIPTION                              VARCHAR2(2000)

 DATE_OF_MANUFACTURE                      DATE

 INITIAL_RETAIL_VALUE                     NUMBER(7,2)

 CURRENT_USED_VALUE                       NUMBER(7,2)

 REPLACEMENT_PRODUCT                      VARCHAR2(30)

 CREATED_DATE                             DATE

 CREATED_BY                               VARCHAR2(30)

 MODIFIED_DATE                            DATE

 MODIFIED_BY                              VARCHAR2(30)

The advantage of not declaring a datatype for a foreign key column is that the foreign key column will have the same datatype definition as its primary key counterpart. The disadvantage is that you can't determine the foreign key column's datatype by examining the CREATE TABLE statement.

Declaring a Foreign Key After Table Creation

As an alternative to declaring a foreign key when you create a table, you can declare a foreign key on an existing table with the ALTER TABLE statement.

SQL> create table AP_Detail (

  2  Bill_Number            NUMBER(4) NOT NULL,

  3  Vendor_Invoice_Number  VARCHAR2(10) NOT NULL,

  4  Item_Number            NUMBER(3) NOT NULL,

  5  Billed_Amount          NUMBER(8,2) NOT NULL,

  6  Approved_Amount        NUMBER(8,2),

  7  Paid_Amount            NUMBER(8,2));

Table created.

SQL> alter table AP_Detail add constraint AP_Detail_Vendor_Inv_Num_FK

  2  foreign key (Vendor_Invoice_Number) references AP_Header;

Table altered.

Primary and Foreign Key Columns

When you define a foreign key, Oracle verifies the following:

As an example, suppose that you have two tables: Employee and Employee_Dependent. The primary key of the Employee table is Employee_ID. Of course, the Employee_ Dependent table also contains Employee_ID.

SQL> desc employee

 Name                            Null?    Type

 ------------------------------- -------- ----

 EMPLOYEE_ID                     NOT NULL NUMBER(4)

 LAST_NAME                       NOT NULL VARCHAR2(30)

 FIRST_NAME                      NOT NULL VARCHAR2(20)

 MIDDLE_INITIAL                           CHAR(1)

 HIRE_DATE                       NOT NULL DATE

 TERMINATION_DATE                         DATE

 DATE_OF_BIRTH                            DATE

 MONTHLY_SALARY                           NUMBER(5)

 MANAGER                         NOT NULL CHAR(1)

 USERNAME                                 VARCHAR2(31)

SQL> desc employee_dependent

 Name                            Null?    Type

 ------------------------------- -------- ----

 EMPLOYEE_ID                     NOT NULL VARCHAR2(4)

 LAST_NAME                                VARCHAR2(30)

 FIRST_NAME                               VARCHAR2(20)

 MIDDLE_INITIAL                           CHAR(1)

 RELATIONSHIP                             VARCHAR2(30)

Observe that the Employee table's definition for Employee_ID is NUMBER(4), but the Employee_Dependent table's definition for Employee_ID is VARCHAR2(4). As a result, Oracle does not allow the foreign key to be defined for Employee_ID in Employee_Dependent, which references Employee_ID in Employee.

SQL> alter table Employee_Dependent add Constraint Employee_Dependent_FK1

  2              Foreign Key (Employee_ID) references Employee;

            Foreign Key (Employee_ID) references Employee

                         *

ERROR at line 2:

ORA-02256: number, type and size of referencing columns must match

referenced columns

Disabling and Enabling Key Constraints

As I've mentioned previously, primary and foreign key constraints enforce two crucial aspects of the relational model: data and referential integrity. You may want to disable these constraints for at least two tasks--;the task of designing the database and the task of migrating the organization's legacy data.

The Process of Designing a Database

A database is usually designed for an organization that has existing information, both computer-based and paper-based information. The database design process is not a one-time task; rather, it is iterative. Regardless of how simple or complex the organization's operations may be, the analysis and design process can be boiled down to these steps:

  1. You develop an initial logical data model by interviewing a cross-section of organization members, analyzing the organization's business processes, and inspecting existing automated and manual forms, reports, and data.
  2. You present the developed logical data model to a representative group of users and obtain feedback from them --;to correct erroneous assumptions on your part, missing entities and attributes, and exceptions of which you weren't aware.
  3. You incorporate the user feedback into the logical data model and present the refined logical data model.

During this process, you will want to look at legacy data to determine if your logical data model is appropriate. The terms legacy system and legacy data refer to the existing information system and its data. The quality of the legacy data depends on how well legacy application software has enforced data and referential integrity. The legacy data may be very clean; that is, it doesn't have any duplicate records or illegal field values. However, clean legacy data is the exception, rather than the rule, in the vast majority of cases. Most legacy data contains some duplicate records. Field values may not correspond to the meaning of the field--;for example, an N/A in a field used to store order quantity. Referential integrity may be violated in numerous instances. As a result, loading legacy data into an Oracle database in which data and referential integrity are properly enforced isn't feasible. Consider the following example.

Suppose you've designed a set of Oracle tables to support accounts payable operations. Two of the tables are

The legacy system has files that correspond to the AP_Header and Vendor tables.

You've already loaded the contents of APMASTR and VENDORS into intermediate Oracle tables--;that is, tables that are structurally equivalent to the legacy files. Here are the contents of the intermediate tables:

SQL> select BILLNO, VENDID, VINVNO

  2  from APMASTR;

   BILLNO VENDID VINVNO

--------- ------ ----------

     1001 M202   AG7001

     1002 S501   TR2111

     1003 G309   QE03911

     1004 T703   WE09834

     9101 N/A    N/A

SQL> select VENDID, COMPNAME

  2  from VENDORS;

VEND COMPNAME

---- -----------------------------------------

M202 METATRON INDUSTRIES

S501 SOLIPSISM INC.

G309 GOLIATH BUSINESS SYSTEMS CO.

T703 TRANSWORLD STONE INC.

Now that the intermediate Oracle tables have been loaded, you decide to insert the rows into the final tables--;AP_Header and Vendor.

SQL> insert into Vendor

  2  (Vendor_ID, Company_Name)

  3  select VENDID, COMPNAME

  4  from VENDORS;

4 rows created.

SQL> commit;

Commit complete.

SQL> insert into AP_Header

  2  (Bill_Number, Vendor_ID, Vendor_Invoice_Number)

  3  select BILLNO, VENDID, VINVNO

  4  from APMASTR;

insert into AP_Header

*

ERROR at line 1:

ORA-02291: integrity constraint (FRAYED_WIRES.AP_HEADER_VENDOR_ID_FK)

violated - parent key not found

When you look at the contents of APMASTR, the reason that Oracle rejected the records is evident. For Bill_Number 9101, the Vendor_ID is listed as N/A--;not applicable, as documented in the legacy data. However, the Vendor table doesn't have a row in which Vendor_ID is N/A. Accordingly, the INSERT statement violates the referential integrity constraint, and the rows aren't inserted into AP_Header.

In reality, hundreds or thousands of legacy records could violate declared constraints. As a temporary measure, you can disable these constraints in this manner:

SQL> alter table AP_Header disable constraint AP_Header_Vendor_ID_FK;

Table altered.

SQL> insert into AP_Header

  2  (Bill_Number, Vendor_ID, Vendor_Invoice_Number)

  3  select BILLNO, VENDID, VINVNO

  4  from APMASTR;

5 rows created.

SQL> commit;

Commit complete.

After the bad data has been corrected or removed, you can enable the constraint.

SQL> delete from AP_Header

  2  where Bill_Number = 9101;

1 row deleted.

SQL> alter table AP_Header enable constraint AP_Header_Vendor_ID_FK;

Table altered.

SQL> select Bill_Number, Vendor_ID, Vendor_Invoice_Number

  2  from AP_Header;

BILL_NUMBER VENDOR VENDOR_INV

----------- ------ ----------

       1001 M202   AG7001

       1002 S501   TR2111

       1003 G309   QE03911

       1004 T703   WE09834

Another way to capture and correct records that violate a constraint is to use an Exceptions table. When you specify the constraint in a CREATE TABLE or ALTER TABLE statement, you can also specify a table to store the ROWIDs of rows that violate the constraint. You can use a SQL script in C:\Orawin95\rdbms72\admin\utlexcpt.sql to create an Exceptions table.

For example, suppose the Demo_Exceptions table doesn't have a primary key.

SQL> select ssn, last_name from demo_exceptions;



SSN         LAST_NAME

----------- ------------------------------

321-45-6789 JOHNSON

123-45-6789 SMITH

321-45-6789 JONES

As you can see, the rows for JOHNSON and JONES have the same Social Security number. When you add a primary key to the table, you can specify an EXCEPTIONS clause that will store the ROWID of each row that violates the primary key constraint.

SQL> alter table demo_exceptions

  2  add constraint demo_exceptions_PK

  3  primary key (ssn)

  4  exceptions into EXCEPTIONS;

alter table demo_exceptions

*

ERROR at line 1:

ORA-02299: cannot add or enable constraint (FRAYED_WIRES.DEMO_EXCEPTIONS_PK)- 

duplicate keys found

The Exceptions table contains information about the rows that violated the constraint.

SQL> select row_id, constraint

  2  from exceptions;



ROW_ID             CONSTRAINT

------------------ ------------------------------

00000E65.0000.0001 DEMO_EXCEPTIONS_PK

00000E65.0002.0001 DEMO_EXCEPTIONS_PK

You can use the ROWID from the Exceptions table to point to the row in the Demo_Exceptions table that violated the constraint.

SQL> select * from demo_exceptions

  2  where

  3  rowid = '00000E65.0000.0001';



SSN         LAST_NAME

----------- ------------------------------

321-45-6789 JOHNSON

You then modify JOHNSON's Social Security number and successfully add the primary key to the table.

SQL> update demo_exceptions

  2  set ssn = '321-45-9876'

  3  where

  4  last_name = 'JOHNSON';



1 row updated.



SQL> alter table demo_exceptions

  2  add constraint demo_exceptions_PK

  3  primary key (ssn)

  4  exceptions into EXCEPTIONS;

Table altered.

Dropping a Primary Key

During the database design process, you may need to drop a table's primary key. The syntax for the ALTER TABLE statement is

ALTER TABLE table-name DROP PRIMARY KEY;

where table-name is the table associated with the primary key.

For the sake of illustration, suppose you create a table for the storage of accounts payable data and define the primary key to be the Vendor's invoice number.

SQL> create table AP_Header (

  2  Bill_Number            NUMBER(4) NOT NULL,

  3  Vendor_Invoice_Number  VARCHAR2(10) NOT NULL,

  4  Vendor_ID              VARCHAR2(6)  NOT NULL,

  5  Date_Received          DATE         NOT NULL,

  6  Bill_Status            VARCHAR2(5),

  7  PRIMARY KEY (Vendor_Invoice_Number));

Table created.

After inspecting some data and pondering this definition, you realize that two different vendors could easily supply the same invoice number. Therefore, you drop the primary key for the table.

SQL> alter table AP_Header drop primary key;

Table altered.

What if you've already declared a foreign key in the AP_Detail table that references the primary key of AP_Header? If such cases, Oracle does not allow you to drop AP_Header's primary key.

SQL> create table AP_Detail (

  2  Bill_Number            NUMBER(4) NOT NULL,

  3  Vendor_Invoice_Number  VARCHAR2(10) NOT NULL,

  4  Item_Number            NUMBER(3) NOT NULL,

  5  Billed_Amount          NUMBER(8,2) NOT NULL,

  6  Approved_Amount        NUMBER(8,2),

  7  Paid_Amount            NUMBER(8,2),

  8  Constraint AP_Detail_FK Foreign Key (Vendor_Invoice_Number)

  9             References AP_Header);

Table created.

SQL> alter table AP_Header drop primary key;

alter table AP_Header drop primary key

*

ERROR at line 1:

ORA-02273: this unique/primary key is referenced by some foreign keys

You can use the keyword CASCADE as an option in the DROP PRIMARY KEY clause, but use this feature with caution! CASCADE drops the primary key as well as any foreign keys that reference it.

SQL> alter table AP_Header drop primary key cascade;

Table altered.

Dropping a Foreign Key

During the database design process, you may find that you've mistakenly defined a column as a foreign key. Dropping a foreign key is a bit different than dropping a primary key. Because a table can have more than one foreign key, the ALTER TABLE statement requires you to supply the constraint name associated with the foreign key. The syntax is

ALTER TABLE table-name DROP CONSTRAINT constraint-name;

where table-name is the table associated with the primary key and constraint-name is the constraint associated with the foreign key.

For instance, suppose that the AP_Header table has an additional column named Vendor_Status, which, coincidentally, has the same datatype and width as Vendor_ID. You mistakenly create a foreign key for Vendor_Status that references the primary key of the Vendor table.

SQL> desc AP_Header

 Name                            Null?    Type

 ------------------------------- -------- ----

 BILL_NUMBER                     NOT NULL NUMBER(4)

 VENDOR_INVOICE_NUMBER           NOT NULL VARCHAR2(10)

 VENDOR_ID                       NOT NULL VARCHAR2(6)

 DATE_RECEIVED                   NOT NULL DATE

 BILL_STATUS                              VARCHAR2(5)

 VENDOR_STATUS                            VARCHAR2(6)

SQL> alter table AP_Header add constraint AP_Header_Vendor_Status_FK

  2                            foreign key (Vendor_Status) references Vendor;

Table altered.

After you try to insert a value into the Vendor_Status column, you quickly realize your error and drop the foreign key assigned to the column.

SQL> alter table AP_Header drop constraint AP_Header_Vendor_Status_FK;

Table altered.

Declaring Unique Constraints

Whereas primary and foreign key constraints enforce data and referential integrity, another type of constraint enforces a unique set of values on a column. (A unique constraint is not a substitute for a primary key constraint.) As an example, most organizations assign a number to each employee--;an employee number. In addition, each employee also has a Social Security number, which is unique. But the Social Security number is an attribute of an employee, whereas the purpose of the Employee_ID is to uniquely identify rows in the Employee table.

SQL> create table Patient (

  2  Patient_ID           varchar2(6) primary key,

  3  Last_Name            varchar2(30) not null,

  4  First_Name           varchar2(20) not null,

  5  Middle_Name          varchar2(20),

  6  Social_Security_Number varchar2(9) unique,

  7  Insurance_Carrier_Code varchar2(4));

Table created.

SQL> insert into Patient

  2  (Patient_ID, Last_Name, First_Name)

  3  values

  4  ('A901', 'NORTON', 'ED');

1 row created.

SQL> insert into Patient

  2  (Patient_ID, Last_Name, First_Name, Social_Security_Number)

  3  values

  4  ('A902', 'KRAMDEN', 'RALPH', '123456789');

1 row created.

SQL> insert into Patient

  2  (Patient_ID, Last_Name, First_Name, Social_Security_Number)

  3  values

  4  ('A903', 'NORTON', 'TRIXIE', '123456789');

insert into Patient

            *

ERROR at line 1:

ORA-00001: unique constraint (FRAYED_WIRES.SYS_C00550) violated

Differences Between Primary Key and Unique Constraints

Several differences between primary key and unique constraints are worth noting. First, a table can have only one primary key--;but it can have many unique constraints. Second, when a primary key is defined, the columns that compose the primary key are automatically mandatory. When a unique constraint is declared, the columns that compose the unique constraint are not automatically defined to be mandatory; you must also specify that the column is NOT NULL.

Table Indexes

Any discussion of primary and foreign key constraints must also examine indexes--;the topics are closely related. A table index is an Oracle object that contains the values that exist in one or more columns in a table. This section describes table indexes and explains how they are used by both the application developer and Oracle.

Oracle provides two types of table indexes: unique and nonunique. Unique indexes enforce primary key and unique constraints. Nonunique indexes improve query performance, which is discussed further in Chapter 33, "Oracle Internals." Both types of indexes are implemented internally via a B*-tree data structure. A B*-tree data structure is graphically depicted as a balanced, inverted tree in which each leaf represents an index value. Understanding the following concepts is critical when you design an application's database:

Creating an Index

The basic syntax for creating an index is

CREATE [UNIQUE] INDEX index-name

ON table-name (column1, ... columnN);

where index-name is the name to be given to the index (subject to Oracle database object naming restrictions), table-name is the table for which the index is created, and column1 through columnN are the columns to be used in creating the index.

Notice that the keyword UNIQUE is optional. If you don't include UNIQUE, the created index is nonunique. In other words, the nonunique index does not restrict the values of a set of columns in any way. If you include the keyword UNIQUE, the index prevents a duplicate set of column values from being stored in the table.

Here's a straightforward example. The primary key of the Employee table is Employee_ID. However, you'll frequently query the Employee table based on an employee's last name. To improve the performance of those queries, you create an index.

SQL> create index Employee_Last_Name on Employee (Last_Name);

Index created.

Why You Shouldn't Create Unique Indexes

Although CREATE UNIQUE INDEX is a legal Oracle statement, you shouldn't use it; instead, declare PRIMARY KEY and UNIQUE constraints. The two principal reasons for this advice are

Indexes and the Navigator

If you're using Personal Oracle7 for Windows 95, you can use the Navigator to examine, create, and drop indexes. To see a list of all the indexes in your local database, double-click the Index folder--;the list of indexes appears on the right side of the Navigator main window. (See Figure 14.1.)

Figure 14.1 Displaying the indexes that exist in the Local Database.

You can sort the list of indexes alphabetically by name--;click the Name column heading--;or by owner--;click the Owner column heading.

Examining an Index

If you want to see the properties of a particular index, select the index and right-click Properties. A window displays the name of the index, the owner of the index, the name of the table on which the index is based, the columns that compose the index, and the columns that are a part of the index. (See Figure 14.2.)

Figure 14.2 Viewing the properties of an index.



Note

Be aware that the Navigator will display all the table's columns in the Index Properties window. However, only those columns with a numeric value in the Order column heading compose the index. For example, a single column with an Order value of 1 indicates that the index is based on that column only.


Creating an Index

To create a new index, select the Index folder and right-click New. The Navigator displays a window in which you specify the index name, index owner, table name, and column information. (See Figure 14.3.)

Figure 14.3 Creating an index with the Navigator.

Enter an appropriate name for the index and select the index owner from the list of all users by pressing the down arrow in the owner field. Also, select the table to be indexed by pressing the down arrow in the table field. Finally, indicate which columns compose the index and their order by clicking the order field to the right of the desired columns. If you have mistakenly specified an order for a column, you can clear it by double-clicking the Order field. Figure 14.3 shows an index on the Loan_Application table based on Borrower_Last_Name and Borrower_First_Name--;in that order.

A table can have multiple indexes as long as each index is based on a different set of columns from the other indexes. For example, you could create another index on the Loan_Application table in which you reversed the order of the two columns, Borrower_First_Name and Borrower_Last_Name as shown in Figure 14.4.

Figure 14.4 Creating an index on a table with a different column order.

However, if you try to create another index with the same column list as an existing index, Personal Oracle7 will return an error message. (See Figure 14.5.)

Figure 14.5 Error when attempting to create an index with the same column order as an existing index.

Deleting an Index

To delete an index, select the index to be deleted and right-click Delete. The Navigator will prompt you to confirm the deletion. (See Figure 14.6.)

Figure 14.6 Message confirming that an index should be deleted.

The syntax to delete an index with SQL*Plus is

DROP INDEX [owner.]index_name

where index_name is the name of the index to be dropped and owner is the name of the Oracle account that owns the index. This parameter is optional; if owner is specified, the user dropping the index must have the DROP ANY INDEX system privilege or have been granted the privilege to drop the specified index.

Mandatory Column Values: NOT NULL Constraints

When you declare a column as NOT NULL, Oracle treats the mandatory requirement as a constraint. In fact, this constraint is the only one that can be seen with the SQL*Plus DESCRIBE command. Here is an illustration:

SQL> describe employee

 Name                            Null?    Type

 ------------------------------- -------- ----

 EMPLOYEE_ID                     NOT NULL NUMBER(4)

 LAST_NAME                       NOT NULL VARCHAR2(30)

 FIRST_NAME                      NOT NULL VARCHAR2(20)

 MIDDLE_INITIAL                           CHAR(1)

 HIRE_DATE                       NOT NULL DATE

 TERMINATION_DATE                         DATE

 DATE_OF_BIRTH                            DATE

 MONTHLY_SALARY                           NUMBER(5)

 MANAGER                         NOT NULL CHAR(1)

 USERNAME                                 VARCHAR2(31)

The DESCRIBE command shows that the Employee table has five mandatory columns. This number is somewhat misleading because Employee_ID was defined as the table's primary key--;which is automatically NOT NULL. You can use the Oracle data dictionary view named USER_CONSTRAINTS to see the constraints associated with a table. The columns returned by USER_CONSTRAINTS include

Following are the results of a query of USER_CONSTRAINTS for all constraints associated with the Employee table: four NOT NULL constraints, a CHECK constraint on the Manager column, and a primary key constraint that is indicated by a value of P for Constraint_Type.

SQL> select constraint_name, constraint_type, search_condition

  2  from user_constraints

  3  where

  4  table_name = 'EMPLOYEE';

CONSTRAINT_NAME                CONST SEARCH_CONDITION

------------------------------ ----- ----------------------------------------

SYS_C00517                     C     LAST_NAME IS NOT NULL

SYS_C00518                     C     FIRST_NAME IS NOT NULL

SYS_C00519                     C     HIRE_DATE IS NOT NULL

SYS_C00520                     P

SYS_C00521                     C     Manager in ('Y','N')

SYS_C00522                     C     MANAGER IS NOT NULL

6 rows selected.

You can specify a constraint name for a NOT NULL constraint in this manner:

SQL> create table demo_not_null (

  2  my_column number constraint Value_Required not null);

Table created.

SQL> select constraint_name, constraint_type, search_condition

  2  from user_constraints

  3  where

  4  table_name = 'DEMO_NOT_NULL';

CONSTRAINT_NAME                CONST SEARCH_CONDITION

------------------------------ ----- ----------------------------------------

VALUE_REQUIRED                 C     MY_COLUMN IS NOT NULL

If you wanted to change the column to allow NULL values, you would use the ALTER TABLE statement in the following way:

SQL> alter table demo_not_null modify

  2  (my_column number null);



Table altered.



SQL> select constraint_name, constraint_type, search_condition

  2  from user_constraints

  3  where

  4  table_name = 'DEMO_NOT_NULL';



no rows selected

As you can see from the previous query, the NOT NULL constraint no longer exists.

Restricting Values with a Column CHECK Constraint

The CHECK constraint is a column-level constraint that serves at least two purposes.

The CHECK constraint is declared in a CREATE TABLE or ALTER_TABLE statement using this syntax:

column-name datatype [CONSTRAINT constraint-name] [CHECK (condition)]

The variables are defined in this way: column-name is the column name.

datatype is the column's datatype, width, and scale.

constraint-name is the constraint name subject to Oracle database object naming restrictions.

condition is a legal Oracle SQL condition that returns a Boolean value.


To illustrate this concept, you can create a table that a hospital database uses to store patient information. One of the columns in this table is the patient's body temperature in degrees Fahrenheit. You should restrict the possible values of this column by defining it as NUMBER(4,1). But this column still accepts numbers from 0.0 to 999.9--;including some obviously nonsensical values for body temperature. You can use a CHECK constraint to restrict the value to a range of 60.0 (for patients suffering from hypothermia) to 110.0.

SQL> create table Patient (

  2  Patient_ID     varchar2(6) primary key,

  3  Body_Temp_Deg_F  number(4,1) constraint Patient_Body_Temp

  4                   Check (Body_Temp_Deg_F >= 60.0 and

  5                          Body_Temp_Deg_F <= 110.0));

Table created.

SQL> insert into Patient

  2  (Patient_ID, Body_Temp_Deg_F)

  3  values

  4  ('A1001', 98.6);

1 row created.

SQL> insert into Patient

  2  (Patient_ID, Body_Temp_Deg_F)

  3  values

  4  ('Q7777', 111.2);

('Q7777', 111.2)

              *

ERROR at line 4:

ORA-02290: check constraint (FRAYED_WIRES.PATIENT_BODY_TEMP) violated

You can use Oracle built-in SQL functions in a CHECK constraint. As an example, here is a CHECK constraint that verifies that a patient's insurance status is either Y or N:

SQL> create table Patient (

  2  Patient_ID     varchar2(6) primary key,

  3  Body_Temp_Deg_F  number(4,1) constraint Patient_Body_Temp

  4                   Check (Body_Temp_Deg_F >= 60.0 and

  5                          Body_Temp_Deg_F <= 110.0),

  6  Insurance_Status Char(1) constraint Patient_Insurance_Status

  7                   Check (Insurance_Status in ('Y','y','N','n')));

Table created.

SQL> insert into Patient

  2  (Patient_ID, Insurance_Status)

  3  values

  4  ('R4321','Y');

1 row created.

SQL> insert into Patient

  2  (Patient_ID, Insurance_Status)

  3  values

  4  ('U3030','U');

('U3030','U')

             *

ERROR at line 4:

ORA-02290: check constraint (FRAYED_WIRES.PATIENT_INSURANCE_STATUS) violated

A Column Can Have More Than One CHECK Constraint

Personal Oracle7 does not restrict the number of CHECK constraints that can be defined for a column or a table. The following code is an example of a column--;Amount_Approved--;that has two constraints:

SQL> create table Loan_Application (

  2  Loan_Application_No      number(6) primary key,

  3  Borrower_Last_Name       varchar2(30) not null,

  4  Borrower_First_Name      varchar2(20) not null,

  5  Borrower_Middle_Name     varchar2(20),

  6  Amount_Requested         number(9,2) not null,

  7  Amount_Approved          number(9,2)

  8                           constraint Amount_Approved_Limit

  9                           check (Amount_Approved <= 1000000)

 10                           constraint Amount_Approved_Interval

 11                           check (mod(Amount_Approved,1000)=0)

 12  );

Table created.

SQL> insert into Loan_Application

  2  (Loan_Application_No, Borrower_Last_Name, Borrower_First_Name,

  3   Amount_Requested, Amount_Approved)

  4  values

  5  (2001, 'RUBRIK', 'STANLEY', 1000000, 999950);

insert into Loan_Application

                   *

ERROR at line 1:

ORA-02290: check constraint (FRAYED_WIRES.AMOUNT_APPROVED_INTERVAL) violated

SQL> insert into Loan_Application

  2  (Loan_Application_No, Borrower_Last_Name, Borrower_First_Name,

  3   Amount_Requested, Amount_Approved)

  4  values

  5  (2001, 'RUBRIK', 'STANLEY', 1000000, 999000);

1 row created.

SQL> insert into Loan_Application

  2  (Loan_Application_No, Borrower_Last_Name, Borrower_First_Name,

  3   Amount_Requested, Amount_Approved)

  4  values

  5  (2001, 'RUBRIK', 'STANLEY', 1000000, 1001000);

insert into Loan_Application

                   *

ERROR at line 1:

ORA-02290: check constraint (FRAYED_WIRES.AMOUNT_APPROVED_LIMIT) violated

In this example you can easily combine both constraints into a single constraint. However, you should consider defining separate constraints if you think you might need to disable a single constraint while allowing other constraints to remain enabled.

Referencing Other Columns in a CHECK Constraint

One of the limitations of a column CHECK constraint is that it cannot reference other columns in the same table. Suppose you're responsible for defining a table for storing loan application information. In this table Amount_Requested contains the loan amount requested by the borrower; Amount_Approved is the amount that was approved by the loan committee. The lender never approves an amount greater than that requested. Nevertheless, you can't use a column CHECK constraint to enforce this business rule.

SQL> create table Loan_Application (

  2  Loan_Application_No      number(6) primary key,

  3  Borrower_Last_Name       varchar2(30) not null,

  4  Borrower_First_Name      varchar2(20) not null,

  5  Borrower_Middle_Name     varchar2(20),

  6  Amount_Requested         number(9,2) not null,

  7  Amount_Approved          number(9,2)

  8                           constraint Amount_Approved_Limit

  9                           check (Amount_Approved <= Amount_Requested)

 10  );

)

*

ERROR at line 10:

ORA-02438: Column check constraint cannot reference other columns

However, you can use a table constraint to reference any column in a table. By adding a comma after the definition of Amount_Approved, the column constraint becomes a table constraint.

SQL> create table Loan_Application (

  2         Loan_Application_No      number(6) primary key,

  3         Borrower_Last_Name       varchar2(30) not null,

  4         Borrower_First_Name      varchar2(20) not null,

  5         Borrower_Middle_Name     varchar2(20),

  6         Amount_Requested         number(9,2) not null,

  7         Amount_Approved          number(9,2),

  8              constraint Amount_Approved_Limit

  9              check (Amount_Approved <= Amount_Requested)

 10  );

Table created.

SQL> insert into Loan_Application

  2  (Loan_Application_No, Borrower_Last_Name, Borrower_First_Name,

  3   Amount_Requested, Amount_Approved)

  4  values

  5  (2001, 'CRANDALL', 'JULIE', 300000, 310000);

insert into Loan_Application

                   *

ERROR at line 1:

ORA-02290: check constraint (FRAYED_WIRES.AMOUNT_APPROVED_LIMIT) violated

SQL> insert into Loan_Application

  2  (Loan_Application_No, Borrower_Last_Name, Borrower_First_Name,

  3   Amount_Requested, Amount_Approved)

  4  values

  5  (2001, 'CRANDALL', 'JULIE', 300000, 300000);

1 row created.

Using Pseudocolumns in a CHECK Constraint

A CHECK constraint cannot reference pseudocolumns such as SYSDATE, ROWNUM, and USER. If you need to define a business rule that refers to these pseudocolumns, rely on a database trigger to restrict column values. Chapter 23, "Enforcing Business Rules with Database Triggers," explains this technique.

But What About the Children: Referential Integrity and the UPDATE and DELETE Statements

Referential integrity is enforced for all DML statements: INSERT, UPDATE, and DELETE. For example, if you attempt to update a table so that a foreign key is set to a value that doesn't exist as a primary key in the referenced table, Oracle rejects the update.

SQL> update Repair_Item

  2  set

  3  Repair_ID = 509

  4  where

  5  Repair_ID = 506;

update Repair_Item

       *

ERROR at line 1:

ORA-02291: integrity constraint (FRAYED_WIRES.REPAIR_ITEM_REPAIR_ID_FK)

violated - parent key not found

The next section discusses the effect of UPDATE and DELETE on a primary key value.

Restricting Changes to a Primary Key

The default behavior of Oracle is to prevent changes to a primary key value if that primary key value has children. The children are the rows in other tables whose foreign key values refer to the primary key value to be changed. Using the repair store example, observe what happens if you try to delete Repair ID 505 from the Repair_Header table.

SQL> set linesize 132

SQL> delete from Repair_Header

  2  where

  3  Repair_ID = 505;

delete from Repair_Header

*

ERROR at line 1:

ORA-02292: integrity constraint (FRAYED_WIRES.REPAIR_ITEM_REPAIR_ID_FK)

violated - child record found

Oracle rejects the DELETE statement because the Repair_Item table has one or more rows in which Repair ID is equal to 505. Similarly, Oracle also rejects an UPDATE statement that attempts to change the value of Repair ID from 505 to 999.

SQL> update Repair_Header

  2  set Repair_ID = 999

  3  where

  4  Repair_ID = 505;

update Repair_Header

       *

ERROR at line 1:

ORA-02292: integrity constraint (FRAYED_WIRES.REPAIR_ITEM_REPAIR_ID_FK)

violated - child record found

Cascading Deletes

Oracle provides an option for a foreign key constraint that causes the deletion of a primary key value to cascade to any child records that reference that value. To use the previous example, if Repair ID 505 was deleted from the Repair_Header table, the DELETE CASCADE option would cause the rows in the Repair_Item table in which Repair ID was 505 to also be deleted. The syntax for this feature is

SQL> alter table Repair_Item

  2  add constraint Repair_Item_Repair_ID_FK

  3  foreign key (Repair_ID) references Repair_Header

  4  on delete cascade;

Table altered.

Now, when Repair ID is deleted from Repair_Header, its child rows in Repair_Item are deleted automatically.

SQL> delete from Repair_Header

  2  where

  3  Repair_ID = 505;

1 row deleted.

SQL> select Repair_ID

  2  from Repair_Item

  3  where

  4  Repair_ID = 505;

no rows selected

Summary

Remember the following concepts when defining table and column constraints: