Home » SQL & PL/SQL » SQL & PL/SQL » Foreign Keys without supporting index
Foreign Keys without supporting index [message #634205] Thu, 05 March 2015 21:03 Go to next message
Kevin Meade
Messages: 2103
Registered: December 1999
Location: Connecticut USA
Senior Member
This was a recent "challenge" in an OraFAQ blog from MarcMartens. There are several answers to this on the WEB I think since it is hardly new. But I could not find a way to post files to the challenge thread so I decided to create a topic here and link back to it. This is one solution I created over the years, long winded but reasonably easy to following logically. Other solutions are more or less sophisticated. The trick is that somewhere in whatever solution you use you must do a set operation with the column list. LISTAGG or my distinct on a column matchup with follow-on group by, or even some variation of RELATIONAL ALGREBRA all work. I have also attached two helper scripts I use frequently that were obvious ways to validate results.

Maybe others would like to share their solutions too.

@showfknoindex.sql

21:10:01 SQL> @showfknoindex.sql

TABLE_OWNER          TABLE_NAME                     CONSTRAINT_NAME                COLUMN_COUNT
-------------------- ------------------------------ ------------------------------ ------------
BI_DATA_CURRENT      EMP                            EMP_FK1                                   1
BI_DATA_CURRENT      PROJECT                        PROJECT_FK1                               1
DBSNMP               BSLN_STATISTICS                BSLN_STATISTICS_FK                        1
EXFSYS               EXF$EXPRSET                    REF_EXPRSET_ATTRSET                       2
EXFSYS               RLM$JOBQUEUE                   RLM$JOINQREF                              2
EXFSYS               RLM$RULESET                    RLM$RSET_STATUS_REF                       1
EXFSYS               RLM$SCHACTLIST                 RLM$SCHACTFKEY                            2
HR                   COUNTRIES                      COUNTR_REG_FK                             1
...

SCOTT                EMP                            FK_DEPTNO                                 1

...
SYSTEM               MVIEW$_ADV_FJG                 MVIEW$_ADV_FJG_FK                         1
SYSTEM               MVIEW$_ADV_GC                  MVIEW$_ADV_GC_FK                          1
SYSTEM               REPCAT$_REFRESH_TEMPLATES      REPCAT$_REFRESH_TEMPLATES_FK1             1
SYSTEM               REPCAT$_REFRESH_TEMPLATES      REPCAT$_REFRESH_TEMPLATES_FK2             1

143 rows selected.

Elapsed: 00:00:00.71
21:19:23 SQL> @showconstraints scott emp

OWNER           TABLE_NAME      CONSTRAINT_NAME      C COLUMN_NAME     PARENT_CHILD_OWNER      PARENT_CHILD_TABLE_NAME        C INDEX_NAME
--------------- --------------- -------------------- - --------------- ----------------------- ------------------------------ - -----------
SCOTT           EMP             PK_EMP               P EMPNO                                                                    PK_EMP

SCOTT           EMP             FK_DEPTNO            R DEPTNO          SCOTT                   DEPT                           P


2 rows selected.

Elapsed: 00:00:03.36
21:19:34 SQL> @showindexes scott emp

INDEX_NAME                     COLUMN_NAME                    INDEX_TYPE                  UNIQUENES TABLESPACE_NAME
------------------------------ ------------------------------ --------------------------- --------- ------------------------------
PK_EMP                         EMPNO                          NORMAL                      UNIQUE    USERS


1 row selected.

Elapsed: 00:00:00.00


Kevin

I have also attached for your pleasure the promotional copy of my book on SQL Tuning, along with its associated scripts. If you enjoy it, you can get the book from Amazon at now 40% reduced price from its original list.

[Updated on: Thu, 05 March 2015 21:08]

Report message to a moderator

Re: Foreign Keys without supporting index [message #634223 is a reply to message #634205] Thu, 05 March 2015 23:43 Go to previous messageGo to next message
rleishman
Messages: 3728
Registered: October 2005
Location: Melbourne, Australia
Senior Member
I'm too lazy to formulate the solution, but it seems like a fun way to do it would be to create tuples of:
OWNER, TABLE_NAME, NESTED_TABLE(COLUMN_NAME, COLUMN_POSITION)

for each of indexes and FK constraints.
Then use SUBMULTISET to find the FKs that have a supporting index and discard them, leaving the ones that dont.

Ross Leishman
Re: Foreign Keys without supporting index [message #634240 is a reply to message #634223] Fri, 06 March 2015 04:36 Go to previous messageGo to next message
Roachcoach
Messages: 1576
Registered: May 2010
Location: UK
Senior Member
I'm lazy, I use T Kytes:

https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4530093713805#26568859366976


>>that should find them all (assuming reasonable key lengths of 8 or less)

tkyte@TKYTE816> select table_name, constraint_name,
  2       cname1 || nvl2(cname2,','||cname2,null) ||
  3       nvl2(cname3,','||cname3,null) || nvl2(cname4,','||cname4,null) ||
  4       nvl2(cname5,','||cname5,null) || nvl2(cname6,','||cname6,null) ||
  5       nvl2(cname7,','||cname7,null) || nvl2(cname8,','||cname8,null)
  6              columns
  7    from ( select b.table_name,
  8                  b.constraint_name,
  9                  max(decode( position, 1, column_name, null )) cname1,
 10                  max(decode( position, 2, column_name, null )) cname2,
 11                  max(decode( position, 3, column_name, null )) cname3,
 12                  max(decode( position, 4, column_name, null )) cname4,
 13                  max(decode( position, 5, column_name, null )) cname5,
 14                  max(decode( position, 6, column_name, null )) cname6,
 15                  max(decode( position, 7, column_name, null )) cname7,
 16                  max(decode( position, 8, column_name, null )) cname8,
 17                  count(*) col_cnt
 18             from (select substr(table_name,1,30) table_name,
 19                          substr(constraint_name,1,30) constraint_name,
 20                          substr(column_name,1,30) column_name,
 21                          position
 22                     from user_cons_columns ) a,
 23                  user_constraints b
 24            where a.constraint_name = b.constraint_name
 25              and b.constraint_type = 'R'
 26            group by b.table_name, b.constraint_name
 27         ) cons
 28   where col_cnt > ALL
 29           ( select count(*)
 30               from user_ind_columns i
 31              where i.table_name = cons.table_name
 32                and i.column_name in (cname1, cname2, cname3, cname4,
 33                                      cname5, cname6, cname7, cname8 )
 34                and i.column_position <= cons.col_cnt
 35              group by i.index_name
 36           )
 37  /
Re: Foreign Keys without supporting index [message #634241 is a reply to message #634205] Fri, 06 March 2015 05:49 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3312
Registered: January 2010
Location: Connecticut, USA
Senior Member
Kevin,

I can't find my pre-LISTAGG script, but here is my LISTAGG based script. It is also taking into account ascending order and will consider FK index missing even if there is column-wise matching index with one or more columns in descending order. It also filters most of ORACLE supplied accounts which, btw, should be rewritten for 12C where it is so much simpler since Oracle added ORACLE_MAINTAINED column to data dictionary. Anyway:

-- List foreign keys without matching index

with t1 as (
            select  a.owner,
                    a.table_name,
                    a.constraint_name,
                    listagg(b.column_name,',') within group(order by b.position) column_list
              from  dba_constraints a,
                    dba_cons_columns b
              where b.owner           = a.owner
                and b.table_name      = a.table_name
                and b.constraint_name = a.constraint_name
                and a.constraint_type = 'R'
              group by a.owner,
                       a.table_name,
                       a.constraint_name
           ),
     t2 as (
            select  table_owner,
                    table_name,
                    listagg(column_name,',') within group(order by column_position) column_list
              from  dba_ind_columns
              where descend = 'ASC'
              group by table_owner,
                       table_name,
                       index_owner,
                       index_name
           )
select  owner,
        table_name,
        constraint_name
  from  t1
  where not exists(
                   select  1
                     from  t2
                     where t2.table_owner = t1.owner
                       and t2.table_name  = t1.table_name
                       and t2.column_list = t1.column_list
                  )
    and owner not in (
                      'CTXSYS',
                      'DBSNMP',
                      'EXFSYS',
                      'MDSYS',
                      'OLAPSYS',
                      'ORDDATA',
                      'SYS',
                      'SYSMAN',
                      'SYSTEM'
                     )
    and owner not like 'APEX%'
  order by owner,
       table_name
/

-- Generate CREATE INDEX for foreign keys without matching index

with t1 as (
            select  a.owner,
                    a.table_name,
                    a.constraint_name,
                    listagg(b.column_name,',') within group(order by b.position) column_list
              from  dba_constraints a,
                    dba_cons_columns b
              where b.owner           = a.owner
                and b.table_name      = a.table_name
                and b.constraint_name = a.constraint_name
                and a.constraint_type = 'R'
              group by a.owner,
                       a.table_name,
                       a.constraint_name
           ),
     t2 as (
            select  table_owner,
                    table_name,
                    listagg(column_name,',') within group(order by column_position) column_list
              from  dba_ind_columns
              where descend = 'ASC'
              group by table_owner,
                       table_name,
                       index_owner,
                       index_name
           ),
     t3 as (
            select  level lvl
              from  dual
              connect by level <= 2
           )
select  case lvl
          when 1 then 'CREATE INDEX ' || owner || '.' || constraint_name || ' ON ' ||
                      owner || '.' || table_name || '(' || column_list || ') NOLOGGING;'
          else 'ALTER INDEX ' || owner || '.' || constraint_name || ' LOGGING;'
        end create_index
  from  t1,
        t3
  where not exists(
                   select  1
                     from  t2
                     where t2.table_owner = t1.owner
                       and t2.table_name  = t1.table_name
                       and t2.column_list = t1.column_list
                  )
    and owner not in (
                      'CTXSYS',
                      'DBSNMP',
                      'EXFSYS',
                      'MDSYS',
                      'OLAPSYS',
                      'ORDDATA',
                      'SYS',
                      'SYSMAN',
                      'SYSTEM'
                     )
    and owner not like 'APEX%'
  order by owner,
           table_name,
           lvl
/


SY.
Re: Foreign Keys without supporting index [message #634249 is a reply to message #634241] Fri, 06 March 2015 08:24 Go to previous messageGo to next message
Kevin Meade
Messages: 2103
Registered: December 1999
Location: Connecticut USA
Senior Member
OK, but those solutions suffer from position ordering issues. For example:

create index t_fk1 on t (c1,c2,c3);

alter table t add constraints fk1 (c3,c2,c1) references tparent;

this constraint has index support. A constraint does not require the columns to be in the same order as the index, only that the leading set of columns in the index be the same. Indeed, if it is not already so, one can even imagine use of SKIP SCAN to relax even that requirment.

So the above solutions that order by position do not work.

Unless my thinking is missing something. Kevin
Re: Foreign Keys without supporting index [message #634326 is a reply to message #634249] Sat, 07 March 2015 14:14 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3312
Registered: January 2010
Location: Connecticut, USA
Senior Member
Just change order by in within group to column_name.

SY.
Re: Foreign Keys without supporting index [message #634330 is a reply to message #634326] Sat, 07 March 2015 14:58 Go to previous message
Solomon Yakobson
Messages: 3312
Registered: January 2010
Location: Connecticut, USA
Senior Member
And if you want to consider a match if existing index is a superset (column-wise) of FK columns:

create or replace
  type ColumnList
    as
      table of varchar2(4000)
/
with t1 as (
            select  a.owner,
                    a.table_name,
                    a.constraint_name,
                    cast(collect(b.column_name) as ColumnList) column_list
              from  dba_constraints a,
                    dba_cons_columns b
              where b.owner           = a.owner
                and b.table_name      = a.table_name
                and b.constraint_name = a.constraint_name
                and a.constraint_type = 'R'
              group by a.owner,
                       a.table_name,
                       a.constraint_name
           ),
     t2 as (
            select  table_owner,
                    table_name,
                    cast(collect(column_name) as ColumnList) column_list
              from  dba_ind_columns
              where descend = 'ASC'
              group by table_owner,
                       table_name,
                       index_owner,
                       index_name
           )
select  owner,
        table_name,
        constraint_name
  from  t1
  where not exists(
                   select  1
                     from  t2
                     where t2.table_owner = t1.owner
                       and t2.table_name  = t1.table_name
                       and t1.column_list multiset except t2.column_list = ColumnList()
                  )
    and owner not in (
                      'CTXSYS',
                      'DBSNMP',
                      'EXFSYS',
                      'MDSYS',
                      'OLAPSYS',
                      'ORDDATA',
                      'SYS',
                      'SYSMAN',
                      'SYSTEM'
                     )
    and owner not like 'APEX%'
  order by owner,
       table_name
/


SY.
Previous Topic: rowid and index
Next Topic: Datapump error inside SQL dev
Goto Forum:
  


Current Time: Thu Aug 27 01:05:43 CDT 2026