Possiamo facilmente immaginare l’inefficienza introdotta dalla doppia indicizzazione (ugualmente ordinata) di un attributo: SQL Server dovrà mantenere aggiornati due indici, organizzati in strutture B-Tree letteralmente identiche, senza trarre alcun beneficio da una delle due.

La stored procedure dbo.usp_drop_double_more_index, definita di seguito, permette di individuare ed eliminare i casi di doppia (tripla, ecc...) indicizzazione dei medesimi attributi.

use [AdventureWorks]
go

if object_id('usp_drop_double_more_index') is not null
  drop procedure dbo.usp_drop_double_more_index
go

create procedure dbo.usp_drop_double_more_index
(
  @mode tinyint,
  @object_name varchar(4000),
  @table_type varchar(10),
  @index_name_prefix_order varchar(8),
  @debug tinyint
)
as
begin
  /*
    Descrizione:
      Individua ed elimina i casi di doppia (tripla, ecc..)
      indicizzazione sui medesimi attributi

    Parametri:
      - @mode
          Modalità di utilizzo
          Può assumere i valori: 1= Exec Mode, 0= Print Mode.
          In exec-mode esegue la cancellazione degli indici
          doppi, in print-mode stampa i comandi T-SQL

      - @object_name
          Nome di una tabella o lista di nomi di tabelle
          nel formato <schema_name>.<table_name> separati da ","
          per le quali di desidera verificare
          ed eventualmente cancellare gli indici doppi
          (esempio <schema_name_1>.<table_name_1>, <schema_name_2>.<table_name_2>, ..., <schema_name_N>.<table_name_N>

      - @table_type
          Tipo di tabella
          Può assumere: "VIEW" o "BASE TABLE"

      - @index_name_prefix_order
          Determina, attraverso ordinamento, quale indice
          mantenere tra quelli doppi. Qualora si desideri
          mantenere un particolare indice o una particolare
          famiglia di indici il cui nome inizia ad esempio
          con "IX_", sarà sufficiente specificare il
          prefisso desiderato in questo parametro

      - @debug
          Modalità di debug
          Può assumere i valori: 1= Debug ON, 0= Debug OFF
  */

  declare
    @msg varchar(256),
    @cmd varchar(max),
    @index_name_prefix_len int


  -- Controllo parametri di input
  -- @mode
  if (@mode not in (0, 1))
  begin
    set @msg = object_name(@@procid) +
              ' : Modalità di esecuzione non supportata, ' +
              '@mode può assumere i seguenti valori: ' +
              '1= Modalità Exec, 0= Modalità Print. ' +
              'Il valore di @mode passato è: ' + ltrim(str(@mode))
    raiserror(@msg, 16, 1)
    return
  end

  -- @debug
  if (@debug not in (0, 1))
  begin
    set @msg = object_name(@@procid) +
              ' : Modalità di debug non supportata, ' +
              '@debug può assumere i seguenti valori: ' +
              '1= Debug ON, 0= Debug OFF. ' +
              'Il valore di @debug passato è: ' + ltrim(str(@debug))
    raiserror(@msg, 16, 1)
    return
  end

  set nocount on

  -- Setup tabelle
  -- tmphelpindex
  if object_id('tmphelpindex') is not null
    drop table tmphelpindex

  create table tmphelpindex
  (id int identity(1, 1) not null,
   index_name sysname,
   index_description varchar(210),
   index_keys varchar(4000))

  -- tmpindexes
  if object_id('tmpindexes') is not null
    drop table tmpindexes

  create table tmpindexes
  (id int identity(1, 1) not null,
   table_schema varchar(128),
   table_name sysname,
   index_name sysname,
   index_description varchar(210),
   index_keys varchar(4000))

  -- tmpindexes1
  if object_id('tmpindexes1') is not null
    drop table tmpindexes1

  create table tmpindexes1
    (id int identity(1, 1) not null,
     table_schema varchar(128),
     table_name sysname,
     index_name sysname,
     index_description varchar(210),
     index_keys varchar(4000))

  set @cmd = ''
  set @index_name_prefix_len = len(ltrim(rtrim(@index_name_prefix_order)))

  if (@debug = 1)
  begin
    print ('@index_name_prefix_order= "' + ltrim(rtrim(@index_name_prefix_order)) + '"')
    print ('@index_name_prefix_len= ' + ltrim(str(@index_name_prefix_len)))
  end

  select
    @cmd = @cmd + 'insert into tmphelpindex ' +
                  'exec sp_helpindex ''' + i.table_schema + '.' + i.table_name + '''; ' +

                  'delete ' +
                    't ' +
                  'from ' +
                    'tmphelpindex t ' +
                  'join ' +

                  '(select ' +
                     'min(t1.id) as id ' +
                   'from ' +
                     'tmphelpindex t1 ' +
                   'where ' +
                     'exists (select ' +
                               'id ' +
                             'from ' +
                               'tmphelpindex t2 ' +
                             'where ' +
                               '(t1.index_keys=t2.index_keys) and ' +
                               '(t2.index_description like ''clustered%'')) and ' +
                     '(t1.index_description like ''nonclustered%'')) tx on tx.id=t.id; ' +

                  'insert into tmpindexes ' +
                  '(table_schema, ' +
                   'table_name, ' +
                   'index_name, ' +
                   'index_description, ' +
                   'index_keys) ' +
                  'select ' +
                    '''' + i.table_schema + ''',' +
                    '''' + i.table_name + ''', ' +
                    'index_name, ' +
                    'index_description, ' +
                    'index_keys ' +
                  'from ' +
                    'tmphelpindex; ' +

                  'delete from tmphelpindex; '
  from
    information_schema.tables as i
  where
    ((charindex((',' + i.table_schema + '.' + i.table_name + ','),
                (',' + replace(@object_name, ' ', '') + ',')) > 0) OR (@object_name='')) and
    (i.table_type=@table_type)
  order by
    i.table_schema,
    i.table_name

  if (@debug = 1)
    print(@cmd)
  exec(@cmd)

  if (ltrim(rtrim(@index_name_prefix_order)) <> '')
  begin
    insert into
      tmpindexes1
      (index_name,
       index_description,
       index_keys,
       table_schema,
       table_name)
    select
      index_name,
      index_description,
      index_keys,
      table_schema,
      table_name
    from
      tmpindexes
    where
      (left(index_name, @index_name_prefix_len) <> @index_name_prefix_order)

    insert into
      tmpindexes1
      (index_name,
       index_description,
       index_keys,
       table_schema,
       table_name)
    select
      index_name,
      index_description,
      index_keys,
      table_schema,
      table_name
    from
      tmpindexes
    where
      (left(index_name, @index_name_prefix_len) = @index_name_prefix_order)
  end
  else
    insert into
      tmpindexes1
      (index_name,
       index_description,
       index_keys,
       table_schema,
       table_name)
    select
      index_name,
      index_description,
      index_keys,
      table_schema,
      table_name
    from
      tmpindexes

  -- Select double index on the same columns
  set @cmd = ''

  select
    @cmd = @cmd +
           'drop index [' + t.table_schema + '].[' + t.table_name + '].[' + t.index_name + ']; ' + char(13) + char(10)
  from
    tmpindexes1 t
  join 
    (select
       max(id) as max_id,
       count(index_keys) as index_count,
       table_schema,
       table_name,
       index_keys,
       index_description     
     from
       tmpindexes1
     group by
       table_schema,
       table_name,
       index_keys,
       index_description
     having
       (count(index_keys) > 1)

    ) t1 on (t.table_schema = t1.table_schema) and
            (t.table_name = t1.table_name) and
            (t.index_keys = t1.index_keys) and
            (t.index_description = t1.index_description) and
            (t.id < t1.max_id)
     order by
       t.index_name

  if (@cmd = '')
  begin
    print ('')
    print ('+-+-+-+-+-+-+-+-+-+-+-+-+')
    print (object_name(@@procid) + ': No double or more index found!')
  end
  else begin
    print ('')
    print ('+-+-+-+-+-+-+-+-+-+-+-+-+')
    if (@mode = 0)
      print (object_name(@@procid) + ': Scrip drop index...')
    else
      print (object_name(@@procid) + ': Start drop index...')
    print ('')

    if (@mode = 0)
      print (@cmd)
    else begin
      exec (@cmd)

      if (@debug = 1)
        print (@cmd)

      print ('')
      if (@@error = 0)
        print ('Commands: ' +
               char(13) + char(10) +
               @cmd + ' completed successfully.')
      else
        print ('Commands: ' +
               char(13) + char(10) +
               @cmd + ' ended with errors.')
      print ('')
    end
  end

  print ('')
  print (object_name(@@procid) + ': ...Execution end.')
  print ('+-+-+-+-+-+-+-+-+-+-+-+-+')

  -- Pulizia tabelle
  -- tmphelpindex
  if object_id('tmphelpindex') is not null
    drop table tmphelpindex

  -- tmpindexes
  if object_id('tmpindexes') is not null
    drop table tmpindexes

  -- tmpindexes1
  if object_id('tmpindexes1') is not null
    drop table tmpindexes1

  set nocount off
end

Esempio:

Ipotizziamo la situazione illustrata nella figura seguente per la tabella Person.Contact del database di esempio AdventureWorks.

L’attributo FirstName è caratterizzato da una tripla (identica) indicizzazione, riproducibile con i seguenti comandi CREATE INDEX:

create nonclustered index
  [IX__Person_Contact_FirstName] on [Person].[Contact](FirstName);
go

create nonclustered index
  [IX__Person_Contact_FirstName_1] on [Person].[Contact](FirstName);
go

create nonclustered index
  [IX__Person_Contact_FirstName_2] on [Person].[Contact](FirstName)

Cosa ci aspettiamo dalla stored procedure dbo.usp_drop_double_more_index ?

Ci aspettiamo che rilevi la tripla indicizzazione della colonna FirstName nella tabella Person.Contact.

Eseguiamo la stored procedure in modalità Print (@mode = 0):

EXEC dbo.usp_drop_double_more_index
  @mode=0,
  @object_name='Person.Contact',
  @table_type='base table',
  @index_name_prefix_order = '',
  @debug=1

L’output ottenuto propone la cancellazione di due, dei tre indici definiti per l’attributo FieldName della tabella Person.Contact:

+-+-+-+-+-+-+-+-+-+-+-+-+
usp_drop_double_more_index: Scrip drop index...
 
drop index [Person].[Contact].[IX__Person_Contact_FirstName];
drop index [Person].[Contact].[IX__Person_Contact_FirstName_1];
 
usp_drop_double_more_index: ...Execution end.
+-+-+-+-+-+-+-+-+-+-+-+-+

Il comando precedente invocato in modalità Exec (@mode = 1) eseguirà la cancellazione fisica dei due indici:

EXEC dbo.usp_drop_double_more_index
  @mode=1,
  @object_name='',
  @table_type='base table',
  @index_name_prefix_order = '',
  @debug=1

Risultato:

+-+-+-+-+-+-+-+-+-+-+-+-+
usp_drop_double_more_index: Start drop index...
 
drop index [Person].[Contact].[IX__Person_Contact_FirstName];
drop index [Person].[Contact].[IX__Person_Contact_FirstName_1];
 
Commands:
drop index [Person].[Contact].[IX__Person_Contact_FirstName];
drop index [Person].[Contact].[IX__Person_Contact_FirstName_1];
 completed successfully.
 
 
usp_drop_double_more_index: ...Execution end.
+-+-+-+-+-+-+-+-+-+-+-+-+

Un'altra soluzione ottimizzata per SS2005 e SS2008 la potete trovare qui: Verificare la presenza di indici identici / duplicati