The previous chapters in this book covered topics that prepare you for the next logical step in creating database objects. For example, you need to install the Oracle binaries and create a database, tablespaces, and users before you start creating tables. Usually, the first objects created for an application are the tables, constraints, and indexes. This chapter focuses on the management of tables and constraints. The administration of indexes is covered in Chapter 8.
A table is the basic storage container for data in a database. You create and modify the table structure via DDL statements, such as CREATE TABLE and ALTER TABLE. You access and manipulate table data via DML statements (INSERT, UPDATE, DELETE, MERGE, SELECT).
Tip One important difference between DDL and DML statements is that DDL statements are implicitly committed, whereas with DML statements, you must explicitly issue a COMMIT or ROLLBACK to end the transaction.
A constraint is a mechanism for enforcing that data adhere to business rules. For example, you may have a business requirement that all customer IDs be unique within a table. In this scenario, you can use a primary key constraint to guarantee that all customer IDs inserted or updated in a CUSTOMER table are unique. Constraints inspect data as they’re inserted, updated, and deleted to ensure that no business rules are violated.
This chapter deals with common techniques for creating and maintaining tables and constraints. Almost always, when you create a table, the table needs one or more constraints defined; therefore, it makes sense to cover constraint management along with tables. The first part of the chapter focuses on common table creation and maintenance tasks. The latter part of the chapter details constraint management.
The Oracle database supports a vast and robust variety of table types. These various types are described in Table 7-1.
Table 7-1. Oracle Table Type Descriptions
This chapter focuses on the table types that are most often used, in particular heap organized, index organized, and temporary tables. Partitioned tables are used extensively in data warehouse environments and are covered separately, in Chapter 12. External tables are covered in Chapter 14. For details on table types not covered in this book, see the SQL Language Reference Guide, which is available for download from the Oracle Technology Network web site (http://otn.oracle.com ).
Understanding Data Types
When creating a table, you must specify the columns names and corresponding data types. As a DBA you should understand the appropriate use of each data type. I’ve seen many application issues (performance and accuracy of data) caused by the wrong choice of data type. For instance, if a character string is used when a date data type should have been used, this causes needless conversions and headaches when attempting to do date math and reporting. Compounding the problem, after an incorrect data type is implemented in a production environment, it can be very difficult to modify data types, as this introduces a change that might possibly break existing code. Once you go wrong, it’s extremely tough to recant and backtrack and choose the right course. It’s more likely you will end up with hack upon hack as you attempt to find ways to force the ill-chosen data type to do the job it was never intended to do.
Having said that, Oracle supports the following groups of data types:
A brief description and usage recommendation are provided in the following sections.
Note Specialized data types, such as XML types, Any types, spatial types, media Types, and user-defined types, are not covered in this book. For more details regarding these data types, see the SQL Language Reference guide available from the Oracle Technology Network web site (http://otn.oracle.com).
Character
Use a character data type to store characters and string data. The following character data types are available in Oracle:
The VARCHAR2 data type is what you should use in most scenarios to hold character/string data. A VARCHAR2 only allocates space based on the number of characters in the string. If you insert a one-character string into a column defined to be VARCHAR2(30), Oracle will only consume space for the one character. The following example verifies this behavior:
create table d(d varchar2(30));
insert into d values ('a'),
select dump(d) from d;
Here is a snippet of the output, verifying that only 1B has been allocated:
DUMP(D)
----------------
Typ=1 Len=1
Note Oracle does have another data type, the VARCHAR (without the “2”). I only mention this because you’re bound to encounter this data type at some point in your Oracle DBA career. Oracle currently defines VARCHAR as synonymous with VARCHAR2. Oracle strongly recommends that you use VARCHAR2 (and not VARCHAR), as Oracle’s documentation states that VARCHAR might serve a different purpose in the future.
When you define a VARCHAR2 column, you must specify a length. There are two ways to do this: BYTE and CHAR. BYTE specifies the maximum length of the string in bytes, whereas CHAR specifies the maximum number of characters. For example, to specify a string that contains at the most 30B, you define it as follows:
varchar2(30 byte)
To specify a character string that can contain at most 30 characters, you define it as follows:
varchar2(30 char)
Many DBAs do not realize that if you don’t specify either BYTE or CHAR, then the default length is calculated in bytes. In other words, VARCHAR2(30) is the same as VARCHAR2(30 byte).
In almost all situations you’re safer specifying the length using CHAR. When working with multibyte character sets, if you specified the length to be VARCHAR2(30 byte), you may not get predictable results, because some characters require more than 1B of storage. In contrast, if you specify VARCHAR2(30 char), you can always store 30 characters in the string, regardless of whether some characters require more than1B.
In almost every scenario a VARCHAR2 is preferable to a CHAR. The VARCHAR2 data type is more flexible and space efficient than CHAR. This is because a CHAR is a fixed-length character field. If you define a CHAR(30) and insert a string that consists of only one character, Oracle will allocate 30B of space. This can be an inefficient use of space. The following example verifies this behavior:
create table d(d char(30));
insert into d values ('a'),
select dump(d) from d;
Here is a snippet of the output, verifying that 30B have been consumed:
DUMP(D)
----------------
Typ=96 Len=30
The NVARCHAR2 and NCHAR data types are useful if you have a database that was originally created with a single-byte, fixed-width character set, but sometime later you need to store multibyte character set data in the same database. You can use the NVARCHAR2 and NCHAR data types to support this requirement.
Note For Oracle Database 11g and lower, 4,000 was the largest size allowed for a VARCHAR2 or NVARCHAR2 data type. In Oracle Database 12c and higher, you can specify up to 32,767 characters in a VARCHAR2 or NVARCHAR2 data type. Prior to 12c, if you wanted to store character data larger greater than 4,000 characters, the logical choice was a CLOB (see the section “LOB,” later in this chapter, for more details).
Numeric
Use a numeric data typeto store data that you’ll potentially need to use with mathematic functions, such as SUM, AVG, MAX, and MIN. Never store numeric information in a character data type. When you use a VARCHAR2 to store data that are inherently numeric, you’re introducing future failures into your system. Eventually, you’ll want to report or run calculations on numeric data, and if they’re not a numeric data type, you’ll get unpredictable and oftentimes wrong results.
Oracle supports three numeric data types:
For most situations, you’ll use the NUMBER data type for any type of number data. Its syntax is
NUMBER(scale, precision)
where scale is the total number of digits, and precision is the number of digits to the right of the decimal point. So, with a number defined as NUMBER(5, 2) you can store values +/–999.99. That’s a total of five digits, with two used for precision to the right of the decimal point.
Tip Oracle allows a maximum of 38 digits for a NUMBER data type. This is almost always sufficient for any type of numeric application.
What sometimes confuses DBAs is that you can create a table with columns defined as INT, INTEGER, REAL, DECIMAL, and so on. These data types are all implemented by Oracle with a NUMBER data type. For example, a column specified as INTEGER is implemented as a NUMBER(38).
The BINARY_DOUBLE and BINARY_FLOAT data types are used for scientific calculations. These map to the DOUBLE and FLOAT Java data types. Unless your application is performing rocket science calculations, then use the NUMBER data type for all your numeric requirements.
When capturing and reporting on date-related information, you should always use a DATE or TIMESTAMP data type (and not VARCHAR2). Using the correct date-related data type allows you to perform accurate Oracle date calculations and aggregations and dependable sorting for reporting. If you use a VARCHAR2 for a field that contains date information, you are guaranteeing future reporting inconsistencies and needless conversion functions (such as TO_DATE and TO_CHAR).
Oracle supports three date-related data types:
The DATE data type contains a date component as well as a time component that is granular to the second. By default, if you don’t specify a time component when inserting data, then the time value defaults to midnight (0 hour at the 0 second). If you need to track time at a more granular level than the second, then use TIMESTAMP; otherwise, feel free to use DATE.
The TIMESTAMP data type contains a date component and a time component that is granular to fractions of a second. When you define a TIMESTAMP, you can specify the fractional second precision component. For instance, if you wanted five digits of fractional precision to the right of the decimal point, you would specify that as
TIMESTAMP(5)
The maximum fractional precision is 9; the default is 6. If you specify 0 fractional precision, then you have the equivalent of the DATE data type.
The TIMESTAMP data type comes in two additional variations: TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE. These are time zone–aware data types, meaning that when the user selects the data, the time value is adjusted to the time zone of the user’s session.
Oracle also provides an INTERVAL data type. This is meant to store a duration, or interval, of time. There are two types: INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND. Use the former when precision to the year and month is required. Use the latter when you need to store interval data granular to the day and second.
CHOOSING YOUR INTERVAL TYPE
When choosing an interval type, let your choice be driven by the level of granularity you desire in your results. For example, you can use INTERVAL DAY TO SECOND to store intervals several years in length—it is just that you will express such intervals in terms of days, perhaps of several hundreds of days. If you record only a number of years and months, then you can never actually get to the correct number of days, because the number of days represented by a year or a month depends on which specific year and month are under discussion.
Similarly, if you need granularity in terms of months, you can’t back into the correct number of months based on the number of days. So, choose the type to match the granularity needed in your application.
RAW
The RAW data typeallows you to store binary data in a column. This type of data is sometimes used for storing globally unique identifiers or small amounts of encrypted data.
Note Prior to Oracle Database 12c, the maximum size for a RAW column was 2,000 bytes. As of Oracle Database 12c you can declare a RAW to have a maximum size of 32,767 bytes. If you have large amounts of binary data to store, then use a BLOB.
If you select data from a RAW column, SQL*Plus implicitly applies the built-in RAWTOHEX function to the data retrieved. The data are displayed in hexadecimal format, using characters 0–9 and A–F. When inserting data into a RAW column, the built-in HEXTORAW is implicitly applied.
This is important because if you create an index on a RAW column, the optimizer may ignore the index, as SQL*Plus is implicitly applying functions where the RAW column is referenced in the SQL. A normal index may be of no use, whereas a function-based index using RAWTOHEX may result in a substantial performance improvement.
When DBAs hear the word ROWID (row identifier), they often think of a pseudocolumn provided with every table row that contains the physical location of the row on disk; that is correct. However, many DBAs do not realize that Oracle supports an actual ROWID data type, meaning that you can create a table with a column defined as the type ROWID.
There are a few practical uses for the ROWID data type. One valid application would be if you’re having problems when trying to enable a referential integrity constraint and want to capture the ROWID of rows that violate a constraint. In this scenario, you could create a table with a column of the type ROWID and store in it the ROWIDs of offending records within the table. This affords you an efficient way to capture and resolve issues with the offending data (see the section “Enabling Constraints,” later in this chapter, for more details).
Tip Never be tempted to use a ROWID data type and the associated ROWID of a row within the table for the primary key value. This is because the ROWID of a row in a table can change. For example, an ALTER TABLE...MOVE command will potentially change every ROWID within a table. Normally, the primary key values of rows within a table should never change. For this reason, instead of using ROWID for a primary key value, use a sequence-generated nonmeaningful number (see the section “Creating a Table with an Autoincrementing (Identity) Column,” later in this chapter, for further discussion).
LOB
Oracle supports storing large amounts of data in a column via a LOB data type. Oracle supports the following types of LOBs:
Tip The LONG and LONG RAW data types are deprecated and should not be used.
If you have textual data that don’t fit within the confines of a VARCHAR2, then you should use a CLOB to store these data. A CLOB is useful for storing large amounts of character data, such as log files. An NCLOB is similar to a CLOB but allows for information encoded in the nation character set of the database.
BLOBs store large amounts of binary data that usually aren’t meant to be human readable. Typical BLOB data include images, audio, and video files.
CLOBs, NCLOBs, and BLOBs are known as internal LOBs. This is because they are stored inside the Oracle database. These data types reside within data files associated with the database.
BFILEs are known as external LOBs. BFILE columns store a pointer to a file on the OS that is outside the database. When it’s not feasible to store a large binary file within the database, then use a BFILE. BFILEs don’t participate in database transactions and aren’t covered by Oracle security or backup and recovery. If you need those features, then use a BLOB and not a BFILE.
Tip See Chapter 11 for a full discussion of LOBs.
Prior to Oracle Database 12c, the maximum length for a VARCHAR2 and NVARCHAR2 was 4,000 bytes, and the maximum length of a RAW column was 2,000 bytes. Starting with Oracle Database 12c, these data types have been extended to accommodate a length of 32,767 bytes.
Check with the current Oracle documentation for the release you’re using to find the exact steps for implementing an extended character type. The procedure for implementing extended character types (as of this writing) is documented in the following list:
SQL> shutdown immediate;
SQL> startup upgrade;
SQL> alter system set max_string_size=extended scope=both;
SQL> @?/rdbms/admin/utl32k.sql
SQL> shutdown immediate;SQL> startup;
Now, you should be able to create an extended column as follows:
SQL> create table d1(dext varchar2(32727)) tablespace users;
The extended character type is actually implemented internally as a LOB, and you can view the LOB details via the USER_LOBS view; for example:
SQL> select table_name, column_name, segment_name, tablespace_name, in_row from user_lobs;
Here is some sample output:
TABLE_NAME COLUMN_NAME SEGMENT_NAME TABLESPACE IN_ROW---------- --------------- ------------------------------ ---------- ----------D1 DEXT SYS_LOB0000043206C00001$$ USERS YES
Following normal LOB storage rules, Oracle stores the first 4,000 bytes inline within the table. Anything greater than 4,000 bytes goes into a LOB segment.
Creating a Table
The number of table features expands with each new version of Oracle. Consider this: the 12c version of the Oracle SQL Language Reference Guide presents more than 80 pages of syntax associated with the CREATE TABLE statement. Moreover, the ALTER TABLE statement takes up another 90-plus pages of details related to table maintenance. For most situations, you typically need to use only a fraction of the table options available.
Listed next are the general factors you should consider when creating a table:
Before you run a CREATE TABLE statement, you need to give some thought to each item in the previous list. To that end, DBAs often use data modeling tools to help manage the creation of DDL scripts that are used to make database objects. Data modeling tools allow you to define visually tables and relationships and the underlying database features.
Creating a Heap-Organized Table
You use the CREATE TABLE statement to create tables. When creating a table, at minimum you must specify the table name, column name(s), and data types and lengths associated with the columns. The Oracle default table type is heap organized. The term heap means that the data aren’t stored in a specific order in the table (instead, they’re a heap of data). Here is a simple example of creating a heap-organized table:
CREATE TABLE dept
(deptno NUMBER(10)
,dname VARCHAR2(14 CHAR)
,loc VARCHAR2(14 CHAR));
If you don’t specify a tablespace, then the table is created in the default permanent tablespace of the user that creates the table. Allowing the table to be created in the default permanent tablespace is fine for a few small test tables. For anything more sophisticated, you should explicitly specify the tablespace in which you want tables created. For reference (in future examples), here are the creation scripts for two sample tablespaces: HR_DATA and HR_INDEX:
CREATE TABLESPACE hr_data
DATAFILE '/u01/dbfile/O12C/hr_data01.dbf' SIZE 1000m
EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 512k SEGMENT SPACE MANAGEMENT AUTO;
--
CREATE TABLESPACE hr_index
DATAFILE '/u01/dbfile/O12C/hr_index01.dbf' SIZE 100m
EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 512k SEGMENT SPACE MANAGEMENT AUTO;
Usually, when you create a table, you should also specify constraints, such as the primary key. The following code shows the most common features you use when creating a table. This DDL defines primary keys, foreign keys, tablespace information, and comments:
CREATE TABLE dept
(deptno NUMBER(10)
,dname VARCHAR2(14 CHAR)
,loc VARCHAR2(14 CHAR)
,CONSTRAINT dept_pk PRIMARY KEY (deptno)
USING INDEX TABLESPACE hr_index
) TABLESPACE hr_data;
--
COMMENT ON TABLE dept IS 'Department table';
--
CREATE UNIQUE INDEX dept_uk1 ON dept(dname)
TABLESPACE hr_index;
--
CREATE TABLE emp
(empno NUMBER(10)
,ename VARCHAR2(10 CHAR)
,job VARCHAR2(9 CHAR)
,mgr NUMBER(4)
,hiredate DATE
,sal NUMBER(7,2)
,comm NUMBER(7,2)
,deptno NUMBER(10)
,CONSTRAINT emp_pk PRIMARY KEY (empno)
USING INDEX TABLESPACE hr_index
) TABLESPACE hr_data;
--
COMMENT ON TABLE emp IS 'Employee table';
--
ALTER TABLE emp ADD CONSTRAINT emp_fk1
FOREIGN KEY (deptno)
REFERENCES dept(deptno);
--
CREATE INDEX emp_fk1 ON emp(deptno)
TABLESPACE hr_index;
When creating a table, I usually don’t specify table-level physical space properties. If you don’t specify table-level space properties, then the table inherits its space properties from the tablespace in which it’s created. This simplifies administration and maintenance. If you have tables that require different physical space properties, then you can create separate tablespaces to hold tables with differing needs. For instance, you might create a HR_DATA_LARGE tablespace with extent sizes of 16MB and a HR_DATA_SMALL tablespace with extent sizes of 128KB and choose where a table is created based on its storage requirements. See Chapter 4 for details regarding the creation of tablespaces.
Table 7-2 lists some guidelines to consider when creating tables. These aren’t hard-and-fast rules; adapt them as needed for your environment. Some of these guidelines may seem like obvious suggestions. However, after inheriting many databases over the years, I’ve seen each of these recommendations violated in some way that makes database maintenance difficult and unwieldy.
Table 7-2. Guidelines to Consider When Creating Tables
Recommendation | Reasoning |
---|---|
Use standards when naming tables, columns, constraints, triggers, indexes, and so on. | Helps document the application and simplifies maintenance |
If a column always contains numeric data, make it a number data type. | Enforces a business rule and allows for the greatest flexibility, performance, and consistency when using Oracle SQL math functions (which may behave differently for a “01” character versus a “1” number) |
If you have a business rule that defines the length and precision of a number field, then enforce it; for example, NUMBER(7,2). If you don’t have a business rule, make it NUMBER(38). | Enforces a business rule and keeps the data cleaner |
For character data that are of variable length, use VARCHAR2 (and not VARCHAR). | Follows Oracle’s recommendation of using VARCHAR2 for character data (instead of VARCHAR). The Oracle documentation states that in the future, VARCHAR will be redefined as a separate data type. |
For character data, specify the size in CHAR; for example, VARCHAR(30 CHAR). | When working with multibyte data, you’ll get more predictable results, as multibyte characters are usually stored in more than1B. |
If you have a business rule that specifies the maximum length of a column, then use that length, as opposed to making all columns VARCHAR2(4000). | Enforces a business rule and keeps the data cleaner |
Use DATE and TIMESTAMP data types appropriately. | Enforces a business rule, ensures that the data are of the appropriate format, and allows for the greatest flexibility when using SQL date functions |
Specify a separate tablespace for the table and indexes. Let the table and indexes inherit storage attributes from the tablespaces. | Simplifies administration and maintenance |
Most tables should be created with a primary key. | Enforces a business rule and allows you to uniquely identify each row |
Create a numeric surrogate key to be the primary key for each table. Populate the surrogate key from a sequence. | Makes joins easier and more efficient |
Create primary key constraints out of line. | Allows you more flexibility when creating the primary key, especially if you have a situation in which the primary key consists of multiple columns |
Create a unique key for the logical user—a recognizable combination of columns that makes a row one of a kind. | Enforces a business rule and keeps the data cleaner |
Create comments for the tables and columns. | Helps document the application and eases maintenance |
Avoid LOB data types if possible. | Prevents maintenance issues associated with LOB columns, such as unexpected growth and performance issues when copying. |
If a column should always have a value, then enforce it with a NOT NULL constraint. | Enforces a business rule and keeps the data cleaner |
Create audit-type columns, such as CREATE_DTT and UPDATE_DTT, that are automatically populated with default values or triggers, or both. | Helps with maintenance and determining when data were inserted or updated, or both. Other types of audit columns to consider include the users that inserted and updated the row. |
Use check constraints where appropriate. | Enforces a business rule and keeps the data cleaner |
Define foreign keys where appropriate. | Enforces a business rule and keeps the data cleaner |
With Oracle Database 11g and higher, you can create a virtual column as part of your table definition. A virtual column is based on one or more existing columns from the same table or a combination of constants, SQL functions, and user-defined PL/SQL functions, or both. Virtual columns aren’t stored on disk; they’re evaluated at runtime, when the SQL query executes. Virtual columns can be indexed and can have stored statistics.
Prior to Oracle Database 11g, you could simulate a virtual column via a SELECT statement or in a view definition. For example, this next SQL SELECT statement generates a virtual value when the query is executed:
select inv_id, inv_count,
case when inv_count <= 100 then 'GETTING LOW'
when inv_count > 100 then 'OKAY'
end
from inv;
Why use a virtual column? The advantages of doing so are as follows:
Here is an example of creating a table with a virtual column:
create table inv(
inv_id number
,inv_count number
,inv_status generated always as (
case when inv_count <= 100 then 'GETTING LOW'
when inv_count > 100 then 'OKAY'
end)
);
In the prior code listing, specifying GENERATED ALWAYS is optional. For example, this listing is equivalent to the previous one:
create table inv(
inv_id number
,inv_count number
,inv_status as (
case when inv_count <= 100 then 'GETTING LOW'
when inv_count > 100 then 'OKAY'
end)
);
I prefer to add GENERATED ALWAYS because it reinforces in my mind that the column is always virtual. The GENERATED ALWAYS helps document inline what you’ve done. This aids in maintenance for other DBAs who come along long after you.
To view values generated by virtual columns, first insert some data into the table:
SQL> insert into inv (inv_id, inv_count) values (1,100);
Next, select from the table to view the generated value:
SQL> select * from inv;
Here is some sample output:
INV_ID INV_COUNT INV_STATUS
---------- ---------- -----------
1 100 GETTING LOW
Note If you insert data into the table, nothing is stored in a column set to GENERATED ALWAYS AS. The virtual value is generated when you select from the table.
You can also alter a table to contain a virtual column:
alter table inv add(
inv_comm generated always as(inv_count * 0.1) virtual
);
And, you can change the definition of an existing virtual column:
alter table inv modify inv_status generated always as(
case when inv_count <= 50 then 'NEED MORE'
when inv_count >50 and inv_count <=200 then 'GETTING LOW'
when inv_count > 200 then 'OKAY'
end);
You can access virtual columns in SQL queries (DML or DDL). For instance, suppose you want to update a permanent column based on the value in a virtual column:
SQL> update inv set inv_count=100 where inv_status='OKAY';
A virtual column itself can’t be updated via the SET clause of an UPDATE statement. However, you can reference a virtual column in the WHERE clause of an UPDATE or DELETE statement.
Optionally, you can specify the data type of a virtual column. If you omit the data type, Oracle derives it from the expression you use to define the virtual column.
Several caveats are associated with virtual columns:
To view the definition of a virtual column, use the DBMS_METADATA package to see the DDL associated with the table. If you’re selecting from SQL*Plus, you need to set the LONG variable to a value large enough to show all data returned:
SQL> set long 10000;
SQL> select dbms_metadata.get_ddl('TABLE','INV') from dual;
Here is a snippet of the output:
CREATE TABLE "INV_MGMT"."INV"
( "INV_ID" NUMBER,
"INV_COUNT" NUMBER,
"INV_STATUS" VARCHAR2(11) GENERATED ALWAYS AS (CASE WHEN "INV_COUNT"<=50 THEN
'NEED MORE' WHEN ("INV_COUNT">50 AND "INV_COUNT"<=200) THEN 'GETTING LOW' WHEN
"INV_COUNT">200 THEN 'OKAY' END) VIRTUAL ...
Implementing Invisible Columns
Starting with Oracle Database 12c, you can create invisible columns. When a column is invisible, it cannot be viewed via
However, the column can be accessed if explicitly specified in a SELECT clause or referenced directly in a DML statement (INSERT, UPDATE, DELETE, or MERGE). Invisible columns can also be indexed (just like visible columns).
The main use for an invisible column is to ensure that adding a column to a table won’t disrupt any of the existing application code. If the application code doesn’t explicitly access the invisible column, then it appears to the application as if the column doesn’t exist.
A table can be created with invisible columns, or a column can be added or altered so as to be invisible. A column that is defined as invisible can also be altered so as to be visible. Here is an example of creating a table with an invisible column:
create table inv
(inv_id number
,inv_desc varchar2(30 char)
,inv_profit number invisible);
Now, when the table is described, note that the invisible column is not displayed:
SQL> desc inv
Name Null? Type
----------------------------------------- -------- ----------------------------
INV_ID NUMBER
INV_DESC VARCHAR2(30 CHAR)
A column that has been defined as invisible is still accessible if you specify it directly in a SELECT statement or any DML operations. For example, when selecting from a table, you can view the invisible column by specifying it in the SELECT clause:
SQL> select inv_id, inv_desc, inv_profit from inv;
Note When you create a table that has invisible columns, at least one column must be visible.
Starting with Oracle Database 11g, you can place individual tables in read-only mode. Doing so prevents any INSERT, UPDATE, or DELETE statements from running against a table. In versions prior to Oracle Database 11g, the only way to make a table read-only was to either place the entire database in read-only mode or place a tablespace in read-only mode (making all tables in the tablespace read-only).
There are several reasons why you may require the read-only feature at the table level:
Use the ALTER TABLE statement to place a table in read-only mode:
SQL> alter table inv read only;
You can verify the status of a read-only table by issuing the following query:
SQL> select table_name, read_only from user_tables where read_only='YES';
To modify a read-only table to read/write, issue the following SQL:
SQL> alter table inv read write;
Note The read-only table feature requires that the database initialization COMPATIBLE parameter be set to 11.1.0 or higher.
Understanding Deferred Segment Creation
Starting with Oracle Database 11g Release 2, when you create a table, the creation of the associated segment is deferred until the first row is inserted into the table. This feature has some interesting implications. For instance, if you have thousands of objects that you’re initially creating for an application (such as when you first install it), no space is consumed by any of the tables (or associated indexes) until data are inserted into the application tables. This means that the initial DDL runs more quickly when you create a table, but the first INSERT statement runs slightly slower.
To illustrate the concept of deferred segments, first create a table:
SQL> create table inv(inv_id number, inv_desc varchar2(30 CHAR));
You can verify that the table has been created by inspecting USER_TABLES:
select
table_name
,segment_created
from user_tables
where table_name='INV';
Here is some sample output:
TABLE_NAME SEG
------------------------------ ---
INV NO
Next, query USER_SEGMENTS to verify that a segment hasn’t yet been allocated for the table:
select
segment_name
,segment_type
,bytes
from user_segments
where segment_name='INV'
and segment_type='TABLE';
Here is the corresponding output for this example:
no rows selected
Now, insert a row into a table:
SQL> insert into inv values(1,'BOOK'),
Rerun the query, selecting from USER_SEGMENTS, and note that a segment has been created:
SEGMENT_NAME SEGMENT_TYPE BYTES
--------------- ------------------ ----------
INV TABLE 65536
If you’re used to working with older versions of Oracle, the deferred–segment creation feature can cause confusion. For example, if you have space-related monitoring reports that query DBA_SEGMENTS or DBA_EXTENTS, be aware that these views aren’t populated for a table or any indexes associated with a table until the first row is inserted into the table.
Note You can disable the deferred–segment creation feature by setting the database initialization parameter DEFERRED_SEGMENT_CREATION to FALSE. The default for this parameter is TRUE.
Creating a Table with an Autoincrementing (Identity) Column
Starting with Oracle Database 12c, you can define a column that is automatically populated and incremented when inserting data. This feature is ideal for automatically populating primary key columns.
Tip Prior to Oracle Database 12c, you would have to create a sequence manually and then access the sequence when inserting into the table. Sometimes, DBAs would create triggers on tables to simulate an autoincrementing column based on a sequence (see Chapter 9 for details).
You define an autoincrementing (identity) column with the GENERATED AS IDENTITY clause. This example creates a table with primary key column that will be automatically populated and incremented:
create table inv(
inv_id number generated as identity
,inv_desc varchar2(30 char));
--
alter table inv add constraint inv_pk primary key (inv_id);
Now, you can populate the table without having to specify the primary key value:
insert into inv (inv_desc) values ('Book'),
insert into inv (inv_desc) values ('Table'),
Selecting from the table shows that the INV_ID column has been automatically populated:
select * from inv;
Here is some sample output:
INV_ID INV_DESC
---------- ------------------------------
1 Book
2 Table
When you create an identity column, Oracle automatically creates a sequence and associates the sequence with the column. You can view the sequence information in USER_SEQUENCES:
SQL> select sequence_name, min_value, increment_by from user_sequences;
Here is some sample output for this example:
SEQUENCE_NAME MIN_VALUE INCREMENT_BY
-------------------- ---------- ------------
ISEQ$$_43216 1 1
You can identify identity columns via this query:
select table_name, identity_column
from user_tab_columns
where identity_column='YES';
When creating a table with an identity column (such as in the prior example), you can’t directly specify a value for the identity column; for example, if you try this:
insert into inv values(3,'Chair'),
you’ll receive an error:
ORA-32795: cannot insert into a generated always identity column
If, for some reason, you need to occasionally insert values into an identity column, then use the following syntax when creating:
create table inv(
inv_id number generated by default on null as identity
,inv_desc varchar2(30 char));
Because the underlying mechanism for populating an identity column is a sequence, you have some control over how the sequence is created (just like you would if you manually created a sequence). For instance, you can specify at what number to start the sequence and by how much the sequence increments each time. This example specifies that the underlying sequence start at the number 50 and increment by two each time:
create table inv(
inv_id number generated as identity (start with 50 increment by 2)
,inv_desc varchar2(30 char));
There are some caveats to be aware of when using autoincrementing (identity) columns:
Also keep in mind that after inserting into a column that is autoincremented, if you issue a rollback, the transaction is rolled back, but not the autoincremented values from the sequence. This is the expected behavior of a sequence. You can roll back such an insert, but the sequence values are used and gone.
Tip See Chapter 9 for details on how to manage a sequence.
Allowing for Default Parallel SQL Execution
If you work with large tables, you may want to consider creating your tables as PARALLEL. This instructs Oracle to set the degree of parallelism for queries and any subsequent INSERT, UPDATE, DELETE, MERGE, and query statements. This example creates a table with a PARALLEL clause of 2:
create table inv
(inv_id number,
inv_desc varchar2(30 char),
create_dtt date default sysdate)
parallel 2;
You can specify PARALLEL, NOPARALLEL, or PARALLEL N. If you don’t specify N, Oracle sets the degree of parallelism based on the PARALLEL_THREADS_PER_CPU initialization parameter. You can verify the degree of parallelism with this query:
SQL> select table_name, degree from user_tables;
The main issue to be aware of here is that if a table has been created with a default degree of parallelism, any subsequent queries will execute with parallel threads. You may wonder why a query or a DML statement is executing in parallel (without explicitly invoking a parallel operation).
WHERE ARE ALL THE P_0 PROCESSES COMING FROM?
I once got a call from a production support person who reported that nobody could connect to the database because of an ORA-00020 maximum number of processes error. I logged into the box and noted that there were hundreds of ora_p parallel query processes running.
I had to kill some of the processes manually so that I could connect to the database. Upon further inspection, I traced the parallel query sessions to an SQL statement and table. The table, in this case, had been created with a default parallel degree of 64 (don’t ask me why), which in turn spawned hundreds of processes and sessions when the table was queried. This maxed out the number of allowed connections to the database and caused the issue. I resolved the problem by setting the table parallel setting to 1.
You can also alter a table to modify its default degree of parallelism:
SQL> alter table inv parallel 1;
As your database grows, you may want to consider table level compression. Compressed data have the benefit of using less disk space and less memory and reduced I/O. Queries that read compressed data potentially run faster because there are fewer blocks to process. However, CPU usage increases as the data are compressed and uncompressed as writes and reads occur, so there is a tradeoff.
Starting with Oracle Database 12c, there are four types of compression available:
Basic compression is enabled with the COMPRESS or COMPRESS BASIC clause (they are synonymous). This example creates a table with basic compression:
create table inv
(inv_id number,
inv_desc varchar2(300 char),
create_dtt timestamp)
compress basic;
Basic compression provides compression as data are direct-path inserted into the table. By default, tables created with COMPRESS BASIC have a PCTFREE setting of 0. You can override this by specifying PCTFREE when creating the table.
Note Basic compression requires the Oracle Enterprise Edition, but doesn’t require an extra license.
Advanced row compression is enabled with the ROW STORE COMPRESS ADVANCED (in 11g this was enabled with COMPRESS FOR OLTP) clause:
create table inv
(inv_id number,
inv_desc varchar2(300 char),
create_dtt timestamp)
row store compress advanced;
Note If you’re using Oracle Database 11g, then use the COMPRESS FOR OLTP clause instead of ROW STORE COMPRESS ADVANCED.
Advanced row compression provides compression when initially inserting data into the table as well as in any subsequent DML operations. You can verify the compression for a table via the following SELECT statement:
select table_name, compression, compress_for
from user_tables
where table_name='INV';
Here is some sample output:
TABLE_NAME COMPRESS COMPRESS_FOR
------------------------------ -------- ------------
INV ENABLED ADVANCED
Note OLTP table compression is a feature of the Oracle Advanced Compression option. This option requires an additional license from Oracle and is only available with the Oracle Enterprise Edition.
You can also create a tablespace with the compression clause. Any table created in that tablespace will inherit the tablespace compression settings. For example, here is how to set the default level of compression for a tablespace:
CREATE TABLESPACE hr_data
DEFAULT ROW STORE COMPRESS ADVANCED
DATAFILE '/u01/dbfile/O12C/hr_data01.dbf' SIZE 100m
EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 512k SEGMENT SPACE MANAGEMENT AUTO;
If you have a table that already exists, you can alter it to allow compression (either basic or advanced):
SQL> alter table inv row store compress advanced;
Note Oracle does not support compression for tables with more than 255 columns.
Altering to allow compression doesn’t compress the existing data in the table. You’ll need to rebuild the table with Data Pump or move the table to compress the data that were in it prior to enabling compression:
SQL> alter table inv move;
Note If you move the table, then you’ll also need to rebuild any associated indexes.
You can disable compression via the NOCOMPRESS clause. This doesn’t affect existing data within the table. Rather, it affects future inserts (basic and advanced row compression) and future DML (advanced row compression); for example,
SQL> alter table inv nocompress;
Oracle also has a warehouse and archive hybrid columnar compression feature that is available when using certain types of storage (such as Exadata). This type of compression is enabled with the COLUMN STORE COMPRESS FOR QUERY LOW|HIGH or COLUMN STORE COMPRESS FOR ARCHIVE LOW|HIGH clause. For more details regarding this type of compression, see the Oracle Technology Network web site (http://otn.oracle.com).
When you’re creating a table, you have the option of specifying the NOLOGGING clause. The NOLOGGING feature can greatly reduce the amount of redo generation for certain types of operations. Sometimes, when you’re working with large amounts of data, it’s desirable, for performance reasons, to reduce the redo generation when you initially create and insert data into a table.
The downside to eliminating redo generation is that you can’t recover the data created via NOLOGGING in the event a failure occurs after the data are loaded (and before you can back up the table). If you can tolerate some risk of data loss, then use NOLOGGING, but back up the table soon after the data are loaded. If your data are critical, then don’t use NOLOGGING. If your data can be easily recreated, then NOLOGGING is desirable when you’re trying to improve performance of large data loads.
One perception is that NOLOGGING eliminates redo generation for the table for all DML operations. That isn’t correct. The NOLOGGING feature never affects redo generation for normal DML statements (regular INSERT, UPDATE, and DELETE).
The NOLOGGING feature can significantly reduce redo generation for the following types of operations:
You need to be aware of some quirks (features) when using NOLOGGING. If your database is in FORCE LOGGING mode, then redo is generated for all operations, regardless of whether you specify NOLOGGING. Likewise, when you’re loading a table, if the table has a referential foreign key constraint defined, then redo is generated regardless of whether you specify NOLOGGING.
You can specify NOLOGGING at one of the following levels:
I prefer to specify the NOLOGGING clause at the statement or table level. In these scenarios it’s obvious to the DBA executing the statement or DDL that NOLOGGING is used. If you specify NOLOGGING at the tablespace level, then each DBA who creates objects within that tablespace must be aware of this tablespace-level setting. In teams with multiple DBAs, it’s easy for one DBA to be unaware that another DBA has created a tablespace with NOLOGGING.
This example first creates a table with the NOLOGGING option:
create table inv(inv_id number)
tablespace users
nologging;
Next, do a direct-path insert with some test data, and commit the data:
insert /*+ append */ into inv select level from dual
connect by level <= 10000;
--
commit;
What happens if you have a media failure after you’ve populated a table in NOLOGGING mode (and before you’ve made a backup of the table)? After a restore and recovery operation, it will appear that the table has been restored:
SQL> desc inv
Name Null? Type
----------------------------------------- -------- ----------------------------
INV_ID NUMBER
However, try to execute a query that scans every block in the table:
SQL> select * from inv;
Here, an error is thrown, indicating that there is logical corruption in the data file:
ORA-01578: ORACLE data block corrupted (file # 5, block # 203)
ORA-01110: data file 5: '/u01/dbfile/O12C/users01.dbf'
ORA-26040: Data block was loaded using the NOLOGGING option
In other words, the data are unrecoverable, because the redo doesn’t exist to restore them. Again, the NOLOGGING option is suitable for large batch loading of data that can easily be reproduced in the event a failure occurs before a backup of the database can be taken after a NOLOGGING operation.
If you specify a logging clause at the statement level, it overrides any table or tablespace setting. If you specify a logging clause at the table level, it sets the default mode for any statements that don’t specify a logging clause and overrides the logging setting at the tablespace. If you specify a logging clause at the tablespace level, it sets the default logging for any CREATE TABLE statements that don’t specify a logging clause.
You verify the logging mode of the database as follows:
SQL> select name, log_mode, force_logging from v$database;
The next statement verifies the logging mode of a tablespace:
SQL> select tablespace_name, logging from dba_tablespaces;
And, this example verifies the logging mode of a table:
SQL> select owner, table_name, logging from dba_tables where logging = 'NO';
You can view the effects of NOLOGGING in a few different ways. One way is to enable autotracing with statistics and view the redo size:
SQL> set autotrace trace statistics;
Then, run a direct-path INSERT statement, and view the redo size statistic:
insert /*+ append */ into inv select level from dual
connect by level <= 10000;
Here is a snippet of the output:
Statistics
----------------------------------------------------------
13772 redo size
With logging disabled, for direct-path operations, you should see a much smaller redo size number than with a regular INSERT statement, such as,
insert into inv select level from dual
connect by level <= 10000;
Here is the partial output, indicating that the redo size is much greater:
Statistics
----------------------------------------------------------
159152 redo size
Another method for determining the effects of NOLOGGING is to measure the amount of redo generated for an operation with logging enabled versus operating in NOLOGGING mode. If you have a development environment that you can test in, you can monitor how often the redo logs switch while the operation is taking place. Another simple test is to time how long the operation takes with and without logging. The operation performed in NOLOGGING mode should be faster (because a minimal amount of redo is being generated).
Sometimes. it’s convenient to create a table based on the definition of an existing table. For instance, say you want to create a quick backup of a table before you modify the table’s structure or data. Use the CREATE TABLE AS SELECT statement (CTAS) to achieve this; for example,
create table inv_backup
as select * from inv;
The previous statement creates an identical table, complete with data. If you don’t want the data included—you just want the structure of the table replicated—then provide a WHERE clause that always evaluates to false (in this example, 1 will never equal 2):
create table inv_empty
as select * from inv
where 1=2;
You can also specify that no redo be logged when a CTAS table is created. For large data sets, this can reduce the amount of time required to create the table:
create table inv_backup
nologging
as select * from inv;
Be aware that using the CTAS technique with the NOLOGGING clause creates the table as NOLOGGING and doesn’t generate the redo required to recover the data that populate the table as the result of the SELECT statement. Also, if the tablespace (in which the CTAS table is being created) is defined as NOLOGGING, then no redo is generated. In these scenarios, you can’t restore and recover your table in the event a failure occurs before you’re able to back up the table. If your data are critical, then don’t use the NOLOGGING clause.
You can also specify parallelism and storage parameters. Depending on the number of CPUs, you may see some performance gains:
create table inv_backup
nologging
tablespace hr_data
parallel 2
as select * from inv;
Note The CTAS technique doesn’t create any indexes or triggers. You have to create indexes and triggers separately if you need those objects from the original table.
Oracle allows you to enable the logging of DDL statements to a log file. This type of logging is switched on with the ENABLE_DDL_LOGGING parameter (the default is FALSE). You can set this at the session or system level. This feature provides you with an audit trail regarding which DDL statements have been issued and when they were run. Here is an example of setting this parameter at the system level:
SQL> alter system set enable_ddl_logging=true scope=both;
After this parameter is set to TRUE, DDL statements will be logged to a log file. Oracle doesn’t log every type of DDL statement, only the most common ones to a log file. The exact location of the DDL logging file and number of files vary by database version. In 11g the logging file is named log.xml. The location (directory path) of this file can be determined via this query:
SQL> select value from v$diag_info where name='Diag Alert';
VALUE
------------------------------------------------
/orahome/app/oracle/diag/rdbms/o11r2/O11R2/alert
You can search the log.xml file in the prior directory for any DDL statements issued after DDL logging has been enabled.
In Oracle Database 12c there are multiple files that capture DDL logging. To find these files, first determine the location of your diagnostic home directory:
SQL> select value from v$diag_info where name='ADR Home';
VALUE
--------------------------------------
/ora01/app/oracle/diag/rdbms/o12c/O12C
Now, change your current working directory to the prior directory and the subdirectory of log; for example,
$ cd /ora01/app/oracle/diag/rdbms/o12c/O12C/log
Within this directory, there will be a file with the format ddl_<SID>.log. This contains a log of DDL statements that have been issued after DDL logging has been enabled. You can also view DDL logging in the log.xml file. This file is located in the ddl subdirectory beneath the previously mentioned log directory; for example,
$ cd /ora01/app/oracle/diag/rdbms/o12c/O12C/log/ddl
Once you navigate to the prior directory, you can view the log.xml file with an OS utility such as vi.
Modifying a Table
Altering a table is a common task. New requirements frequently mean that you need to rename, add, drop, or change column data types. In development environments, changing a table can be a trivial task: you don’t often have large quantities of data or hundreds of users simultaneously accessing a table. However, for active production systems, you need to understand the ramifications of trying to change tables that are currently being accessed or that are already populated with data, or both.
When you modify a table, you must have an exclusive lock on the table. One issue is that if a DML transaction has a lock on the table, you can’t alter it. In this situation, you receive this error:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
The prior error message is somewhat confusing in that it leads you to believe that you can resolve the problem by acquiring a lock with NOWAIT. However, this is a generic message that is generated when the DDL you’re issuing can’t obtain an exclusive lock on the table. In this situation, you have a few options:
The last item in the previous list instructs Oracle to repeatedly attempt to run a DDL statement until it obtains the required lock on the table. You can set the DDL_LOCK_TIMEOUT parameter at the system or session level. This next example instructs Oracle to repeatedly try to obtain a lock for 100 seconds:
SQL> alter session set ddl_lock_timeout=100;
The default value for the system-level DDL_LOCK_TIMEOUT initialization parameter is 0. If you want to modify the default behavior for every session in the system, issue an ALTER SYSTEM SET statement. The following command sets the default time-out value to 10 seconds for the system:
SQL> alter system set ddl_lock_timeout=10 scope=both;
Renaming a Table
There are a couple of reasons for renaming a table:
This example renames a table, from INV to INV_OLD:
SQL> rename inv to inv_old;
If successful, you should see this message:
Table renamed.
Use the ALTER TABLE ... ADD statement to add a column to a table. This example adds a column to the INV table:
SQL> alter table inv add(inv_count number);
If successful, you should see this message:
Table altered.
Occasionally, you need to alter a column to adjust its size or change its data type. Use the ALTER TABLE ... MODIFY statement to adjust the size of a column. This example changes the size of a column to 256 characters:
SQL> alter table inv modify inv_desc varchar2(256 char);
If you decrease the size of a column, first ensure that no values exist that are greater than the decreased size value:
SQL> select max(length(<column_name>)) from <table_name>;
When you change a column to NOT NULL, there must be a valid value for each column. First, verify that there are no NULL values:
SQL> select <column_name> from <table_name> where <column_name> is null;
If any rows have a NULL value for the column you’re modifying to NOT NULL, then you must first update the column to contain a value. Here is an example of modifying a column to NOT NULL:
SQL> alter table inv modify(inv_desc not null);
You can also alter the column to have a default value. The default value is used any time a record is inserted into the table, but no value is provided for a column:
SQL> alter table inv modify(inv_desc default 'No Desc'),
If you want to remove the default value of a column, then set it to NULL:
SQL> alter table inv modify(inv_desc default NULL);
Sometimes, you need to change a table’s data type; for example, a column that was originally incorrectly defined as a VARCHAR2 needs to be changed to a NUMBER. Before you change a column’s data type, first verify that all values for an existing column are valid numeric values. Here is a simple PL/SQL script to do this:
create or replace function isnum(v_in varchar2)
return varchar is
val_err exception;
pragma exception_init(val_err, -6502); -- char to num conv. error
scrub_num number;
begin
scrub_num := to_number(v_in);
return 'Y';
exception when val_err then
return 'N';
end;
/
You can use the ISNUM function to detect whether data in a column are numeric. The function defines a PL/SQL pragma exception for the ORA-06502 character-to-number conversion error. When this error is encountered, the exception handler captures it and returns an N. If the value passed in to the ISNUM function is a number, then a Y is returned. If the value can’t be converted to a number, then an N is returned. Here is a simple example illustrating the prior concepts:
SQL> create table stage(hold_col varchar2(30));
SQL> insert into stage values(1);
SQL> insert into stage values('x'),
SQL> select hold_col from stage where isnum(hold_col)='N';
HOLD_COL
------------------------------
X
Similarly, when you modify a character column to a DATE or TIMESTAMP data type, it’s prudent to check first to see whether the data can be successfully converted. Here is a function that does that:
create or replace function isdate(p_in varchar2, f_in varchar2)
return varchar is
scrub_dt date;
begin
scrub_dt := to_date(p_in, f_in);
return 'Y';
exception when others then
return 'N';
end;
/
When you call the ISDATE function, you need to pass it a valid date-format mask, such as YYYYMMDD. Here is a simple example to demonstrate the prior concept:
SQL> create table stage2 (hold_col varchar2(30));
SQL> insert into stage2 values('20130103'),
SQL> insert into stage2 values('03-JAN-13'),
SQL> select hold_col from stage2 where isdate(hold_col,'YYYYMMDD')='N';
HOLD_COL
------------------------------
03-JAN-13
Renaming a Column
There are a couple of reasons to rename a column:
Use the ALTER TABLE ... RENAME statement to rename a column:
SQL> alter table inv rename column inv_count to inv_amt;
Dropping a Column
Tables sometimes end up having columns that are never used. This may be because the initial requirements changed or were inaccurate. If you have a table that contains an unused column, you should consider dropping it. If you leave an unused column in a table, you may run into issues with future DBAs’ not knowing what the column is used for, and the column can potentially consume space unnecessarily.
Before you drop a column, I recommend that you first rename it. Doing so gives you an opportunity to determine whether any users or applications are using the column. After you’re confident the column isn’t being used, first make a backup of the table, using Data Pump export, and then drop the column. These strategies provide you with options if you drop a column and then subsequently realize that it’s needed.
To drop a column, use the ALTER TABLE ... DROP statement:
SQL> alter table inv drop (inv_name);
Be aware that the DROP operation may take some time if the table from which you’re removing the column contains a large amount of data. This time lag may result in the delay of transactions while the table is being modified (because the ALTER TABLE statement locks the table). In scenarios such as this, you may want to first mark the column unused and then later drop it, when you have a maintenance window:
SQL> alter table inv set unused (inv_name);
When you mark a column unused, it no longer shows up in the table description. The SET UNUSED clause doesn’t incur the overhead associated with dropping the column. This technique allows you to quickly stop the column from being seen or used by SQL queries or applications. Any query that attempts to access an unused column receives the following error:
ORA-00904: ... invalid identifier
You can later drop any unused columns when you’ve scheduled some downtime for the application. Use the DROP UNUSED clause to remove any columns marked UNUSED.
SQL> alter table inv drop unused columns;
Sometimes, DBAs do a poor job of documenting what DDL is used when creating or modifying a table. Normally, you should maintain the database DDL code in a source control repository or in some sort of modeling tool. If your shop doesn’t have the DDL source code, there are a few ways that you can manually reproduce DDL:
Back in the olden days, say, version 7 and earlier, DBAs often wrote SQL that queried the data dictionary in an attempt to extract the DDL required to recreate objects. Although this method was better than nothing, it was often prone to errors because the SQL didn’t account for every object creation feature.
The exp and imp utilities are useful for generating DDL. The basic idea is that you export the object in question and then use the imp utility with the SCRIPT or SHOW option to display the DDL. This is a good method, but you often have to edit the output of the imp utility manually to produce the desired DDL.
The Data Pump utility is an excellent method for generating the DDL used to create database objects. Using Data Pump to generate DDL is covered in detail in Chapter 13.
The GET_DDL function of the DBMS_METADATA package is usually the quickest way to display the DDL required to create an object. This example shows how to generate the DDL for a table named INV:
SQL> set long 10000
SQL> select dbms_metadata.get_ddl('TABLE','INV') from dual;
Here is some sample output:
DBMS_METADATA.GET_DDL('TABLE','INV')
-------------------------------------
CREATE TABLE "MV_MAINT"."INV"
( "INV_ID" NUMBER,
"INV_DESC" VARCHAR2(30 CHAR),
"INV_COUNT" NUMBER
) SEGMENT CREATION DEFERRED
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
TABLESPACE "USERS"
The following SQL statement displays all the DDL for the tables in a schema:
select
dbms_metadata.get_ddl('TABLE',table_name)
from user_tables;
If you want to display the DDL for a table owned by another user, add the SCHEMA parameter to the GET_DDL procedure:
select
dbms_metadata.get_ddl(object_type=>'TABLE', name=>'INV', schema=>'INV_APP')
from dual;
Note You can display the DDL for almost any database object type, such as INDEX, FUNCTION, ROLE, PACKAGE, MATERIALIZED VIEW, PROFILE, CONSTRAINT, SEQUENCE, and SYNONYM.
If you want to remove an object, such as a table, from a user, use the DROP TABLE statement. This example drops a table named INV:
SQL> drop table inv;
You should see the following confirmation:
Table dropped.
If you attempt to drop a parent table that has a primary key or unique keys referenced as a foreign key in a child table, you see an error such as
ORA-02449: unique/primary keys in table referenced by foreign keys
You need to either drop the referenced foreign key constraint(s) or use the CASCADE CONSTRAINTS option when dropping the parent table:
SQL> drop table inv cascade constraints;
You must be the owner of the table or have the DROP ANY TABLE system privilege to drop a table. If you have the DROP ANY TABLE privilege, you can drop a table in a different schema by prepending the schema name to the table name:
SQL> drop table inv_mgmt.inv;
If you don’t prepend the table name to a user name, Oracle assumes you’re dropping a table in your current schema.
Tip If you’re using Oracle Database 10g or higher, keep in mind that you can flash back a table to before drop for an accidentally dropped table.
Undropping a Table
Suppose you accidentally drop a table, and you want to restore it. First, verify that the table you want to restore is in the recycle bin:
SQL> show recyclebin;
Here is some sample output:
ORIGINAL NAME RECYCLEBIN NAME OBJECT TYPE DROP TIME
---------------- ------------------------------ ------------ -------------------
INV BIN$0F27WtJGbXngQ4TQTwq5Hw==$0 TABLE 2012-12-08:12:56:45
Next, use the FLASHBACK TABLE...TO BEFORE DROP statement to recover the dropped table:
SQL> flashback table inv to before drop;
Note You can’t use the FLASHBACK TABLE...TO BEFORE DROP statement for a table created in the SYSTEM tablespace.
In Oracle Database 10g and higher, when you issue a DROP TABLE statement (without PURGE), the table is actually renamed (to a name that starts with BIN$) and placed in the recycle bin. The recycle bin is a mechanism that allows you to view some of the metadata associated with a dropped object. You can view complete metadata regarding renamed objects by querying DBA_SEGMENTS:
select
owner
,segment_name
,segment_type
,tablespace_name
from dba_segments
where segment_name like 'BIN$%';
The FLASHBACK TABLE statement simply renames the table its original name. By default, the RECYCLEBIN feature is enabled in Oracle Database 10g and higher. You can change the default by setting the RECYCLEBIN initialization parameter to OFF.
I recommend that you not disable the RECYCLEBIN feature. It’s safer to leave this feature enabled and purge the RECYCLEBIN to remove objects that you want permanently deleted. This means that the space associated with a dropped table isn’t released until you purge your RECYCLEBIN. If you want to purge the entire contents of the currently connected user’s recycle bin, use the PURGE RECYCLEBIN statement:
SQL> purge recyclebin;
If you want to purge the recycle bin for all users in the database, then do the following, as a DBA-privileged user:
SQL> purge dba_recyclebin;
If you want to bypass the RECYCLEBIN feature and permanently drop a table, use the PURGE option of the DROP TABLE statement:
SQL> drop table inv purge;
You can’t use the FLASHBACK TABLE statement to retrieve a table dropped with the PURGE option. All space used by the table is released, and any associated indexes and triggers are also dropped.
You can use either the DELETE statement or the TRUNCATE statement to remove records from a table. You need to be aware of some important differences between these two approaches. Table 7-3 summarizes the attributes of the DELETE and TRUNCATE statements.
Table 7-3. Features of DELETE and TRUNCATE
DELETE | TRUNCATE | |
---|---|---|
Choice of COMMIT or ROLLBACK | YES | NO |
Generates undo | YES | NO |
Resets the high-water mark to 0 | NO | YES |
Affected by referenced and enabled foreign key constraints | NO | YES |
Performs well with large amounts of data | NO | YES |
Using DELETE
One big difference is that the DELETE statement can be either committed or rolled back. Committing a DELETE statement makes the changes permanent:
SQL> delete from inv;
SQL> commit;
If you issue a ROLLBACK statement instead of COMMIT, the table contains data as they were before the DELETE was issued.
Using TRUNCATE
TRUNCATE is a DDL statement. This means that Oracle automatically commits the statement (and the current transaction) after it runs, so there is no way to roll back a TRUNCATE statement. If you need the option of choosing to roll back (instead of committing) when removing data, then you should use the DELETE statement. However, the DELETE statement has the disadvantage of generating a great deal of undo and redo information. Thus, for large tables, a TRUNCATE statement is usually the most efficient way to remove data.
This example uses a TRUNCATE statement to remove all data from the COMPUTER_SYSTEMS table:
SQL> truncate table computer_systems;
By default, Oracle deallocates all space used for the table, except the space defined by the MINEXTENTS table-storage parameter. If you don’t want the TRUNCATE statement to deallocate the extents, use the REUSE STORAGE parameter:
SQL> truncate table computer_systems reuse storage;
The TRUNCATE statement sets the high-water mark of a table back to 0. When you use a DELETE statement to remove data from a table, the high-water mark doesn’t change. One advantage of using a TRUNCATE statement and resetting the high-water mark is that full table scans only search for rows in blocks below the high-water mark. This can have significant performance implications.
You can’t truncate a table that has a primary key defined that is referenced by an enabled foreign key constraint in a child table—even if the child table contains zero rows. Oracle prevents you from doing this because in a multiuser system, there is the possibility that another session can populate the child table with rows in between the time you truncate the child table and the time you subsequently truncate the parent table. In this scenario, you must temporarily disable the referenced foreign key constraints, issue the TRUNCATE statement, and then reenable the constraints.
Because a TRUNCATE statement is DDL, you can’t truncate two separate tables as one transaction. Compare this behavior with that of DELETE. Oracle does allow you to use the DELETE statement to remove rows from a parent table while the constraints are enabled that reference a child table. This is because DELETE generates undo, is read consistent, and can be rolled back.
Note Another way to remove data from a table is to drop and recreate the table. However, this means that you also have to recreate any indexes, constraints, grants, and triggers that belong to the table. Additionally, when you drop a table, it’s unavailable until you recreate it and reissue any required grants. Usually, dropping and recreating a table are acceptable only in a development or test environment.
Viewing and Adjusting the High-Water Mark
Oracle defines the high-water mark of a table as the boundary between used and unused space in a segment. When you create a table, Oracle allocates a number of extents to the table, defined by the MINEXTENTS table-storage parameter. Each extent contains a number of blocks. Before data are inserted into the table, none of the blocks have been used, and the high-water mark is 0.
As data are inserted into a table, and extents are allocated, the high-water mark boundary is raised. A DELETE statement doesn’t reset the high-water mark.
You need to be aware of a couple of performance-related issues regarding the high-water mark
Oracle sometimes needs to scan every block of a table (under the high-water mark) when performing a query. This is known as a full-table scan. If a significant amount of data have been deleted from a table, a full-table scan can take a long time to complete, even for a table with zero rows.
Also, when doing direct-path loads, Oracle inserts data above the high-water mark line. Potentially, you can end up with a large amount of unused space in a table that is regularly deleted from and that is also loaded via a direct-path mechanism.
There are several methods for detecting space below the high-water mark:
The autotrace tool offers a simple method for detecting high-water mark issues. Autotrace is advantageous because it’s easy to use, and the output is simple to interpret.
You can use the DBMS_SPACE package to determine the high-water mark of objects created in tablespaces that use autospace segment management. The DBMS_SPACE package allows you to check for high-water mark problems programmatically. The downside to this approach is that the output is somewhat cryptic and sometimes difficult to derive concrete answers from.
Selecting from DBA/ALL/USER_EXTENTS provides you with information such as the number of extents and bytes consumed. This is a quick and easy way to detect high-water mark issues.
Tracing to Detect Space Below the High-Water Mark
You can run this simple test to detect whether you have an issue with unused space below the high-water mark:
If the number of rows processed is low, but the number of logical I/Os is high, you may have an issue with the number of free blocks below the high-water mark. Here is a simple example to illustrate this technique:
SQL> set autotrace trace statistics
The next query generates a full-table scan on the INV table:
SQL> select * from inv;
Here is a snippet of the output from AUTOTRACE:
no rows selected
Statistics
----------------------------------------------------------
4 recursive calls
0 db block gets
7371 consistent gets
2311 physical reads
The number of rows returned is zero, yet there are 7,371consistent gets (memory accesses) and 2,311 physical reads from disk, indicating free space beneath the high-water mark.
Next, truncate the table, and run the query again:
SQL> truncate table inv;
SQL> select * from inv;
Here is a partial listing from the output of AUTOTRACE:
no rows selected
Statistics
----------------------------------------------------------
6 recursive calls
0 db block gets
12 consistent gets
0 physical reads
Note that the number of memory accesses and physical reads are now quite small.
Using DBMS_SPACE to Detect Space Below the High-Water Mark
You can use the DBMS_SPACE package to detect free blocks beneath the high-water mark. Here is an anonymous block of PL/SQL that you can call from SQL*Plus:
set serverout on size 1000000
declare
p_fs1_bytes number;
p_fs2_bytes number;
p_fs3_bytes number;
p_fs4_bytes number;
p_fs1_blocks number;
p_fs2_blocks number;
p_fs3_blocks number;
p_fs4_blocks number;
p_full_bytes number;
p_full_blocks number;
p_unformatted_bytes number;
p_unformatted_blocks number;
begin
dbms_space.space_usage(
segment_owner => user,
segment_name => 'INV',
segment_type => 'TABLE',
fs1_bytes => p_fs1_bytes,
fs1_blocks => p_fs1_blocks,
fs2_bytes => p_fs2_bytes,
fs2_blocks => p_fs2_blocks,
fs3_bytes => p_fs3_bytes,
fs3_blocks => p_fs3_blocks,
fs4_bytes => p_fs4_bytes,
fs4_blocks => p_fs4_blocks,
full_bytes => p_full_bytes,
full_blocks => p_full_blocks,
unformatted_blocks => p_unformatted_blocks,
unformatted_bytes => p_unformatted_bytes
);
dbms_output.put_line('FS1: blocks = '||p_fs1_blocks);
dbms_output.put_line('FS2: blocks = '||p_fs2_blocks);
dbms_output.put_line('FS3: blocks = '||p_fs3_blocks);
dbms_output.put_line('FS4: blocks = '||p_fs4_blocks);
dbms_output.put_line('Full blocks = '||p_full_blocks);
end;
/
In this scenario, you want to check the INV table for free space below the high-water mark. Here is the output of the previous PL/SQL:
FS1: blocks = 0
FS2: blocks = 0
FS3: blocks = 0
FS4: blocks = 3646
Full blocks = 0
In the prior output the FS1 parameter shows that 0 blocks have 0 to 25 percent free space. The FS2 parameter shows that 0 blocks have 25 to 50 percent free space. The FS3 parameter shows that 0 blocks have 50 to 75 percent free space. The FS4 parameter shows there are 3,646 blocks with 75 to 100 percent free space. Finally, there are 0 full blocks. Because there are no full blocks, and a large number of blocks are mostly empty, you can see that free space exists below the high-water mark.
Selecting from Data Dictionary Extents View
You can also detect tables with high-water mark issues by selecting from DBA/ALL/USER_EXTENTS views. If a table has a large number of extents allocated to it, but has zero rows, that’s an indication that an extensive amount of data have been deleted from the table; for example,
SQL> select count(*) from user_extents where segment_name='INV';
COUNT(*)
----------
44
Now, inspect the number of rows in the table:
SQL> select count(*) from inv;
COUNT(*)
----------
0
The prior table most likely has had data inserted into it, which resulted in extents’ being allocated. And, subsequently, data were deleted, and the extents remained.
How can you reduce a table’s high-water mark? You can use several techniques to set the high-water mark back to0:
Using the TRUNCATE statement was discussed earlier in this chapter (see the section “Using TRUNCATE”). Shrinking a table and moving a table are discussed in the following sections.
Shrinking a Table
To readjust the high-water mark, you must enable row movement for the table and then use the ALTER TABLE...SHRINK SPACE statement. The tablespace in which the table is created must have been built with automatic segment space management enabled. You can determine the tablespace segment space management type via this query:
SQL> select tablespace_name, segment_space_management from dba_tablespaces;
The SEGMENT_SPACE_MANAGEMENT value must be AUTO for the tablespace in which the table is created. Next, you need to enable row movement for the table to be shrunk. This example enables row movement for the INV table:
SQL> alter table inv enable row movement;
Now, you can shrink the space used by the table:
SQL> alter table inv shrink space;
You can also shrink the space associated with any index segments via the CASCADE clause:
SQL> alter table inv shrink space cascade;
Moving a Table
Moving a table means either rebuilding the table in its current tablespace or building it in a different tablespace. You may want to move a table because its current tablespace has disk space storage issues or because you want to lower the table’s high-water mark.
Use the ALTER TABLE ... MOVE statement to move a table from one tablespace to another. This example moves the INV table to the USERS tablespace:
SQL> alter table inv move tablespace users;
You can verify that the table has been moved by querying USER_TABLES:
SQL> select table_name, tablespace_name from user_tables where table_name='INV';
TABLE_NAME TABLESPACE_NAME
-------------------- ------------------------------
INV USERS
Note The ALTER TABLE ... MOVE statement doesn’t allow DML to execute while it’s running.
You can also specify NOLOGGING when you move a table:
SQL> alter table inv move tablespace users nologging;
Moving a table with NOLOGGING eliminates most of the redo that would normally be generated when the table is relocated. The downside to using NOLOGGING is that if a failure occurs immediately after the table is moved (and hence, you don’t have a backup of the table after it’s moved), then you can’t restore the contents of the table. If the data in the table are critical, then don’t use NOLOGGING when moving them.
When you move a table, all its indexes are rendered unusable. This is because a table’s index includes the ROWID as part of the structure. The table ROWID contains information about the physical location. Given that the ROWID of a table changes when the table moves from one tablespace to another (because the table rows are now physically located in different data files), any indexes on the table contain incorrect information. To rebuild the index, use the ALTER INDEX ... REBUILD command.
ORACLE ROWID
Every row in every table has an address. The address of a row is determined from a combination of the following:
Datafile number
Block number
Location of the row within the block
Object number
You can display the address of a row in a table by querying the ROWID pseudocolumn; for example,
SQL> select rowid, emp_id from emp;
Here is some sample output:
ROWID EMP_ID
------------------ ----------
AAAFJAAAFAAAAJfAAA 1
The ROWID pseudocolumn value isn’t physically stored in the database. Oracle calculates its value when you query it. The ROWID contents are displayed as base 64 values that can contain the characters A–Z, a–z, 0–9, +, and /. You can translate the ROWID value into meaningful information via the DMBS_ROWID package. For instance, to display the relative file number in which a row is stored, issue this statement:
SQL> select dbms_rowid.rowid_relative_fno(rowid), emp_id from emp;
Here is some sample output:
DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) EMP_ID
------------------------------------ ----------
5 1
You can use the ROWID value in the SELECT and WHERE clauses of an SQL statement. In most cases, the ROWID uniquely identifies a row. However, it’s possible to have rows in different tables that are stored in the same cluster and that therefore contain rows with the same ROWID.
Use the CREATE GLOBAL TEMPORARY TABLE statement to create a table that stores data only provisionally. You can specify that the temporary table retain the data for a session or until a transaction commits. Use ON COMMIT PRESERVE ROWS to specify that the data be deleted at the end of the user’s session. In this example, the rows will be retained until the user either explicitly deletes the data or terminates the session:
create global temporary table today_regs
on commit preserve rows
as select * from f_registrations
where create_dtt > sysdate - 1;
Specify ON COMMIT DELETE ROWSto indicate that the data should be deleted at the end of the transaction. The following example creates a temporary table named TEMP_OUTPUT and specifies that records should be deleted at the end of each committed transaction:
create global temporary table temp_output(
temp_row varchar2(30))
on commit delete rows;
Note If you don’t specify a commit method for a global temporary table, then the default is ON COMMIT DELETE ROWS.
You can create a temporary table and grant other users access to it. However, a session can only view the data that it inserts into a table. In other words, if two sessions are using the same temporary table, a session can’t select any data inserted into the temporary table by a different session.
A global temporary table is useful for applications that need to briefly store data in a table structure. After you create a temporary table, it exists until you drop it. In other words, the definition of the temporary table is “permanent”—it’s the data that are short-lived (in this sense, the term temporary table can be misleading).
You can view whether a table is temporary by querying the TEMPORARY column of DBA/ALL/USER_TABLES:
SQL> select table_name, temporary from user_tables;
Temporary tables are designated with a Y in the TEMPORARY column. Regular tables contain an N in the TEMPORARY column.
When you create records in a temporary table, space is allocated in your default temporary tablespace. You can verify this by running the following SQL:
SQL> select username, contents, segtype from v$sort_usage;
If you’re working with a large number of rows and need better performance for selectively retrieving rows, you may want to consider creating an index on the appropriate columns in your temporary table:
SQL> create index temp_index on temp_output(temp_row);
Use the DROP TABLE command to drop a temporary table:
SQL> drop table temp_output;
TEMPORARY TABLE REDO
No redo data are generated for changes to blocks of a global temporary table. However, rollback (undo) data are generated for a transaction against a temporary table. Because the rollback data generate redo, some redo data are associated with a transaction for a temporary table. You can verify this by turning on statistics tracing and viewing the redo size as you insert records into a temporary table:
SQL> set autotrace on
Next, insert a few records into the temporary table:
SQL> insert into temp_output values(1);
Here is a snippet of the output (only showing the redo size):
140 redo size
The redo load is less for temporary tables than normal tables because the redo generated is only associated with the rollback (undo) data for a temporary table transaction.
Additionally, starting with Oracle Database 12c, the undo for temporary objects is stored in the temporary tablespace, not the undo tablespace.
Creating an Index-Organized Table
Index-organized tables (IOTs) are efficient objects when the table data are typically accessed through querying on the primary key. Use the ORGANIZATION INDEX clause to create an IOT:
create table prod_sku
(prod_sku_id number,
sku varchar2(256),
create_dtt timestamp(5),
constraint prod_sku_pk primary key(prod_sku_id)
)
organization index
including sku
pctthreshold 30
tablespace inv_data
overflow
tablespace inv_data;
An IOT stores the entire contents of the table’s row in a B-tree index structure. IOTs provide fast access for queries that have exact matches or range searches, or both, on the primary key.
All columns specified, up to and including the column specified in the INCLUDING clause, are stored in the same block as the PROD_SKU_ID primary key column. In other words, the INCLUDING clause specifies the last column to keep in the table segment. Columns listed after the column specified in the INCLUDING clause are stored in the overflow data segment. In the previous example, the CREATE_DTT column is stored in the overflow segment.
PCTTHRESHOLD specifies the percentage of space reserved in the index block for the IOT row. This value can be from 1 to 50 and defaults to 50 if no value is specified. There must be enough space in the index block to store the primary key.
The OVERFLOW clause details which tablespace should be used to store overflow data segments. Note that DBA/ALL/USER_TABLES includes an entry for the table name used when creating an IOT. Additionally, DBA/ALL/USER_INDEXES contains a record with the name of the primary key constraint specified. The INDEX_TYPE column contains a value of IOT - TOP for IOTs:
SQL> select index_name,table_name,index_type from user_indexes;
Managing Constraints
The next several sections in this chapter deal with constraints. Constraints provide a mechanism for ensuring that data conform to certain business rules. You must be aware of what types of constraints are available and when it’s appropriate to use them. Oracle offers several types of constraints:
Implementing and managing these constraints are discussed in the following sections.
Creating Primary Key Constraints
When you implement a database, most tables you create require a primary key constraint to guarantee that every record in the table can be uniquely identified. There are multiple techniques for adding a primary key constraint to a table. The first example creates the primary key inline with the column definition:
create table dept(
dept_id number primary key
,dept_desc varchar2(30));
If you select the CONSTRAINT_NAME from USER_CONSTRAINTS, note that Oracle generates a cryptic name for the constraint (such as SYS_C003682). Use the following syntax to explicitly give a name to a primary key constraint:
create table dept(
dept_id number constraint dept_pk primary key using index tablespace users,
dept_desc varchar2(30));
Note When you create a primary key constraint, Oracle also creates a unique index with the same name as the constraint. You can control which tablespace the unique index is placed in via the USING INDEX TABLESPACE clause.
You can also specify the primary key constraint definition after the columns have been defined. The advantage of doing this is that you can define the constraint on multiple columns. The next example creates the primary key when the table is created, but not inline with the column definition:
create table dept(
dept_id number,
dept_desc varchar2(30),
constraint dept_pk primary key (dept_id)
using index tablespace users);
If the table has already been created, and you want to add a primary key constraint, use the ALTER TABLE statement. This example places a primary key constraint on the DEPT_ID column of the DEPT table:
alter table dept
add constraint dept_pk primary key (dept_id)
using index tablespace users;
When a primary key constraint is enabled, Oracle automatically creates a unique index associated with the primary key constraint. Some DBAs prefer to first create a nonunique index on the primary key column and then define the primary key constraint:
SQL> create index dept_pk on dept(dept_id) tablespace users;
SQL> alter table dept add constraint dept_pk primary key (dept_id);
The advantage of this approach is that you can drop or disable the primary key constraint independently of the index. When you’re working with large data sets, you may want that sort of flexibility. If you don’t create the index before creating the primary key constraint, then whenever you drop or disable the primary key constraint, the index is automatically dropped.
Confused about which method to use to create a primary key? All the methods are valid and have their merits. Table 7-4 summarizes the primary key and unique key constraint creation methods. I’ve used all these methods to create primary key constraints. Usually, I use the ALTER TABLE statement, which adds the constraint after the table has been created.
Table 7-4. Primary Key and Unique Key Constraint Creation Methods
Constraint Creation Method | Advantages | Disadvantages |
---|---|---|
Inline, no name | Very simple | Oracle-generated name makes troubleshooting harder; less control over storage attributes; only applied to a single column |
Inline, with name | Simple; user-defined name makes troubleshooting easier | Requires more thought than inline without name |
Inline, with name and tablespace definition | User-defined name and tablespace; makes troubleshooting easier | Less simple |
After column definition (out of line) | User-defined name and tablespace; can operate on multiple columns | Less simple |
ALTER TABLE add just constraint | Lets you manage constraints in statements (and files) separate from table creation scripts; can operate on multiple columns | More complicated |
CREATE INDEX, ALTER TABLE add constraint | Separates the index and constraint, so you can drop/disable constraints without affecting the index; can operate on multiple columns | Most complicated: more to maintain, more moving parts |
In addition to creating a primary key constraint, you should create unique constraints on any combinations of columns that should always be unique within a table. For example, for the primary key for a table, it’s common to use a numeric key (sometimes called a surrogate key) that is populated via a sequence. Besides the surrogate primary key, sometimes users have a column (or columns) that the business uses to uniquely identify a record (also called a logical key). Using both a surrogate key and a logical key
A unique key guarantees uniqueness on the defined column(s) within a table. There are some subtle differences between primary key and unique key constraints. For example, you can define only one primary key per table, but there can be several unique keys. Also, a primary key doesn’t allow a NULL value in any of its columns, whereas a unique key allows NULL values.
As with the primary key constraint, you can use several methods to create a unique column constraint. This method uses the UNIQUE keyword inline with the column:
create table dept(
dept_id number
,dept_desc varchar2(30) unique);
If you want to explicitly name the constraint, use the CONSTRAINT keyword:
create table dept(
dept_id number
,dept_desc varchar2(30) constraint dept_desc_uk1 unique);
As with primary keys, Oracle automatically creates an index associated with the unique key constraint. You can specify inline the tablespace information to be used for the associated unique index:
create table dept(
dept_id number
,dept_desc varchar2(30) constraint dept_desc_uk1
unique using index tablespace users);
You can also alter a table to include a unique constraint:
alter table dept
add constraint dept_desc_uk1 unique (dept_desc)
using index tablespace users;
And you can create an index on the columns of interest before you define a unique key constraint:
SQL> create index dept_desc_uk1 on dept(dept_desc) tablespace users;
SQL> alter table dept add constraint dept_desc_uk1 unique(dept_desc);
This can be helpful when you’re working with large data sets, and you want to be able to disable or drop the unique constraint without dropping the associated index.
Tip You can also enforce a unique key constraint with a unique index. See Chapter 8 for details on using unique indexes to enforce unique constraints.
Creating Foreign Key Constraints
Foreign key constraints are used to ensure that a column value is contained within a defined list of values. Using a foreign key constraint is an efficient way of enforcing that data be a predefined value before an insert or update is allowed. This technique works well for the following scenarios:
For example, suppose the EMP table is created with a DEPT_ID column. To ensure that each employee is assigned a valid department, you can create a foreign key constraint that enforces the rule that each DEPT_ID in the EMP table must exist in the DEPT table.
Tip If the condition you want to check for consists of a small list that doesn’t change very often, consider using a check constraint instead of a foreign key constraint. For instance, if you have a column that will always be defined as containing either a 0 or a 1, a check constraint is an efficient solution.
For reference, here’s how the parent table DEPT table was created for these examples:
create table dept(
dept_id number primary key,
dept_desc varchar2(30));
A foreign key must reference a column in the parent table that has a primary key or a unique key defined on it. DEPT is the parent table and has a primary key defined on DEPT_ID.
You can use several methods to create a foreign key constraint. The following example creates a foreign key constraint on the DEPT_ID column in the EMP table:
create table emp(
emp_id number,
name varchar2(30),
dept_id constraint emp_dept_fk references dept(dept_id));
Note that the DEPT_ID data type isn’t explicitly defined. The foreign key constraint derives the data type from the referenced DEPT_ID column of the DEPT table. You can also explicitly specify the data type when you define a column (regardless of the foreign key definition):
create table emp(
emp_id number,
name varchar2(30),
dept_id number constraint emp_dept_fk references dept(dept_id));
You can also specify the foreign key definition out of line from the column definition in the CREATE TABLE statement:
create table emp(
emp_id number,
name varchar2(30),
dept_id number,
constraint emp_dept_fk foreign key (dept_id) references dept(dept_id)
);
And, you can alter an existing table to add a foreign key constraint:
alter table emp
add constraint emp_dept_fk foreign key (dept_id)
references dept(dept_id);
Note Unlike with primary key and unique key constraints, Oracle doesn’t automatically add an index to foreign key columns; you must explicitly create indexes on them. See Chapter 8 for a discussion on why it’s important to create indexes on foreign key columns and how to detect foreign key columns that don’t have associated indexes.
Checking for Specific Data Conditions
A check constraint works well for lookups when you have a short list of fairly static values, such as a column that can be either Y or N. In this situation the list of values most likely won’t change, and no information needs to be stored other than Y or N, so a check constraint is the appropriate solution. If you have a long list of values that needs to be periodically updated, then a table and a foreign key constraint are a better solution.
Also, a check constraint works well for a business rule that must always be enforced and that can be written with a simple SQL expression. If you have sophisticated business logic that must be validated, then the application code is more appropriate.
You can define a check constraint when you create a table. The following enforces the ST_FLG column to contain either a 0 or 1:
create table emp(
emp_id number,
emp_name varchar2(30),
st_flg number(1) CHECK (st_flg in (0,1))
);
A slightly better method is to give the check constraint a name:
create table emp(
emp_id number,
emp_name varchar2(30),
st_flg number(1) constraint st_flg_chk CHECK (st_flg in (0,1))
);
A more descriptive way to name the constraint is to embed information in the constraint name that describes the condition that was violated; for example,
create table emp(
emp_id number,
emp_name varchar2(30),
st_flg number(1) constraint "st_flg must be 0 or 1" check (st_flg in (0,1))
);
You can also alter an existing column to include a constraint. The column must not contain any values that violate the constraint being enabled:
SQL> alter table emp add constraint
"st_flg must be 0 or 1" check (st_flg in (0,1));
Note The check constraint must evaluate to a true or unknown (NULL) value in the row being inserted or updated. You can’t use subqueries or sequences in a check constraint. Also, you can’t reference the SQL functions UID, USER, SYSDATE, or USERENV or the pseudocolumns LEVEL or ROWNUM.
Enforcing Not Null Conditions
Another common condition to check for is whether a column is null; you use the NOT NULL constraint to do this. The NOT NULL constraint can be defined in several ways. The simplest technique is shown here:
create table emp(
emp_id number,
emp_name varchar2(30) not null);
A slightly better approach is to give the NOT NULL constraint a name that makes sense to you:
create table emp(
emp_id number,
emp_name varchar2(30) constraint emp_name_nn not null);
Use the ALTER TABLE command if you need to modify a column for an existing table. For the following command to work, there must not be any NULL values in the column being defined as NOT NULL:
SQL> alter table emp modify(emp_name not null);
Note If there are currently NULL values in a column that is being defined as NOT NULL, you must first update the table so that the column has a value in every row.
One nice feature of Oracle is that you can disable and enable constraints without dropping and recreating them. This means that you avoid having to know the DDL statements that would be required to recreate the dropped constraints.
Occasionally, you need to disable constraints. For example, you may be trying to truncate a table but receive the following error message:
ORA-02266: unique/primary keys in table referenced by enabled foreign keys
Oracle doesn’t allow a truncate operation on a parent table with a primary key that is referenced by an enabled foreign key in a child table. If you need to truncate a parent table, you first have to disable all the enabled foreign key constraints that reference the parent table’s primary key. Run this query to determine the names of the constraints that need to be disabled:
col primary_key_table form a18
col primary_key_constraint form a18
col fk_child_table form a18
col fk_child_table_constraint form a18
--
select
b.table_name primary_key_table
,b.constraint_name primary_key_constraint
,a.table_name fk_child_table
,a.constraint_name fk_child_table_constraint
from dba_constraints a
,dba_constraints b
where a.r_constraint_name = b.constraint_name
and a.r_owner = b.owner
and a.constraint_type = 'R'
and b.owner = upper('&table_owner')
and b.table_name = upper('&pk_table_name'),
For this example, there is only one foreign key dependency:
PRIMARY_KEY_TAB PRIMARY_KEY_CON FK_CHILD_TABLE FK_CHILD_TABLE_
--------------- --------------- --------------- ---------------
DEPT DEPT_PK EMP EMP_DEPT_FK
Use the ALTER TABLE statement to disable constraints on a table. In this case, there is only one foreign key to disable:
SQL> alter table emp disable constraint emp_dept_fk;
You can now truncate the parent table:
SQL> truncate table dept;
Don’t forget to reenable the foreign key constraints after the truncate operation has completed, like this:
SQL> alter table emp enable constraint emp_dept_fk;
You can disable a primary key and all dependent foreign key constraints with the CASCADE option of the DISABLE clause. For example, the next line of code disables all foreign key constraints related to the primary key constraint:
SQL> alter table dept disable constraint dept_pk cascade;
This statement doesn’t cascade through all levels of dependencies; it only disables the foreign key constraints directly dependent on DEPT_PK. Also keep in mind that there is no ENABLE...CASCADE statement. To reenable the constraints, you have to query the data dictionary to determine which constraints have been disabled and then reenable them individually.
Sometimes, you run into situations, when loading data, in which it’s convenient to disable all the foreign keys before loading the data (perhaps from a schema-level import, using the imp utility). In these situations the imp utility imports the tables in alphabetical order and doesn’t ensure that child tables are imported before parent tables. You may also want to run several import jobs in parallel to take advantage of parallel hardware. In such scenarios, you can disable the foreign keys, perform the import, and then reenable the foreign keys.
Here is a script that uses SQL to generate SQL to disable all foreign key constraints for a user:
set lines 132 trimsp on head off feed off verify off echo off pagesize 0
spo dis_dyn.sql
select 'alter table ' || a.table_name
|| ' disable constraint ' || a.constraint_name || ';'
from dba_constraints a
,dba_constraints b
where a.r_constraint_name = b.constraint_name
and a.r_owner = b.owner
and a.constraint_type = 'R'
and b.owner = upper('&table_owner'),
spo off;
This script generates a file, named dis_dyn.sql, that contains the SQL statements to disable all the foreign key constraints for a user.
This section contains a few scripts to help you enable constraints that you’ve disabled. Listed next is a script that creates a file with the SQL statements required to reenable any foreign key constraints for tables owned by a specified user:
set lines 132 trimsp on head off feed off verify off echo off pagesize 0
spo enable_dyn.sql
select 'alter table ' || a.table_name
|| ' enable constraint ' || a.constraint_name || ';'
from dba_constraints a
,dba_constraints b
where a.r_constraint_name = b.constraint_name
and a.r_owner = b.owner
and a.constraint_type = 'R'
and b.owner = upper('&table_owner'),
spo off;
When enabling constraints, by default, Oracle checks to ensure that the data don’t violate the constraint definition. If you’re fairly certain that the data integrity is fine and that you don’t need to incur the performance hit by revalidating the constraint, you can use the NOVALIDATE clause when reenabling the constraints. Here is an example:
select 'alter table ' || a.table_name
|| ' modify constraint ' || a.constraint_name || ' enable novalidate;'
from dba_constraints a
,dba_constraints b
where a.r_constraint_name = b.constraint_name
and a.r_owner = b.owner
and a.constraint_type = 'R'
and b.owner = upper('&table_owner'),
The NOVALIDATE clause instructs Oracle not to validate the constraints being enabled, but it does enforce that any new DML activities adhere to the constraint definition.
In multiuser systems the possibility exists that another session has inserted data into the child table while the foreign key constraint was disabled. If that happens, you see the following error when you attempt to reenable the foreign key:
ORA-02298: cannot validate (<owner>.<constraint>) - parent keys not found
In this scenario, you can use the ENABLE NOVALIDATE clause:
SQL> alter table emp enable novalidate constraint emp_dept_fk;
To clean up the rows that violate the constraint, first ensure that you have an EXCEPTIONS table created in your currently connected schema. If you don’t have an EXCEPTIONS table, use this script to create one:
SQL> @?/rdbms/admin/utlexcpt.sql
Next, populate the EXCEPTIONS table with the rows that violate the constraint, using the EXCEPTIONS INTO clause:
SQL> alter table emp modify constraint emp_dept_fk validate
exceptions into exceptions;
This statement still throws the ORA-02298 error as long as there are rows that violate the constraint. The statement also inserts records into the EXCEPTIONS table for any bad rows. You can now use the ROW_ID column of the EXCEPTIONS table to remove any records that violate the constraint.
Here, you see that one row needs to be removed from the EMP table:
SQL> select * from exceptions;
Here is some sample output:
ROW_ID OWNER TABLE_NAME CONSTRAINT
------------------ -------- ---------- --------------------
AAAFKQAABAAAK8JAAB MV_MAINT EMP EMP_DEPT_FK
To remove the offending record, issue a DELETE statement:
SQL> delete from emp where rowid = 'AAAFKQAABAAAK8JAAB';
If the EXCEPTIONS table contains many records, you can run a query such as the following to delete by OWNER and TABLE_NAME:
delete from emp where rowid in
(select row_id
from exceptions
where owner=upper('&owner') and table_name = upper('&table_name'));
You may also run into situations in which you need to disable primary key or unique key constraints, or both. For instance, you may want to perform a large data load and for performance reasons want to disable the primary key and unique key constraints. You don’t want to incur the overhead of having every row checked as it’s inserted.
The same general techniques used for disabling foreign keys are applicable for disabling primary and unique keys. Run this query to display the primary key and unique key constraints for a user:
select
a.table_name
,a.constraint_name
,a.constraint_type
from dba_constraints a
where a.owner = upper('&table_owner')
and a.constraint_type in ('P','U')
order by a.table_name;
When the table name and constraint name are identified, use the ALTER TABLE statement to disable the constraint:
SQL> alter table dept disable constraint dept_pk;
Note Oracle doesn’t let you disable a primary key or unique key constraint that is referenced in an enabled foreign key constraint. You first have to disable the foreign key constraint.
Summary
This chapter focused on basic activities related to creating and maintaining tables. Tables are the containers that store the data within the database. Key table management tasks include modifying, moving, deleting from, shrinking, and dropping. You must also be familiar with how to implement and use special table types, such as temporary, IOT, and read-only.
Oracle also provides various constraints to help you manage the data within tables. Constraints form the bedrock of data integrity. In most cases, each table should include a primary key constraint that ensures that every row is uniquely identifiable. Additionally, any parent–child relationships should be enforced with foreign key constraints. You can use unique constraints to implement business rules that require a column or combination of columns to be unique. Check and NOT NULL constraints ensure that columns contain business-specified data requirements.
After you create tables, the next logical activity is to create indexes where appropriate. Indexes are optional database objects that help improve performance. Index creation and maintenance tasks are covered in the next chapter.