Mike Ault schema documentation scripts

 

Caution - These are extremely powerful Oracle data dictionary scripts.  They should only be used by a certified Oracle DBA, and after a careful review of the Oracle data dictionary script functionality.  These scripts are NOT supported and knowledge of the data dictionary is required to fully utilize these scripts.  USE AT YOUR OWN RISK.

 
 
Introduction
 
Many times the Oracle DBA is hired once the database becomes to much to handle without one, inherits the position or is appointed to it “since they are so good with computers”. In any case, they usually get an undocumented system that follows no conventions and holds many traps for the unwary DBA.
 
In other situations the DBA may find they have the task of recreating the database for which they are responsible. If export and import can be used, this task is fairly easy to accomplish, however, sometimes the DBA will be required to provide DDL (Data Definition Language) for this purpose. The DBA may also wish to document existing procedures, views, constraints and such other structures as they see fit, with human readable output and a minimum of re-editing.
 
In all of the above cases, the Oracle provided methods fall woefully short of the mark in providing the DBA with documentation. It falls on the DBA’s shoulders to develop SQL, PL/SQL and SQLPLUS code to delve into the inner workings of the Oracle Data Dictionary tables and re-generate the required DDL.
 
This presentation will demonstrate techniques to use the Oracle instance to document itself.
 
The Oracle Data Dictionary, an Overview
 
The heart of the whole matter is a collection of C constructs, Oracle tables and Oracle views that are collectively called the Oracle Data Dictionary. At the lowest level are the “hidden” C structures known as the X$ tables. These X$ tables are usually best left alone. Indeed, to even see the contents a DBA has to jump through a few hoops and once they do get to them, they are not well documented and have such logical attributes as “mglwmp” or “tabsrpr”. Needless to say, unless the DBA has several long nights available and a penchant for solving riddles it is suggest that they  leave these to Oracle.
 
The next layer of the Oracle Data Dictionary is the $ tables. These are more human readable cuts of the X$ tables and have such names as COL$ or TAB$. While being a step above the X$ structures, it is still suggested that the DBA only use them when it is really needed. Some of the reports discussed in this paper will use these tables.
 
The third layer of the dictionary is the V$ views and their cousins the V_$ tables (actually, Siamese twins since for nearly all of the V$ views there is a corresponding V_$ view). These are the workhorses of the Data Dictionary and what most of the scripts in this paper deal with.
 
The final layer of the dictionary (for our purposes) is the DBA_ series of views. These views are made from the V$ and $ sets of views and tables and provide a very friendly interface for viewing the insides of your Oracle Data Dictionary. The USER_ and ALL_ views are based on the DBA_ views. Some of the user specific scripts, such as for constraints, uses the USER_ subset of views, however, the script can be made more general by adding reference to the “OWNER” attribute and changing USER_ to DBA_ if the DBA desires.
 
Documenting or Rebuilding?
 
This paper will use the terms documenting and rebuilding interchangeably. A good set of build scripts will provide excellent documentation for an instance. Essentially there are two types of documentation, “hard” documentation such as DDL that an experienced database developer can understand, and “soft” documentation such as generalized reports showing structures and relations that anyone with a smattering of database experience can use. The paper will deal more with the “hard” type of documentation, that is, the scripts to rebuild the database, where applicable, reports that provide “soft” documentation will be mentioned, and included with the paper.
 
The Database
 
Databases contain the following items:
 

 
Hard Objects:
 
Tablespaces and their associated Datafiles
Control Files
Redo Logs
Rollback Segments
Tables
Indexes
Database Initialization file
Clusters
 
 
 
Stored Objects:
 
View definitions
Constraints
Procedures
Functions
Packages
Package Bodies
Triggers
User definitions
Roles
Grants
Database Links
Snapshots and Snapshot Logs

 
 
 
In the following pages this paper will cover how to generate the required DDL to recreate or document each of the above database objects using SQL, PLSQL and SQLPLUS. This paper will also discuss creation of scripts to allow dropping a set of objects (such as constraints) and recreating them.
 
The Control File
 
To begin at the beginning, we should first document the Control file since this is the only location where the information for MAXDATAFILES, MAXLOGFILES MAXMEMBERS and other instance specific data can be found. This information will be added by hand to the Database rebuild script covered later in the paper.
 
Having a script around that rebuilds the control file is a very good idea. In the case where the Oracle instance was built by people with very little understanding of how Oracle builds default instances (poorly) you will usually get a very dysfunctional database from a vanilla build. The DBA will find their MAXDATAFILES will be set to 20 or maybe 32 and that other hard database limits are probably set too low as well. Once the DBA generates a script to rebuild their control files, they can change these hard database limits without rebuilding the database by simply rebuilding the control files. In other situations, a disk crash can wipe out the control file (hopefully there is more than one, but maybe a DBA didn’t create the database?) and leave the database unable to even startup (actually there is a way, but it is beyond the scope of this paper.) With a rebuild script on hand, it makes these situations easier to handle.
 
A DBA can always backup the control file with the command:
 
ALTER DATABASE BACKUP CONTROLFILE to ‘filename’ REUSE;
 
However, the above command only makes a machine readable copy, a good idea for when changes are made to structures, A DBA should usually backup the control file with each set of backups. Of course, this doesn’t provide  documentation of the control file.
 
Since there are no tables which document the control files (other than v$parameter which only documents their location) the DBA must depend on a system command to provide them with the require script:
 
ALTER DATABASE
            BACKUP CONTROLFILE TO TRACE NORESTLOGS;
 
The output of this script looks like so:
 
 Dump file H:\ORAWIN\RDBMS71\trace\ORA14071.TRC
Sat Mar 30 10:05:53 1996
ORACLE V7.1.4.1.0 - Production.
vsnsta=0
vsnsql=a vsnxtr=3
MS-WINDOWS Version 3.10
Sat Mar 30 10:05:52 1996
Sat Mar 30 10:05:53 1996
 
*** SESSION ID:(5.3)
# The following commands will create a new control file and use it
# to open the database.
# No data other than log history will be lost. Additional logs may
# be required for media recovery of offline data files. Use this
# only if the current version of all online logs are available.
STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "ORACLE" NORESETLOGS NOARCHIVELOG
    MAXLOGFILES 32
    MAXLOGMEMBERS 2
    MAXDATAFILES 32
    MAXINSTANCES 16
    MAXLOGHISTORY 1600
LOGFILE
  GROUP 1 'H:\ORAWIN\DBS\wdblog1.ora'  SIZE 500K,
  GROUP 2 'H:\ORAWIN\DBS\wdblog2.ora'  SIZE 500K
DATAFILE
  'H:\ORAWIN\DBS\wdbsys.ora' SIZE 10M,
  'H:\ORAWIN\DBS\wdbuser.ora' SIZE 3M,
  'H:\ORAWIN\DBS\wdbrbs.ora' SIZE 3M,
  'H:\ORAWIN\DBS\wdbtemp.ora' SIZE 2M
;
 
# Recovery is required if any of the datafiles are restored backups,
# or if the last shutdown was not normal or immediate.
RECOVER DATABASE
 
# Database can now be opened normally.
ALTER DATABASE OPEN;
 
As can be seen from a glance at the above script, all the required “hard” database setpoints are now documented. The output from the command is located wherever the system places its system level trace files (check v$parameter on a wildcard name search for ‘%dump%’).
 
Documenting the Database Initialization file
 
The initialization file, commonly called INIT.ORA or initSID.ora where the SID is the database instance name, is another key file for the database documentation set. Sadly, the initialization file is often overlooked, in fact many “DBAs” don’t even know how to find it. All of the values used in the initialization process are stored in the V$PARAMETER table. The following script will provide a complete INIT.ORA
file for a system:
 
REM
REM NAME                  : init_ora_rct.sql
REM FUNCTION            : Recreate the instance init.ora file
REM USE                                 : GENERAL
REM Limitations            : None
REM
SET NEWPAGE 0 VERIFY OFF
SET ECHO OFF feedback off termout off PAGES 300 lines 80 heading off
column name  format a41
column value format a37
column dbname new_value db noprint
select value dbname from v$parameter where name = ‘db_name’;
DEFINE OUTPUT = 'rep_out\&db\init.ora'
SPOOL &OUTPUT
SELECT '# Init.ora file from v$parameter' name, null value from dual
union
select '# generated on:'||sysdate name, null value from dual
union
select '# script by MRA 11/7/95 TRECOM' name, null value from dual
union
select '#' name, null value from dual
UNION
SELECT name||’            =‘ name,value FROM V$PARAMETER
WHERE value is not null;
SPOOL OFF
CLEAR COLUMNS
set termout on
pause Press enter to continue
exit
 
 
An example output from this report follows:
 
#                                                                              
# Init.ora file from v$parameter                                               
# generated on:06-MAR-96                                                       
# script by MRA 11/7/95 TRECOM                                                
audit_file_dest   =                           ?/rdbms/audit                        
audit_trail          =                               NONE                                 
background_core_dump  =                      full                                 
background_dump_dest  =        /bto/sys/oracle/product/7.2.2/rdbms/log                                   
blank_trimming  =                            FALSE                                
cache_size_threshold     =                      20                                   
ccf_io_size        =                               65536                                
checkpoint_process       =                        FALSE                                
cleanup_rollback_entries            =                  20                                   
close_cached_open_cursors       =                 FALSE                                
commit_point_strength   =                     1                                    
compatible        =                                7.1.0.0                              
control_files       =                                         /vol2/oracle1/ORCSPCD1/ctrl1ORCSPCD101.ctl,/vol2/oracle2/ORCSPCD1/ctrl2ORCSPCD102.ctl,/vol3/oracle3/ORCSPCD1/ctrl3ORCSPCD103.ctl                  
core_dump_dest            =                    /bto/sys/oracle/product/7.2.2/dbs    
cpu_count         =                                 1                                    
cursor_space_for_time   =                     FALSE                                
.
. (the actual output is 3 pages long, so this is the short version)
.
thread   =                                    0                                    
timed_statistics =                          FALSE                                
transactions      =                              66                                   
transactions_per_rollback_segment         =         16                                   
use_readv          =                                 FALSE                                
user_dump_dest            =              /bto/sys/oracle/product/7.2.2/rdbms/log                                   
 
The DBA will have to edit the output to add guotes and parenthesis, but this is still better than rebuilding the file by hand.
 
Most DBAs will see a number of parameters they weren’t aware existed in their listing from this script. In fact, many of the parameters may not be documented. This script lists all of the parameters available on your platform and uses their default value if the DBA hasn’t supplied a value via the initialization file.
 
The Database Itself
 
Now that the control file settings and the initialization file settings have been documented, it is time to document the database creation script itself. The following script will generate a “bare bones” CREATE DATABASE command. The values from the CREATE CONTROLFILE script for the setting of MAX parameters generated prior to this should be edited into the command script.
 
REM FUNCTION: SCRIPT FOR CREATING DB
REM
REM           This script must be run by a user with the DBA role.
REM
REM           This script is intended to run with Oracle .
REM
REM           Running this script will in turn create a script to
REM           rebuild the database.  This created
REM           script, crt_db.sql,  is run by SQLPLUS
REM
REM           Only preliminary testing of this script was performed. 
REM           Be sure to test it completely before relying on it.
REM
REM M. Ault 3/29/96 TRECOM
REM
 
set verify off;
set termout off;
set feedback off;
set echo off;
set pagesize 0;
 
set termout on
prompt Creating db build script...
set termout off;
create table db_temp
     (lineno NUMBER,  text VARCHAR2(255))
/
 
 
DECLARE
   CURSOR dbf_cursor IS
            select  file_name,bytes  
            from  dba_data_files
            where tablespace_name='SYSTEM';
   CURSOR mem_cursor (grp_num number) IS
            select   member
            from     v$logfile
            where    group#=grp_num
            order by member;
    CURSOR thread_cursor IS
            select thread#, group#
            from v$log
            order by thread#;
 
   grp_member               v$logfile.member%TYPE;
   db_name                    varchar2(8);
   db_string            VARCHAR2(255);
   db_lineno            number := 0;
   thrd                            number;
   grp                             number;
   filename                     dba_data_files.file_name%TYPE;
   sz                              number;
   begin_count                number;
 
   procedure write_out(p_line INTEGER,
                               p_string VARCHAR2) is
   begin
      insert into db_temp (lineno,text)
                 values (db_lineno,db_string);
   end;
 
BEGIN
            db_lineno:=db_lineno+1;
   SELECT 'CREATE DATABASE '||value into db_string 
            FROM v$parameter where name='db_name';
     write_out(db_lineno,db_string);
            db_lineno:=db_lineno+1;
   SELECT 'CONTROLFILE REUSE' into db_string
            FROM dual;
     write_out(db_lineno,db_string);
            db_lineno:=db_lineno+1;
   SELECT 'LOGFILE (' into db_string
            FROM dual;
     write_out(db_lineno,db_string);
commit;
if thread_cursor%ISOPEN then
            close thread_cursor;
            open thread_cursor;
else
            open thread_cursor;
end if;
loop
            fetch thread_cursor into thrd,grp;
            exit when thread_cursor%NOTFOUND;
            db_lineno:=db_lineno+1;
            db_string:= 'THREAD '||thrd||' GROUP '||grp||' (';
            write_out(db_lineno,db_string);
            if mem_cursor%ISOPEN then
                        close mem_cursor;
                        open mem_cursor(grp);
            else
                        OPEN mem_cursor(grp);
            end if;
            db_lineno:=db_lineno+1;
            begin_count:=db_lineno;
            loop
                        fetch mem_cursor into grp_member;
                        exit when mem_cursor%NOTFOUND;
                        if begin_count=db_lineno then
                                    db_string:=''''||grp_member||'''';
                                    write_out(db_lineno,db_string);
                                    db_lineno:=db_lineno+1;
                        else
                                    db_string:=','||'''||grp_member||''';
                                    write_out(db_lineno,db_string);
                                    db_lineno:=db_lineno+1;
                        end if;
            end loop;
            db_lineno:=db_lineno+1;
            db_string:=' )';
            write_out(db_lineno,db_string);
end loop;
db_lineno:=db_lineno+1;
SELECT ')' into db_string from dual;
write_out(db_lineno,db_string);
commit;
if dbf_cursor%ISOPEN then
            close dbf_cursor;
            open dbf_cursor;
else
            open dbf_cursor;
end if;
begin_count:=db_lineno;
loop
            fetch dbf_cursor into filename, sz;
            exit when dbf_cursor%NOTFOUND;
            if begin_count=db_lineno then
                        db_string:='DATAFILE '||''''||filename||''''||' SIZE '||sz||' REUSE';
            else
                        db_string:=','||''''||filename||''''||' SIZE '||sz||' REUSE';
            end if;
            db_lineno:=db_lineno+1;
            write_out(db_lineno,db_string);
end loop;
commit;
SELECT decode(value,'TRUE','ARCHIVELOG','FALSE','NOARCHIVELOG')
            into db_string from v$parameter where name='log_archive_start';
            db_lineno:=db_lineno+1;
            write_out(db_lineno,db_string);
SELECT ';' into db_string from  dual;
            db_lineno:=db_lineno+1;
            write_out(db_lineno,db_string);
CLOSE dbf_cursor;
CLOSE mem_cursor;
CLOSE thread_cursor;
commit;
END;
/
column dbname new_value db noprint
select value dbname from v$parameter where name='db_name';
set heading off pages 0 verify off
set recsep off
spool rep_out\&db\crt_db.sql
col text format a80 word_wrap
select   text
from     db_temp
order by  lineno;
spool off
set feedback on verify on termout on
drop table db_temp;
prompt Press enter to exit
exit
 
The reason this script is so long is that there may be multiple datafiles for the SYSTEM tablespace and there most certainly will be multiple redo logs, redo log groups and possibly redo log threads. This multiplicity of files results in the need for a cursor for each of the possible recursions and a loop-end loop construct to support the selection of the data from the database.
 
It is ironic that all of the above code is used to produce the following output:
 
CREATE DATABASE ORCSPCD1                                                                            
CONTROLFILE REUSE                                                                                  
LOGFILE (                                                                                          
THREAD 1 GROUP 1 (                                                                                 
'/vol2/oracle1/ORCSPCD1/log1ORCSPCD1.dbf'                                                          
)                                                                                                   
THREAD 1 GROUP 2 (                                                                                 
'/vol2/oracle2/ORCSPCD1/log2ORCSPCD1.dbf'                                                          
)                                                                                                   
THREAD 1 GROUP 3 (                                                                                 
'/vol3/oracle3/ORCSPCD1/log3ORCSPCD1.dbf'                                                           
)                                                                                                  
)                                                                                                  
DATAFILE '/vol2/oracle1/ORCSPCD1/systORCSPCD1.dbf' SIZE 41943040 REUSE                             
NOARCHIVELOG                                                                                       
;                                                                                                   
 
The generated script would be longer if there were multiple datafiles in the SYSTEM tablespace and if mirrored redo logs where in use. As was said above, the script should have the MAX set of parameters from the CREATE CONTROLFILE command edited into it between the DATAFILE and NOARCHIVELOG (or ARCHIVELOG) clauses.
 
Soft documentation for database related items would consist of reports on the redo logs, system parameters, and, datafiles for the SYSTEM tablespace. The scripts redo_log.sql, db_thrd.sql, db_parm.sql and datafile.sql provide soft documentation reports of these items. The soft documentation scripts are in appendix A.
 
Documenting Tablespaces
 
Tablespaces and their underlying datafiles should be created immediately after the database itself. Therefore, we will cover the generation of the tablespace DDL next. The following script will create the DDL required to make an exact duplicate of a systems existing tablespace/datafile profile. The DBA may wish to edit the resulting script to combine multiple datafiles into a single large datafile per tablespace or, the inverse, spread a large datafile across several physical platters for improved performance. The script follows:
 
REM
REM  FUNCTION: SCRIPT FOR CREATING TABLESPACES
REM
REM  FUNCTION: This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM FUNCTION: Running this script will in turn create a script to build
REM FUNCTION: all the tablespaces in the database.  This created script,
REM FUNCTION: crt_tbls.sql, can be run by any user with the DBA role
REM FUNCTION: or with the 'CREATE TABLESPACE' system privilege.
REM
REM Only preliminary testing of this script was performed.  Be sure to test
REM it completely before relying on it.
REM
 
set verify off;
set termout off;
set feedback off;
set echo off;
set pagesize 0;
 
set termout on;
select 'Creating tablespace build script...' from dual;
set termout off;
 
create table ts_temp (lineno number, ts_name varchar2(30),
                    text varchar2(800))
/
DECLARE
   CURSOR ts_cursor IS select   tablespace_name,
                                initial_extent,
                                next_extent,
                                min_extents,
                                max_extents,
                                pct_increase,
                                status
                        from    sys.dba_tablespaces
                        where tablespace_name != 'SYSTEM'
                        and status != 'INVALID'
                        order by tablespace_name;
   CURSOR df_cursor (c_ts VARCHAR2) IS
            select   file_name,
               bytes
      from     sys.dba_data_files
      where    tablespace_name = c_ts
               and tablespace_name != 'SYSTEM'
      order by file_name;
   lv_tablespace_name   sys.dba_tablespaces.tablespace_name%TYPE;
   lv_initial_extent    sys.dba_tablespaces.initial_extent%TYPE;
   lv_next_extent       sys.dba_tablespaces.next_extent%TYPE;
   lv_min_extents       sys.dba_tablespaces.min_extents%TYPE;
   lv_max_extents       sys.dba_tablespaces.max_extents%TYPE;
   lv_pct_increase      sys.dba_tablespaces.pct_increase%TYPE;
   lv_status            sys.dba_tablespaces.status%TYPE;
   lv_file_name         sys.dba_data_files.file_name%TYPE;
   lv_bytes             sys.dba_data_files.bytes%TYPE;
   lv_first_rec         BOOLEAN;
   lv_string            VARCHAR2(800);
   lv_lineno            number := 0;
 
   procedure write_out(p_line INTEGER, p_name VARCHAR2,
             p_string VARCHAR2) is
   begin
     insert into ts_temp (lineno, ts_name, text) values
            (p_line, p_name, p_string);
   end;
 
BEGIN
   OPEN ts_cursor;
   LOOP
      FETCH ts_cursor INTO lv_tablespace_name,
                           lv_initial_extent,
                           lv_next_extent,
                           lv_min_extents,
                           lv_max_extents,
                           lv_pct_increase,
                           lv_status;
      EXIT WHEN ts_cursor%NOTFOUND;
      lv_lineno := 1;
      lv_string := ('CREATE TABLESPACE '||lower(lv_tablespace_name));
      lv_first_rec := TRUE;
      write_out(lv_lineno, lv_tablespace_name, lv_string);
      OPEN df_cursor(lv_tablespace_name);
      LOOP
         FETCH df_cursor INTO lv_file_name,
                              lv_bytes;
         EXIT WHEN df_cursor%NOTFOUND;
         if (lv_first_rec) then
            lv_first_rec := FALSE;
            lv_string := 'DATAFILE ';
         else
            lv_string := lv_string || ',';
         end if;
         lv_string:=lv_string||''''||lv_file_name||''''||
                    ' SIZE '||to_char(lv_bytes) || ' REUSE';
      END LOOP;
      CLOSE df_cursor;
         lv_lineno := lv_lineno + 1;
         write_out(lv_lineno, lv_tablespace_name, lv_string);
         lv_lineno := lv_lineno + 1;
         lv_string := (' DEFAULT STORAGE (INITIAL ' ||
                      to_char(lv_initial_extent) ||
                      ' NEXT ' || lv_next_extent);
         write_out(lv_lineno, lv_tablespace_name, lv_string);
         lv_lineno := lv_lineno + 1;
         lv_string := (' MINEXTENTS ' ||
                      lv_min_extents ||
                      ' MAXEXTENTS ' || lv_max_extents);
         write_out(lv_lineno, lv_tablespace_name, lv_string);
         lv_lineno := lv_lineno + 1;
         lv_string := (' PCTINCREASE ' ||
                      lv_pct_increase || ')');
         write_out(lv_lineno, lv_tablespace_name, lv_string);
         lv_string := ('   '||lv_status);
         write_out(lv_lineno, lv_tablespace_name, lv_string);
         lv_lineno := lv_lineno + 1;
         lv_string:='/';
         write_out(lv_lineno, lv_tablespace_name, lv_string);
         lv_lineno := lv_lineno + 1;
         lv_string:='                                                  ';
         write_out(lv_lineno, lv_tablespace_name, lv_string);
   END LOOP;
   CLOSE ts_cursor;
END;
/
 
spool rep_out\cre_tbsp.sql
set heading off
set recsep off
col text format a80 word_wrap
select   text
from     ts_temp
order by ts_name, lineno;
spool off;
drop table ts_temp;
exit
 
 
Example output from this script follows:
 
CREATE TABLESPACE users
DATAFILE '/oracle1/ORCSPCD1/data/users01.dbf' SIZE 157286400 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
CREATE TABLESPACE temp
DATAFILE '/oracle1/ORCSPCD1/data/temp01.dbf' SIZE 157286400 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
CREATE TABLESPACE rbs
DATAFILE '/oracle1/ORCSPCD1/data/rbs01.dbf' SIZE 157286400 REUSE
DEFAULT STORAGE (INITIAL 102400 NEXT 102400
MINEXTENTS 2 MAXEXTENTS 121
PCTINCREASE 0)
ONLINE
/
 
 
CREATE TABLESPACE cspc_data_dyn
DATAFILE '/oracle1/ORCSPCD1/data/cspc_data_dyn01.dbf' SIZE 157286400 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
CREATE TABLESPACE cspc_data_stat
DATAFILE '/oracle2/ORCSPCD1/data/cspc_data_stat01.dbf' SIZE 52428800 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
CREATE TABLESPACE cspc_indx
DATAFILE '/oracle3/ORCSPCD1/data/cspc_indx01.dbf' SIZE 157286400 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
CREATE TABLESPACE cspc_rbs
DATAFILE '/oracle4/ORCSPCD1/data/cspc_rbs01.dbf' SIZE 157286400 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
CREATE TABLESPACE cspc_temp
DATAFILE '/oracle4/ORCSPCD1/data/cspc_temp01.dbf' SIZE 31457280 REUSE
DEFAULT STORAGE (INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121
PCTINCREASE 50)
ONLINE
/
 
One thing to note about this output is that the SYSTEM tablespace will not be included, if you have multiple datafiles for your existing SYSTEM tablespace and want to rebuild it exactly as it exists, add a ALTER TABLESPACE system ADD DATAFILE section to the script for these files. The sizes and locations can be obtained from the DBA_DATA_FILES view with the following select:
 
SELECT file_name,bytes
from DBA_DATA_FILES
where tablespace_name=‘SYSTEM’;
 
Soft documentation of the tablespaces in a database can be obtained from the scripts datafile.sql, free_spc.sql, db_tbsp.sql located in Appendix A.
 
Documentation of Rollback Segments
 
Before anything else in the database can be created, the rollback segments have to be built. If the scripts so far have been run a functioning database will result, a copy of our tablespaces, but only the default SYSTEM rollback segment. The next script will create DDL to make an exact copy of the existing rollback segment profile.
 
REM rbk_rct.sql
REM FUNCTION: SCRIPT FOR CREATING ROLLBACK SEGMENTS
REM
REM This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM Running this script will in turn create a script to re-build
REM the database rollback segments.  The created script is called
REM crt_rbks.sql and can be run by any user with the DBA
REM role or with the 'CREATE ROLLBACK SEGMENT' system privilege.
REM
REM NOTE:  This script will NOT capture the optimal storage for
REM        a rollback segment that is offline.
REM
REM        The rollback segments must be manually brought online
REM        after running the crt_rbks.sql script.
REM
REM        Only preliminary testing of this script was performed.  Be
REM        sure to test it completely before relying on it.
REM
 
set verify off
set feedback off
rem set termout off
rem set echo off
set pagesize 0
 
set termout on
prompt Creating rollback segment build script...
set termout off
 
create table rb_temp (lineno NUMBER, rb_name varchar2(30),
                            text varchar2(800))
                                    tablespace temp;
 
DECLARE
   CURSOR rb_cursor IS select segment_name,
                                          tablespace_name,
                                          decode (owner, 'PUBLIC', 'PUBLIC ', NULL),
                                          segment_id,
                                          initial_extent,
                                          next_extent,
                                          min_extents,
                                          max_extents,
                                          status
                               from sys.dba_rollback_segs
                                          where segment_name <> 'SYSTEM';
   CURSOR rb_optimal (r_no number) IS select usn,
                                           decode(optsize, null, 'NULL', to_char(optsize))
                                    from sys.v_$rollstat
                                           where usn=r_no;
   lv_segment_name        sys.dba_rollback_segs.segment_name%TYPE;
   lv_tablespace_name     sys.dba_rollback_segs.tablespace_name%TYPE;
   lv_owner               VARCHAR2(10);
   lv_segment_id          sys.dba_rollback_segs.segment_id%TYPE;
   lv_initial_extent      sys.dba_rollback_segs.initial_extent%TYPE;
   lv_next_extent         sys.dba_rollback_segs.next_extent%TYPE;
   lv_min_extents         sys.dba_rollback_segs.min_extents%TYPE;
   lv_max_extents         sys.dba_rollback_segs.max_extents%TYPE;
   lv_status              sys.dba_rollback_segs.status%TYPE;
   lv_usn                 sys.v_$rollstat.usn%TYPE;
   lv_optsize             VARCHAR2(40);
   lv_string              VARCHAR2(800);
   lv_lineno            number := 0;
 
   procedure write_out(p_line INTEGER, p_name VARCHAR2, p_string VARCHAR2) is
   begin
       insert into rb_temp (lineno, rb_name, text) values
                  (p_line, p_name, p_string);
   end;
 
BEGIN
  OPEN rb_cursor;
  LOOP
     FETCH rb_cursor INTO lv_segment_name,
                                      lv_tablespace_name,
                                      lv_owner,
                                      lv_segment_id,
                                      lv_initial_extent,
                                      lv_next_extent,
                                      lv_min_extents,
                                      lv_max_extents,
                                      lv_status;
     EXIT WHEN rb_cursor%NOTFOUND;
      lv_lineno := 1;
  OPEN rb_optimal(lv_segment_id);
  LOOP
     FETCH rb_optimal INTO lv_usn,
                                       lv_optsize;
     EXIT WHEN rb_optimal%NOTFOUND;           
  END LOOP;
  CLOSE rb_optimal;
if lv_status = 'ONLINE' then
lv_string:='CREATE ' || lv_owner || 'ROLLBACK SEGMENT ' ||
                 lower(lv_segment_name);
write_out(lv_lineno, lv_segment_name, lv_string);
lv_lineno := lv_lineno + 1;
lv_string:='TABLESPACE ' || lower(lv_tablespace_name);
write_out(lv_lineno, lv_segment_name, lv_string);
lv_lineno := lv_lineno + 1;
lv_string:='STORAGE ' || '(INITIAL ' || lv_initial_extent || ' NEXT ' ||
                 lv_next_extent || ' MINEXTENTS ' || lv_min_extents ||
                 ' MAXEXTENTS ' || lv_max_extents ||
                 ' OPTIMAL ' || lv_optsize || ')' ;
write_out(lv_lineno, lv_segment_name, lv_string);
lv_lineno := lv_lineno + 1;
lv_string:='/';
write_out(lv_lineno, lv_segment_name, lv_string);
else
lv_string:='CREATE ' || lv_owner || 'ROLLBACK SEGMENT ' ||
                 lower(lv_segment_name);
write_out(lv_lineno, lv_segment_name, lv_string);
lv_lineno := lv_lineno + 1;
lv_string:='TABLESPACE ' || lower(lv_tablespace_name);
write_out(lv_lineno, lv_segment_name, lv_string);
lv_lineno := lv_lineno + 1;
lv_string:='STORAGE ' || '(INITIAL ' || lv_initial_extent || ' NEXT ' ||
                 lv_next_extent || ' MINEXTENTS ' || lv_min_extents ||
                 ' MAXEXTENTS ' || lv_max_extents || ')' ;
write_out(lv_lineno, lv_segment_name, lv_string);
lv_lineno := lv_lineno + 1;
lv_string:='/';
write_out(lv_lineno, lv_segment_name, lv_string);
end if;
lv_lineno := lv_lineno + 1;
lv_string:='                                                  ';
write_out(lv_lineno, lv_segment_name, lv_string);
END LOOP;
  CLOSE rb_cursor;
END;
/  
 column dbname new_value db noprint
select value dbname from v$parameter where name='db_name';
spool rep_out\&db\crt_rbks.sql
set heading off
col text format a80 word_wrap
select   text
from     rb_temp
order by rb_name, lineno;
spool off;
Drop table rb_temp;
exit
 
 
Example output from this script follows:
 
CREATE ROLLBACK SEGMENT r01                                                                        
TABLESPACE cspc_rbs                                                                                 
STORAGE (INITIAL 1048576 NEXT 1048576 MINEXTENTS 2 MAXEXTENTS 121 OPTIMAL 4216832)                                                                                           
/                                                                                                   
                                                                                                   
CREATE ROLLBACK SEGMENT r02                                                                        
TABLESPACE cspc_rbs                                                                                 
STORAGE (INITIAL 1048576 NEXT 1048576 MINEXTENTS 2 MAXEXTENTS 121 OPTIMAL 4216832)                                                                                           
/                                                                                                   
                                                                                                   
CREATE ROLLBACK SEGMENT r03                                                                         
TABLESPACE cspc_rbs                                                                                
STORAGE (INITIAL 1048576 NEXT 1048576 MINEXTENTS 2 MAXEXTENTS 121 OPTIMAL 4216832)                                                                                            
/                                                                                                  
                                                                                                    
CREATE ROLLBACK SEGMENT r04                                                                        
TABLESPACE cspc_rbs                                                                                
STORAGE (INITIAL 1048576 NEXT 1048576 MINEXTENTS 2 MAXEXTENTS 121 OPTIMAL 4216832)                                                                                           
/                                                                                                  
 
The script documents the rollbacks segments as they currently exist, the DBA should edit the scripts to make any improvements that are required or desired before executing it on the new database.
 
 
 
 
Documenting Roles, Grants and Users
 
Before database objects such as tables, indexes, constraints and the like can be created, there must be users (or schemas) with the appropriate roles and grants to create and own them. The next set of scripts is used to generate DDL to create users, roles and grants. Since a user must have roles and grants, the scripts to document these objects will be discussed first.
 
Roles
 
A script to recreate the roles in the database follows:
 
REM role_rct.sql
REM
REM FUNCTION: SCRIPT FOR CREATING ROLES
REM
REM This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM Running this script will in turn create a script to build all the
REM roles in the database.  This created file, crt_role.sql, can 
REM be run by any user with the DBA role or with 'CREATE ROLE' system
REM privilege.
REM
REM Since it is not possible to create a role under a specific schema,
REM it is essential that the original creator be granted 'ADMIN' option
REM to the role. Therefore, such grants will be made at the end of the
REM crt_role.sql script.  Since it is not possible to distinguish the
REM creator from someone who was simply granted 'WITH ADMIN OPTION', all
REM grants will be spooled. In addition, the user who creates the role
REM is automatically granted 'ADMIN' option on the role, therefore, if
REM this script is run a second time, this user will also be granted
REM 'ADMIN' on all the roles.  You must explicitly revoke 'ADMIN OPTION'
REM from this user to prevent this from happening.
REM
REM NOTE:  This script will not capture the create or grant on the
REM Oracle predefined roles, CONNECT, RESOURCE, DBA, EXP_FULL_DATABASE,
REM or IMP_FULL_DATABASE.
REM
REM Only preliminary testing of this script was performed.  Be sure to
REM test it completely before relying on it.
REM
set verify off feedback off termout off echo off pagesize 0 embedded on
set termout on
Prompt 'Creating role build script...'
set termout off
 
 column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\crt_role.sql
 
select 'CREATE ROLE ' || lower(role) || ' NOT IDENTIFIED;'
  from sys.dba_roles
  where role not in ('CONNECT','RESOURCE','DBA', 'EXP_FULL_DATABASE',
                    'IMP_FULL_DATABASE')
  and password_required='NO'
/
select 'CREATE ROLE ' || lower(role) || ' IDENTIFIED BY VALUES ' ||
       '''' || password || '''' || ';'
  from sys.dba_roles, sys.user$
  where role not in ('CONNECT','RESOURCE','DBA', 'EXP_FULL_DATABASE',
                    'IMP_FULL_DATABASE')
  and password_required='YES' and
     dba_roles.role=user$.name
     and user$.type=0
/
 
select 'GRANT ' || lower(granted_role) || ' TO ' || lower(grantee) ||
       ' WITH ADMIN OPTION;'
  from sys.dba_role_privs
  where admin_option='YES'
  and granted_role not in ('CONNECT','RESOURCE','DBA', 'EXP_FULL_DATABASE',
                           'IMP_FULL_DATABASE')
  order by grantee
/
spool off
exit
 
Example output from the above script follows:
 
CREATE ROLE cspc_dev NOT IDENTIFIED;
CREATE ROLE application_developer NOT IDENTIFIED;
CREATE ROLE cspc_user NOT IDENTIFIED;
GRANT cspc_dev TO sys WITH ADMIN OPTION;
GRANT application_developer TO system WITH ADMIN OPTION;
GRANT cspc_user TO system WITH ADMIN OPTION;
 
The next step for roles is recreating the grants which have been given to the roles created. The next script creates the DDL to grant the privileges to the roles created above:
 
REM FUNCTION:   SCRIPT FOR CAPTURING ROLE GRANTS
REM
REM This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM Running this script will create a script of all the grants
REM of roles to users and other roles.  This created script,
REM grt_role.sql, must be run by a user with the DBA role.
REM
REM Since role grants are not dependant on the schema that issued the
REM grant, the grt_role.sql script will not issue the grant of a role by 
REM the original grantor.  All grants will be issued by the user
REM specified when running this script.
REM
REM NOTE:  Grants made to 'SYS','CONNECT','RESOURCE','DBA',
REM                 'EXP_FULL_DATABASE','IMP_FULL_DATABASE' are not captured.
REM
REM        Only preliminary testing of this script was performed.  Be
REM        sure to test it completely before relying on it.
REM
set verify off feedback off termout off echo off pagesize 0 embedded on
 
set termout on
select 'Creating role grant script...' from dual;
set termout off
 
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\grt_role.sql
 
select 'GRANT ' || lower(granted_role) || ' TO ' || lower(grantee) ||
       decode(admin_option,'YES',' WITH ADMIN OPTION;',';')
  from sys.dba_role_privs
  where grantee not in ('SYS','CONNECT','RESOURCE','DBA',
            'EXP_FULL_DATABASE','IMP_FULL_DATABASE')             
order by grantee
/
spool off
exit
 
Example Output from above script:
 
GRANT connect TO cspcdba WITH ADMIN OPTION;
GRANT cspc_dev TO cspcdba;
GRANT dba TO cspcdba WITH ADMIN OPTION;
GRANT exp_full_database TO cspcdba WITH ADMIN OPTION;
GRANT imp_full_database TO cspcdba WITH ADMIN OPTION;
GRANT resource TO cspcdba WITH ADMIN OPTION;
GRANT connect TO cspcdev_1;
GRANT cspc_user TO cspcdev_1;
GRANT connect TO ops$oracle;
GRANT dba TO ops$oracle;
GRANT resource TO ops$oracle;
 
The above script will capture all privileges granted to roles. Any system level privileges and be captured by a similar script that uses the DBA_SYS_GRANTS view.
 
REM FUNCTION:   SCRIPT FOR CAPTURING ROLE SYSTEM GRANTS
REM
REM This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM Running this script will create a script of all the grants
REM of roles to users and other roles.  This created script,
REM grt_sys.sql, must be run by a user with the DBA role.
REM
REM Since role grants are not dependant on the schema that issued the
REM grant, the grt_role.sql script will not issue the grant of a role by
REM the original grantor.  All grants will be issued by the user
REM specified when running this script.
REM
REM NOTE:  Grants made to 'SYS','CONNECT','RESOURCE','DBA',
REM                 'EXP_FULL_DATABASE','IMP_FULL_DATABASE' are not captured.
REM
REM        Only preliminary testing of this script was performed.  Be
REM        sure to test it completely before relying on it.
REM
set verify off feedback off termout off echo off pagesize 0 embedded on
set heading off
 
set termout on
prompt Creating role sys grant script...
set termout off
 
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\grt_sys.sql
 
select 'GRANT ' || lower(privilege) || ' TO ' || lower(grantee) ||
       decode(admin_option,'YES',' WITH ADMIN OPTION;',';')
  from sys.dba_sys_privs
  where grantee not in ('SYS','CONNECT','RESOURCE','DBA',
            'EXP_FULL_DATABASE','IMP_FULL_DATABASE')             
order by grantee
/
spool off
exit
 
Example output from the above script:
 
GRANT alter session TO application_developer;
GRANT create sequence TO application_developer;
GRANT create session TO application_developer;
GRANT create table TO application_developer;
GRANT create view TO application_developer;
GRANT unlimited tablespace TO cspcdba WITH ADMIN OPTION;
GRANT create session TO cspcdev_1;
GRANT create procedure TO cspc_dev;
GRANT create sequence TO cspc_dev;
GRANT create synonym TO cspc_dev;
GRANT create table TO cspc_dev;
GRANT create trigger TO cspc_dev;
GRANT create view TO cspc_dev;
GRANT create session TO cspc_user;
GRANT analyze any TO ops$oracle;
GRANT unlimited tablespace TO ops$oracle;
GRANT unlimited tablespace TO system WITH ADMIN OPTION;
 
The next layer of grants which must be allowed for are table grants. The following script will regenerate table grants for roles and users:
 
REM FUNCTION:   SCRIPT FOR CAPTURING TABLE GRANTS
REM
REM This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM Running this script will create a script of all the grants
REM of roles to users and other roles.  This created script,
REM grt_sys.sql, must be run by a user with the DBA role.
REM
REM Since role grants are not dependant on the schema that issued the
REM grant, the grt_role.sql script will not issue the grant of a role by
REM the original grantor.  All grants will be issued by the user
REM specified when running this script.
REM
REM NOTE:  Grants made to 'SYS','CONNECT','RESOURCE','DBA',
REM                 'EXP_FULL_DATABASE','IMP_FULL_DATABASE' are not captured.
REM
REM        Only preliminary testing of this script was performed.  Be
REM        sure to test it completely before relying on it.
REM
set verify off feedback off termout off echo off pagesize 0 embedded on
 
set termout on
prompt Creating table grant script...
set termout off
 
break on line1
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\grt_tabs.sql
 
select 'CONNECT '||grantor||'/'||grantor line1,
'GRANT
'||lower(privilege)||' on '||owner||'.'||table_name||' to '|| lower(grantee) ||
decode(grantable,'YES',' WITH ADMIN OPTION;',';')
from sys.dba_tab_privs
where grantee not in ('SYS','CONNECT','RESOURCE','DBA',
'EXP_FULL_DATABASE','IMP_FULL_DATABASE')
order by line1,grantee
/
spool off
exit
 
This script will produce a list of grantors and the commands to grant whatever access they have granted to other users and roles on their tables. The script will have to be edited to insert the appropriate passwords for the granting users.
 
Example of the output of this script:
 
CONNECT CSPCDBA/CSPCDBA
GRANT
alter on CSPCDBA.ACTSEQ to cspcdev_1;
GRANT
select on CSPCDBA.ACTSEQ to cspcdev_1;
GRANT
delete on CSPCDBA.ACCCAR to cspcdev_1;
GRANT
select on CSPCDBA.CSPCSEQ to cspcdev_1;
GRANT
update on CSPCDBA.ACCCAR to cspcdev_1;
GRANT
delete on CSPCDBA.ACT to cspcdev_1;
GRANT
select on CSPCDBA.ACT to cspcdev_1;
 
The final layer of grants are the column level grants. The next script will allow documentation and recreation of these grants.
 
REM FUNCTION:   SCRIPT FOR CAPTURING TABLE COLUMN GRANTS
REM
REM This script must be run by a user with the DBA role.
REM
REM This script is intended to run with Oracle .
REM
REM Running this script will create a script of all the grants
REM of roles to users and other roles.  This created script,
REM grt_sys.sql, must be run by a user with the DBA role.
REM
REM Since role grants are not dependant on the schema that issued the
REM grant, the grt_role.sql script will not issue the grant of a role by
REM the original grantor.  All grants will be issued by the user
REM specified when running this script.
REM
REM NOTE:  Grants made to 'SYS','CONNECT','RESOURCE','DBA',
REM                 'EXP_FULL_DATABASE','IMP_FULL_DATABASE' are not captured.
REM
REM        Only preliminary testing of this script was performed.  Be
REM        sure to test it completely before relying on it.
REM
set verify off feedback off termout off echo off pagesize 0 embedded on
set heading off
 
set termout on
prompt Creating table grant script...
set termout off
 
break on grantor
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\grt_tabc.sql
 
select 'connect '||grantor||'/'||grantor line1,
'grant
'||lower(privilege)||' on '||owner||'.'||table_name||'.'||column_name||'
'||' to '||lower(grantee)||
decode(grantable,'YES',' WITH ADMIN OPTION;',';')
 from sys.dba_col_privs
where grantee not in ('SYS','CONNECT','RESOURCE','DBA',
'EXP_FULL_DATABASE','IMP_FULL_DATABASE')
order by grantor,grantee
/
spool off
exit
 
Example of the output from the above script follows:
 
connect SYSTEM/SYSTEM
grant
update on SYSTEM.TOOL_DEPENDENT.DEPCHANGED
 to public;
 
The scripts to create the roles and role grants can be run at any time. The scripts for user grants and table grants must be run after the users and tables are created. The next script will recreate the database users as they currently exist (except for passwords which the DBA will have to edit in afterwards.)
 
REM rct_usrs.sql
REM
REM FUNCTION: Create a script to recreate users
REM
REM This script is designed to run on an Oracle .x database
REM
REM This script creates a script called crt_usrs.sql that
REM recreates the CREATE USER commands required to rebuild the database
REM user community. The script includes the tablespace quota grants for REM each user as a set of ALTER USER commands. The user’s passwords are
REM initially set to the username so editing is suggested if other
REM values are desired.
REM
REM Only preliminary testing has been accomplished on this script,
REM please fully qualify it for your environment before use
REM
REM M. Ault TRECOM 3.30.96
REM
set verify off feedback off termout off echo off pagesize 0 embedded on
 
set termout on
prompt Creating user create script...
set termout off
 
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\crt_usrs.sql
 
SELECT 'create user '||username||' identified by '||username||'
 '||' default tablespace '||default_tablespace||'
 '||' temporary tablespace '||temporary_tablespace||'
 '||' profile '||profile||'
 '||' quota unlimited on '||default_tablespace||';'
FROM dba_users
WHERE username not in (‘SYS’,’SYSTEM’)
UNION
SELECT 'alter user '||username||'
'||'quota '||bytes||' on '||tablespace_name||';'
FROM dba_ts_quotas
WHERE username not in (‘SYS’,’SYSTEM’)
/
spool off
exit
 
Example output from the above script:
 
create user CSPCDEV_1 identified by CSPCDEV_1
  default tablespace CSPC_DATA_DYN
  temporary tablespace CSPC_TEMP
  profile DEFAULT
  quota unlimited on CSPC_DATA_DYN;
 
create user OPS$ORACLE identified by OPS$ORACLE
  default tablespace USERS
  temporary tablespace CSPC_TEMP
  profile DEFAULT
  quota unlimited on USERS;
 
Now we have fully documented all users, roles and grants. The next sets of scripts provide documentation for the tables and their associated indexes, constraints, procedures, triggers and views.  
 
Soft documentation of roles, grants and users can be obtained by using the db_roles.sql, db_grnts.sql and db_usrs.sql scripts located in Appendix A.
 
Documenting Tables
 
So far the database control file, redo logs, initialization parameters, tablespaces, rollback segments, roles, grants and users have been documented. All non-data related structures. It is now time to document the application tables. If the database has been properly set up (always a questionable proposition) the tables and other related data structures should be owned by one application owner or schema. If the application is owned by more than one schema, or, there is more than one application in the database, the following script will need to be run multiple times.
 
The script to document existing tables follows:
 
REM tab_rct.sql
REM
REM FUNCTION: SCRIPT FOR CREATING TABLES
REM
REM           This script can be run by any user .
REM
REM           This script is intended to run with Oracle .
REM
REM           Running this script will in turn create a script to
REM           build all the tables owned by the user in the database.
REM           This script, crt_tab.sql, can be run by any user with the
REM           'CREATE TABLE' system privilege.
REM
REM NOTE:     The script will NOT include constraints on tables.  This
REM           script will also NOT capture tables created by user 'SYS'.
REM
REM Only preliminary testing of this script was performed.  Be sure to
REM test it completely before relying on it.
REM
set verify off
rem set feedback off
rem set termout off
set echo off;
set pagesize 0
set termout on
Prompt Creating table build script...
set termout off
 
create table t_temp
     (lineno NUMBER, tb_owner VARCHAR2(30), tb_name VARCHAR2(30),
      text VARCHAR2(255))
/
 
DECLARE
   CURSOR tab_cursor IS
            select               table_name,
                        pct_free,
                        pct_used,
                        ini_trans,
                        max_trans,
                        tablespace_name,
                        initial_extent,
                        next_extent,
                        min_extents,
                        max_extents,
                        pct_increase
            from     user_tables
            order by  table_name;
 
   CURSOR col_cursor (c_tab VARCHAR2) IS
            select  
              column_name,
              data_type,
              data_length,
              data_precision,
              data_scale,
              nullable
            from     user_tab_columns
            where    table_name = c_tab
            order by column_name;
 
   lv_table_name                                     user_tables.table_name%TYPE;
   lv_pct_free                  user_tables.pct_free%TYPE;
   lv_pct_used                user_tables.pct_used%TYPE;
   lv_ini_trans                 user_tables.ini_trans%TYPE;
   lv_max_trans                          user_tables.max_trans%TYPE;
   lv_tablespace_name   user_tables.tablespace_name%TYPE;
   lv_initial_extent           user_tables.initial_extent%TYPE;
   lv_next_extent            user_tables.next_extent%TYPE;
   lv_min_extents            user_tables.min_extents%TYPE;
   lv_max_extents           user_tables.max_extents%TYPE;
   lv_pct_increase           user_tables.pct_increase%TYPE;
   lv_column_name         user_tab_columns.column_name%TYPE;
   lv_data_type               user_tab_columns.data_type%TYPE;
   lv_data_length             user_tab_columns.data_length%TYPE;
   lv_data_precision        user_tab_columns.data_precision%TYPE;
   lv_data_scale              user_tab_columns.data_scale%TYPE;
   lv_nullable                  user_tab_columns.nullable%TYPE;
   lv_first_rec                  BOOLEAN;
   lv_lineno                     NUMBER := 0;
   lv_string                     VARCHAR2(80);
 
   procedure write_out(p_line INTEGER,  p_name VARCHAR2,
                               p_string VARCHAR2) is
              begin
                 insert into t_temp (lineno, tb_name, text)
                             values (p_line,p_name,p_string);
               end;
 BEGIN
   OPEN tab_cursor;
   LOOP
      FETCH tab_cursor INTO       lv_table_name,
                                                lv_pct_free,
                                                lv_pct_used,
                                                lv_ini_trans,
                                                lv_max_trans,
                                                lv_tablespace_name,
                                                lv_initial_extent,
                                                lv_next_extent,
                                                lv_min_extents,
                                                lv_max_extents,
                                                lv_pct_increase;
      EXIT WHEN tab_cursor%NOTFOUND;
            lv_lineno := 1;
            lv_string := 'DROP TABLE '|| lower(lv_table_name)||';';
            write_out(lv_lineno,  lv_table_name, lv_string);
            lv_lineno := lv_lineno + 1;
            lv_first_rec := TRUE;
            lv_string := 'CREATE TABLE '|| lower(lv_table_name)||' (';
            write_out(lv_lineno,  lv_table_name, lv_string);
            lv_lineno := lv_lineno + 1;
      lv_string := null;
      OPEN col_cursor(lv_table_name);
      LOOP
             FETCH col_cursor INTO  lv_column_name,
                                                lv_data_type,
                                                lv_data_length,
                                                lv_data_precision,
                                                lv_data_scale,
                                                lv_nullable;
             EXIT WHEN col_cursor%NOTFOUND;
             if (lv_first_rec) then
                lv_first_rec := FALSE;
             else
                lv_string :=  ',';
             end if;
             lv_string := lv_string || lower(lv_column_name) ||
                             ' ' || lv_data_type;
             if ((lv_data_type = 'CHAR') or (lv_data_type = 'VARCHAR2')) then
                lv_string := lv_string || '(' || lv_data_length || ')';
             end if;
             if (lv_nullable = 'N') then
                lv_string := lv_string || ' NOT NULL';
             end if;
      write_out(lv_lineno, lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      END LOOP;
      CLOSE col_cursor;
      lv_string := ')';
      write_out(lv_lineno, lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := null;
      lv_string := 'PCTFREE ' || to_char(lv_pct_free) ||
                        '   PCTUSED ' || to_char(lv_pct_used);
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'INITRANS ' || to_char(lv_ini_trans) ||
                          ' MAXTRANS ' || to_char(lv_max_trans);
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'TABLESPACE ' || lv_tablespace_name;
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'STORAGE (';
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'INITIAL ' || to_char(lv_initial_extent) ||
                             ' NEXT ' || to_char(lv_next_extent);
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'MINEXTENTS ' || to_char(lv_min_extents) ||
                          ' MAXEXTENTS ' || to_char(lv_max_extents) ||
                         ' PCTINCREASE ' || to_char(lv_pct_increase) || ')';
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := '/';
      write_out(lv_lineno,  lv_table_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string:='                                                  ';
      write_out(lv_lineno,  lv_table_name, lv_string);
   END LOOP;
   CLOSE tab_cursor;
END;
/
 
set heading off
spool rep_out\crt_tabs.sql
select   text
from     T_temp
order by  tb_name, lineno;
spool off
drop table t_temp
exit
 
Again, the recursion required by the multiple entries in the USER_TAB_COLUMNS view cause multiple cursors to be used and multiple loop-end loop constructs to be required. This script can be made generic by adding reference to the OWNER column of the DBA_TABLES and DBA_TAB_COLUMNS views throughout the logic. An example of this scripts output follows:
 
DROP TABLE accar;
CREATE TABLE acccar (
acccaridcd NUMBER
,acccarrdrccd VARCHAR2(3)
,acccartypcd VARCHAR2(10)
,acccustnmabbr VARCHAR2(3) NOT NULL
,custcarnmabbr VARCHAR2(3)
,fk_cococd VARCHAR2(15) NOT NULL
,fk_cofk_leleid NUMBER NOT NULL
)
PCTFREE 60 PCTUSED 40
INITRANS 1 MAXTRANS 255
TABLESPACE CSPC_DATA_DYN
STORAGE (
INITIAL 118784 NEXT 55296
MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50)
/
 DROP TABLE act;
CREATE TABLE act (
actseqnbr NUMBER NOT NULL
,actstpdt DATE
,actstrtdt DATE NOT NULL
,actvyhilgtind VARCHAR2(1)
,fk_acttypacttypnm VARCHAR2(16)
,fk_acttypacttypvrs CHAR(7)
,fk_eventevntid NUMBER NOT NULL
)
PCTFREE 60 PCTUSED 40
INITRANS 1 MAXTRANS 255
TABLESPACE CSPC_DATA_DYN
STORAGE (
INITIAL 4096 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50)
/
.
. (Since this database has nearly 300 tables, the output has been
.  truncated)
.
DROP TABLE wrkgrp_table;
CREATE TABLE wrkgrp_table (
wrkgrpnm VARCHAR2(8) NOT NULL
)
PCTFREE 10 PCTUSED 40
INITRANS 1 MAXTRANS 255
TABLESPACE CSPC_DATA_DYN
STORAGE (
INITIAL 10240 NEXT 10240
MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50)
/
 
As the remarks section of the code relates, this code does not capture table constraints. The reasoning for this is to allow data loading to occur before the constraints are enabled. In the following sections a script the re-creates all primary key, unique, foreign key, non-”not null” check type and default value constraints will be examined.
 
Soft documentation of tables can be generated using the db_tbl.sql, and tab_stat.sql scripts in Appendix A.
 
A subset of tables, called clusters, are documented using the cluster scripts in Appendix A. Since very few applications use clusters, this paper won’t cover rebuilding them.
 
 
Documenting Database Constraints
 
The next database objects which will be documented are the table constraints. In many cases the constraints will have been specified during the CREATE TABLE command and probably will not have been named. This lack of naming for a constraint will result in Oracle selecting a name consisting of SYS_C with a sequence number appended to it, such as SYS_C00123. Using a derivation of the next script these constraints can be renamed to a more informative naming convention, however that is the topic for another paper and beyond the scope of this one.
 
Constraints come is several forms, primary key (P), foreign key (R), unique (U), check (C) and default value (V). Check constraints can be the standard NOT-NULL type constraint or can be a full blown verification algorithm. Default value constraints provide a default value for those fields effected by them.
 
The following script generates three SQL scripts, one for primary key and unique constraints, one for foreign key constraints and one for non-NOT-NULL check constraints and default value constraints:
 
REM
REM FUNCTION: SCRIPT FOR RE-CREATING DATABASE CONSTRAINTS
REM
REM FUNCTION: This script must be run by the constraint owner. 
REM
REM FUNCTION: This script is intended to run with Oracle .
REM
REM FUNCTION: Running this script will in turn create scripts to build
REM FUNCTION: constraints owned in the database. The scripts are called
REM FUNCTION: 'pk_rct.sql', ‘fk_rct.sql’, and ‘ck_rct.sql’.
REM FUNCTION: The primary key and unique
REM FUNCTION: constraints will include the USING INDEX clause, you may
REM FUNCTION: point this to an INDEX tablespace if the  constraints
REM FUNCTION: don’t already. The foreign key constraints build an index REM FUNCTION: if one exists with the same name as the constraint.
REM FUNCTION: Otherwise, the foreign key isn’t built.
REM FUNCTION: You may wish to edit this part of the script if this isn’t REM FUNCTION: the case in your database
REM
REM Only preliminary testing of this script was performed.  Be sure to
REM test it completely before relying on it. 
REM
REM M. Ault 2/25/96 TRECOM
REM
 
set verify off
rem set feedback off
rem set termout off
rem set echo off
set pagesize 0
set long 4000
set termout on
select 'Creating constraint build script...' from dual;
rem set termout off
 
create table cons_temp (owner varchar2(30),
                                    constraint_name varchar2(30),
                                    constraint_type varchar2(11),
                                    search_condition varchar2(2000),
                                    table_name varchar2(30),
                                    referenced_owner varchar2(30),
                                    referenced_constraint varchar2(30),
                                    delete_rule varchar2(9),
                                    constraint_columns varchar2(2000),
                                    con_number number);
 
DECLARE
 
   CURSOR cons_cursor IS select           owner,
                                                constraint_name,
   decode(constraint_type,'P','Primary Key',
                                    'R','Foreign Key',
                                    'U','Unique',
                                    'C','Check',
                                    'D','Default'),
                                                table_name,
                                                search_condition,
                                                r_owner,
                                                r_constraint_name,
                                                delete_rule
     from user_constraints
     where owner not in ('SYS','SYSTEM')
     order by owner;
 
     cursor cons_col is select
                        owner,
                        constraint_name,
                        column_name
     from user_cons_columns
     where owner not in ('SYS','SYSTEM')
     order by owner, constraint_name,
                                                position;
 
            cursor get_cons (tab_nam in varchar2) is
            select distinct
                        OWNER,TABLE_NAME,CONSTRAINT_NAME,CONSTRAINT_TYPE
            from cons_temp
            where table_name=tab_nam
            and constraint_type='Foreign Key'
            order by owner,table_name,constraint_name;
 
            cursor get_tab_nam is
            select distinct table_name
            from cons_temp
            where constraint_type='Foreign Key'
            order by table_name;
 
            tab_nam                        user_constraints.table_name%TYPE;
            cons_owner                   user_constraints.owner%TYPE;
            cons_name                   user_constraints.constraint_name%TYPE;
            cons_type                     varchar2(11);
            cons_sc                        user_constraints.search_condition%TYPE;
            cons_tname                  user_constraints.table_name%TYPE;
            cons_rowner                  user_constraints.r_owner%TYPE;
            cons_rcons                   user_constraints.r_constraint_name%TYPE;
            cons_dr             user_constraints.delete_rule%TYPE;  
            cons_col_own    user_cons_columns.owner%TYPE;
            cons_col_nam   user_cons_columns.constraint_name%TYPE;
            cons_column                 user_cons_columns.column_name%TYPE;
            cons_tcol_name            user_cons_columns.table_name%TYPE;
            all_columns       varchar2(2000);
            counter             integer:=0;
            cons_nbr           integer;
 
BEGIN
   OPEN cons_cursor;
   LOOP
      FETCH cons_cursor INTO     cons_owner,
                                                cons_name,
                                                cons_type,
                                                cons_sc,
                                                cons_tname,
                                                cons_rowner,
                                                            cons_rcons,
                                                cons_dr;
      EXIT WHEN cons_cursor%NOTFOUND;
            all_columns :='';
            counter := 0;
            open cons_col;
            loop
                        fetch cons_col into
                                                            cons_col_own,
                                                            cons_col_nam,
                                                            cons_column;
            exit when cons_col%NOTFOUND;
                        if cons_owner = cons_col_own and cons_name=cons_col_nam
                        then
                                    counter := counter+1;
                                    if counter = 1 then
                                    all_columns := all_columns||cons_column;
                                    else
                                    all_columns := all_columns||', '||cons_column;
                                    end if;
                        end if;
            end loop;
            close cons_col;
            insert into cons_temp values (cons_owner,
                                                      cons_name,
                                                      cons_type,
                                                      cons_tname,
                                                      cons_sc,
                                                      cons_rowner,
                                                      cons_rcons,
                                                      cons_dr,
                                                all_columns,
                                                0);
   commit;
   END LOOP;
   CLOSE cons_cursor;
   commit;
begin
 open get_tab_nam;
loop
  fetch get_tab_nam into tab_nam;
  exit when get_tab_nam%NOTFOUND;
/*sys.dbms_output.put_line(tab_nam);*/
  open get_cons (tab_nam);
  cons_nbr:=0;
  loop
    fetch get_cons into cons_owner,
                                    cons_tname,
                                    cons_name,
                                    cons_type;
    exit when get_cons%NOTFOUND;
    cons_nbr:=cons_nbr+1;
/*    sys.dbms_output.put_line('cons_nbr='||cons_nbr);*/
/*sys.dbms_output.put_line(cons_owner||'.'||cons_name||' '||cons_type);*/
    update cons_temp set con_number=cons_nbr where
            constraint_name=cons_name and
            constraint_type=cons_type and
            owner=cons_owner;
  end loop;
  close get_cons;
  commit;
end loop;
close get_tab_nam;
commit;
end;
END;
/
REM The following indexes make the create part of this script run
REM much faster
REM
create index pk_cons_temp on cons_temp(constraint_name);
create index lk_cons_temp on cons_temp(constraint_type);
create index lk_cons_temp2 on cons_temp(referenced_constraint);
 
set heading off feedback off pages 0 termout off echo off
set recsep off verify off
set embedded on
REM
REM Do the check and default values first since they are usually the smallest
REM
spool ck_rct.sql
select 'alter table '||a.table_name||'
drop constraint '||a.constraint_name||';
alter table '||a.table_name||' add constraint '||a.constraint_name||'
'||constraint_type||'( '||a.search_condition||' )
;'
from cons_temp a where constraint_type in ( 'Check','Default' ) and
search_condition not like '%NULL%'
/
spool off
REM
REM Next, do the primary constraints
REM
spool pk_rct.sql
select 'alter table '||a.table_name||'
drop constraint '||a.constraint_name||';'||'
drop index '||a.constraint_name||';'||'
alter table '||a.table_name||' add constraint '||a.constraint_name||'
'||constraint_type||'( '||a.constraint_columns||' )'||'
'||'using index tablespace '||b.tablespace_name||'
'||'pctfree '||b.pct_free||'
'||'initrans '||b.ini_trans||'
'||'maxtrans '||b.max_trans||'
'||'storage (
'||'initial '||b.initial_extent||'
'||'next '||b.next_extent||'
'||'minextents '||b.min_extents||'
'||'maxextents '||b.max_extents||'
'||'pctincrease '||b.pct_increase||'
'||' freelist groups '||b.freelist_groups||')
;'
from cons_temp a, user_indexes b where
a.constraint_type in ('Primary Key','Unique') and
a.constraint_name=b.index_name
/
spool off
REM
REM Finally do the foreign key constraints
REM
spool fk_rct.sql
select 'alter table '||a.table_name||'
drop constraint '||a.constraint_name||';
drop index '||c.index_name||';
alter table '||a.table_name||' add constraint '||a.constraint_name||' '||a.constraint_type||'(
 '||a.constraint_columns||' )
'||'references '||a.referenced_owner||'.'||b.table_name||' (
'||b.constraint_columns||' )
'||decode (a.delete_rule,'CASCADE','on delete cascade','')||'
;'||'
'||'create index '||c.index_name||' on '||a.table_name||'(
'||a.constraint_columns||')
'||'tablespace '||c.tablespace_name||'
'||'pctfree '||c.pct_free||' '||'initrans '||c.ini_trans||' maxtrans '||c.max_trans||'
'||'storage ( '||'initial '||c.initial_extent||' next '||c.next_extent||'
'||'minextents '||c.min_extents||' maxextents '||c.max_extents||'
'||'pctincrease '||c.pct_increase||'
'||' freelist groups '||c.freelist_groups||')
;'
from cons_temp a, cons_temp b, user_indexes c
where a.constraint_type = 'Foreign Key' and
a.referenced_constraint=b.constraint_name
and a.constraint_name=c.index_name
/
drop table cons_temp;
exit
 
As the remarks at the top of the script indicate, this script expects the foreign key indexes to exist and be named the same as the foreign key. If this isn’t the case, modify the last bit of code to either not build the index at all or hard code the location of the index and its name and storage parameters.
 
An excerpt from the pk_rct.sql script follows:
 
alter table ACCCAR
drop constraint PK_ACCCAR;
drop index PK_ACCCAR;
alter table ACCCAR add constraint PK_ACCCAR
Primary Key( FK_COCOCD, FK_COFK_LELEID )
using index tablespace CSPC_INDX
pctfree 10
initrans 2
maxtrans 255
storage (
initial 10240
next 10240
minextents 1
maxextents 121
pctincrease 50
 freelist groups 1)
;
alter table ACT
drop constraint PK_ACT;
drop index PK_ACT;
alter table ACT add constraint PK_ACT
Primary Key( FK_EVENTEVNTID, ACTSEQNBR )
using index tablespace CSPC_INDX
pctfree 10
initrans 2
maxtrans 255
storage (
initial 10240
next 24576
minextents 1
maxextents 121
pctincrease 50
 freelist groups 1)
;
alter table ACTASGT
drop constraint PK_ACTASGT;
drop index PK_ACTASGT;
alter table ACTASGT add constraint PK_ACTASGT
Primary Key( FK_ACTACTSEQNBR, FK_ACTFK_EVENTEVNT, ACTASGTROLECD )
using index tablespace CSPC_INDX
pctfree 10
initrans 2
maxtrans 255
storage (
initial 10240
next 10240
minextents 1
maxextents 121
pctincrease 50
 freelist groups 1)
;
 
An excerpt from the fk_rct.sql script follows:
 
alter table ACCCAR
drop constraint FK_ACCCAR_1;
drop index FK_ACCCAR_1;
alter table ACCCAR add constraint FK_ACCCAR_1 Foreign Key(
 FK_COFK_LELEID, FK_COCOCD )
references CSPCDBA.CO (
FK_LELEID, COCD )
on delete cascade
;
create index FK_ACCCAR_1 on ACCCAR(
FK_COFK_LELEID, FK_COCOCD)
tablespace CSPC_INDX
pctfree 10 initrans 2 maxtrans 255
storage ( initial 10240 next 10240
minextents 1 maxextents 121
pctincrease 50
 freelist groups 1)
;
alter table ACT
drop constraint FK_ACT_1;
drop index FK_ACT_1;
alter table ACT add constraint FK_ACT_1 Foreign Key(
 FK_EVENTEVNTID )
references CSPCDBA.EVENT (
EVNTID )
on delete cascade
;
 
The scripts for constraint recreation should be run after the tables have been built and loaded with data. They should be run in the following order:
 
pk_rct.sql
fk_rct.sql
ck_rct.sql
 
There may be additional lookup indexes in the database, in this case, these must also be recreated or performance may suffer. The next script allows you to recreate the other indexes in the database.
 
The soft documentation for constraints can be generated through use of the pk_fk.sql script in Appendix A.
 
 
Documenting Indexes in the Database
 
Indexes are usually create for one of four purposes, enforcing a primary key, enforcing a unique value, lookup support on a foreign key or performance enhancement. The constraints rebuilding script handles primary key, unique and foreign key type indexes. The next script will allow the DBA to rebuild all indexes in a database, since Oracle will not allow multiple indexes on the same set of columns, running the resulting DDL after rebuilding your constraints will allow creation of any look up indexes without the possibility of damaging already existing indexes used in support of primary, unique or foreign key constraints.
 
REM in_rct.sql
REM
REM FUNCTION: SCRIPT FOR CREATING INDEXES
REM
REM           This script must be run by a user with the DBA role.
REM
REM           This script is intended to run with Oracle .
REM
REM           Running this script will in turn create a script to
REM           build all the indexes in the database.  This created
REM           script, create_index.sql, can be run by any user with
REM           the DBA role or with the 'CREATE ANY INDEX' system
REM           privilege.
REM
REM           The script will NOT capture the indexes created by
REM           the user 'SYS'.
REM
REM NOTE:     Indexes automatically created by table CONSTRAINTS will
REM           also be INCLUDED in the create_index.sql script.  It may
REM           cause a problem to create an index with a system assigned
REM           name such as SYS_C00333.
REM
REM           Only preliminary testing of this script was performed. 
REM           Be sure to test it completely before relying on it.
REM
 
set verify off;
set termout off;
set feedback off;
set echo off;
set pagesize 0;
 
set termout on
Prompt Creating index build script...
set termout off;
 
create table i_temp
     (lineno NUMBER, id_owner VARCHAR2(30), id_name VARCHAR2(30),
      text VARCHAR2(255))
/
 
DECLARE
   CURSOR ind_cursor IS select   owner,
                                                 index_name,
                                                 table_owner,
                                                 table_name,
                                                 uniqueness,
                                                 tablespace_name,
                                                 ini_trans,
                                                 max_trans,
                                                 initial_extent,
                                                 next_extent,
                                                 min_extents,
                                                 max_extents,
                                                 pct_increase,
                                                 pct_free
                                    from     dba_indexes
                                    where owner != 'SYS'
                                    order by index_name;
   CURSOR col_cursor (i_own VARCHAR2, c_ind VARCHAR2, c_tab VARCHAR2) IS
                   select   column_name
                                                             from     dba_ind_columns
                                                             where    index_owner = i_own
                                                               and    index_name = c_ind
                                                               and    table_name = c_tab
                                                             order by column_position;
 
   lv_index_owner           dba_indexes.owner%TYPE;
   lv_index_name            dba_indexes.index_name%TYPE;
   lv_table_owner            dba_indexes.table_owner%TYPE;
   lv_table_name             dba_indexes.table_name%TYPE;
   lv_uniqueness             dba_indexes.uniqueness%TYPE;
   lv_tablespace_name   dba_indexes.tablespace_name%TYPE;
   lv_ini_trans                 dba_indexes.ini_trans%TYPE;
   lv_max_trans              dba_indexes.max_trans%TYPE;
   lv_initial_extent           dba_indexes.initial_extent%TYPE;
   lv_next_extent            dba_indexes.next_extent%TYPE;
   lv_min_extents            dba_indexes.min_extents%TYPE;
   lv_max_extents           dba_indexes.max_extents%TYPE;
   lv_pct_increase           dba_indexes.pct_increase%TYPE;
   lv_pct_free                  dba_indexes.pct_free%TYPE;
   lv_column_name         dba_ind_columns.column_name%TYPE;
   lv_first_rec                  BOOLEAN;
   lv_string                     VARCHAR2(80);
   lv_lineno                     NUMBER := 0;
 
   procedure write_out(p_line INTEGER, p_owner varchar2, p_name VARCHAR2,
                               p_string VARCHAR2) is
   begin
      insert into i_temp (lineno,id_owner, id_name,text)
                 values (p_line,p_owner,p_name,p_string);
   end;
 
BEGIN
   OPEN ind_cursor;
   LOOP
      FETCH ind_cursor INTO     lv_index_owner,
                                                lv_index_name,
                                                lv_table_owner,
                                                lv_table_name,
                                                lv_uniqueness,
                                                lv_tablespace_name,
                                                lv_ini_trans,
                                                lv_max_trans,
                                                lv_initial_extent,
                                                lv_next_extent,
                                                lv_min_extents,
                                                lv_max_extents,
                                                lv_pct_increase,
                                                lv_pct_free;
      EXIT WHEN ind_cursor%NOTFOUND;
            lv_lineno := 1;
      lv_first_rec := TRUE;
      lv_string:= ‘DROP INDEX ‘ || lower(lv_index_owner) || ‘.’ ||
                                    lower(lv_index_name)||’;’;
             write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
            lv_lineno := lv_lineno+1;
      if (lv_uniqueness = 'UNIQUE') then
             lv_string:= 'CREATE UNIQUE INDEX ' || lower(lv_index_owner) || '.' ||
                             lower(lv_index_name);
             write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
             lv_lineno := lv_lineno + 1;
      else
             lv_string:= 'CREATE INDEX ' || lower(lv_index_owner) || '.' ||
                             lower(lv_index_name);
             write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
             lv_lineno := lv_lineno + 1;
      end if;
      OPEN col_cursor(lv_index_owner,lv_index_name,lv_table_name);
      LOOP
             FETCH col_cursor INTO  lv_column_name;
             EXIT WHEN col_cursor%NOTFOUND;
             if (lv_first_rec) then
                lv_string := '   ON '|| lower(lv_table_owner) || '.' ||
                                     lower(lv_table_name)||' (';
             lv_first_rec := FALSE;
             else
                lv_string := lv_string || ',';
             end if;
             lv_string := lv_string || lower(lv_column_name);
      END LOOP;
      CLOSE col_cursor;
      lv_string := lv_string || ')';
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := null;
      lv_string := 'PCTFREE ' || to_char(lv_pct_free);
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'INITRANS ' || to_char(lv_ini_trans) ||
                        ' MAXTRANS ' || to_char(lv_max_trans);
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'TABLESPACE ' || lv_tablespace_name || ' STORAGE (';
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'INITIAL ' || to_char(lv_initial_extent) ||
                           ' NEXT ' || to_char(lv_next_extent);
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := 'MINEXTENTS ' || to_char(lv_min_extents) ||
                        ' MAXEXTENTS ' || to_char(lv_max_extents) ||
                        ' PCTINCREASE ' || to_char(lv_pct_increase) || ')';
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_string := '/';
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
      lv_lineno := lv_lineno + 1;
      lv_lineno := lv_lineno + 1;
      lv_string:='                                                  ';
      write_out(lv_lineno, lv_index_owner, lv_index_name, lv_string);
   END LOOP;
   CLOSE ind_cursor;
END;
/
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\crt_indx.sql
set heading off
set recsep off
col text format a80 word_wrap
select   text
from     I_temp
order by id_owner, id_name, lineno;
spool off
drop table i_temp;
exit
 
 
An excerpt from the resulting DDL script follows:
 
DROP INDEX cspcdba.lu_ofctypview_1;
CREATE INDEX cspcdba.lu_ofctypview_1 
ON cspcdba.ofctypview (fk_viewnmviewnm,fk_viewnmviewtyp) 
PCTFREE 60 
INITRANS 2 MAXTRANS 255 
TABLESPACE DSPT_INDX STORAGE (
INITIAL 4096 NEXT 2048 
MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50)
/
DROP INDEX cspcdba.lu_ofctypview_2;
CREATE INDEX cspcdba.lu_ofctypview_2 
ON cspcdba.ofctypview (fk_ofctypofctypcd) 
PCTFREE 60 
INITRANS 2 MAXTRANS 255 
TABLESPACE DSPT_INDX STORAGE (
INITIAL 4096 NEXT 2048 
MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50)
.
.
.
DROP INDEX cspcdba.lu_eventcloitem_2;
CREATE INDEX cspcdba.lu_eventcloitem_2
ON cspcdba.eventcloitem
(fk_cloitemcloitemn,fk_cloitemfk_clofk,
fk_cloitemfk_cloc0,fk_cloitemfk_clocl)
PCTFREE 60
INITRANS 2 MAXTRANS 255
TABLESPACE DSPT_INDX STORAGE (
INITIAL 4096 NEXT 2048
MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50)
/
 
So far the “hard” database objects and a few “soft” ones have been documented. What is left are the remainder of the “soft” database objects, these being packages, package bodies, procedures, functions, triggers and views. The next script handles packages, package bodies, procedures and functions.
 
 
 
 
Documenting Sequences
 
Since sequences aren’t a part of standard SQL many people tend to overlook them, until they break. Sequences can break if you exceed their maximum (for an ascending sequence) or minimum (for a descending sequence) values or place to much stress on a single sequence. By documenting your sequences you can see how they were created and have the script to recreate them if it becomes needed. In some situations, such as import or use of SQL*loader, sequences used for key values can be come out of sync with the tables they relate to and will require resetting. Use of a documentation script can speed this process. The following script will generate the DDL to rebuild your indexes:
 
REM
REM    FUNCTION: SCRIPT FOR RE-CREATING DATABASE SEQUENCES
REM
REM              This script must be run by 'SYS'. 
REM
REM              This script is intended to run with Oracle .
REM
REM              Running this script will in turn create a script to
REM              build all the sequences in the database.  This created
REm              script is called 'crt_seq.sql'.
REM
REM              This script will start the sequence (start with value)
REM              at the last value of the sequence at the time the
REM              script is run (LAST_NUMBER).
REM
REM
REM Only preliminary testing of this script was performed.  Be sure to
REM test it completely before relying on it. 
REM
 
set verify off feedback off termout off echo off pages 0
 
set termout on
select 'Creating sequence build script...' from dual;
set termout off
 
create table seq_temp (grantor_owner varchar2(30),
text VARCHAR2(255))
/
DECLARE
   CURSOR seq_cursor IS select   sequence_owner,
                                                  sequence_name,
                                                  min_value,
                                                  max_value,
                                                 increment_by,
                                                decode(cycle_flag,'Y','CYCLE','NOCYCLE'),
                                                decode(order_flag,'Y','ORDER','NOORDER'),
            decode(to_char(cache_size),'0','NOCACHE','CACHE '||to_char(cache_size)),
                                                last_number
                                      from dba_sequences
                                    where sequence_owner not in ('SYS','SYSTEM')
                                     order by sequence_owner;
            seq_owner       dba_sequences.sequence_owner%TYPE;
            seq_name        dba_sequences.sequence_name%TYPE;
            seq_min         dba_sequences.min_value%TYPE;
            seq_max         dba_sequences.max_value%TYPE;
            seq_inc         dba_sequences.increment_by%TYPE;
            seq_order       VARCHAR2(7);
            seq_cycle       VARCHAR2(7);
            seq_cache       VARCHAR2(15);
            seq_lnum        dba_sequences.last_number%TYPE;  
            seq_string     VARCHAR2(255);
   procedure write_out(p_string VARCHAR2) is
   begin
      insert into seq_temp (grantor_owner,text)
                                    values (seq_owner,p_string);
   end;
 
BEGIN
   OPEN seq_cursor;
   LOOP
      FETCH seq_cursor INTO     seq_owner,
                                                seq_name,
                                                seq_min,
                                                seq_max,
                                                seq_inc,
                                                seq_order,
                                                seq_cycle,
                                                seq_cache,
                                                seq_lnum;
      EXIT WHEN seq_cursor%NOTFOUND;
            seq_string:=('CREATE SEQUENCE '||seq_owner||'.'||seq_name||'
                                                INCREMENT BY '||seq_inc||'
                                                START WITH '||seq_lnum||'
                                                MAXVALUE '||seq_max||'
                                                MINVALUE '||seq_min||'
                                                '||seq_cycle||'
                                                '||seq_cache||'
                                                '||seq_order||';');  
write_out(seq_string);
   END LOOP;
   CLOSE seq_cursor;
END;
/
column dbname new_value db noprint
select value dbname from v$parameter where name=‘db_name’;
spool rep_out\&db\crt_seqs.sql
break on downer skip 1
col text format a60 word_wrap
col downer noprint
select    grantor_owner downer,text
from     seq_temp
order by downer
/
spool off
drop table seq_temp;
 set termout on verify on feedback on
prompt Finished build
exit
 
Example output from the above script follwos:
 
CREATE SEQUENCE DSPTDBA.ACTSEQ                                                                      
INCREMENT BY 1                                                                                     
START WITH 27                                                                                      
MAXVALUE 999999999999999999999999999                                                               
MINVALUE 1                                                                                         
NOORDER                                                                                             
CACHE 20                                                                                           
NOCYCLE;                                                                                           
                                                                                                    
CREATE SEQUENCE DSPTDBA.CSPCSEQ                                                                    
INCREMENT BY 1                                                                                      
START WITH 217094                                                                                  
MAXVALUE 999999999999999999999999999                                                               
MINVALUE 0                                                                                          
NOORDER                                                                                            
CACHE 20                                                                                            
NOCYCLE;                                                                                           
 
The 9’s can be removed by eliminating the MAXVALUE specification, allowing it to default to the maximum value allowed.
 
Soft documentation for sequences can be generated using db_seqs.sql from Appendix A.
 
Documenting Packages, Package Bodies, Procedures and Functions
 
Under Oracle 7 PLSQL constructs can be stored in the database as stored objects. The lowest level of objects are procedures and functions. The procedures and functions in an Oracle 7 database can be grouped by application or related function into packages (and associated package bodies). All of the DDL to define PLSQL stored objects such as packages, package bodies, procedures and functions is stored in the DAB_SOURCE view and additional information on them stored in the DBA_OBJECTS view. By creating a script which joins these two tables we can re-create any of the objects mentioned above. The scripts shown below could more readily be done in PLSQL, but was deliberately done in SQL and SQLPLUS to show that it can be done. The first script is the script which does the actual work of selecting the text from the database and reconstructing the text into a command, it is called fprc_rct.sql:
 
REM
REM NAME:      FPRC_RPT.SQL
REM
REM FUNCTION: Build a script to re-create functions, procedures,
REM           packages or package bodies.
REM
REM
set termout off verify off feedback off lines 132 pages 0 heading off
set recsep off space 0
column text format a79
column line noprint
select 'create or replace '||text,line
from
            dba_source
where
            owner = upper('&&1') and
            type = upper('&&2') and
            name = upper('&&3') and
            line = 1;
select text,line
from
            dba_OBJECTS s1,
            dba_source s2
where
            s1.OBJECT_type = upper('&&2') and
            s1.owner = upper('&&1') and
            s1.object_name = upper('&&3') and
            s1.OBJECT_type = s2.type and
            s1.owner = s2.owner and
            s1.OBJECT_NAME = s2.name and
            line > 1
order by
            2;
 
This script can be run standalone by calling it with the values for owner, object type and object name. However, it was designed to be called by a reiterative program called do_fprc.sql which is created by a script run_fprc.sql, shown below:
 
REM
REM NAME                  : RUN_FPRC.SQL
REM FUNCTION            : Generate and execute the crt_fprc.sql procedure
REM USE                     : Document the procedures and packages and functions
REM               for a user or users
REM Limitations            : Must have access to dba_source and dba_objects.
rem               The FPRC_RCT.SQL procedure must be in same directory
REM
column dbname new_value db noprint
pause Use % for a wildcard - Press enter to continue
accept owner prompt 'Enter object owner:'
accept type  prompt 'Enter object type:'
accept name  prompt 'Enter object name:'
prompt Working....
set echo off heading off verify off feedback off
select value dbname from v$parameter where name='db_name';
spool rep_out\&db\do_fprc.sql
select unique('start fprc_rct '||owner||' '||'"'||type||'"'||' '||name)
from
            dba_source
where
            owner like upper('&owner') and
            type like upper('&type') and
            name like upper('&name');
spool off
set termout off
spool rep_out\&db\crt_fprc.sql
start rep_out\&db\do_fprc.sql
spool off
exit
 
This script generates a sequence of calls to fprc_rct.sql:
 
start fprc_rct CSPCDBA "PROCEDURE" ADDRMANOTE
start fprc_rct CSPCDBA "PROCEDURE" ENTERRULES
start fprc_rct CSPCDBA "PROCEDURE" ESCALATE
start fprc_rct CSPCDBA "PROCEDURE" GETDETAILDATA
start fprc_rct CSPCDBA "PROCEDURE" GETLEAST
start fprc_rct CSPCDBA "PROCEDURE" GETMAXACTSEQNBR
start fprc_rct CSPCDBA "PROCEDURE" GETSUMMARYHEADERS
start fprc_rct CSPCDBA "PROCEDURE" GET_PAWS0001
start fprc_rct CSPCDBA "PROCEDURE" INSADMINAREA
start fprc_rct CSPCDBA "PROCEDURE" INSAFFLNROL
start fprc_rct CSPCDBA "PROCEDURE" INSAUTSYSAREA
start fprc_rct CSPCDBA "PROCEDURE" INSCO
start fprc_rct CSPCDBA "PROCEDURE" INSCOORG
start fprc_rct CSPCDBA "PROCEDURE" INSCOORGAREA
start fprc_rct CSPCDBA "PROCEDURE" INSCOORGAUTH
start fprc_rct CSPCDBA "PROCEDURE" INSEMP
start fprc_rct CSPCDBA "PROCEDURE" INSERTTMPTABLE
start fprc_rct CSPCDBA "PROCEDURE" INSINDIV
start fprc_rct CSPCDBA "PROCEDURE" INSINDIVAUTH
start fprc_rct CSPCDBA "PROCEDURE" INSORGEMP
start fprc_rct CSPCDBA "PROCEDURE" INSSUM
start fprc_rct CSPCDBA "PROCEDURE" PAWS1
start fprc_rct CSPCDBA "PROCEDURE" POSTERROR
 
The script run_fprc.sql then executes the do_fprc.sql script spooling output to the crt_fprc.sql script. The crt_fprc.sql script contains the commands to rebuild the specified object or objects:
 
create or replace PROCEDURE AddRmaNote (       
            hv_eventid         IN event.evntid%TYPE,
            hv_actseqnbr     IN act.actseqnbr%TYPE,  
            hv_user             IN INDIV.CUIDNBR%TYPE,  
            hv_notes           IN VARCHAR2,       
            hv_status          IN OUT NUMBER     
            )    
AS    
            leid       NUMBER(10);  
            date1    DATE;       
BEGIN 
            hv_status := 0;   
            leid := 0;        
            BEGIN
            SELECT FK_LELEID INTO leid FROM INDIV WHERE ( CUIDNBR = hv_user );        
            EXCEPTION WHEN OTHERS THEN  
               hv_status := 1;
            END; 
            SELECT SYSDATE INTO date1 FROM dual; 
            IF ( leid > 0 ) THEN        
            INSERT INTO actvy_rmk ( ACTVYRMKDT, ACTVYRMKTXT, FK_ACTFK_EVENTEVNT,
                        FK_ACTACTSEQNBR, FK_INDIVFK_LELEID )  
            VALUES ( date1, hv_notes, hv_eventid, hv_actseqnbr, leid );  
            END IF;  
            COMMIT;  
END AddRmaNote;
/    
.
.
.
create or replace PROCEDURE wkgrp_usr (        
            hv_wrkgrp          IN COORG.COORGCD%TYPE, 
            hv_grptyp          IN COORG.COORGTYPCD%TYPE,  
            hv_status          IN OUT NUMBER     
            )    
AS    
   CURSOR wkgrpusr_cur is
            SELECT indiv.CUIDNBR, indiv.INDIVLASTNM, indiv.INDIVFRSTNM 
   FROM orgemp, emp, afflnrol, le, indiv       
   WHERE (
            indiv.FK_LELEID = le.leid AND    
            le.letypcd = 'EMP' AND      
            afflnrol.FK_LELEID = le.leid AND 
            emp.FK_AFFLNROLAFFLNRO = afflnrol.AFFLNROLCD AND        
            emp.FK_AFFLNROLFK_AFFL = afflnrol.FK_AFFLNAFFLNID AND   
            emp.FK_AFFLNROLFK_LELE = afflnrol.FK_LELEID AND
            orgemp.FK_EMPEMPSSN = emp.EMPSSN AND 
            orgemp.FK_COORGCOORGTYPCD = hv_grptyp AND     
            orgemp.FK_COORGCOORGCD = hv_wrkgrp   
            );   
   id       indiv.CUIDNBR%TYPE;    
   fnm indiv.INDIVFRSTNM%TYPE;    
   lnm indiv.INDIVLASTNM%TYPE;    
BEGIN 
   hv_status := 0; 
   BEGIN  
   OPEN wkgrpusr_cur;        
   FETCH wkgrpusr_cur INTO id, lnm, fnm;       
   WHILE NOT wkgrpusr_cur%NOTFOUND LOOP        
   INSERT INTO user_table (cuid,lastnm,frstnm) VALUES (id,lnm,fnm );  
   FETCH wkgrpusr_cur INTO id, lnm, fnm;       
   END LOOP;       
   CLOSE wkgrpusr_cur;       
   END;   
   COMMIT;
END wkgrp_usr;     
 
The crt_fprc.sql script, with minor editing if the developers tended to string commands over several lines, can then be executed to rebuild whichever object or objects it was invoked to recreate. If the DBA doesn’t mind editing the crt_fprc.sql the code for the packages, package bodies, procedures or functions can be modified to be more readable and to adhere to any coding standards. In shops where many developers may be working on a single project, the diversity of coding techniques can be astounding, this allows the DBA to enforce some semblance of order on the code in the database.
 
 
 
Documenting Triggers
 
Triggers are used to enforce referential integrity constraints, enforce snapshot logic, provide updates on calculated table values and a number of other database functions. The triggers required for constraint enforcement and snapshot upkeep are automatically generated by the Oracle kernel. The following script will recreate the triggers in the database. It is suggested that all triggers created by other than Oracle processes be preceded by some unique set of characters so that the script can be made more selective to ignore the Oracle created triggers.
 
REM  trig_rct.sql
REM
REM    FUNCTION: SCRIPT FOR RE-CREATING DATABASE TRIGGERS
REM
REM           This script can be run by anyone with access to dba_ views
REM
REM              This script is intended to run with Oracle .
REM
REM              Running this script will in turn create a script to
REM              build all the triggers in the database.  This created
REM              script is called 'crt_trig.sql'.
REM
REM              Only preliminary testing of this script was performed. 
REM              Be sure to test it completely before relying on it. 
REM
REM M. Ault 3/29/96 TRECOM
REM
set verify off feedback off termout off echo off pages 0 long 4000
 
set termout on
select 'Creating trigger build script...' from dual;
set termout off
 
create table trig_temp (owner varchar2(30),
                                    trigger_name varchar2(30),
                                    trigger_type varchar2(16),
                                    triggering_event varchar2(26),
                                    table_owner varchar2(30),
                                    table_name varchar2(30),
                                    referencing_names varchar2(87),
                                    when_clause varchar2(2000),
                                    trigger_body long,
                                    trigger_columns varchar2(400)) ;
 
DECLARE
   CURSOR trig_cursor IS select owner,
                                                trigger_name,
                                                trigger_type ,
                                                triggering_event,
                                                'on '||table_owner,
                                                table_name,
                                                referencing_names,
                                                'when '||when_clause,
                                                trigger_body
                                    from dba_triggers
                                    where owner not in ('SYS','SYSTEM')
                                    order by owner;
            cursor trig_col is select trigger_owner,
                                                trigger_name,
                                                column_name
                                    from dba_trigger_cols
                                    where trigger_owner not in ('SYS','SYSTEM')
                                    order by trigger_owner, trigger_name;
            trig_owner         dba_triggers.owner%TYPE;
            trig_name          dba_triggers.trigger_name%TYPE;
            trig_type            dba_triggers.trigger_type%TYPE;
            trig_event          dba_triggers.triggering_event%TYPE;
            trig_towner        dba_triggers.table_owner%TYPE;
            trig_tname         dba_triggers.table_name%TYPE;
            trig_rnames       dba_triggers.referencing_names%TYPE;
            trig_wclause      dba_triggers.when_clause%TYPE;
            trig_body           dba_triggers.trigger_body%TYPE;  
            trig_col_own      dba_trigger_cols.trigger_owner%TYPE;
            trig_col_nam      dba_trigger_cols.trigger_name%TYPE;
            trig_column       dba_trigger_cols.column_name%TYPE;  
            all_columns       varchar2(400);
            counter             integer:=0;
 
BEGIN
   OPEN trig_cursor;
   LOOP
      FETCH trig_cursor INTO    trig_owner,
                                                trig_name,
                                                trig_type,
                                                trig_event,
                                                trig_towner,
                                                trig_tname,
                                                trig_rnames,
                                                trig_wclause,
                                                trig_body;
      EXIT WHEN trig_cursor%NOTFOUND;
            all_columns :='';      
            counter := 0;
            open trig_col;
            loop
                        fetch trig_col into     trig_col_own,
                                                            trig_col_nam,
                                                            trig_column;
            exit when trig_col%NOTFOUND;
                        if trig_owner = trig_col_own and trig_name=trig_col_nam
                        then
                                    counter := counter+1;
                                    if counter = 1 then
                                    all_columns := ' of '||all_columns||trig_column;
                                    else
                                    all_columns := all_columns||', '||trig_column;
                                    end if;
                        end if;
            end loop;
            close trig_col;
            if trig_rnames = 'REFERENCING NEW AS NEW OLD AS OLD' then
                        trig_rnames := '';
            end if;
            if trig_wclause = 'when ' then
                        trig_wclause := '';
            end if;
            insert into trig_temp values (trig_owner,
                                                trig_name,
                                                trig_type,
                                                trig_event,
                                                trig_towner,
                                                trig_tname,
                                                trig_rnames,
                                                trig_wclause,
                                                trig_body,
                                                all_columns);
   END LOOP;
   CLOSE trig_cursor;
   commit;
END;
/
 
spool rep_out\crt_trgs.sql
set heading off
set recsep off
 
select 'create trigger '||owner||'.'||trigger_name||'
'||decode(trigger_type,'BEFORE EACH ROW','BEFORE ',
                        'AFTER EACH ROW','AFTER ',trigger_type)||triggering_event||'
'||trigger_columns||'
'||table_owner||'.'||table_name||'
'||referencing_names||'
'||decode(trigger_type,'BEFORE EACH ROW','ON EACH ROW',
                        'AFTER EACH ROW','ON EACH ROW','')||'
'||when_clause,
trigger_body,' '||'
/'||'
'
from trig_temp
order by owner;
spool off
drop table trig_temp;  
exit
 
The output from this script may require more editing than the other scripts shown thus far. An example of its output follows:
 
create trigger CSPCDBA.ACTPER_BEFORE_INSERT                                             
BEFORE INSERT OR UPDATE                                                                            
 of FK_AUTOSYSAUTOSYSC, FK_AUTOSYSAUTOSYSV                                                         
on CSPCDBA.ACTPER                                                                                                                                                                             
ON EACH ROW                                                                                        
when     NEW.FK_AUTOSYSAUTOSYSV IS NOT NULL   
            AND NEW.FK_AUTOSYSAUTOSYSC IS NOT NULL                  
DECLARE                                                                                             
    DUMMY INTEGER;                                                               /                 
  BEGIN                                                                                             
    DUMMY := 0;                                                                                    
    SELECT COUNT(*) INTO DUMMY                                                                     
       FROM AUTOSYS                                                                                 
       WHERE     AUTOSYS.AUTOSYSVRSNNBR = :NEW.FK_AUTOSYSAUTOSYSV        
                        AND AUTOSYS.AUTOSYSCD = :NEW.FK_AUTOSYSAUTOSYSC;                                                             
    IF DUMMY = 0 THEN                                                                              
        RAISE_APPLICATION_ERROR(-20000, 'INVALID FOREIGN KEY');                                    
    END IF;                                                                                         
END;