< previous page page_198 next page > Page 198 SET PAGESIZE 14 SET LONG 80 SET LINESIZE 80 Notes about the LIST_COLUMNS script The first part of the script simply initializes a few settings and clears any previously existing column and break settings. The next part of the script, which is a bit more complex, breaks the first argument up so the owner's name and the table name are in separate substitution variables. The script is intended to be invoked like this: @LIST_COLUMNS [owner. ] table_name The first argument, referenced by &&1, will contain a table name that is possibly qualified with an owner name. The first task of the script is to interpret this first argument. The following DECODE statement will return the table owner, if one was specified: DECODE(INSTR(&&1,.), 0,USER, /*Default to current user.*/ UPPER(SUBSTR(&&1,1,INSTR(&&1,.)-1))) owner_name, If no period is contained in the argument, then no owner name was explicitly specified, and DECODE will call the USER function to return the name of the currently logged on user. If a table owner was specified, the position of the period will be non-zero, causing DECODE to return the value of the last expression, which uses SUBSTR to return all characters before the period as the owner. A similar approach is taken to get the table name: DECODE(INSTR(&&1,.), 0,UPPER(&&1), /*Only the table name was passed in.*/ UPPER (SUBSTR (&&1, INSTR(&&1,.)+1))) table_name The issue here is that if a period was specified, the table name follows the period. Otherwise, the entire string is taken as the table name. The table name and owner name are selected from the DUAL table in order to get them into the substitution variables s_table_name and s_owner_name. The NEW_VALUE clause of the COLUMN command makes this happen. The next part of the script is the SELECT statement, which returns information about each column in the table. The only complicated part of this query is the DECODE statement that starts out like this: DECODE (data_type, VARCHAR2, VARCHAR2 ( ¦¦ TO_CHAR(data_length) ¦¦ ), NVARCHAR2, NVARCHAR2 ( ¦¦ TO_CHAR(data_length) ¦¦ ), CHAR, CHAR ( ¦¦ TO_CHAR(data_length) ¦¦ ), NCHAR, NCHAR ( ¦¦ TO_CHAR(data_length) ¦¦ ) , NUMBER, DECODE (data_precision, < previous page page_198 next page > < previous page page_199 next page > Page 199 NULL, NUMBER, NUMBER ( ¦¦ TO_CHAR(data_precision) ¦¦ , ¦¦ TO_CHAR(data_scale) ¦¦ )), The long DECODE expression exists because some datatypes have a length associated with them, some have a precision and scale, and some have neither. This DECODE statement contains one expression for each possible datatype, and that expression returns the appropriate information for that datatype. Consider the VARCHAR2 datatype, for example. All VARCHAR2 columns have a length associated with them. In order to display that length, the following two expressions are included in the DECODE statement: VARCHAR2, VARCHAR2 ( ¦¦ TO_CHAR(data_length) ¦¦ ) The two expressions are separated by commas. The first expression evaluates to the string VARCHAR2. When DECODE is evaluating a datatype that matches that string, it will return the value of the second expression in the pair. In the case of VARCHAR2, that second expression is: VARCHAR2 ( ¦¦ TO_CHAR(data_length) ¦¦ ) As you can see, this second expression concatenates the string VARCHAR2 ( with the length, then follows that with a closing parentheses. The result will be a string resembling the one shown here: VARCHAR2 (40) The NUMBER and FLOAT datatypes add a bit more complexity. A NUMBER, for example, can be defined as floating- point or fixed-point. Floating-point numbers have null values for data_precision and data_scale. If a NUMBER field is floating-point, the data_precision will be null, and the nested DECODE will return just NUMBER as the datatype. Otherwise, the nested DECODE will return NUMBER (precision, scale). A second SELECT is done to return any default values defined for columns in the table. This is done in a separate query primarily because some versions of SQL*Plus delivered with early versions of Oracle8 (notably 8.0.3) generated errors whenever a LONG column contained a null value. The query in the preceding script avoids this problem by specifying WHERE data_default IS NOT NULL in the WHERE clause. Running List_Columns To run LIST_COLUMNS, execute the script from SQL*Plus and specify the table name as a command-line argument. You may optionally qualify the table name with an owner by using standard dot notation. The following example shows how to list columns for a table you own. < previous page page_199 next page > < previous page page_200 next page > Page 200 SQL> @list_columns employee COLUMN LISTING FOR JEFF.EMPLOYEE Column Name Data Type Nullable? EMPLOYEE_ID NUMBER NOT NULL EMPLOYEE_NAME VARCHAR2 (40) EMPLOYEE_HIRE_DATE DATE EMPLOYEE_TERMINATION_DATE DATE EMPLOYEE_BILLING_RATE NUMBER DEFAULT VALUES FOR JEFF.EMPLOYEE Column Name Default Value EMPLOYEE_HIRE_DATE sysdate The following example shows how you can look at the column definitions for a table owned by another user, in this case the SYS user: SQL> @list_columns sys.dual COLUMN LISTING FOR SYS.DUAL Column Name Data Type Nullable? DUMMY VARCHAR2 (1) Table Constraints Information about constraints can be obtained from two data dictionary views: ALL_CONSTRAINTS and ALL_CONS_COLUMNS. ALL_CONSTRAINTS returns one row for each constraint, and is the only view you need to look at for the definition of a check constraint. Foreign key, primary key, and unique constraints are defined on one or more columns, so for these there is a one-to-many relationship between ALL_CONSTRAINTS and ALL_CONS_COLUMNS. Foreign keys are the most complicated, because to get a complete picture of a foreign key constraint, you need to join the ALL_CONS_COLUMNS table to itself, then refer back to the ALL_CONSTRAINTS view again. The reason for this is that a foreign key may be attached to either a primary key constraint or a unique constraint. It's important to know which of the columns in the parent table matter. Different Constraint Types Need Different Queries There are four different types of constraints that can be created on a table. The four types are: CHECK PRIMARY KEY < previous page page_200 next page > < previous page page_201 next page > Page 201 UNIQUE FOREIGN KEY These types are different enough that, with the exception of the PRIMARY KEY and UNIQUE constraints, you need a slightly different query for each in order to see the definition. CHECK constraints A check constraint is simply an expression that must be true for each row in a table. This is the simplest of the constraint types when it comes to querying the data dictionary tables. The check expression is stored in the SEARCH_CONDITION column of the ALL_CONSTRAINTS table. The following query will get you the definition of all CHECK constraints on a particular table: SELECT constraint_name, search_condition FROM all_constraints WHERE owner = username AND table_name = table_name AND constraint_type = C As you can see, the query is very simple. You don't even have to join any tables. With the other constraint types, the query gets more complex. PRIMARY KEY and UNIQUE constraints Primary key and unique constraints are similar in that they both force each row in a table to have a unique value in one column or combination of columns. The only difference between the two is semantics. When looking at constraints of these two types, you need to include the ALL_CONS_COLUMNS view in your query in order to get a list of the columns involved. The following query shows how to do this: SELECT ac.constraint_name, ac.constraint_type, ac.table_name, acc.column_name FROM all_constraints ac, all_cons_columns acc WHERE ac.owner = username AND ac.table_name = table_name AND ac.constraint_type in (P,U) AND ac.constraint_name = acc.constraint_name AND ac.owner = acc.owner ORDER BY ac.constraint_name, acc.position Ordering the columns in the constraint definition by the POSITION column is done so the output matches the column order used when originally defining a constraint. Oracle enforces unique and primary key constraints by creating unique indexes. The column order used when creating the indexes will match that used in defining the constraints, and can affect the performance of queries issued against the table. < previous page page_201 next page > < previous page page_202 next page > Page 202 Foreign Key constraints Foreign key constraints are the most complex. A foreign key defines a list of columns in one table, called the child table, that correlates to either a primary key or a unique constraint on a parent table. When a row is inserted into the child table, Oracle checks to be sure a corresponding parent record exists. Foreign key constraints involve two lists of columns, one in the child table on which the constraint is defined, and another in the parent table. The trick with foreign key constraints is to first find the name of the parent table, then find the names of the columns in the parent table that correspond to the columns in the child table. The key to doing this is to use the R_OWNER and R_CONSTRAINT_NAME columns in the ALL_CONSTRAINTS table. The constraint type code for foreign key constraints is R. A foreign key always relates to either a primary key constraint or a unique constraint on the parent table. The name of this related constraint will be in the R_CONSTRAINT_NAME column. Usually, the R_OWNER column will match the OWNER column, but that doesn't have to be the case. To see the definition of all the foreign key constraints for a given table, you can start with the query used for primary key constraints, and modify it to look only at constraint type R: SELECT ac.constraint_name, ac.constraint_type, ac.owner, ac.table_name, acc.column_name FROM all_constraints ac, all_cons_columns acc WHERE ac.owner = username AND ac.table_name = table_name AND ac.constraint_type = R AND ac.constraint_name = acc.constraint_name AND ac.owner = acc.owner ORDER BY ac.constraint_name, acc.position This will give you the constraint name, the table name, and a list of column names. It won't tell you the name of the parent table or the names of the corresponding columns in that table. For that, you need to join ALL_CONSTRAINTS to itself via the R_CONSTRAINT_NAME and R_OWNER columns. This will give you access to the parent table's name. SELECT ac.constraint_name, ac.constraint_type, ac.owner, ac.table_name, acc.column_name, r_ac.owner, r_ac.table_name FROM all_constraints ac, all_cons_columns acc, all_constraints r_ac WHERE ac.owner = username AND ac.table_name = table_name AND ac.constraint_type = R < previous page page_202 next page > < previous page page_203 next page > Page 203 AND ac.constraint_name = acc.constraint_name AND ac.owner = acc.owner AND ac.r_owner = r_ac.owner AND ac.r_constraint_name = r_ac. constraint_name ORDER BY ac.constraint_Name, acc.position Since most foreign key constraints relate to the parent table's primary key, you may want to just stop here. However, since it is possible to relate a foreign key to a unique key on the parent table, you may want see the corresponding list of parent table columns in order to fully understand the constraint. To do this, you must join with ALL_CONS_COLUMNS once again and pick up the columns that go with the related parent table constraint. For example: SELECT ac.constraint_name, ac.constraint_type, ac.owner, ac.table_name, acc.column_name, r_ac.owner, r_ac.table_name,r_acc.column_name FROM all_constraints ac, all_cons_columns acc, all_constraints r_ac, all_cons_columns r_acc WHERE ac.owner = username AND ac. table_name = table_name AND ac.constraint_type = R AND ac.owner = acc.owner AND ac.constraint_name = acc.constraint_name AND ac.r_owner = r_ac.owner AND ac.r_constraint_name = r_ac.constraint_name AND r_ac.owner = r_acc.owner AND r_ac.constraint_name = r_acc.constraint_name AND acc.position = r_acc.position ORDER BY ac.constraint_name, acc.position Notice that the ALL_CONS_COLUMN table's POSITION column forms part of the join criteria. This ensures that matching columns are output together, on the same line. You will see how this works in the next section, which presents a script that will list all constraints defined for a table. Listing the Constraints for a Table With the three queries shown in the previous section, you have a good start on a script to show all constraints defined on a table. Start by putting the three queries in a script, then qualify each with a table name. Use a substitution variable for the table name so you can pass it to the script as a parameter. Next, format the results with some combination of the COLUMN, BREAK, and TTITLE commands. The LIST_CONSTRAINTS script, shown next, shows one possible approach you can take. < previous page page_203 next page > < previous page page_204 next page > Page 204 The LIST_CONSTRAINTS.SQL script DESCRIPTION List all the constraints on a table. INPUTS Param 1 A table name, optionally qualified by an owner name. For example: SYS.ALL_TAB_COLUMNS. SET ECHO OFF SET NEWPAGE 1 SET VERIFY OFF SET FEEDBACK OFF SET HEADING OFF SET PAGESIZE 9999 SET RECSEP OFF SET CONCAT ON CLEAR BREAKS CLEAR COMPUTES CLEAR COLUMNS TTITLE OFF BTITLE OFF Dissect the input argument, and get the owner name and table name into two seperate substitution variables. The owner name defaults to the current user. DEFINE s_owner_name = DEFINE s_table_name = COLUMN owner_name NOPRINT NEW_VALUE s_owner_name COLUMN table_name NOPRINT NEW_VALUE s_table_name SELECT DECODE(INSTR(&&1, .), 0,USER, /*Default to current user.*/ UPPER(SUBSTR(&&1,1,INSTR(&&1,.)-1))) owner_name, DECODE(INSTR(&&1,.), 0,UPPER(&&1), /*Only the table name was passed in.*/ UPPER(SUBSTR(&&1, INSTR(&&1,.)+1))) table_name FROM dual; Clear column definitions so that no future queries affect the substitution variables that were just set. CLEAR COLUMNS Format the columns COLUMN constraint_name NOPRINT NEW_VALUE constraint_name_var COLUMN constraint_type_desc NOPRINT NEW_VALUE constraint_type_var COLUMN r_owner NOPRINT NEW_VALUE r_owner_var COLUMN r_table_name NOPRINT NEW_VALUE r_table_name_var COLUMN status NOPRINT NEW_VALUE status_var COLUMN delete_rule_desc NOPRINT NEW_VALUE delete_rule_var COLUMN search_condition FORMAT A50 WORD_WRAPPED COLUMN column_name FORMAT A30 COLUMN r_column_name FORMAT A30 < previous page page_204 next page > < previous page page_205 next page > Page 205 This script breaks on each constraint, and each constraint has its own page title. The page title shows information relating to the constraint as a whole. BREAK ON constraint_name SKIP PAGE Tell the user he is going to see constraints. PROMPT CONSTRAINTS ON TABLE &&s_owner_name &&s_table_name First show the primary key and unique constraints. Set up the page title. TTITLE LEFT constraint_name_var constraint_type_var status_var SKIP 1 Run the query to display primary key and unique constraints. SELECT ac.constraint_name, DECODE(ac.constraint_type, P, PRIMARY KEY,U,UNIQUE) constraint_type_desc, table_name, indent, acc.column_name, ac.status FROM all_constraints ac, all_cons_columns acc WHERE ac.owner=&&s_owner_name AND ac.table_name = &&s_table_name AND ac.owner =acc.owner AND ac.constraint_name = acc.constraint_name AND ac.constraint_type IN (P, U,) ORDER BY DECODE(ac.constraint_type, P,1,U,2), ac.constraint_name, acc.position; Then show the foreign key constraints Set up the page title for the foreign key constraints TTITLE LEFT constraint_name_var status_var SKIP 1 - FOREIGN KEY TO r_owner_var . r_table_name_var SKIP 1 Run the query to show foreign key constraints SELECT ac.constraint_name, indent, acc.column_name, r_acc.owner r_owner, r_acc.table_name r_table_name, r_acc.column_name r_column_name FROM all_constraints ac, all_cons_columns acc, all_cons_columns r_acc WHERE ac.owner=&&s_owner_name and ac.table_name = &&s_table_name AND ac.constraint_type = R AND ac.owner = acc.owner < previous page page_205 next page > < previous page page_206 next page > Page 206 AND ac.constraint_name = acc.constraint_name AND ac.r_owner = r_acc.owner AND ac.r_constraint_name = r_acc.constraint_name AND acc.position = r_acc.position ORDER BY ac.constraint_name, acc.position; Then show the check constraints Set up the page title for the check constraints TTITLE LEFT constraint_name_var CHECK status_var SKIP 1 SELECT ac.constraint_name, indent, search_condition FROM all_constraints ac WHERE ac.owner = &&s_owner_name AND ac.table_name = &&s_table_name AND ac.constraint_type = C; Undefine variables and restore settings to their defaults. UNDEFINE s_owner_name UNDEFINE s_table_name SET NEWPAGE 1 SET VERIFY ON SET FEEDBACK ON SET HEADING ON SET PAGESIZE 14 SET RECSEP WRAPPED CLEAR BREAKS CLEAR COMPUTES CLEAR COLUMNS TTITLE OFF Running the LIST_CONSTRAINTS script You can show all the constraints on a table by invoking the LIST_CONSTRAINTS script and providing the table name as the first argument. The following example shows the constraints defined on the PROJECT_HOURS table: SQL> @list_constraints project_hours CONSTRAINTS ON TABLE JEFF.PROJECT_HOURS PROJECT_HOURS_PK PRIMARY KEY ENABLED PROJECT_ID EMPLOYEE_ID TIME_LOG_DATE PROJECT_HOURS_UNIQUE UNIQUE ENABLED PROJECT_ID EMPLOYEE_ID TIME_LOG_DATE HOURS_LOGGED DOLLARS_CHARGE . another in the parent table. The trick with foreign key constraints is to first find the name of the parent table, then find the names of the columns in the parent table that correspond to the. specified, the table name follows the period. Otherwise, the entire string is taken as the table name. The table name and owner name are selected from the DUAL table in order to get them into the substitution. expression concatenates the string VARCHAR2 ( with the length, then follows that with a closing parentheses. The result will be a string resembling the one shown here: VARCHAR2 (40) The NUMBER and FLOAT