rem
rem     Script:     index_est_proc_2.sql
rem     Author:     Jonathan Lewis
rem     Dated:      August 2005 (updated Apr 2009)
rem     Purpose:    Fast analysis of indexes to help identify
rem                 extreme degeneration.
rem
rem     Last tested
rem             11.1.0.7
rem             10.2.0.3
rem             10.1.0.4
rem              9.2.0.8
rem              8.1.7.4
rem     Not tested
rem             11.2.0.1
rem             10.2.0.4
rem
rem     Usage:
rem     Set the values in the "define" section
rem     Log on with the privilege to see the "dba_" views
rem     using SQL*Plus and run the script.
rem
rem     Notes:
rem     This script assumes that statistics have been collected in
rem     the fairly recent past, and uses some approximations to
rem     compare the number of leaf blocks with the number of leaf
rem     blocks that ought to be needed to hold the data.
rem
rem     There are various little oddities with the way that
rem         (a) Oracle calculates average column lenght and
rem         (b) I use the available data
rem     that mean that at small sizes and in extreme cases the
rem     numbers I produce can be wrong.  In particular, for indexes
rem     where a lot of the table data has nulls (so no entry in the
rem     index), the estimated size can be significantly larger than
rem     they finally turn out to be.
rem
rem
rem     Targets
rem     =======
rem     Where the estimate is very much smaller than the actual, then
rem     you may be looking at a "FIFO" index, emptying out in the past
rem     and filling in the future. This type of index is a candidate for
rem     a regular "coalesce" - although you may want to rebuild it once
rem     to get it to the right starting size and release excess space
rem     back to the tablespace.
rem
rem     See https://jonathanlewis.wordpress.com/2008/09/26/index-analysis/
rem     for an example and discussion on this type of index.
rem
rem     Where the estimate is about half the size of the actual, then
rem     it is worth checking whether there is any special treatment of
rem     the data that is making this happen. 50% utilisation is fairly
rem     common in RAC for indexes based on a sequence with a large cache
rem     size, so it may be best to leave the indexes at that level. 
rem     However, you may find that rebuilding (perhaps just once) with
rem     a pctfree in the region of 30% may give you a slightly more efficient
rem     index in non-RAC systems.
rem
rem     If your index is running at 50% and is not strongly sequence based
rem     then you may be suffering from the concurrency/ITL bug and may want
rem     to rebuild the index and force a maxtrans setting into the index.
rem
rem     If the index is running at a fairly uniform 25%, it may be subject
rem     to side effects of both sequencing and the concurrency effects.
rem
rem     Usage:
rem     ======
rem     This script takes a username (table owner), percent usage, and
rem     scaling factor.  It reports the estimated leaf block count of
rem     all simple indexes for that schema where the size of the index
rem     would be smaller than the supplied fraction of the current size
rem     when rebuilt at the supplied percentage utilisation. Current settings
rem     are 90% (which equates to the default pctfree 10) and 0.6 which means
rem     the index would be running at about 50% empty wastage - which is the
rem     point at which it begins to be a possible target for investigation.
rem     The script does not report any index smaller than 10,000 leaf blocks,
rem     and assumes an 8KB block size.
rem
rem     Technical notes:
rem     ================
rem     Don't need to add a length byte after using dbms_stats
rem     Don't need a 'descending' byte because it's automatically included
rem     Don't need to adjust for num_nulls because it's automatically included
rem     Reverse key indexes don't affect column lengths
rem
rem     Need to worry about COMPRESSED indexes. At present compression
rem     may reduce the size of an index so that I don't notice it should
rem     still be smaller than it is.
rem
rem     Index types that can be used (with partitioned = 'NO')
rem         NORMAL
rem         NORMAL/REV
rem         FUNCTION-BASED NORMAL
rem
rem     Still needs enhancing for partitioned and subpartitioned indexes
rem     Check dba_part_indexes for locality, partitioning_type, subpartitioning_type
rem     But does handle global indexes on partitioned tables.
rem
rem     To investigate
rem         LOB
rem         IOT - TOP
rem         IOT - NESTED
rem         SECONDARY
rem         BITMAP  (and BITMAP JOIN)
rem         FUNCTION-BASED BITMAP
rem         CLUSTER
rem         ANSI ?
rem
rem     Probably not possible
rem         DOMAIN
rem         FUNCTION-BASED DOMAIN
rem
rem     Need to avoid partitioned, temporary, unusable and dropped indexes
rem
rem
rem    Update : December 2016 by Mohamed Houri to consider Locally 
rem             Partitioned and SUB-Partitioned indexes
rem           : Any error is this script is mine. 
rem           : This script has been tested on running systems and seems giving
rem             acceptable results. But still I have not enough feedback. So use it
rem             carefully.
rem
set serveroutput on size 1000000 format wrapped
 
define  m_owner         = '&m_schemaname'
define m_blocksize = 8192
define  m_target_use    = 90 -- equates to pctfree 10
define  m_scale_factor  = 0.6
define m_minimum = 1000
define m_overhead = 192 -- leaf block "lost" space in index_stats

set verify off
set serveroutput on size 1000000 format wrapped

declare
    m_leaf_estimate number;
    ln_size         number;
begin
    for r in 
    (
       select
            ww.table_owner,
            ww.table_name,
            ww.index_owner,
            ww.index_name,
            ww.partition_name,
            ww.leaf_blocks,
            ww.status,
            ww.part_level
       from -- top level partitioned index
           (
        	select
            	a.table_owner,
            	a.table_name,
            	b.index_owner,
            	b.index_name,
            	b.partition_name,
            	b.leaf_blocks,
            	b.status,
            	'TOP' part_level
        	from
            	dba_indexes        a
               ,dba_ind_partitions b
        	where
            	a.owner       = b.index_owner
            and a.index_name  = b.index_name
            and a.owner       = upper('&m_owner')
        	and a.partitioned = 'YES'
        	and a.temporary   = 'N'
        	and a.dropped     = 'NO'
        	and b.status     != 'UNUSABLE'
        	and a.last_analyzed is not null
           union all
              -- sub partitioned indexes
           	select
            	a.table_owner,
            	a.table_name,
            	c.index_owner,
            	c.index_name,
            	c.subpartition_name,
            	c.leaf_blocks,
            	c.status,
            	'SUB' part_level
        	from
            	dba_indexes           a
               ,dba_ind_subpartitions c
        	where
            	a.owner       = c.index_owner
            and a.index_name  = c.index_name
            and a.owner       = upper('&m_owner')
        	and a.partitioned = 'YES'
        	and a.temporary   = 'N'
        	and a.dropped     = 'NO'
        	and c.status     != 'UNUSABLE'
        	and a.last_analyzed is not null
        	)ww
     order by
            ww.index_owner, ww.table_name, ww.index_name,ww.partition_name
    ) loop
        if r.leaf_blocks > &m_minimum then
            select
                round( 100 / &m_target_use * (ind.num_rows * (tab.rowid_length + ind.uniq_ind + 4) + sum((tc.avg_col_len) *(tab.num_rows)) ) /(&m_blocksize - &m_overhead)
         )               index_leaf_estimate
                into    m_leaf_estimate
            from
                (
                select  /*+ no_merge */
                    a.partition_name,
                    a.num_rows,
                    10 rowid_length
                from
                    dba_ind_partitions a
                where
                    a.index_owner    = r.index_owner
                and a.index_name     = r.index_name
                and a.partition_name = r.partition_name
                union all
                select  /*+ no_merge */
                    a.subpartition_name,
                    a.num_rows,
                    10 rowid_length
                from
                    dba_ind_subpartitions a
                where
                    a.index_owner       = r.index_owner
                and a.index_name        = r.index_name
                and a.subpartition_name = r.partition_name
                )               tab,
                (
                select  /*+ no_merge */
                    a.index_name,
                    a.num_rows,
                    decode(uniqueness,'UNIQUE',0,1) uniq_ind
                from
                    dba_ind_partitions a
                   ,dba_indexes        b
                where
                    a.index_name     = b.index_name
                and a.index_owner    = r.index_owner
                and b.table_name     = r.table_name
                and b.owner          = r.index_owner
                and a.index_name     = r.index_name
                and a.partition_name = r.partition_name
                union all
                select  /*+ no_merge */
                    c.index_name,
                    c.num_rows,
                    decode(uniqueness,'UNIQUE',0,1) uniq_ind
                from
                    dba_ind_subpartitions c
                   ,dba_indexes           b
                where
                    c.index_name        = b.index_name
                and c.index_owner       = r.index_owner
                and b.table_name        = r.table_name
                and b.owner             = r.index_owner
                and c.index_name        = r.index_name
                and c.subpartition_name = r.partition_name
                )               ind,
                (
                select  /*+ no_merge */
                    column_name
                from
                    dba_ind_columns
                where
                    table_owner = r.table_owner
                and index_owner = r.index_owner
                and table_name  = r.table_name
                and index_name  = r.index_name
                )               ic,
                (
                select  /*+ no_merge */
                    column_name,
                    avg_col_len
                from
                    dba_tab_cols
                where
                    owner       = r.table_owner
                and table_name  = r.table_name
                )               tc
            where
                tc.column_name = ic.column_name
            group by
                ind.num_rows,
                ind.uniq_ind,
                tab.rowid_length
            ;
            if m_leaf_estimate < &m_scale_factor * r.leaf_blocks then
 
              select sum(round(bytes/1024/1024,2)) into ln_size
              from dba_segments a
              where segment_name = r.index_name
              and partition_name = r.partition_name;
              
              if ln_size is not null 
              then
                 dbms_output.new_line;
                dbms_output.put_line
                (
                    'table name ' || ': ' || trim(r.table_name) || ' - ' ||
                    'index name ' || ': ' || trim(r.index_name) || 
					' - Partition Name :' || trim(r.partition_name) || 
					' - Partition Level:' || r.part_level
                );
 
                dbms_output.put_line
                (
                    '--> Current partition index size (MB): ' ||
                    round(ln_size,2) ||
                    '         Expected Partition index size (MB): ' ||
                    round(ln_size * (m_leaf_estimate/r.leaf_blocks),2)
                );
 
                dbms_output.new_line;
 
            end if;
        end if;
      end if;
    end loop;
end;
/
set verify on
 