Exploration and Summary of Missing Indexes for Foreign Keys in ORACLE

In an ORACLE database, when a foreign key constraint is defined, ORACLE does not automatically create the corresponding index; it must manually create the index on the columns associated with the foreign key constraint.Is it necessary to create an index on a foreign key field?If necessary, when patrolling, how can I find out the related tables on foreign key fields that do not have indexes created and generate scripts for the corresponding indexes?

 

Foreign Key Missing Index Impact

 

Missing an index on a foreign key column can cause three problems, limiting concurrency, affecting performance, and possibly causing deadlocks.So for most scenarios, we should try to create an index on the foreign key

 

1. Influences performance.If the child table foreign key does not create an index, the child table will be scanned in full when the parent table queries the associated child table.Affects how tables are joined.

2. Affect concurrency.Whether updating the primary key of the parent table or deleting a parent record, a table lock is added to the child table (no modifications to the child table are allowed until this statement is complete).This will be unnecessary

Lock more rows, affecting concurrency

3: Deadlock may also occur under special circumstances.

 

Let's start with a simple example of whether a subtable scans a full table when a foreign key is missing an index, as shown below, where table EMP has a primary foreign key relationship to DEPT:

SQL> set autotrace on;
SQL> 
SQL> SELECT  D.DEPTNO, COUNT(*)
  2  FROM SCOTT.EMP E INNER JOIN SCOTT.DEPT D ON E.DEPTNO =D.DEPTNO
  3  GROUP BY D.DEPTNO;
 
    DEPTNO   COUNT(*)
---------- ----------
        30          6
        20          5
        10          3
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4067220884
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |     9 |     4  (25)| 00:00:01 |
|   1 |  HASH GROUP BY     |      |     3 |     9 |     4  (25)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| EMP  |    14 |    42 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("E"."DEPTNO" IS NOT NULL)
 
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        665  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          3  rows processed

 

As shown above, when a foreign key field does not have an index, the parent table scans the child table when it is associated with a child table. Below, I can avoid scanning the child table once the foreign key field has been indexed.

 

CREATE INDEX SCOTT.IX_DEPTNO ON SCOTT.EMP ("DEPTNO") TABLESPACE USERS;

 

 

 

Of course, the amount of data in these two tables is really too small, the performance difference is not big, when the amount of data increases, the performance difference will be more obvious.As an example below, we construct a parent and child table with a relatively large amount of data:

 

create table parent_tb_test 
(
    id         number(10),
    name    varchar2(32),
    constraint pk_parent_tb_test primary key(id)
);
 
create table child_tb_test
(
     c_id   number(10),
     f_id        number(10),
     child_name    varchar2(32),
     constraint pk_child_tb_test primary key(c_id),
     foreign key(f_id) references parent_tb_test
);
 
 
begin
    
for index_num in 1 .. 10000 loop
    insert into parent_tb_test
    select index_num , 'kerry' || to_char(index_num) from dual;
    
    if mod(index_num,100) = 0 then
        commit;
    end if;
end loop;
 
     commit;
    
end;
/
 
declare index_num number :=1;
begin
 
    for index_parent  in 1 .. 10000 loop
        for index_child in 1 .. 1000 loop
             insert into child_tb_test
             select index_num, index_parent, 'child' || to_char(index_child) from dual;
             
             index_num := index_num +1;
             if mod(index_child,1000) = 0 then
                 commit;
             end if;
        end loop;
    end loop;
 
    commit;
end;
/
 
 
SQL> execute dbms_stats.gather_table_stats(ownname => 'TEST', tabname =>'PARENT_TB_TEST', estimate_percent =>DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR ALL COLUMNS SIZE AUTO');
 
PL/SQL procedure successfully completed.
 
SQL> execute dbms_stats.gather_table_stats(ownname => 'TEST', tabname =>'CHILD_TB_TEST', estimate_percent =>DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR ALL COLUMNS SIZE AUTO');
 
PL/SQL procedure successfully completed.
 
SQL>

The above script constructs examples and data for testing, so let's compare the foreign keys to see if they have an index:

 

SQL> set linesize 1200
SQL> set autotrace traceonly
SQL> select p.id , p.name,c.child_name
  2  from test.parent_tb_test p
  3  inner join test.child_tb_test c on p.id = c.f_id 
  4  where p.id=1000;
 
1000 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 901213199
 
--------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                   |  1009 | 44396 |  4706  (21)| 00:00:07 |
|   1 |  NESTED LOOPS                |                   |  1009 | 44396 |  4706  (21)| 00:00:07 |
|   2 |   TABLE ACCESS BY INDEX ROWID| PARENT_TB_TEST    |     1 |    31 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | PK_PARENT_TB_TEST |     1 |       |     1   (0)| 00:00:01 |
|*  4 |   TABLE ACCESS FULL          | CHILD_TB_TEST     |  1009 | 13117 |  4705  (21)| 00:00:07 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("P"."ID"=1000)
   4 - filter("C"."F_ID"=1000)
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      32855  consistent gets
      32772  physical reads
          0  redo size
      29668  bytes sent via SQL*Net to client
       1218  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1000  rows processed
 
SQL> 

 

After creating the index, let's look at its execution plan again and note the differences between the execution plans before and after the index is created, as follows:

 

SQL> create index ix_child_tb_test on child_tb_test(f_id);
 
SQL> set linesize 1200
SQL> set autotrace traceonly
SQL> select p.id , p.name,c.child_name
  2  from test.parent_tb_test p
  3  inner join test.child_tb_test c on p.id = c.f_id 
  4  where p.id=1000;

 

Next, let's look at how missing foreign key indexes affect concurrency and cause deadlocks. As shown below, create tables dead_lock_parent and dead_lock_foreign, which have a primary foreign key relationship, and distribute and insert two test data:

 

SQL> create table dead_lock_parent( id number primary key, name varchar2(32));
 
Table created.
 
SQL> create table dead_lock_foreign(fid  number, fname varchar2(32), foreign key(fid) references dead_lock_parent);
 
Table created.
 
SQL> insert into dead_lock_parent values( 1, 'kerry');
 
1 row created.
 
SQL> insert into dead_lock_foreign values(1, 'kerry_fk');  
 
1 row created.
 
SQL> insert into dead_lock_parent values(2, 'jimmy');
 
1 row created.
 
SQL> insert into dead_lock_foreign values(2, 'jimmy_fk');
 
1 row created.
 
SQL> commit;
 
Commit complete.
 
SQL> 

 

1: Execute the following SQL statements in Session 1 (Session ID 789):

 

SQL> show user;
USER by "TEST"
SQL> select * from v$mystat where rownum=1;
 
       SID STATISTIC#      VALUE
---------- ---------- ----------
       789          0          1
 
SQL> delete from dead_lock_foreign where fid=1;
 
One row has been deleted.
 

 

2: Execute the following SQL statements in Session 2 (Session ID 766):

 

SQL> show user;
USER is "TEST"
SQL> select * from v$mystat where rownum=1;
 
       SID STATISTIC#      VALUE
---------- ---------- ----------
       766          0          1
 
SQL> delete from dead_lock_foreign where fid=2;
 
1 row deleted.

 

 

3: Next, delete the record with ID 1 in dead_lock_parent in session 1 (session ID 789):

SQL> delete from dead_lock_parent where id=1;

 

 

You will find that the session is blocked and we can use the following SQL to query for specific blocking information.

COL MODE_HELD FOR A14;
COL LOCK_TYPE FOR A8;
COL MODE_REQUESTED FOR A10;
COL OBJECT_TYPE FOR A14;
COL OBJECT_NAME FOR A20;
SELECT LK.SID,
       DECODE(LK.TYPE,
              'TX',
              'Transaction',
              'TM',
              'DML',
              'UL',
              'PL/SQL User Lock',
              LK.TYPE) LOCK_TYPE,
       DECODE(LK.LMODE,
              0,
              'None',
              1,
              'Null',
              2,
              'Row-S (SS)',
              3,
              'Row-X (SX)',
              4,
              'Share',
              5,
              'S/Row-X (SSX)',
              6,
              'Exclusive',
              TO_CHAR(LK.LMODE)) MODE_HELD,
       DECODE(LK.REQUEST,
              0,
              'None',
              1,
              'Null',
              2,
              'Row-S (SS)',
              3,
              'Row-X (SX)',
              4,
              'Share',
              5,
              'S/Row-X (SSX)',
              6,
              'Exclusive',
              TO_CHAR(LK.REQUEST)) MODE_REQUESTED, 
       OB.OBJECT_TYPE,
       OB.OBJECT_NAME,
       LK.BLOCK,
       SE.LOCKWAIT
  FROM V$LOCK LK, DBA_OBJECTS OB, V$SESSION SE
 WHERE LK.TYPE IN ('TM', 'UL')
   AND LK.SID = SE.SID
   AND LK.ID1 = OB.OBJECT_ID(+)
 AND SE.SID IN (766,789)
 ORDER BY SID;

 

If you don't understand the above information, you can look at the script below and be sure you can see it in more detail.

SQL> SELECT S.SID                             SID, 
         S.USERNAME                        USERNAME, 
         S.MACHINE                         MACHINE, 
         L.TYPE                            TYPE, 
         O.OBJECT_NAME                     OBJECT_NAME, 
         DECODE(L.LMODE, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Sh/Row Exlusive', 
                         6, 'Exclusive')   lmode, 
    DECODE(L.REQUEST, 0, 'None', 
                           1, 'Null', 
                           2, 'Row Share', 
                           3, 'Row Exlusive', 
                           4, 'Share', 
                           5, 'Sh/Row Exlusive', 
                           6, 'Exclusive') request, 
         L.BLOCK                           BLOCK 
  FROM   V$LOCK L, 
         V$SESSION S, 
         DBA_OBJECTS O 
  WHERE  L.SID = S.SID 
         AND USERNAME != 'SYSTEM' 
         AND O.OBJECT_ID(+) = L.ID1 
         AND S.SID IN ( 766,789) 
  ORDER  BY S.SID; 
 
       SID USERNAME MACHINE        TY OBJECT_NAME          LMODE           REQUEST         BLOCK
---------- -------- -------------- -- -------------------- --------------- --------------- -----
       766 TEST     XXXX\GET253194 TX                      Exclusive       None                0
       766 TEST     XXXX\GET253194 TM DEAD_LOCK_FOREIGN    Row Exlusive    None                1
       766 TEST     XXXX\GET253194 TM DEAD_LOCK_PARENT     Row Exlusive    None                0
       789 TEST     DB-Server.loca TX                      Exclusive       None                0
                    ldomain
 
       789 TEST     DB-Server.loca TM DEAD_LOCK_PARENT     Row Exlusive    None                0
                    ldomain
 
       789 TEST     DB-Server.loca TM DEAD_LOCK_FOREIGN    Row Exlusive    Sh/Row Exlusive     0
                    ldomain    

Next execute the following SQL in Session 2, deleting the record with id=2 in the main table

SQL> delete from dead_lock_parent where id=2;

 

You will find Deadlock in Session 1

 

 

If you create an index on a foreign key field, there will be no deadlock in this case.Not to be repeated here.Interested in testing.

 

Foreign Key Indexing Tips

 

Although increasing the index may incur additional performance overhead (increased DML operation overhead) and disk space overhead, these additional overhead can actually be ignored in comparison to the performance improvements it brings.If there are no other special cases, it is recommended that all foreign key fields be indexed.This book on the programming art of Oracle Oracle Database 9i/10g/11g describes when it is not necessary to index foreign keys. The following three conditions must be met:

1: Rows in the parent table will not be deleted.

2: The unique/primary key field values of the parent table will not be updated, intentionally or unintentionally.

3: You will not join from the parent table to the child table, or more generally, foreign key columns do not support an important access path to the child table, and you do not use these foreign keys in the predicate to accumulate data from the child table.

 

Find foreign keys that are not indexed

 

We can first find out which tables in the entire database have primary foreign key relationships and list the primary foreign key constraints through the following script.

 

--View all tables with primary foreign key relationships across the database (excluding some system users)

--View all tables with primary foreign key relationships across the database (excluding some system users)   
SELECT DC.OWNER                   AS "PARENT_TABLE_OWNER", 
       DC.TABLE_NAME              AS "PARENT_TABLE_NAME", 
       DC.CONSTRAINT_NAME         AS "PRIMARY CONSTRAINT NAME", 
       DF.CONSTRAINT_NAME         AS "REFERENCED CONSTRAINT NAME", 
       DF.OWNER                   AS "CHILD_TABLE_OWNER", 
       DF.TABLE_NAME              AS "CHILD_TABLE_NAME" 
FROM   DBA_CONSTRAINTS DC, 
       (SELECT C.OWNER, 
               C.CONSTRAINT_NAME, 
               C.R_CONSTRAINT_NAME, 
               C.TABLE_NAME 
        FROM   DBA_CONSTRAINTS C 
        WHERE  CONSTRAINT_TYPE = 'R') DF 
WHERE  DC.CONSTRAINT_NAME =DF.R_CONSTRAINT_NAME 
       AND DC.OWNER NOT IN ( 'SYSTEM', 'SYS', 'DBSNMP', 'EXFSYS', 
                            'ORDDATA', 'CTXSYS', 'OLAPSYS', 'MDSYS', 
                            'SYSMAN' ); 

 

--View all tables with primary foreign key relationships under a Schema

--View a Schema All tables below that have primary foreign key relationships   
SELECT DC.OWNER           AS "PARENT_TABLE_OWNER", 
       DC.TABLE_NAME      AS "PARENT_TABLE_NAME", 
       DC.CONSTRAINT_NAME AS "PRIMARY CONSTRAINT NAME", 
       DF.CONSTRAINT_NAME AS "REFERENCED CONSTRAINT NAME", 
       DF.OWNER           AS "CHILD_TABLE_OWNER", 
       DF.TABLE_NAME      AS "CHILD_TABLE_NAME" 
FROM   DBA_CONSTRAINTS DC, 
       (SELECT C.OWNER, 
               C.CONSTRAINT_NAME, 
               C.R_CONSTRAINT_NAME, 
               C.TABLE_NAME 
        FROM   DBA_CONSTRAINTS C 
        WHERE  CONSTRAINT_TYPE = 'R') DF 
WHERE  DC.CONSTRAINT_NAME = DF.R_CONSTRAINT_NAME 
       AND DC.OWNER =UPPER('&OWNER');  

 

--See if a specific table has a primary foreign key relationship with another table

--See if a specific table has a primary foreign key relationship with another table  
SELECT DC.OWNER           AS "PARENT_TABLE_OWNER", 
       DC.TABLE_NAME      AS "PARENT_TABLE_NAME", 
       DC.CONSTRAINT_NAME AS "PRIMARY CONSTRAINT NAME", 
       DF.CONSTRAINT_NAME AS "REFERENCED CONSTRAINT NAME", 
       DF.OWNER           AS "CHILD_TABLE_OWNER", 
       DF.TABLE_NAME      AS "CHILD_TABLE_NAME" 
FROM   DBA_CONSTRAINTS DC, 
       (SELECT C.OWNER, 
               C.CONSTRAINT_NAME, 
               C.R_CONSTRAINT_NAME, 
               C.TABLE_NAME 
        FROM   DBA_CONSTRAINTS C 
        WHERE  CONSTRAINT_TYPE = 'R') DF 
WHERE  DC.CONSTRAINT_NAME = DF.R_CONSTRAINT_NAME 
       AND DC.OWNER =UPPER('&OWNER')
       AND DC.TABLE_NAME=UPPER('&TABLE_NAME');

 

Next, we want to find out if there is an index on a specific foreign key field, and the script looks like this:

SELECT   CON.OWNER ,
         CON.TABLE_NAME,
         CON.CONSTRAINT_NAME,
         CON.COL_LIST,
         'No Indexed' AS INDEX_STATUS
FROM
     (SELECT CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME,
           MAX(DECODE(POSITION, 1,     '"' ||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 2,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 3,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 4,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 5,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 6,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 7,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 8,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 9,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 10,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) COL_LIST
           FROM DBA_CONSTRAINTS DC, DBA_CONS_COLUMNS CC
           WHERE DC.OWNER = CC.OWNER
           AND DC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME
           AND DC.CONSTRAINT_TYPE = 'R'
           AND DC.OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS', 'ADMIN')
           GROUP BY CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME 
     ) CON
      WHERE NOT EXISTS (
        SELECT 1 FROM
                  ( SELECT TABLE_OWNER, TABLE_NAME,   
                       MAX(DECODE(COLUMN_POSITION, 1,     '"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 2,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 3,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 4,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 5,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 6,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 7,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 8,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 9,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 10,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) COL_LIST
                       FROM DBA_IND_COLUMNS 
                   WHERE TABLE_OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS')
                   GROUP BY TABLE_OWNER, TABLE_NAME, INDEX_NAME ) COL
    WHERE CON.OWNER = COL.TABLE_OWNER 
    AND CON.TABLE_NAME = COL.TABLE_NAME  
    AND CON.COL_LIST = SUBSTR(COL.COL_LIST, 1, LENGTH(CON.COL_LIST) ) )  ;  

 

If you have the analysis function LISTAGG in your database for ORACLE 11g or above, you can use the following script

SELECT CASE 
         WHEN B.TABLE_NAME IS NULL THEN 'NO INDEXED' 
         ELSE 'INDEXED' 
       END               AS STATUS, 
       A.TABLE_OWNER     AS TABLE_OWNER, 
       A.TABLE_NAME      AS TABLE_NAME, 
       A.CONSTRAINT_NAME AS FK_NAME, 
       A.FK_COLUMNS      AS FK_COLUMNS, 
       B.INDEX_NAME      AS INDEX_NAME, 
       B.INDEX_COLUMNS   AS INDEX_COLUMNS 
FROM   (SELECT A.OWNER                              AS TABLE_OWNER, 
               A.TABLE_NAME                         AS TABLE_NAME, 
               A.CONSTRAINT_NAME                    AS CONSTRAINT_NAME, 
               LISTAGG(A.COLUMN_NAME, ',') 
                 WITHIN GROUP (ORDER BY A.POSITION) FK_COLUMNS 
        FROM   DBA_CONS_COLUMNS A, 
               DBA_CONSTRAINTS B 
        WHERE  A.CONSTRAINT_NAME = B.CONSTRAINT_NAME 
               AND B.CONSTRAINT_TYPE = 'R' 
               AND A.OWNER = B.OWNER 
               AND A.OWNER NOT IN ( 'SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 
                                    'MDSYS' ) 
        GROUP  BY A.OWNER, 
                  A.TABLE_NAME, 
                  A.CONSTRAINT_NAME) A, 
       (SELECT TABLE_OWNER, 
               TABLE_NAME, 
               INDEX_NAME, 
               LISTAGG(C.COLUMN_NAME, ',') 
                 WITHIN GROUP (ORDER BY C.COLUMN_POSITION) INDEX_COLUMNS 
        FROM   DBA_IND_COLUMNS C 
        GROUP  BY TABLE_OWNER, 
                  TABLE_NAME, 
                  INDEX_NAME) B 
WHERE  A.TABLE_NAME = B.TABLE_NAME(+) 
       AND A.TABLE_OWNER = B.TABLE_OWNER(+) 
       AND B.INDEX_COLUMNS(+) LIKE A.FK_COLUMNS 
                                   || '%' 
ORDER  BY 1 DESC

 

Automatically generate script to create foreign key index

 

These scripts can already find out which foreign key fields are indexed or not. If you manually create an index on a table that lacks an index on a foreign key field, if there is a large number of tables, the workload will be enormous. You can use this script to automatically generate the missing index below

/*******************************************************************************************
--Description of scripting capabilities:
--  For fields in the database where the foreign key is missing an index, generate the corresponding index (excluding some system accounts, such as sys, system), if the foreign key index exceeds ten fields
--  This script will not generate the appropriate index correctly, and of course few foreign keys are set in more than 10 fields.Additional index table is empty
--  The space is the same as the data table space, and it is recommended that you make adjustments here if there is any separation.
********************************************************************************************/
SELECT    'CREATE INDEX ' || OWNER || '.' || REPLACE(CONSTRAINT_NAME,'FK_','IX_') || 
        ' ON ' || OWNER || '.' || TABLE_NAME || ' (' || COL_LIST ||') TABLESPACE ' 
        || (SELECT TABLESPACE_NAME FROM DBA_TABLES WHERE OWNER= CON.OWNER AND TABLE_NAME= CON.TABLE_NAME) 
        AS  CREATE_INDEXES_ON_FOREIGN_KEY 
FROM
     (SELECT CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME,
           MAX(DECODE(POSITION, 1,     '"' ||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 2,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 3,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 4,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 5,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 6,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 7,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 8,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 9,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 10,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) COL_LIST
           FROM DBA_CONSTRAINTS DC, DBA_CONS_COLUMNS CC
           WHERE DC.OWNER = CC.OWNER
           AND DC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME
           AND DC.CONSTRAINT_TYPE = 'R'
           AND DC.OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS', 'ADMIN')
           GROUP BY CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME 
     ) CON
      WHERE NOT EXISTS (
        SELECT 1 FROM
                  ( SELECT TABLE_OWNER, TABLE_NAME,   
                       MAX(DECODE(COLUMN_POSITION, 1,     '"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 2,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 3,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 4,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 5,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 6,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 7,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 8,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 9,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 10,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) COL_LIST
                       FROM DBA_IND_COLUMNS 
                   WHERE TABLE_OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS')
                   GROUP BY TABLE_OWNER, TABLE_NAME, INDEX_NAME ) COL
    WHERE CON.OWNER = COL.TABLE_OWNER 
    AND CON.TABLE_NAME = COL.TABLE_NAME  
    AND CON.COL_LIST = SUBSTR(COL.COL_LIST, 1, LENGTH(CON.COL_LIST) ) )  ; 

 

--The script uses the analysis function LISTAGG for ORACLE 11g and above. If the database version is Oracle 11g or above, you can use this script instead of the above script.

SELECT 'CREATE INDEX ' 
              || OWNER 
              || '.' 
              || REPLACE(CONSTRAINT_NAME,'FK_','IX_') 
              || ' ON ' 
              || OWNER 
              || '.' 
              || TABLE_NAME 
              || ' (' 
              || FK_COLUMNS 
              ||') TABLESPACE ' 
              || 
       ( 
              SELECT TABLESPACE_NAME 
              FROM   DBA_TABLES 
              WHERE  OWNER= CON.OWNER 
              AND    TABLE_NAME= CON.TABLE_NAME) CREATE_INDEXES_ON_FOREIGN_KEY 
FROM   ( 
                SELECT   CC.OWNER, 
                         CC.TABLE_NAME, 
                         CC.CONSTRAINT_NAME, 
                         LISTAGG(CC.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY CC.POSITION) FK_COLUMNS
                FROM     DBA_CONS_COLUMNS CC, 
                         DBA_CONSTRAINTS DC 
                WHERE    CC.CONSTRAINT_NAME = DC.CONSTRAINT_NAME 
                AND      DC.CONSTRAINT_TYPE = 'R' 
                AND      CC.OWNER = DC.OWNER 
                AND      DC.OWNER NOT IN ( 'SYS', 
                                          'SYSTEM', 
                                          'OLAPSYS', 
                                          'SYSMAN', 
                                          'MDSYS', 
                                          'ADMIN' ) 
                GROUP BY CC.OWNER, 
                         CC.TABLE_NAME, 
                         CC.CONSTRAINT_NAME) CON 
  WHERE NOT EXISTS 
       ( 
              SELECT 1 
              FROM   ( 
                              SELECT   TABLE_OWNER, 
                                       TABLE_NAME, 
                                       INDEX_NAME,
                                       LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_POSITION) FK_COLUMNS
                              FROM     DBA_IND_COLUMNS 
                              WHERE    INDEX_OWNER NOT IN ( 'SYS', 
                                                           'SYSTEM', 
                                                           'OLAPSYS', 
                                                           'SYSMAN', 
                                                           'MDSYS', 
                                                           'ADMIN' ) 
                                                         
                              GROUP BY TABLE_OWNER, 
                                       TABLE_NAME ,INDEX_NAME) COL 
              WHERE  CON.OWNER = COL.TABLE_OWNER 
              AND    CON.TABLE_NAME = COL.TABLE_NAME 
              AND    CON.FK_COLUMNS = SUBSTR(COL.FK_COLUMNS, 1, LENGTH(CON.FK_COLUMNS)) ) 
              ORDER BY 1;

 

Reference material:

http://www.dba-oracle.com/t_foreign_key_indexing.htm

Keywords: Oracle SQL Session Database

Added by PHP Man on Mon, 08 Jul 2019 19:07:27 +0300