Full Code of MichelleUfford/sql-scripts for AI

master f4e22da31874 cached
19 files
170.7 KB
37.2k tokens
9 symbols
1 requests
Download .txt
Repository: MichelleUfford/sql-scripts
Branch: master
Commit: f4e22da31874
Files: 19
Total size: 170.7 KB

Directory structure:
gitextract_ag5izxye/

├── LICENSE
├── README.md
├── admin/
│   ├── dba_findWastedSpace_sp.sql
│   ├── dba_recompile_sp.sql
│   ├── dba_replicationLatencyGet_sp.sql
│   ├── dba_replicationLatencyMonitor_sp.sql
│   └── sql_agent_job_history.sql
├── dev/
│   ├── bcp_script_generator.sql
│   ├── dba_parseString_udf.sql
│   ├── insert_statement_generator.sql
│   └── teradata_ddl_generator.sql
├── indexes/
│   ├── dba_indexDefrag_sp.sql
│   ├── dba_indexLookup_sp.sql
│   ├── dba_indexStats_sp.sql
│   ├── dba_missingIndexStoredProc_sp.sql
│   ├── index_definition.sql
│   ├── missing.sql
│   └── unused.sql
└── misc/
    └── dba_viewPageData_sp.sql

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: README.md
================================================
sql-scripts
===========
Repo for sharing my SQL Server scripts and stored procedures. These have largely been tested on both Standard & Enterprise versions of SQL 2005, 2008, 2008R2, and 2012; you may need to tweak for 2014 and newer versions.

# what's available

## admin
* dba_findWastedSpace_sp.sql
  * Finds wasted space on a database and/or table
* dba_recompile_sp.sql
  * Recompiles all procs in a specific database or all procs; can recompile a specific table, too.
* dba_replicationLatencyGet_sp.sql
  * Retrieves the amount of replication latency in seconds
* dba_replicationLatencyMonitor_sp.sql
  * Stored procedure for retrieving & storing the amount of replication latency in seconds
* sql-agent-job-history.sql
  * Explores SQL Agent Job metadata to get job statuses — when the job last ran, when it will run again, an aggregate count of the number of successful and failed executions in the queried time period, T-SQL code to disable the job, etc.

## dev
* bcp_script_generator.sql
  * Generates bcp scripts using SQL Server metadata
* dba_parseString_udf.sql
  * This function parses string input using a variable delimiter.
* insert_statement_generator.sql
  * Generates insert statements for Teradata using SQL Server metadata. This is useful for easily migrating small tables (i.e. < 1000 rows) from SQL Server to Teradata. DO NOT use on large tables. 
* teradata_ddl_generator.sql
  * Generates Teradata DDL using SQL Server metadata
  
## indexes
* dba_indexDefrag_sp.sql
  * award-winning index defrag script
* dba_indexLookup_sp.sql
  * Retrieves index information for the specified table name.
* dba_indexStats_sp.sql
  * etrieves information regarding indexes; will return drop SQL statement for non-clustered indexes.
* dba_missingIndexStoredProc_sp.sql
  * Retrieves stored procedures with missing indexes in their cached query plans.
* index_definition.sql
  * Displays the definition of indexes; useful to audit indexes across servers & environments
* missing.sql
  * Displays potential missing indexes for a given database. Adding the indexes via the provided CREATE scripts may improve server performance. 
* unused.sql
  *  Displays potential unused indexes for the current database. Dropping these indexes may improve database performance. These statistics are reset each time the server is rebooted, so make sure to review the [sqlserver_start_time] value to ensure the  statistics are captured for a meaningful time period.
  
## misc
* dba_viewPageData_sp.sql
  * Retrieves page data for the specified table/page.

  
# contributing
Contributions are welcome! To contribute a change or enhancement, please issue a pull request for me to review and merge. If you have any questions, I can be reached on Twitter @sqlfool. 


================================================
FILE: admin/dba_findWastedSpace_sp.sql
================================================
If ObjectProperty(Object_ID('dbo.dba_findWastedSpace_sp'), N'IsProcedure') Is Null
Begin
    Execute ('Create Procedure dbo.dba_findWastedSpace_sp As Print ''Hello World!''')
    RaisError('Procedure dba_findWastedSpace_sp created.', 10, 1);
End;
Go

Set ANSI_Nulls On;
Set Quoted_Identifier On;
Go

Alter Procedure dbo.dba_findWastedSpace_sp

    /* Declare Parameters */
      @databaseName     sysname     = 'AdventureWorks'
    , @tableName        sysname     = 'Sales.SalesOrderDetail'
    , @percentGrowth    tinyint     = 10    /* allow for up to 10% growth by default */
    , @displayUnit      char(2)     = 'GB'  /* KB, MB, GB, or TB */
    , @debug            bit         = 1

As
/**********************************************************************************************************

    NAME:           dba_findWastedSpace_sp

    SYNOPSIS:       Finds wasted space on a database and/or table

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2011-03-14
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;
Set Ansi_Padding On;
Set Ansi_Warnings On;
Set ArithAbort On;
Set Concat_Null_Yields_Null On;
Set Numeric_RoundAbort Off;

Begin

    /* Make sure our environment is clean and ready to go */
    If Exists(Select object_id From tempdb.sys.tables Where name = '##values')
        Drop Table ##values;

    If Exists(Select object_id From tempdb.sys.tables Where name = '##definition')
        Drop Table ##definition;

    If Exists(Select object_id From tempdb.sys.tables Where name = '##spaceRequired')
        Drop Table ##spaceRequired;

    If Exists(Select object_id From tempdb.sys.tables Where name = '##results')
        Drop Table ##results;

    /* Declare Variables */
    Declare @sqlStatement_getColumnList     nvarchar(max)
        , @sqlStatement_values              nvarchar(max)
        , @sqlStatement_columns             nvarchar(max)
        , @sqlStatement_tableDefinition1    nvarchar(max)
        , @sqlStatement_tableDefinition2    nvarchar(max)
        , @sqlStatement_tableDefinition3    nvarchar(max)
        , @sqlStatement_spaceRequired       nvarchar(max)
        , @sqlStatement_results             nvarchar(max)
        , @sqlStatement_displayResults      nvarchar(max)
        , @sqlStatement_total               nvarchar(max)
        , @currentRecord                    int
        , @growthPercentage                 float;

    Declare @columnList Table
    (
          id            int identity(1,1)
        , table_id      int
        , columnName    varchar(128)
        , user_type_id  tinyint
        , max_length    smallint
        , columnStatus  tinyint
    );

    /* Initialize variables
            I'm doing it this way to support 2005 environments, too */
    Select @sqlStatement_tableDefinition1   = ''
        , @sqlStatement_tableDefinition2    = ''
        , @sqlStatement_tableDefinition3    = ''
        , @sqlStatement_spaceRequired       = 'Select '
        , @sqlStatement_results             = 'Select '
        , @sqlStatement_displayResults      = ''
        , @sqlStatement_total               = 'Select ''Total'', Null, '
        , @sqlStatement_values              = 'Select '
        , @sqlStatement_columns             = 'Select '
        , @growthPercentage                 = 1+(@percentGrowth/100.0);

    Set @sqlStatement_getColumnList = '
    Select c.object_id As [table_id]
        , c.name
        , t.user_type_id
        , c.max_length
        , 0 /* not yet columnStatus */
    From ' + @databaseName + '.sys.columns As c
    Join ' + @databaseName + '.sys.types As t 
        On c.user_type_id = t.user_type_id
    Where c.object_id = IsNull(Object_Id(''' + @databaseName + '.' + @tableName + '''), c.object_id)
        And t.user_type_id In (48, 52, 56, 127, 167, 175, 231, 239);'

    If @Debug = 1
    Begin
        Select @sqlStatement_getColumnList;
    End;

    Insert Into @columnList 
    Execute sp_executeSQL @sqlStatement_getColumnList;

    If @Debug = 1
    Begin
        Select * From @columnList;
    End;

    /* Begin our loop.  We're going to run through this for every column.  */
    While Exists(Select * From @columnList Where columnStatus = 0)
    Begin

        /* Grab a column that hasn't been processed yet */
        Select Top 1 @currentRecord = id 
        From @columnList
        Where columnStatus = 0
        Order By id;

        /* First, let's build the statement we're going to use to get our min/max values */
        Select @sqlStatement_values = @sqlStatement_values + Case When user_type_id In (48, 52, 56, 127) 
                Then 'Max(' + columnName + ') As [' + columnName + '], ' 
                    + 'Min(' + columnName + ') As [min' + columnName + '], '
                Else 'Max(Len(' + columnName + ')) As [' + columnName + '], ' 
                    + 'Avg(Len(' + columnName + ')) As [avg' + columnName + '], '
                End 
        From @columnList
        Where id = @currentRecord;

        /* Next, let's build the statement that's going to show us how much space the column is currently consuming */
        Select @sqlStatement_columns = @sqlStatement_columns 
            + Case  When user_type_id = 48  Then '1' -- tinyint
                    When user_type_id = 52  Then '2' -- smallint
                    When user_type_id = 56  Then '4' -- int
                    When user_type_id = 127 Then '8' -- bigint
                    When user_type_id In (167, 175) Then Cast(max_length As varchar(10))-- varchar or char
                    Else Cast(max_length * 2 As varchar(10)) -- nvarchar or nchar
                    --Else '0'
                End + ' As [' + columnName + '], ' 
        From @columnList
        Where id = @currentRecord;

        /* This section is used to build a table definition */
        Select @sqlStatement_tableDefinition1 = @sqlStatement_tableDefinition1 + '[' + columnName + '] ' 
            + Case  
                When user_type_id = 48  Then 'tinyint'
                When user_type_id = 52  Then 'smallint'
                When user_type_id = 56  Then 'int'
                When user_type_id = 127 Then 'bigint'
                Else 'smallint'
              End + ', ' 
            + Case When user_type_id In (48, 52, 56, 127) Then '[min' Else '[avg' End + columnName + '] '
            + Case  
                When user_type_id = 48  Then 'tinyint'
                When user_type_id = 52  Then 'smallint'
                When user_type_id = 56  Then 'int'
                When user_type_id = 127 Then 'bigint'
                Else 'smallint'
              End + ', ' 
        From @columnList
        Where id = @currentRecord;

        /* More dynamic table definition code */
        Select @sqlStatement_tableDefinition2 = @sqlStatement_tableDefinition2 + '[' + columnName + '] ' 
            + Case  
                When user_type_id = 48  Then 'tinyint'
                When user_type_id = 52  Then 'smallint'
                When user_type_id = 56  Then 'int'
                When user_type_id = 127 Then 'bigint'
                Else 'smallint'
              End + ', ' 
        From @columnList
        Where id = @currentRecord;

        /* And yet more dynamic table definition code */
        Select @sqlStatement_tableDefinition3 = @sqlStatement_tableDefinition3 + columnName + ' smallint, '
                                                    + columnName + '_bytes bigint, '
        From @columnList
        Where id = @currentRecord;

        /* This is where we see how much space we actually need, based on our min/max values.
           This is where we consider the % of growth that we expect to see in a reasonable period of time. */
        Select @sqlStatement_spaceRequired = @sqlStatement_spaceRequired + 
            Case When user_type_id In (48, 52, 56, 127)
                Then 'Case When ([' + columnName + '] * ' + Cast(@growthPercentage As varchar(5)) + ') <= 255 
                                And [min' + columnName + '] >= 0 
                                    Then 1
                           When ([' + columnName + '] * ' + Cast(@growthPercentage As varchar(5)) + ') <= 32768 
                                And [min' + columnName + '] >= -32768 
                                    Then 2
                           When ([' + columnName + '] * ' + Cast(@growthPercentage As varchar(5)) + ') <= 2147483647 
                                And [min' + columnName + '] >= -2147483647 
                                    Then 4
                           Else 8 End '
                Else columnName
            End + ' As [' + columnName + '], '
        From @columnList
        Where id = @currentRecord;
        
        /* This is where the analysis occurs to tell us how much space we're potentially wasting */
        Select @sqlStatement_results = @sqlStatement_results + 
            'd.[' + columnName + '] - sr.[' + columnName + '] As [' + columnName + '], ' +
            '(d.[' + columnName + '] - sr.[' + columnName + ']) * rowCnt As [bytes], '
        From @columnList
        Where id = @currentRecord;

        /* This is where we get our pretty results table from */
        Select @sqlStatement_displayResults = @sqlStatement_displayResults + 'Select ''' + columnName + ''' As [columnName] '
                                                + ', ' + columnName + ' As [byteReduction] '
                                                -- + ', ' + columnName + '_bytes As [estimatedSpaceSavings] '
                                                + ', ' + columnName + '_bytes / 1024.0 / 1024.0 As [estimatedSpaceSavings] '
                                                + ' From ##results'
                                                + ' Union All '
        From @columnList
        Where id = @currentRecord;

        /* And lastly, this is where we get our total from */
        Select @sqlStatement_total = @sqlStatement_total + '([' + columnName + '_bytes] / 1024.0 / 1024.0) + ' 
        From @columnList
        Where id = @currentRecord;


        /* Mark the column as processed so we can move on to the next one */
        Update @columnList 
        Set columnStatus = 1
        Where id = @currentRecord;

    End;

    Select @sqlStatement_values = @sqlStatement_values + ' Count(*) As [rowCnt], 1 As [id] From ' + @databaseName + '.' + @tableName + ' Option (MaxDop 1);'
        , @sqlStatement_columns = @sqlStatement_columns + ' ' + Cast(@currentRecord As varchar(4)) + ' As [columnCnt], 1 As [id];';

    Set @sqlStatement_tableDefinition1 = 'Create Table ##values(' 
                                        + @sqlStatement_tableDefinition1 
                                        + ' rowCnt bigint, id tinyint)';

    Set @sqlStatement_tableDefinition2 = 'Create Table ##definition(' 
                                        + @sqlStatement_tableDefinition2
                                        + ' columnCnt bigint, id tinyint)';

    Set @sqlStatement_tableDefinition3 = 'Create Table ##results(' 
                                        + @sqlStatement_tableDefinition3
                                        + ' id tinyint)';

    Set @sqlStatement_spaceRequired = @sqlStatement_spaceRequired + '1 As [id] Into ##spaceRequired From ##values;'

    Set @sqlStatement_results = @sqlStatement_results + '1 As [id] From ##definition As d Join ##spaceRequired As sr On d.id = sr.id Join ##values As v On d.id = v.id;'

    Set @sqlStatement_displayResults = @sqlStatement_displayResults + @sqlStatement_total + '0 From ##results';

    /* Print our dynamic SQL statements in case we need to troubleshoot */
    If @debug = 1
    Begin
        Select @sqlStatement_values As '@sqlStatement_values'
            , @sqlStatement_columns As '@sqlStatement_columns'
            , @sqlStatement_tableDefinition1 As '@sqlStatement_tableDefinition1'
            , @sqlStatement_tableDefinition2 As '@sqlStatement_tableDefinition2'
            , @sqlStatement_spaceRequired As '@sqlStatement_spaceRequired'
            , @sqlStatement_results As '@sqlStatement_results'
            , @sqlStatement_displayResults As '@sqlStatement_displayResults'
            , @sqlStatement_total As '@sqlStatement_total';
    End;

    Select @sqlStatement_tableDefinition1 As 'Table Definition 1';
    Execute sp_executeSQL @sqlStatement_tableDefinition1;

    Select @sqlStatement_tableDefinition2 As 'Table Definition 2';
    Execute sp_executeSQL @sqlStatement_tableDefinition2;

    Select @sqlStatement_tableDefinition3 As 'Table Definition 3';
    Execute sp_executeSQL @sqlStatement_tableDefinition3;

    Select @sqlStatement_values As 'Insert 1';
    Insert Into ##values 
    Execute sp_executeSQL @sqlStatement_values;

    Select @sqlStatement_columns As 'Insert 2';
    Insert Into ##definition 
    Execute sp_executeSQL @sqlStatement_columns;

    Select @sqlStatement_spaceRequired As 'Execute space required';
    Execute sp_executeSQL @sqlStatement_spaceRequired;

    Select @sqlStatement_results As 'Execute results';
    Insert Into ##results
    Execute sp_executeSQL @sqlStatement_results;

    /* Output our table values for troubleshooting purposes */
    If @debug = 1
    Begin
        Select 'definition' As 'tableType', * From ##definition y 
        Select 'values' As 'tableType', * from ##values x 
        Select 'spaceRequired' As 'tableType', * From ##spaceRequired;
        Select 'results' As 'tableType', * From ##results;
    End;

    Select @sqlStatement_displayResults As 'Final results';
    Execute sp_executeSQL @sqlStatement_displayResults;

    /* Clean up our mess */
    --Drop Table ##values;
    --Drop Table ##definition;
    --Drop Table ##spaceRequired;
    --Drop Table ##results;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go

================================================
FILE: admin/dba_recompile_sp.sql
================================================
Use dbaTools;
Go

If ObjectProperty(Object_ID('dbo.dba_recompile_sp'), N'IsProcedure') Is Null
Begin
    Execute ('Create Procedure dbo.dba_recompile_sp As Print ''Hello World!''')
    RaisError('Procedure dba_recompile_sp created.', 10, 1);
End;
Go

Set ANSI_Nulls On;
Set Quoted_Identifier On;
Go

Alter Procedure dbo.dba_recompile_sp

        /* Declare Parameters */
          @databaseName nvarchar(128) = Null /* Null = all databases */
        , @tableName    nvarchar(128) = Null /* Null = all tables */        

As
/**********************************************************************************************************

    NAME:           dba_recompile_sp

    SYNOPSIS:       Recompiles all procs in a specific database or all procs; can recompile a specific table, too.

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2009-09-12
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;
Set Ansi_Padding On;
Set Ansi_Warnings On;
Set ArithAbort On;
Set Concat_Null_Yields_Null On;
Set Numeric_RoundAbort Off;

Begin

    /* Make sure the global temp tables do not already exist, i.e. failed execution */
    If Exists(Select * From tempdb.sys.tables Where name = '###databaseList')
        Drop Table #databaseList;
        
    If Exists(Select * From tempdb.sys.tables Where name = '##tableList')
        Drop Table tableList;

    /* Declare Temp Tables */
    Create Table ##databaseList
    (
          databaseName  nvarchar(128)
        , processed     bit
    );

    Create Table ##tableList
    (
          databaseName  nvarchar(128)
        , tableName     nvarchar(128)
        , processed     bit
    );

    Insert Into ##databaseList
    Select name As databaseName
        , 0 As processed
    From sys.databases
    Where name = IsNull(@databaseName, name);
    
    While Exists(Select Top 1 databaseName From ##databaseList Where processed = 0)
    Begin
        
        Execute sp_msforeachdb 'Use ?;
            Select name As tableName
            Into ##tableList
            From sys.tables
            Where name = IsNull(@tableName, name);

            Declare @tableName nvarchar(128) = (Select Top 1 tableName From #tableList);

            While Exists(Select Top 1 * From #tableList)
            Begin
                Execute sp_recompile @tableName;
                Delete From #tableList Where tableName = @tableName;
                Select Top 1 @tableName = tableName From #tableList Order By tableName;
            End;

            Drop Table ##tableList;'

    End

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go

If ObjectProperty(Object_ID('dbo.dba_recompile_sp'), N'IsProcedure') = 1 
    RaisError('Procedure dba_recompile_sp was successfully updated.', 10, 1);
Else
    RaisError('Procedure dba_recompile_sp FAILED to create!', 16, 1);
Go

================================================
FILE: admin/dba_replicationLatencyGet_sp.sql
================================================
If ObjectProperty(Object_ID('dbo.dba_replicationLatencyGet_sp'), N'IsProcedure') = 1
Begin
    Drop Procedure dbo.dba_replicationLatencyGet_sp;
    Print 'Procedure dba_replicationLatencyGet_sp dropped';
End;
Go

Set Quoted_Identifier On
Go
Set ANSI_Nulls On
Go

Create Procedure dbo.dba_replicationLatencyGet_sp

        /* Declare Parameters */
          @publicationToTest sysname        = N'goDaddyWebsiteTracking01'
        , @replicationDelay  varchar(10)    = N'00:00:30'
        , @iterations        int            = 5
        , @iterationDelay    varchar(10)    = N'00:00:30'
        , @deleteTokens      bit            = 1
        , @deleteTempTable   bit            = 1
As
/**********************************************************************************************************

    NAME:           dba_replicationLatencyGet_sp

    SYNOPSIS:       Retrieves the amount of replication latency in seconds

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer
                    
    NOTES:          Default settings will run 1 test every minute for 5 minutes.

                    @publicationToTest = defaults to goDaddyWebsiteTracking publication
    
                    @replicationDelay = how long to wait for the token to replicate;
                        probably should not set to anything less than 10 (in seconds)
    
                    @iterations = how many tokens you want to test
    
                    @iterationDelay = how long to wait between sending test tokens
                        (in seconds)
    
                    @deleteTokens = whether you want to retain tokens when done
    
                    @deleteTempTable = whether or not to retain the temporary table
                        when done.  Data stored to ##tokenResults; set @deleteTempTable 
                        flag to 0 if you do not want to delete when done.

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2008-05-22
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    USAGE:          EXEC dbo.dba_replicationLatencyGet_sp
                      @publicationToTest    = N'your_publication'
                    , @replicationDelay     = N'00:00:05'
                    , @iterations           = 1
                    , @iterationDelay       = N'00:00:05'
                    , @deleteTokens         = 1
                    , @deleteTempTable      = 1;

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;

Begin

    /* Declare Variables */
    Declare @currentIteration   int
          , @tokenID            bigint
          , @currentDateTime    smalldatetime;

    If Object_ID('tempdb.dbo.##tokenResults') Is Null
    Begin
        Create Table ##tokenResults
                        ( iteration           int             Null
                        , tracer_id           int             Null
                        , distributor_latency int             Null
                        , subscriber          varchar(1000)   Null
                        , subscriber_db       varchar(1000)   Null
                        , subscriber_latency  int             Null
                        , overall_latency     int             Null );
    End;

    /* Initialize our variables */
    Select @currentIteration = 0
         , @currentDateTime  = GetDate();

    While @currentIteration < @iterations
    Begin

        /* Insert a new tracer token in the publication database */
        Execute sys.sp_postTracerToken 
          @publication = @publicationToTest,
          @tracer_token_id = @tokenID OutPut;

        /* Give a few seconds to allow the record to reach the subscriber */
        WaitFor Delay @replicationDelay;

        /* Store our results in a temp table for retrieval later */
        Insert Into ##tokenResults
        (
            distributor_latency
          , subscriber
          , subscriber_db
          , subscriber_latency
          , overall_latency
        )
        Execute sys.sp_helpTracerTokenHistory @publicationToTest, @tokenID;

        /* Assign the iteration and token id to the results for easier investigation */
        Update ##tokenResults
        Set iteration = @currentIteration + 1
          , tracer_id = @tokenID
        Where iteration Is Null;

        /* Wait for the specified time period before creating another token */
        WaitFor Delay @iterationDelay;

        /* Avoid endless looping... :) */
        Set @currentIteration = @currentIteration + 1;

    End;

    Select * From ##tokenResults;

    If @deleteTempTable = 1
    Begin
        Drop Table ##tokenResults;
    End;

    If @deleteTokens = 1
    Begin
       Execute sp_deleteTracerTokenHistory @publication = @publicationToTest, @cutoff_date = @currentDateTime;
    End;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go
Set ANSI_Nulls On;
Go

If ObjectProperty(Object_ID('dbo.dba_replicationLatencyGet_sp'), N'IsProcedure') = 1 
    RaisError('Procedure dba_replicationLatencyGet_sp was successfully created.', 10, 1);
Else
    RaisError('Procedure dba_replicationLatencyGet_sp FAILED to create!', 16, 1);
Go


================================================
FILE: admin/dba_replicationLatencyMonitor_sp.sql
================================================
Use DBAHoldings;
Go

If Object_ID('dbo.dba_replicationMonitor') Is Null
Begin
    Create Table dbo.dba_replicationMonitor
    ( 
          monitor_id            int Identity(1,1)   Not Null
        , monitorDate           smalldatetime       Not Null 
        , publicationName       sysname             Not Null
        , publicationDB         sysname             Not Null
        , iteration             int                 Null
        , tracer_id             int                 Null
        , distributor_latency   int                 Null
        , subscriber            varchar(1000)       Null
        , subscriber_db         varchar(1000)       Null
        , subscriber_latency    int                 Null
        , overall_latency       int                 Null 
    );
End;

If ObjectProperty(Object_ID('dbo.dba_replicationLatencyMonitor_sp'), N'IsProcedure') = 1
Begin
    Drop Procedure dbo.dba_replicationLatencyMonitor_sp;
    Print 'Procedure dba_replicationLatencyMonitor_sp dropped';
End;
Go

Set Quoted_Identifier On
Go
Set ANSI_Nulls On
Go

Create Procedure dbo.dba_replicationLatencyMonitor_sp

        /* Declare Parameters */
          @publicationToTest    sysname        = N'YourPublication01'
        , @publicationDB        sysname        = N'YourPublicationDB'
        , @replicationDelay     varchar(10)    = N'00:00:30'
        , @iterations           int            = 5
        , @iterationDelay       varchar(10)    = N'00:00:30'
        , @displayResults       bit            = 0
        , @deleteTokens         bit            = 1
As
/**********************************************************************************************************

    NAME:           dba_replicationLatencyMonitor_sp

    SYNOPSIS:       Retrieves the amount of replication latency in seconds

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer
                    
    NOTES:          Default settings will run 1 test every minute for 5 minutes.

                      @publicationToTest = defaults to the specified publication
                      
                      @publicationDB = the database that is the source for the publication.
      				        The tracer procs are found in the publishing DB.
      
                      @replicationDelay = how long to wait for the token to replicate;
                          probably should not set to anything less than 10 (in seconds)
      
                      @iterations = how many tokens you want to test
      
                      @iterationDelay = how long to wait between sending test tokens
                          (in seconds)
      
                      @displayResults = print results to screen when complete
      
                      @deleteTokens = whether you want to retain tokens when done

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2008-11-23
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    USAGE:          EXEC dbo.dba_replicationLatencyMonitor_sp
                      @publicationToTest    = N'myTestPublication'
                    , @publicationDB        = N'sandbox_publisher'
                    , @replicationDelay     = N'00:00:05'
                    , @iterations           = 1
                    , @iterationDelay       = N'00:00:05'
                    , @displayResults       = 1
                    , @deleteTokens         = 1;

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;

Begin

    /* Declare Variables */
    Declare @currentIteration   int
          , @tokenID            bigint
          , @currentDateTime    smalldatetime
          , @sqlStatement       nvarchar(200)
          , @parmDefinition		nvarchar(500);

    Declare @tokenResults Table
    ( 
          iteration             int             Null
        , tracer_id             int             Null
        , distributor_latency   int             Null
        , subscriber            varchar(1000)   Null
        , subscriber_db         varchar(1000)   Null
        , subscriber_latency    int             Null
        , overall_latency       int             Null 
    );

    /* Initialize our variables */
    Select @currentIteration = 0
         , @currentDateTime  = GetDate();

    While @currentIteration < @iterations
    Begin

		/* Prepare the stored procedure execution string */
		Set @sqlStatement = N'Execute ' + @publicationDB + N'.sys.sp_postTracerToken ' + 
							N'@publication = @VARpublicationToTest , ' +
							N'@tracer_token_id = @VARtokenID OutPut;'
	
		/* Define the parameters used by the sp_ExecuteSQL later */
		Set @parmDefinition = N'@VARpublicationToTest sysname, ' +
			N'@VARtokenID bigint OutPut';

        /* Insert a new tracer token in the publication database */
        Execute sp_executesql 
              @sqlStatement
            , @parmDefinition
            , @VARpublicationToTest = @publicationToTest
            , @VARtokenID = @TokenID OutPut;

        /* Give a few seconds to allow the record to reach the subscriber */
        WaitFor Delay @replicationDelay;
        
        /* Prepare our statement to retrieve tracer token data */
        Select @sqlStatement = 'Execute ' + @publicationDB + '.sys.sp_helpTracerTokenHistory ' +
                    N'@publication = @VARpublicationToTest , ' +
                    N'@tracer_id = @VARtokenID'
            , @parmDefinition = N'@VARpublicationToTest sysname, ' +
                    N'@VARtokenID bigint';

        /* Store our results for retrieval later */
        Insert Into @tokenResults
        (
            distributor_latency
          , subscriber
          , subscriber_db
          , subscriber_latency
          , overall_latency
        )
        Execute sp_executesql 
              @sqlStatement
            , @parmDefinition
            , @VARpublicationToTest = @publicationToTest
            , @VARtokenID = @TokenID;

        /* Assign the iteration and token id to the results for easier investigation */
        Update @tokenResults
        Set iteration = @currentIteration + 1
          , tracer_id = @tokenID
        Where iteration Is Null;

        /* Wait for the specified time period before creating another token */
        WaitFor Delay @iterationDelay;

        /* Avoid endless looping... :) */
        Set @currentIteration = @currentIteration + 1;

    End;

    /* Display our results */
    If @displayResults = 1
    Begin
        Select 
              iteration
            , tracer_id
            , IsNull(distributor_latency, 0) As 'distributor_latency'
            , subscriber
            , subscriber_db
            , IsNull(subscriber_latency, 0) As 'subscriber_latency'
            , IsNull(overall_latency, 
                IsNull(distributor_latency, 0) + IsNull(subscriber_latency, 0))
                As 'overall_latency'
        From @tokenResults;
    End;

    /* Store our results */
    Insert Into dbo.dba_replicationMonitor
    (
          monitorDate
        , publicationName
        , publicationDB
        , iteration
        , tracer_id
        , distributor_latency
        , subscriber
        , subscriber_db
        , subscriber_latency
        , overall_latency
    )
    Select 
          @currentDateTime
        , @publicationToTest
        , @publicationDB
        , iteration
        , tracer_id
        , IsNull(distributor_latency, 0)
        , subscriber
        , subscriber_db
        , IsNull(subscriber_latency, 0)
        , IsNull(overall_latency, 
            IsNull(distributor_latency, 0) + IsNull(subscriber_latency, 0))
    From @tokenResults;

    /* Delete the tracer tokens if requested */
    If @deleteTokens = 1
    Begin
    
        Select @sqlStatement = 'Execute ' + @publicationDB + '.sys.sp_deleteTracerTokenHistory ' +
                    N'@publication = @VARpublicationToTest , ' +
                    N'@cutoff_date = @VARcurrentDateTime'
            , @parmDefinition = N'@VARpublicationToTest sysname, ' +
                    N'@VARcurrentDateTime datetime';
	        
        Execute sp_executesql 
              @sqlStatement
            , @parmDefinition
            , @VARpublicationToTest = @publicationToTest
            , @VARcurrentDateTime = @currentDateTime;
    
    End;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go
Set ANSI_Nulls On;
Go

If ObjectProperty(Object_ID('dbo.dba_replicationLatencyMonitor_sp'), N'IsProcedure') = 1 
    RaisError('Procedure dba_replicationLatencyMonitor_sp was successfully created.', 10, 1);
Else
    RaisError('Procedure dba_replicationLatencyMonitor_sp FAILED to create!', 16, 1);
Go


================================================
FILE: admin/sql_agent_job_history.sql
================================================
/**********************************************************************************************************

    NAME:           sql-agent-job-history.sql

    SYNOPSIS:       Explores SQL Agent Job metadata to get job statuses — when the job last ran, when it
                    will run again, an aggregate count of the number of successful and failed executions
                    in the queried time period, T-SQL code to disable the job, etc.

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com

    CREATED:        2012-12-18

    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER:
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

DECLARE @jobHistory TABLE
(
      job_id                UNIQUEIDENTIFIER
    , success               INT
    , cancel                INT
    , fail                  INT
    , retry                 INT
    , last_execution_id     INT
    , last_duration         CHAR(8)
    , last_execution_start  DATETIME
);

WITH lastExecution
AS
(
    SELECT job_id
    , MAX(instance_id) AS last_instance_id
FROM msdb.dbo.sysjobhistory
WHERE step_id = 0
GROUP BY job_id
)

INSERT INTO @jobHistory
SELECT sjh.job_id
    , SUM(CASE WHEN sjh.run_status = 1 AND step_id = 0 THEN 1 ELSE 0 END) AS success
    , SUM(CASE WHEN sjh.run_status = 3 AND step_id = 0 THEN 1 ELSE 0 END) AS cancel
    , SUM(CASE WHEN sjh.run_status = 0 AND step_id = 0 THEN 1 ELSE 0 END) AS fail
    , SUM(CASE WHEN sjh.run_status = 2 THEN 1 ELSE 0 END) AS retry
    , MAX(CASE WHEN sjh.step_id = 0 THEN instance_id ELSE NULL END) last_execution_id
    , SUBSTRING(CAST(MAX(CASE WHEN le.job_id IS NOT NULL THEN sjh.run_duration ELSE NULL END) + 1000000 AS VARCHAR(7)),2,2) + ':'
            + SUBSTRING(CAST(MAX(CASE WHEN le.job_id IS NOT NULL THEN sjh.run_duration ELSE NULL END) + 1000000 AS VARCHAR(7)),4,2) + ':'
            + SUBSTRING(CAST(MAX(CASE WHEN le.job_id IS NOT NULL THEN sjh.run_duration ELSE NULL END) + 1000000 AS VARCHAR(7)),6,2)
            AS last_duration
    , MAX(CASE WHEN le.last_instance_id IS NOT NULL THEN
        CONVERT(datetime, RTRIM(run_date))
        + ((run_time / 10000 *  3600)
        + ((run_time % 10000) / 100 * 60)
        + (run_time  % 10000) % 100) / (86399.9964)
      ELSE '1900-01-01' END) AS last_execution_start
FROM msdb.dbo.sysjobhistory AS sjh
LEFT JOIN lastExecution     AS le
    ON sjh.job_id = le.job_id
   AND sjh.instance_id = le.last_instance_id
GROUP BY sjh.job_id;

/* We need to parse the schedule into something we can understand */
DECLARE @weekDay TABLE (
      mask          INT
    , maskValue     VARCHAR(32)
);

INSERT INTO @weekDay
SELECT 1, 'Sunday'      UNION ALL
SELECT 2, 'Monday'      UNION ALL
SELECT 4, 'Tuesday'     UNION ALL
SELECT 8, 'Wednesday'   UNION ALL
SELECT 16, 'Thursday'   UNION ALL
SELECT 32, 'Friday'     UNION ALL
SELECT 64, 'Saturday';


/* Now let's get our schedule information */
WITH myCTE
AS(
    SELECT sched.name AS 'scheduleName'
        , sched.schedule_id
        , jobsched.job_id
        , CASE
            WHEN sched.freq_type = 1
                THEN 'Once'
            WHEN sched.freq_type = 4
                AND sched.freq_interval = 1
                    THEN 'Daily'
            WHEN sched.freq_type = 4
                THEN 'Every ' + CAST(sched.freq_interval AS VARCHAR(5)) + ' days'
            WHEN sched.freq_type = 8 THEN
                REPLACE( REPLACE( REPLACE((
                    SELECT maskValue
                    FROM @weekDay AS x
                    WHERE sched.freq_interval & x.mask <> 0
                    ORDER BY mask FOR XML RAW)
                , '"/><row maskValue="', ', '), '<row maskValue="', ''), '"/>', '')
                + CASE
                    WHEN sched.freq_recurrence_factor <> 0
                        AND sched.freq_recurrence_factor = 1
                            THEN '; weekly'
                    WHEN sched.freq_recurrence_factor <> 0
                        THEN '; every '
                + CAST(sched.freq_recurrence_factor AS VARCHAR(10)) + ' weeks' END
            WHEN sched.freq_type = 16 THEN 'On day '
                + CAST(sched.freq_interval AS VARCHAR(10)) + ' of every '
                + CAST(sched.freq_recurrence_factor AS VARCHAR(10)) + ' months'
            WHEN sched.freq_type = 32 THEN
                CASE
                    WHEN sched.freq_relative_interval = 1 THEN 'First'
                    WHEN sched.freq_relative_interval = 2 THEN 'Second'
                    WHEN sched.freq_relative_interval = 4 THEN 'Third'
                    WHEN sched.freq_relative_interval = 8 THEN 'Fourth'
                    WHEN sched.freq_relative_interval = 16 THEN 'Last'
                END +
                CASE
                    WHEN sched.freq_interval = 1 THEN ' Sunday'
                    WHEN sched.freq_interval = 2 THEN ' Monday'
                    WHEN sched.freq_interval = 3 THEN ' Tuesday'
                    WHEN sched.freq_interval = 4 THEN ' Wednesday'
                    WHEN sched.freq_interval = 5 THEN ' Thursday'
                    WHEN sched.freq_interval = 6 THEN ' Friday'
                    WHEN sched.freq_interval = 7 THEN ' Saturday'
                    WHEN sched.freq_interval = 8 THEN ' Day'
                    WHEN sched.freq_interval = 9 THEN ' Weekday'
                    WHEN sched.freq_interval = 10 THEN ' Weekend'
                END
                + CASE
                    WHEN sched.freq_recurrence_factor <> 0
                        AND sched.freq_recurrence_factor = 1
                            THEN '; monthly'
                    WHEN sched.freq_recurrence_factor <> 0
                        THEN '; every '
                + CAST(sched.freq_recurrence_factor AS VARCHAR(10)) + ' months'
                  END
            WHEN sched.freq_type = 64   THEN 'StartUp'
            WHEN sched.freq_type = 128  THEN 'Idle'
          END AS 'frequency'
        , ISNULL('Every ' + CAST(sched.freq_subday_interval AS VARCHAR(10)) +
            CASE
                WHEN sched.freq_subday_type = 2 THEN ' seconds'
                WHEN sched.freq_subday_type = 4 THEN ' minutes'
                WHEN sched.freq_subday_type = 8 THEN ' hours'
            END, 'Once') AS 'subFrequency'
        , REPLICATE('0', 6 - LEN(sched.active_start_time))
            + CAST(sched.active_start_time AS VARCHAR(6)) AS 'startTime'
        , REPLICATE('0', 6 - LEN(sched.active_end_time))
            + CAST(sched.active_end_time AS VARCHAR(6)) AS 'endTime'
        , REPLICATE('0', 6 - LEN(jobsched.next_run_time))
            + CAST(jobsched.next_run_time AS VARCHAR(6)) AS 'nextRunTime'
        , CAST(jobsched.next_run_date AS CHAR(8)) AS 'nextRunDate'
    FROM msdb.dbo.sysschedules      AS sched
    JOIN msdb.dbo.sysjobschedules   AS jobsched
        ON sched.schedule_id = jobsched.schedule_id
    WHERE sched.enabled = 1
)

/* Finally, let's look at our actual jobs and tie it all together */
SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY('Servername'))             AS [serverName]
    , job.job_id                                                        AS [jobID]
    , job.name                                                          AS [jobName]
    , CASE WHEN job.enabled = 1 THEN 'Enabled' ELSE 'Disabled' END      AS [jobStatus]
    , COALESCE(sched.scheduleName, '(unscheduled)')                     AS [scheduleName]
    , COALESCE(sched.frequency, '')                                     AS [frequency]
    , COALESCE(sched.subFrequency, '')                                  AS [subFrequency]
    , COALESCE(SUBSTRING(sched.startTime, 1, 2) + ':'
        + SUBSTRING(sched.startTime, 3, 2) + ' - '
        + SUBSTRING(sched.endTime, 1, 2) + ':'
        + SUBSTRING(sched.endTime, 3, 2), '')                           AS [scheduleTime] -- HH:MM
    , COALESCE(SUBSTRING(sched.nextRunDate, 1, 4) + '/'
        + SUBSTRING(sched.nextRunDate, 5, 2) + '/'
        + SUBSTRING(sched.nextRunDate, 7, 2) + ' '
        + SUBSTRING(sched.nextRunTime, 1, 2) + ':'
        + SUBSTRING(sched.nextRunTime, 3, 2), '')                       AS [nextRunDate]
      /* Note: the sysjobschedules table refreshes every 20 min, so nextRunDate may be out of date */
    , COALESCE(jh.success, 0)                                           AS [success]
    , COALESCE(jh.cancel, 0)                                            AS [cancel]
    , COALESCE(jh.fail, 0)                                              AS [fail]
    , COALESCE(jh.retry, 0)                                             AS [retry]
    , COALESCE(jh.last_execution_id, 0)                                 AS [lastExecutionID]
    , jh.last_execution_start                                           AS [lastExecutionStart]
    , COALESCE(jh.last_duration, '00:00:01')                            AS [lastDuration]
    , 'EXECUTE msdb.dbo.sp_update_job @job_id = '''
        + CAST(job.job_id AS CHAR(36)) + ''', @enabled = 0;'            AS [disableSQLScript]
FROM msdb.dbo.sysjobs               AS job
LEFT JOIN myCTE                     AS sched
    ON job.job_id = sched.job_id
LEFT JOIN @jobHistory               AS jh
    ON job.job_id = jh.job_id
WHERE job.enabled = 1 -- do not display disabled jobs
    --AND jh.last_execution_start >= DATEADD(day, -1, GETDATE()) /* Pull just the last 24 hours */
ORDER BY nextRunDate;

================================================
FILE: dev/bcp_script_generator.sql
================================================
/**********************************************************************************************************

    NAME:           bcp_script_generator.sql

    SYNOPSIS:       Generates bcp scripts using SQL Server metadata

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2012-05-17
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

-- User-defined variables --

DECLARE @tableToBCP NVARCHAR(128)   = 'sandbox.dbo.example_table'
    , @Top          VARCHAR(10)     = NULL -- Leave NULL for all rows
    , @Delimiter    VARCHAR(4)      = '|'
    , @UseNULL      BIT             = 1
    , @OverrideChar CHAR(1)         = '~'
    , @MaxDop       CHAR(1)         = '1'
    , @Directory    VARCHAR(256)    = 'D:\dba\mufford\scripts';


-- Script-defined variables -- 

DECLARE @columnList TABLE (columnID INT);

DECLARE @bcpStatement NVARCHAR(MAX) = 'BCP "SELECT '
    , @currentID INT
    , @firstID INT;

INSERT INTO @columnList
SELECT column_id 
FROM sys.columns 
WHERE object_id = OBJECT_ID(@tableToBCP)
ORDER BY column_id;

IF @Top IS NOT NULL
    SET @bcpStatement = @bcpStatement + 'TOP (' + @Top + ') ';

SELECT @firstID = MIN(columnID) FROM @columnList;

WHILE EXISTS(SELECT * FROM @columnList)
BEGIN

    SELECT @currentID = MIN(columnID) FROM @columnList;

    IF @currentID <> @firstID
        SET @bcpStatement = @bcpStatement + ',';

    SELECT @bcpStatement = @bcpStatement + 
                            CASE 
                                WHEN user_type_id IN (231, 167, 175, 239) 
                                THEN 'CASE WHEN ' + name + ' = '''' THEN ' 
                                    + CASE 
                                        WHEN is_nullable = 1 THEN 'NULL' 
                                        ELSE '''' + REPLICATE(@OverrideChar, max_length) + ''''
                                      END
                                    + ' WHEN ' + name + ' LIKE ''%' + @Delimiter + '%'''
                                        + ' OR ' + name + ' LIKE ''%'' + CHAR(9) + ''%''' -- tab
                                        + ' OR ' + name + ' LIKE ''%'' + CHAR(10) + ''%''' -- line feed
                                        + ' OR ' + name + ' LIKE ''%'' + CHAR(13) + ''%''' -- carriage return
                                        + ' THEN ' 
                                        + CASE 
                                            WHEN is_nullable = 1 THEN 'NULL' 
                                            ELSE '''' + REPLICATE(@OverrideChar, max_length) + ''''
                                          END
                                    + ' ELSE ' + name + ' END' 
                                ELSE name 
                            END 
    FROM sys.columns 
    WHERE object_id = OBJECT_ID(@tableToBCP)
        AND column_id = @currentID;

    DELETE FROM @columnList WHERE columnID = @currentID;


END;

SET @bcpStatement = @bcpStatement + ' FROM ' + @tableToBCP 
    + ' WITH (NOLOCK) OPTION (MAXDOP 1);" queryOut '
    + @Directory + REPLACE(@tableToBCP, '.', '_') + '.dat -S' + @@SERVERNAME
    + ' -T -t"' + @Delimiter + '" -c -C;'

SELECT @bcpStatement;

================================================
FILE: dev/dba_parseString_udf.sql
================================================

/* Let's create our parsing function... */
CREATE FUNCTION dbo.dba_parseString_udf
(
          @stringToParse VARCHAR(8000)  
        , @delimiter     CHAR(1)
)
RETURNS @parsedString TABLE (stringValue VARCHAR(128))
AS
/*********************************************************************************
    Name:       dba_parseString_udf
 
    Author:     Michelle Ufford, http://sqlfool.com
 
    Purpose:    This function parses string input using a variable delimiter.
 
    Notes:      Two common delimiter values are space (' ') and comma (',')

    Date        Initials    Description
    ----------------------------------------------------------------------------
    2011-05-20  MFU         Initial Release
*********************************************************************************
Usage: 		
    SELECT *
    FROM dba_parseString_udf(<string>, <delimiter>);

Test Cases:

    1.  multiple strings separated by space
        SELECT * FROM dbo.dba_parseString_udf('  aaa  bbb  ccc ', ' ');

    2.  multiple strings separated by comma
        SELECT * FROM dbo.dba_parseString_udf(',aaa,bbb,,,ccc,', ',');
*********************************************************************************/
BEGIN

    /* Declare variables */
    DECLARE @trimmedString  VARCHAR(8000);

    /* We need to trim our string input in case the user entered extra spaces */
    SET @trimmedString = LTRIM(RTRIM(@stringToParse));

    /* Let's create a recursive CTE to break down our string for us */
    WITH parseCTE (StartPos, EndPos)
    AS
    (
        SELECT 1 AS StartPos
            , CHARINDEX(@delimiter, @trimmedString + @delimiter) AS EndPos
        UNION ALL
        SELECT EndPos + 1 AS StartPos
            , CharIndex(@delimiter, @trimmedString + @delimiter , EndPos + 1) AS EndPos
        FROM parseCTE
        WHERE CHARINDEX(@delimiter, @trimmedString + @delimiter, EndPos + 1) <> 0
    )

    /* Let's take the results and stick it in a table */  
    INSERT INTO @parsedString
    SELECT SUBSTRING(@trimmedString, StartPos, EndPos - StartPos)
    FROM parseCTE
    WHERE LEN(LTRIM(RTRIM(SUBSTRING(@trimmedString, StartPos, EndPos - StartPos)))) > 0
    OPTION (MaxRecursion 8000);

    RETURN;   
END  

================================================
FILE: dev/insert_statement_generator.sql
================================================
/**********************************************************************************************************

    NAME:           insert_statement_generator.sql

    SYNOPSIS:       Generates insert statements for Teradata using SQL Server metadata.
                    This is useful for easily migrating small tables (i.e. < 1000 rows) 
                    from SQL Server to Teradata. DO NOT use on large tables. 

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2012-07-26
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

-- User-defined variables --
DECLARE 
      @tableName            NVARCHAR(128)   = 'dbo.example_table'
    , @Top                  VARCHAR(10)     = 1000 -- Leave NULL for all rows
    , @Execute              BIT             = 1
    , @GenerateSchema       BIT             = 1
    , @GenerateTruncate     BIT             = 1
    , @TeradataDatabase     VARCHAR(30)     = 'mufford'
    , @TeradataTable        VARCHAR(30)     = NULL -- Will generate if you leave NULL

-- Script-defined variables -- 

DECLARE @columnList TABLE (columnID INT);
DECLARE @TeradataTableName VARCHAR(60);

IF @TeradataTable IS NULL
    SET @TeradataTableName = @TeradataDatabase + '.tmp_' + SUBSTRING(@tableName,PATINDEX('%.%',@tableName)+1,26);
ELSE 
    SET @TeradataTableName = @TeradataDatabase + '.' + @TeradataTable;

DECLARE @insertStatement    NVARCHAR(MAX) = '' --= 'SELECT '
    , @columnStatement      NVARCHAR(MAX) = 'INSERT INTO ' + @TeradataTableName + ' ('
    , @schemaStatement      NVARCHAR(MAX) = 'CREATE TABLE ' + @TeradataTableName + '('
    , @currentID            INT
    , @firstID              INT;

INSERT INTO @columnList
SELECT column_id 
FROM sys.columns 
WHERE object_id = OBJECT_ID(@tableName)
ORDER BY column_id;

SELECT @firstID = MIN(columnID) FROM @columnList;

WHILE EXISTS(SELECT * FROM @columnList)
BEGIN

    SELECT @currentID = MIN(columnID) FROM @columnList;

    IF @currentID <> @firstID
    BEGIN
        SELECT 
              @columnStatement = @columnStatement + ','
            , @schemaStatement = @schemaStatement + ','
            , @insertStatement = @insertStatement + '+'',''+';
    END

    SELECT @columnStatement = @columnStatement + '"' + SUBSTRING(name, 1, 30) + '"'
    FROM sys.columns
    WHERE object_id = OBJECT_ID(@tableName)
        AND column_id = @currentID;

    SELECT @schemaStatement = @schemaStatement + '"' + SUBSTRING(c.name, 1, 30) + '" ' 
        + CASE 
            WHEN t.name = 'BIT'                             THEN 'BYTEINT'
            WHEN t.name = 'TINYINT'                         THEN 'SMALLINT'
            WHEN t.name = 'UNIQUEIDENTIFIER'                THEN 'CHAR(38)'
            WHEN t.name = 'DATETIME'                        THEN 'TIMESTAMP(3)'
            WHEN t.name = 'MONEY'                           THEN 'DECIMAL(18,4)'
            WHEN t.name = 'XML'                             THEN 'CLOB'
            WHEN t.name IN ('SMALLDATETIME', 'DATETIME2')   THEN 'TIMESTAMP(0)'
            WHEN t.name IN ('NVARCHAR','NCHAR')
                THEN SUBSTRING(t.name, 2, 10) + '(' + CAST(c.max_length / 2 AS VARCHAR(4)) + ') CHARACTER SET UNICODE NOT CASESPECIFIC'
            WHEN t.name IN ('VARCHAR','CHAR')
                THEN t.name + '(' + CAST(c.max_length AS VARCHAR(4)) + ')'
            ELSE t.name
        END
        + CASE
            WHEN c.is_nullable = 1 THEN ' NULL'
            ELSE ' NOT NULL'
        END
    FROM sys.columns    AS c
    JOIN sys.types      AS t
        ON c.system_type_id = t.system_type_id
    WHERE c.object_id = OBJECT_ID(@tableName)
        AND c.column_id = @currentID;

    SELECT DISTINCT @insertStatement = @insertStatement 
            + 'CASE WHEN ' + QUOTENAME(c.name) + ' IS NULL THEN ''NULL'' ELSE ' + 
            + CASE 
                WHEN t.name IN ('tinyint','smallint','int','real','float','bit','decimal','numeric','smallmoney','bigint') /* number-based columns */
                    THEN 'CAST(' + QUOTENAME(c.name) + ' AS VARCHAR(' + CAST(c.precision AS VARCHAR(10)) + '))' 
                WHEN t.name IN ('datetime', 'date', 'datetime2', 'smalldatetime') /* date-based columns */
                    THEN '''''''''+' + 'CONVERT(VARCHAR(23),' + QUOTENAME(c.name) + ',126)' + '+'''''''''
                WHEN t.name IN ('uniqueidentifier') /* guid columns */
                    THEN '''''''''+' + 'CAST(' + QUOTENAME(c.name) + ' AS CHAR(36))' + '+'''''''''
                WHEN t.name IN ('XML') /* xml columns */
                    THEN '''''''''+' + 'CAST(' + QUOTENAME(c.name) + ' AS VARCHAR(MAX))' + '+'''''''''
                ELSE '''''''''+REPLACE(' + QUOTENAME(c.name) + ','''''''','''''''''''')+''''''''' --'''+''''''+''' /* character-based columns */
              END 
                + ' END '
    FROM sys.columns    AS c
    JOIN sys.types      AS t
        ON c.system_type_id = t.system_type_id
    WHERE c.object_id = OBJECT_ID(@tableName)
        AND c.column_id = @currentID;

    DELETE FROM @columnList WHERE columnID = @currentID;

END;

SET @insertStatement = 'SELECT ' + CASE WHEN @Top IS NOT NULL THEN 'TOP (' + @Top + ') ' ELSE '' END + '''' + @columnStatement + ') VALUES (''+' + @insertStatement + '+'');'' FROM ' + @tableName + ' WITH (NOLOCK);';

IF @GenerateSchema = 1
    SELECT @schemaStatement + ');' AS 'Execute this statement in Teradata to create the table:'

IF @GenerateTruncate = 1
    SELECT 'DELETE FROM ' + @TeradataTableName + ';' AS 'Execute this statement in Teradata to truncate the table:'

IF @Execute = 1
    EXECUTE sp_executeSQL @insertStatement;
ELSE
    SELECT @insertStatement AS 'Execute this statement in SQL Server to generate commands:';


================================================
FILE: dev/teradata_ddl_generator.sql
================================================
/**********************************************************************************************************

    NAME:           teradata_ddl_generator.sql

    SYNOPSIS:       Generates Teradata DDL using SQL Server metadata

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2012-05-17
    
    VERSION:        1.0

    LICENSE:        Apache License v2
    
    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

-- User-defined variables --
DECLARE 
      @tableName            NVARCHAR(128)   = 'dbo.example_table'
    , @TeradataDatabase     VARCHAR(30)     = 'mufford'
    , @TeradataTable        VARCHAR(30)     = NULL -- Will generate if you leave NULL


-- Script-defined variables -- 

DECLARE @columnList TABLE (columnID INT);
DECLARE @TeradataTableName VARCHAR(60);

IF @TeradataTable IS NULL
    SET @TeradataTableName = @TeradataDatabase + '.tmp_' + SUBSTRING(@tableName,PATINDEX('%.%',@tableName)+1,26);
ELSE 
    SET @TeradataTableName = @TeradataDatabase + '.' + @TeradataTable;

DECLARE 
      @schemaStatement      NVARCHAR(MAX) = 'CREATE TABLE ' + @TeradataTableName + '('
    , @currentID            INT
    , @firstID              INT;

INSERT INTO @columnList
SELECT column_id 
FROM sys.columns 
WHERE object_id = OBJECT_ID(@tableName)
ORDER BY column_id;

SELECT @firstID = MIN(columnID) FROM @columnList;

WHILE EXISTS(SELECT * FROM @columnList)
BEGIN

    SELECT @currentID = MIN(columnID) FROM @columnList;

    IF @currentID <> @firstID
    BEGIN
        SELECT 
            @schemaStatement = @schemaStatement + ',';
    END

    SELECT @schemaStatement = @schemaStatement + '"' + c.name + '" ' 
        + CASE 
            WHEN t.name = 'BIT'                             THEN 'BYTEINT'
            WHEN t.name = 'TINYINT'                         THEN 'SMALLINT'
            WHEN t.name = 'UNIQUEIDENTIFIER'                THEN 'CHAR(38)'
            WHEN t.name = 'DATETIME'                        THEN 'TIMESTAMP(3)'
            WHEN t.name = 'MONEY'                           THEN 'DECIMAL(18,4)'
            WHEN t.name = 'XML'                             THEN 'CLOB'
            WHEN t.name IN ('SMALLDATETIME', 'DATETIME2')   THEN 'TIMESTAMP(0)'
            WHEN t.name IN ('NVARCHAR','NCHAR')
                THEN SUBSTRING(t.name, 2, 10) + '(' + CAST(c.max_length / 2 AS VARCHAR(4)) + ') CHARACTER SET UNICODE NOT CASESPECIFIC'
            WHEN t.name IN ('VARCHAR','CHAR')
                THEN t.name + '(' + CAST(c.max_length AS VARCHAR(4)) + ')'
            ELSE t.name
        END
        + CASE
            WHEN c.is_nullable = 1 THEN ' NULL'
            ELSE ' NOT NULL'
        END
    FROM sys.columns    AS c
    JOIN sys.types      AS t
        ON c.system_type_id = t.system_type_id
    WHERE c.object_id = OBJECT_ID(@tableName)
        AND c.column_id = @currentID;

    DELETE FROM @columnList WHERE columnID = @currentID;

END;

SELECT @schemaStatement + ');' AS 'Execute this statement in Teradata to create the table:'

================================================
FILE: indexes/dba_indexDefrag_sp.sql
================================================
/*** Scroll down to the see important notes, disclaimers, and licensing information ***/

/* Let's create our parsing function... */
IF EXISTS ( SELECT  [object_id]
            FROM    sys.objects
            WHERE   name = 'dba_parseString_udf' )
    DROP FUNCTION dbo.dba_parseString_udf;
GO

CREATE FUNCTION dbo.dba_parseString_udf
(
          @stringToParse VARCHAR(8000)  
        , @delimiter     CHAR(1)
)
RETURNS @parsedString TABLE (stringValue VARCHAR(128))
AS
/*********************************************************************************
    Name:       dba_parseString_udf
 
    Author:     Michelle Ufford, http://sqlfool.com
 
    Purpose:    This function parses string input using a variable delimiter.
 
    Notes:      Two common delimiter values are space (' ') and comma (',')
 
    Date        Initials    Description
    ----------------------------------------------------------------------------
    2011-05-20  MFU         Initial Release
*********************************************************************************
Usage: 		
    SELECT *
    FROM dba_parseString_udf(<string>, <delimiter>);
 
Test Cases:
 
    1.  multiple strings separated by space
        SELECT * FROM dbo.dba_parseString_udf('  aaa  bbb  ccc ', ' ');
 
    2.  multiple strings separated by comma
        SELECT * FROM dbo.dba_parseString_udf(',aaa,bbb,,,ccc,', ',');
*********************************************************************************/
BEGIN
 
    /* Declare variables */
    DECLARE @trimmedString  VARCHAR(8000);
 
    /* We need to trim our string input in case the user entered extra spaces */
    SET @trimmedString = LTRIM(RTRIM(@stringToParse));
 
    /* Let's create a recursive CTE to break down our string for us */
    WITH parseCTE (StartPos, EndPos)
    AS
    (
        SELECT 1 AS StartPos
            , CHARINDEX(@delimiter, @trimmedString + @delimiter) AS EndPos
        UNION ALL
        SELECT EndPos + 1 AS StartPos
            , CHARINDEX(@delimiter, @trimmedString + @delimiter , EndPos + 1) AS EndPos
        FROM parseCTE
        WHERE CHARINDEX(@delimiter, @trimmedString + @delimiter, EndPos + 1) <> 0
    )
 
    /* Let's take the results and stick it in a table */  
    INSERT INTO @parsedString
    SELECT SUBSTRING(@trimmedString, StartPos, EndPos - StartPos)
    FROM parseCTE
    WHERE LEN(LTRIM(RTRIM(SUBSTRING(@trimmedString, StartPos, EndPos - StartPos)))) > 0
    OPTION (MaxRecursion 8000);
 
    RETURN;   
END
GO

/* First, we need to take care of schema updates, in case you have a legacy 
   version of the script installed */
DECLARE @indexDefragLog_rename      VARCHAR(128)
  , @indexDefragExclusion_rename    VARCHAR(128)
  , @indexDefragStatus_rename       VARCHAR(128);

SELECT  @indexDefragLog_rename = 'dba_indexDefragLog_obsolete_' + CONVERT(VARCHAR(10), GETDATE(), 112)
      , @indexDefragExclusion_rename = 'dba_indexDefragExclusion_obsolete_' + CONVERT(VARCHAR(10), GETDATE(), 112);

IF EXISTS ( SELECT  [object_id]
            FROM    sys.indexes
            WHERE   name = 'PK_indexDefragLog' ) 
    EXECUTE sp_rename dba_indexDefragLog, @indexDefragLog_rename;

IF EXISTS ( SELECT  [object_id]
            FROM    sys.indexes
            WHERE   name = 'PK_indexDefragExclusion' ) 
    EXECUTE sp_rename dba_indexDefragExclusion, @indexDefragExclusion_rename;

IF NOT EXISTS ( SELECT  [object_id]
                FROM    sys.indexes
                WHERE   name = 'PK_indexDefragLog_v40' )
BEGIN

    CREATE TABLE dbo.dba_indexDefragLog
    (
         indexDefrag_id     INT IDENTITY(1, 1)  NOT NULL
       , databaseID         INT                 NOT NULL
       , databaseName       NVARCHAR(128)       NOT NULL
       , objectID           INT                 NOT NULL
       , objectName         NVARCHAR(128)       NOT NULL
       , indexID            INT                 NOT NULL
       , indexName          NVARCHAR(128)       NOT NULL
       , partitionNumber    SMALLINT            NOT NULL
       , fragmentation      FLOAT               NOT NULL
       , page_count         INT                 NOT NULL
       , dateTimeStart      DATETIME            NOT NULL
       , dateTimeEnd        DATETIME            NULL
       , durationSeconds    INT                 NULL
       , sqlStatement       VARCHAR(4000)       NULL
       , errorMessage       VARCHAR(1000)       NULL 

        CONSTRAINT PK_indexDefragLog_v40 
            PRIMARY KEY CLUSTERED (indexDefrag_id)
    );

    PRINT 'dba_indexDefragLog Table Created';

END

IF NOT EXISTS ( SELECT  [object_id]
                FROM    sys.indexes
                WHERE   name = 'PK_indexDefragExclusion_v40' )
BEGIN

    CREATE TABLE dbo.dba_indexDefragExclusion
    (
         databaseID         INT             NOT NULL
       , databaseName       NVARCHAR(128)   NOT NULL
       , objectID           INT             NOT NULL
       , objectName         NVARCHAR(128)   NOT NULL
       , indexID            INT             NOT NULL
       , indexName          NVARCHAR(128)   NOT NULL
       , exclusionMask      INT             NOT NULL
            /* 1=Sunday, 2=Monday, 4=Tuesday, 8=Wednesday, 16=Thursday, 32=Friday, 64=Saturday */

         CONSTRAINT PK_indexDefragExclusion_v40 
            PRIMARY KEY CLUSTERED (databaseID, objectID, indexID)
    );

    PRINT 'dba_indexDefragExclusion Table Created';

END
                
IF NOT EXISTS ( SELECT  [object_id]
                FROM    sys.indexes
                WHERE   name = 'PK_indexDefragStatus_v40' )
BEGIN

    CREATE TABLE dbo.dba_indexDefragStatus
    (
         databaseID         INT             NOT NULL
       , databaseName       NVARCHAR(128)   NOT NULL
       , objectID           INT             NOT NULL
       , indexID            INT             NOT NULL
       , partitionNumber    SMALLINT        NOT NULL
       , fragmentation      FLOAT           NOT NULL
       , page_count         INT             NOT NULL
       , range_scan_count   BIGINT          NOT NULL
       , schemaName         NVARCHAR(128)   NULL
       , objectName         NVARCHAR(128)   NULL
       , indexName          NVARCHAR(128)   NULL
       , scanDate           DATETIME        NOT NULL
       , defragDate         DATETIME        NULL
       , printStatus        BIT DEFAULT (0) NOT NULL
       , exclusionMask      INT DEFAULT (0) NOT NULL
    
        CONSTRAINT PK_indexDefragStatus_v40 
            PRIMARY KEY CLUSTERED (databaseID, objectID, indexID, partitionNumber)
    );

    PRINT 'dba_indexDefragStatus Table Created';

END;

IF OBJECTPROPERTY(OBJECT_ID('dbo.dba_indexDefrag_sp'), N'IsProcedure') = 1 
    BEGIN
        DROP PROCEDURE dbo.dba_indexDefrag_sp;
        PRINT 'Procedure dba_indexDefrag_sp dropped';
    END;
Go

CREATE PROCEDURE dbo.dba_indexDefrag_sp

    /* Declare Parameters */
    @minFragmentation       FLOAT               = 10.0  
        /* in percent, will not defrag if fragmentation less than specified */
  , @rebuildThreshold       FLOAT               = 30.0  
        /* in percent, greater than @rebuildThreshold will result in rebuild instead of reorg */
  , @executeSQL             BIT                 = 1     
        /* 1 = execute; 0 = print command only */
  , @defragOrderColumn      NVARCHAR(20)        = 'range_scan_count'
        /* Valid options are: range_scan_count, fragmentation, page_count */
  , @defragSortOrder        NVARCHAR(4)         = 'DESC'
        /* Valid options are: ASC, DESC */
  , @timeLimit              INT                 = 720 /* defaulted to 12 hours */
        /* Optional time limitation; expressed in minutes */
  , @database               VARCHAR(128)        = NULL
        /* Option to specify one or more database names, separated by commas; NULL will return all */
  , @tableName              VARCHAR(4000)       = NULL  -- databaseName.schema.tableName
        /* Option to specify a table name; null will return all */
  , @forceRescan            BIT                 = 0
        /* Whether or not to force a rescan of indexes; 1 = force, 0 = use existing scan, if available */
  , @scanMode               VARCHAR(10)         = N'LIMITED'
        /* Options are LIMITED, SAMPLED, and DETAILED */
  , @minPageCount           INT                 = 8 
        /*  MS recommends > 1 extent (8 pages) */
  , @maxPageCount           INT                 = NULL
        /* NULL = no limit */
  , @excludeMaxPartition    BIT                 = 0
        /* 1 = exclude right-most populated partition; 0 = do not exclude; see notes for caveats */
  , @onlineRebuild          BIT                 = 1     
        /* 1 = online rebuild; 0 = offline rebuild; only in Enterprise */
  , @sortInTempDB           BIT                 = 1
        /* 1 = perform sort operation in TempDB; 0 = perform sort operation in the index's database */
  , @maxDopRestriction      TINYINT             = NULL
        /* Option to restrict the number of processors for the operation; only in Enterprise */
  , @printCommands          BIT                 = 0     
        /* 1 = print commands; 0 = do not print commands */
  , @printFragmentation     BIT                 = 0
        /* 1 = print fragmentation prior to defrag; 
           0 = do not print */
  , @defragDelay            CHAR(8)             = '00:00:05'
        /* time to wait between defrag commands */
  , @debugMode              BIT                 = 0
        /* display some useful comments to help determine if/WHERE issues occur */
AS /*********************************************************************************
    Name:       dba_indexDefrag_sp

    Author:     Michelle Ufford, http://sqlfool.com

    Purpose:    Defrags one or more indexes for one or more databases

    Notes:

    CAUTION: TRANSACTION LOG SIZE SHOULD BE MONITORED CLOSELY WHEN DEFRAGMENTING.
             DO NOT RUN UNATTENDED ON LARGE DATABASES DURING BUSINESS HOURS.

      @minFragmentation     defaulted to 10%, will not defrag if fragmentation 
                            is less than that
      
      @rebuildThreshold     defaulted to 30% AS recommended by Microsoft in BOL;
                            greater than 30% will result in rebuild instead

      @executeSQL           1 = execute the SQL generated by this proc; 
                            0 = print command only

      @defragOrderColumn    Defines how to prioritize the order of defrags.  Only
                            used if @executeSQL = 1.  
                            Valid options are: 
                            range_scan_count = count of range and table scans on the
                                               index; in general, this is what benefits 
                                               the most FROM defragmentation
                            fragmentation    = amount of fragmentation in the index;
                                               the higher the number, the worse it is
                            page_count       = number of pages in the index; affects
                                               how long it takes to defrag an index

      @defragSortOrder      The sort order of the ORDER BY clause.
                            Valid options are ASC (ascending) or DESC (descending).

      @timeLimit            Optional, limits how much time can be spent performing 
                            index defrags; expressed in minutes.

                            NOTE: The time limit is checked BEFORE an index defrag
                                  is begun, thus a long index defrag can exceed the
                                  time limitation.

      @database             Optional, specify specific database name to defrag;
                            If not specified, all non-system databases will
                            be defragged.

      @tableName            Specify if you only want to defrag indexes for a 
                            specific table, format = databaseName.schema.tableName;
                            if not specified, all tables will be defragged.

      @forceRescan          Whether or not to force a rescan of indexes.  If set
                            to 0, a rescan will not occur until all indexes have
                            been defragged.  This can span multiple executions.
                            1 = force a rescan
                            0 = use previous scan, if there are indexes left to defrag

      @scanMode             Specifies which scan mode to use to determine
                            fragmentation levels.  Options are:
                            LIMITED - scans the parent level; quickest mode,
                                      recommended for most cases.
                            SAMPLED - samples 1% of all data pages; if less than
                                      10k pages, performs a DETAILED scan.
                            DETAILED - scans all data pages.  Use great care with
                                       this mode, AS it can cause performance issues.

      @minPageCount         Specifies how many pages must exist in an index in order 
                            to be considered for a defrag.  Defaulted to 8 pages, AS 
                            Microsoft recommends only defragging indexes with more 
                            than 1 extent (8 pages).  

                            NOTE: The @minPageCount will restrict the indexes that
                            are stored in dba_indexDefragStatus table.

      @maxPageCount         Specifies the maximum number of pages that can exist in 
                            an index and still be considered for a defrag.  Useful
                            for scheduling small indexes during business hours and
                            large indexes for non-business hours.

                            NOTE: The @maxPageCount will restrict the indexes that
                            are defragged during the current operation; it will not
                            prevent indexes FROM being stored in the 
                            dba_indexDefragStatus table.  This way, a single scan
                            can support multiple page count thresholds.

      @excludeMaxPartition  If an index is partitioned, this option specifies whether
                            to exclude the right-most populated partition.  Typically,
                            this is the partition that is currently being written to in
                            a sliding-window scenario.  Enabling this feature may reduce
                            contention.  This may not be applicable in other types of 
                            partitioning scenarios.  Non-partitioned indexes are 
                            unaffected by this option.
                            1 = exclude right-most populated partition
                            0 = do not exclude

      @onlineRebuild        1 = online rebuild; 
                            0 = offline rebuild

      @sortInTempDB         Specifies whether to defrag the index in TEMPDB or in the
                            database the index belongs to.  Enabling this option may
                            result in faster defrags and prevent database file size 
                            inflation.
                            1 = perform sort operation in TempDB
                            0 = perform sort operation in the index's database 

      @maxDopRestriction    Option to specify a processor limit for index rebuilds

      @printCommands        1 = print commands to screen; 
                            0 = do not print commands

      @printFragmentation   1 = print fragmentation to screen;
                            0 = do not print fragmentation

      @defragDelay          Time to wait between defrag commands; gives the
                            server a little time to catch up 

      @debugMode            1 = display debug comments; helps with troubleshooting
                            0 = do not display debug comments

    Called by:  SQL Agent Job or DBA

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------
    LICENSE: 
    This index defrag script is free to download and use for personal, educational, 
    and internal corporate purposes, provided that this header is preserved. 
    Redistribution or sale of this index defrag script, in whole or in part, is 
    prohibited without the author's express written consent.
    ----------------------------------------------------------------------------
    Date        Initials	Version Description
    ----------------------------------------------------------------------------
    2007-12-18  MFU         1.0     Initial Release
    2008-10-17  MFU         1.1     Added @defragDelay, CIX_temp_indexDefragList
    2008-11-17  MFU         1.2     Added page_count to log table
                                    , added @printFragmentation option
    2009-03-17  MFU         2.0     Provided support for centralized execution
                                    , consolidated Enterprise & Standard versions
                                    , added @debugMode, @maxDopRestriction
                                    , modified LOB and partition logic  
    2009-06-18  MFU         3.0     Fixed bug in LOB logic, added @scanMode option
                                    , added support for stat rebuilds (@rebuildStats)
                                    , support model and msdb defrag
                                    , added columns to the dba_indexDefragLog table
                                    , modified logging to show "in progress" defrags
                                    , added defrag exclusion list (scheduling)
    2009-08-28  MFU         3.1     Fixed read_only bug for database lists
    2010-04-20  MFU         4.0     Added time limit option
                                    , added static table with rescan logic
                                    , added parameters for page count & SORT_IN_TEMPDB
                                    , added try/catch logic and additional debug options
                                    , added options for defrag prioritization
                                    , fixed bug for indexes with allow_page_lock = off
                                    , added option to exclude right-most partition
                                    , removed @rebuildStats option
                                    , refer to http://sqlfool.com for full release notes
    2011-04-28  MFU         4.1     Bug fixes for databases requiring []
                                    , cleaned up the create table section
                                    , updated syntax for case-sensitive databases
                                    , comma-delimited list for @database now supported
*********************************************************************************
    Example of how to call this script:

        EXECUTE dbo.dba_indexDefrag_sp
              @executeSQL           = 1
            , @printCommands        = 1
            , @debugMode            = 1
            , @printFragmentation   = 1
            , @forceRescan          = 1
            , @maxDopRestriction    = 1
            , @minPageCount         = 8
            , @maxPageCount         = NULL
            , @minFragmentation     = 1
            , @rebuildThreshold     = 30
            , @defragDelay          = '00:00:05'
            , @defragOrderColumn    = 'page_count'
            , @defragSortOrder      = 'DESC'
            , @excludeMaxPartition  = 1
            , @timeLimit            = NULL
            , @database             = 'sandbox,sandbox_caseSensitive';
*********************************************************************************/																
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET QUOTED_IDENTIFIER ON;

BEGIN

    BEGIN TRY

        /* Just a little validation... */
        IF @minFragmentation IS NULL 
            OR @minFragmentation NOT BETWEEN 0.00 AND 100.0
                SET @minFragmentation = 10.0;

        IF @rebuildThreshold IS NULL
            OR @rebuildThreshold NOT BETWEEN 0.00 AND 100.0
                SET @rebuildThreshold = 30.0;

        IF @defragDelay NOT LIKE '00:[0-5][0-9]:[0-5][0-9]'
            SET @defragDelay = '00:00:05';

        IF @defragOrderColumn IS NULL
            OR @defragOrderColumn NOT IN ('range_scan_count', 'fragmentation', 'page_count')
                SET @defragOrderColumn = 'range_scan_count';

        IF @defragSortOrder IS NULL
            OR @defragSortOrder NOT IN ('ASC', 'DESC')
                SET @defragSortOrder = 'DESC';

        IF @scanMode NOT IN ('LIMITED', 'SAMPLED', 'DETAILED')
            SET @scanMode = 'LIMITED';

        IF @executeSQL IS NULL
            SET @executeSQL = 0;

        IF @debugMode IS NULL
            SET @debugMode = 0;

        IF @forceRescan IS NULL
            SET @forceRescan = 0;
            
        IF @minPageCount IS NULL
            SET @minPageCount = 8;

        IF @sortInTempDB IS NULL
            SET @sortInTempDB = 1;

        IF @onlineRebuild IS NULL
            SET @onlineRebuild = 0;

        IF @debugMode = 1 RAISERROR('Undusting the cogs AND starting up...', 0, 42) WITH NOWAIT;

        /* Declare our variables */
        DECLARE   @objectID                 INT
                , @databaseID               INT
                , @databaseName             NVARCHAR(128)
                , @indexID                  INT
                , @partitionCount           BIGINT
                , @schemaName               NVARCHAR(128)
                , @objectName               NVARCHAR(128)
                , @indexName                NVARCHAR(128)
                , @partitionNumber          SMALLINT
                , @fragmentation            FLOAT
                , @pageCount                INT
                , @sqlCommand               NVARCHAR(4000)
                , @rebuildCommand           NVARCHAR(200)
                , @datetimestart            DATETIME
                , @dateTimeEnd              DATETIME
                , @containsLOB              BIT
                , @editionCheck             BIT
                , @debugMessage             NVARCHAR(4000)
                , @updateSQL                NVARCHAR(4000)
                , @partitionSQL             NVARCHAR(4000)
                , @partitionSQL_Param       NVARCHAR(1000)
                , @LOB_SQL                  NVARCHAR(4000)
                , @LOB_SQL_Param            NVARCHAR(1000)
                , @indexDefrag_id           INT
                , @startdatetime            DATETIME
                , @enddatetime              DATETIME
                , @getIndexSQL              NVARCHAR(4000)
                , @getIndexSQL_Param        NVARCHAR(4000)
                , @allowPageLockSQL         NVARCHAR(4000)
                , @allowPageLockSQL_Param   NVARCHAR(4000)
                , @allowPageLocks           INT
                , @excludeMaxPartitionSQL   NVARCHAR(4000);

        /* Initialize our variables */
        SELECT @startdatetime = GETDATE()
            , @enddatetime = DATEADD(minute, @timeLimit, GETDATE());

        /* Create our temporary tables */
        CREATE TABLE #databaseList
        (
              databaseID        INT
            , databaseName      VARCHAR(128)
            , scanStatus        BIT
        );

        CREATE TABLE #processor 
        (
              [index]           INT
            , Name              VARCHAR(128)
            , Internal_Value    INT
            , Character_Value   INT
        );

        CREATE TABLE #maxPartitionList
        (
              databaseID        INT
            , objectID          INT
            , indexID           INT
            , maxPartition      INT
        );

        IF @debugMode = 1 RAISERROR('Beginning validation...', 0, 42) WITH NOWAIT;

        /* Make sure we're not exceeding the number of processors we have available */
        INSERT INTO #processor
        EXECUTE xp_msver 'ProcessorCount';

        IF @maxDopRestriction IS NOT NULL AND @maxDopRestriction > (SELECT Internal_Value FROM #processor)
            SELECT @maxDopRestriction = Internal_Value
            FROM #processor;

        /* Check our server version; 1804890536 = Enterprise, 610778273 = Enterprise Evaluation, -2117995310 = Developer */
        IF (SELECT ServerProperty('EditionID')) IN (1804890536, 610778273, -2117995310) 
            SET @editionCheck = 1 -- supports online rebuilds
        ELSE
            SET @editionCheck = 0; -- does not support online rebuilds

        /* Output the parameters we're working with */
        IF @debugMode = 1 
        BEGIN

            SELECT @debugMessage = 'Your SELECTed parameters are... 
            Defrag indexes WITH fragmentation greater than ' + CAST(@minFragmentation AS VARCHAR(10)) + ';
            REBUILD indexes WITH fragmentation greater than ' + CAST(@rebuildThreshold AS VARCHAR(10)) + ';
            You' + CASE WHEN @executeSQL = 1 THEN ' DO' ELSE ' DO NOT' END + ' want the commands to be executed automatically; 
            You want to defrag indexes in ' + @defragSortOrder + ' order of the ' + UPPER(@defragOrderColumn) + ' value;
            You have' + CASE WHEN @timeLimit IS NULL THEN ' NOT specified a time limit;' ELSE ' specified a time limit of ' 
                + CAST(@timeLimit AS VARCHAR(10)) END + ' minutes;
            ' + CASE WHEN @database IS NULL THEN 'ALL databases' ELSE 'The ' + @database + ' database(s)' END + ' will be defragged;
            ' + CASE WHEN @tableName IS NULL THEN 'ALL tables' ELSE 'The ' + @tableName + ' TABLE' END + ' will be defragged;
            We' + CASE WHEN EXISTS(SELECT Top 1 * FROM dbo.dba_indexDefragStatus WHERE defragDate IS NULL)
                AND @forceRescan <> 1 THEN ' WILL NOT' ELSE ' WILL' END + ' be rescanning indexes;
            The scan will be performed in ' + @scanMode + ' mode;
            You want to limit defrags to indexes with' + CASE WHEN @maxPageCount IS NULL THEN ' more than ' 
                + CAST(@minPageCount AS VARCHAR(10)) ELSE
                ' BETWEEN ' + CAST(@minPageCount AS VARCHAR(10))
                + ' AND ' + CAST(@maxPageCount AS VARCHAR(10)) END + ' pages;
            Indexes will be defragged' + CASE WHEN @editionCheck = 0 OR @onlineRebuild = 0 THEN ' OFFLINE;' ELSE ' ONLINE;' END + '
            Indexes will be sorted in' + CASE WHEN @sortInTempDB = 0 THEN ' the DATABASE' ELSE ' TEMPDB;' END + '
            Defrag operations will utilize ' + CASE WHEN @editionCheck = 0 OR @maxDopRestriction IS NULL 
                THEN 'system defaults for processors;' 
                ELSE CAST(@maxDopRestriction AS VARCHAR(2)) + ' processors;' END + '
            You' + CASE WHEN @printCommands = 1 THEN ' DO' ELSE ' DO NOT' END + ' want to PRINT the ALTER INDEX commands; 
            You' + CASE WHEN @printFragmentation = 1 THEN ' DO' ELSE ' DO NOT' END + ' want to OUTPUT fragmentation levels; 
            You want to wait ' + @defragDelay + ' (hh:mm:ss) BETWEEN defragging indexes;
            You want to run in' + CASE WHEN @debugMode = 1 THEN ' DEBUG' ELSE ' SILENT' END + ' mode.';

            RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
        
        END;

        IF @debugMode = 1 RAISERROR('Grabbing a list of our databases...', 0, 42) WITH NOWAIT;

        /* Retrieve the list of databases to investigate */
        /* If @database is NULL, it means we want to defrag *all* databases */
        IF @database IS NULL
        BEGIN

            INSERT INTO #databaseList
            SELECT database_id
                , name
                , 0 -- not scanned yet for fragmentation
            FROM sys.databases
            WHERE [name] NOT IN ('master', 'tempdb')-- exclude system databases
                AND [state] = 0 -- state must be ONLINE
                AND is_read_only = 0;  -- cannot be read_only

        END;
        ELSE
        /* Otherwise, we're going to just defrag our list of databases */
        BEGIN

            INSERT INTO #databaseList
            SELECT database_id
                , name
                , 0 -- not scanned yet for fragmentation
            FROM sys.databases AS d
            JOIN dbo.dba_parseString_udf(@database, ',') AS x
                ON d.name COLLATE database_default = x.stringValue
            WHERE [name] NOT IN ('master', 'tempdb')-- exclude system databases
                AND [state] = 0 -- state must be ONLINE
                AND is_read_only = 0;  -- cannot be read_only

        END; 

        /* Check to see IF we have indexes in need of defrag; otherwise, re-scan the database(s) */
        IF NOT EXISTS(SELECT Top 1 * FROM dbo.dba_indexDefragStatus WHERE defragDate IS NULL)
            OR @forceRescan = 1
        BEGIN

            /* Truncate our list of indexes to prepare for a new scan */
            TRUNCATE TABLE dbo.dba_indexDefragStatus;

            IF @debugMode = 1 RAISERROR('Looping through our list of databases and checking for fragmentation...', 0, 42) WITH NOWAIT;

            /* Loop through our list of databases */
            WHILE (SELECT COUNT(*) FROM #databaseList WHERE scanStatus = 0) > 0
            BEGIN

                SELECT Top 1 @databaseID = databaseID
                FROM #databaseList
                WHERE scanStatus = 0;

                SELECT @debugMessage = '  working on ' + DB_NAME(@databaseID) + '...';

                IF @debugMode = 1
                    RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;

               /* Determine which indexes to defrag using our user-defined parameters */
                INSERT INTO dbo.dba_indexDefragStatus
                (
                      databaseID
                    , databaseName
                    , objectID
                    , indexID
                    , partitionNumber
                    , fragmentation
                    , page_count
                    , range_scan_count
                    , scanDate
                )
                SELECT
                      ps.database_id AS 'databaseID'
                    , QUOTENAME(DB_NAME(ps.database_id)) AS 'databaseName'
                    , ps.[object_id] AS 'objectID'
                    , ps.index_id AS 'indexID'
                    , ps.partition_number AS 'partitionNumber'
                    , SUM(ps.avg_fragmentation_in_percent) AS 'fragmentation'
                    , SUM(ps.page_count) AS 'page_count'
                    , os.range_scan_count
                    , GETDATE() AS 'scanDate'
                FROM sys.dm_db_index_physical_stats(@databaseID, OBJECT_ID(@tableName), NULL , NULL, @scanMode) AS ps
                JOIN sys.dm_db_index_operational_stats(@databaseID, OBJECT_ID(@tableName), NULL , NULL) AS os
                    ON ps.database_id = os.database_id
                    AND ps.[object_id] = os.[object_id]
                    AND ps.index_id = os.index_id
                    AND ps.partition_number = os.partition_number
                WHERE avg_fragmentation_in_percent >= @minFragmentation 
                    AND ps.index_id > 0 -- ignore heaps
                    AND ps.page_count > @minPageCount 
                    AND ps.index_level = 0 -- leaf-level nodes only, supports @scanMode
                GROUP BY ps.database_id 
                    , QUOTENAME(DB_NAME(ps.database_id)) 
                    , ps.[object_id]
                    , ps.index_id 
                    , ps.partition_number 
                    , os.range_scan_count
                OPTION (MAXDOP 2);

                /* Do we want to exclude right-most populated partition of our partitioned indexes? */
                IF @excludeMaxPartition = 1
                BEGIN

                    SET @excludeMaxPartitionSQL = '
                        SELECT ' + CAST(@databaseID AS VARCHAR(10)) + ' AS [databaseID]
                            , [object_id]
                            , index_id
                            , MAX(partition_number) AS [maxPartition]
                        FROM [' + DB_NAME(@databaseID) + '].sys.partitions
                        WHERE partition_number > 1
                            AND [rows] > 0
                        GROUP BY object_id
                            , index_id;';

                    INSERT INTO #maxPartitionList
                    EXECUTE sp_executesql @excludeMaxPartitionSQL;

                END;
                
                /* Keep track of which databases have already been scanned */
                UPDATE #databaseList
                SET scanStatus = 1
                WHERE databaseID = @databaseID;

            END

            /* We don't want to defrag the right-most populated partition, so
               delete any records for partitioned indexes where partition = MAX(partition) */
            IF @excludeMaxPartition = 1
            BEGIN

                DELETE ids
                FROM dbo.dba_indexDefragStatus AS ids
                JOIN #maxPartitionList AS mpl
                    ON ids.databaseID = mpl.databaseID
                    AND ids.objectID = mpl.objectID
                    AND ids.indexID = mpl.indexID
                    AND ids.partitionNumber = mpl.maxPartition;

            END;

            /* Update our exclusion mask for any index that has a restriction ON the days it can be defragged */
            UPDATE ids
            SET ids.exclusionMask = ide.exclusionMask
            FROM dbo.dba_indexDefragStatus AS ids
            JOIN dbo.dba_indexDefragExclusion AS ide
                ON ids.databaseID = ide.databaseID
                AND ids.objectID = ide.objectID
                AND ids.indexID = ide.indexID;
         
        END

        SELECT @debugMessage = 'Looping through our list... there are ' + CAST(COUNT(*) AS VARCHAR(10)) + ' indexes to defrag!'
        FROM dbo.dba_indexDefragStatus
        WHERE defragDate IS NULL
            AND page_count BETWEEN @minPageCount AND ISNULL(@maxPageCount, page_count);

        IF @debugMode = 1 RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;

        /* Begin our loop for defragging */
        WHILE (SELECT COUNT(*) 
               FROM dbo.dba_indexDefragStatus 
               WHERE (
                           (@executeSQL = 1 AND defragDate IS NULL) 
                        OR (@executeSQL = 0 AND defragDate IS NULL AND printStatus = 0)
                     )
                AND exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0
                AND page_count BETWEEN @minPageCount AND ISNULL(@maxPageCount, page_count)) > 0
        BEGIN

            /* Check to see IF we need to exit our loop because of our time limit */        
            IF ISNULL(@enddatetime, GETDATE()) < GETDATE()
            BEGIN
                RAISERROR('Our time limit has been exceeded!', 11, 42) WITH NOWAIT;
            END;

            IF @debugMode = 1 RAISERROR('  Picking an index to beat into shape...', 0, 42) WITH NOWAIT;

            /* Grab the index with the highest priority, based on the values submitted; 
               Look at the exclusion mask to ensure it can be defragged today */
            SET @getIndexSQL = N'
            SELECT TOP 1 
                  @objectID_Out         = objectID
                , @indexID_Out          = indexID
                , @databaseID_Out       = databaseID
                , @databaseName_Out     = databaseName
                , @fragmentation_Out    = fragmentation
                , @partitionNumber_Out  = partitionNumber
                , @pageCount_Out        = page_count
            FROM dbo.dba_indexDefragStatus
            WHERE defragDate IS NULL ' 
                + CASE WHEN @executeSQL = 0 THEN 'AND printStatus = 0' ELSE '' END + '
                AND exclusionMask & Power(2, DatePart(weekday, GETDATE())-1) = 0
                AND page_count BETWEEN @p_minPageCount AND ISNULL(@p_maxPageCount, page_count)
            ORDER BY + ' + @defragOrderColumn + ' ' + @defragSortOrder;
                       
            SET @getIndexSQL_Param = N'@objectID_Out        INT OUTPUT
                                     , @indexID_Out         INT OUTPUT
                                     , @databaseID_Out      INT OUTPUT
                                     , @databaseName_Out    NVARCHAR(128) OUTPUT
                                     , @fragmentation_Out   INT OUTPUT
                                     , @partitionNumber_Out INT OUTPUT
                                     , @pageCount_Out       INT OUTPUT
                                     , @p_minPageCount      INT
                                     , @p_maxPageCount      INT';

            EXECUTE sp_executesql @getIndexSQL
                , @getIndexSQL_Param
                , @p_minPageCount       = @minPageCount
                , @p_maxPageCount       = @maxPageCount
                , @objectID_Out         = @objectID         OUTPUT
                , @indexID_Out          = @indexID          OUTPUT
                , @databaseID_Out       = @databaseID       OUTPUT
                , @databaseName_Out     = @databaseName     OUTPUT
                , @fragmentation_Out    = @fragmentation    OUTPUT
                , @partitionNumber_Out  = @partitionNumber  OUTPUT
                , @pageCount_Out        = @pageCount        OUTPUT;

            IF @debugMode = 1 RAISERROR('  Looking up the specifics for our index...', 0, 42) WITH NOWAIT;

            /* Look up index information */
            SELECT @updateSQL = N'UPDATE ids
                SET schemaName = QUOTENAME(s.name)
                    , objectName = QUOTENAME(o.name)
                    , indexName = QUOTENAME(i.name)
                FROM dbo.dba_indexDefragStatus AS ids
                INNER JOIN ' + @databaseName + '.sys.objects AS o
                    ON ids.objectID = o.[object_id]
                INNER JOIN ' + @databaseName + '.sys.indexes AS i
                    ON o.[object_id] = i.[object_id]
                    AND ids.indexID = i.index_id
                INNER JOIN ' + @databaseName + '.sys.schemas AS s
                    ON o.schema_id = s.schema_id
                WHERE o.[object_id] = ' + CAST(@objectID AS VARCHAR(10)) + '
                    AND i.index_id = ' + CAST(@indexID AS VARCHAR(10)) + '
                    AND i.type > 0
                    AND ids.databaseID = ' + CAST(@databaseID AS VARCHAR(10));

            EXECUTE sp_executesql @updateSQL;

            /* Grab our object names */
            SELECT @objectName  = objectName
                , @schemaName   = schemaName
                , @indexName    = indexName
            FROM dbo.dba_indexDefragStatus
            WHERE objectID = @objectID
                AND indexID = @indexID
                AND databaseID = @databaseID;

            IF @debugMode = 1 RAISERROR('  Grabbing the partition COUNT...', 0, 42) WITH NOWAIT;

            /* Determine if the index is partitioned */
            SELECT @partitionSQL = 'SELECT @partitionCount_OUT = COUNT(*)
                                        FROM ' + @databaseName + '.sys.partitions
                                        WHERE object_id = ' + CAST(@objectID AS VARCHAR(10)) + '
                                            AND index_id = ' + CAST(@indexID AS VARCHAR(10)) + ';'
                , @partitionSQL_Param = '@partitionCount_OUT INT OUTPUT';

            EXECUTE sp_executesql @partitionSQL, @partitionSQL_Param, @partitionCount_OUT = @partitionCount OUTPUT;

            IF @debugMode = 1 RAISERROR('  Seeing IF there are any LOBs to be handled...', 0, 42) WITH NOWAIT;
        
            /* Determine if the table contains LOBs */
            SELECT @LOB_SQL = ' SELECT @containsLOB_OUT = COUNT(*)
                                FROM ' + @databaseName + '.sys.columns WITH (NoLock) 
                                WHERE [object_id] = ' + CAST(@objectID AS VARCHAR(10)) + '
                                   AND (system_type_id IN (34, 35, 99)
                                            OR max_length = -1);'
                                /*  system_type_id --> 34 = IMAGE, 35 = TEXT, 99 = NTEXT
                                    max_length = -1 --> VARBINARY(MAX), VARCHAR(MAX), NVARCHAR(MAX), XML */
                    , @LOB_SQL_Param = '@containsLOB_OUT INT OUTPUT';

            EXECUTE sp_executesql @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OUTPUT;

            IF @debugMode = 1 RAISERROR('  Checking for indexes that do NOT allow page locks...', 0, 42) WITH NOWAIT;

            /* Determine if page locks are allowed; for those indexes, we need to always REBUILD */
            SELECT @allowPageLockSQL = 'SELECT @allowPageLocks_OUT = COUNT(*)
                                        FROM ' + @databaseName + '.sys.indexes
                                        WHERE object_id = ' + CAST(@objectID AS VARCHAR(10)) + '
                                            AND index_id = ' + CAST(@indexID AS VARCHAR(10)) + '
                                            AND Allow_Page_Locks = 0;'
                , @allowPageLockSQL_Param = '@allowPageLocks_OUT INT OUTPUT';

            EXECUTE sp_executesql @allowPageLockSQL, @allowPageLockSQL_Param, @allowPageLocks_OUT = @allowPageLocks OUTPUT;

            IF @debugMode = 1 RAISERROR('  Building our SQL statements...', 0, 42) WITH NOWAIT;

            /* IF there's not a lot of fragmentation, or if we have a LOB, we should REORGANIZE */
            IF (@fragmentation < @rebuildThreshold OR @containsLOB >= 1 OR @partitionCount > 1)
                AND @allowPageLocks = 0
            BEGIN
            
                SET @sqlCommand = N'ALTER INDEX ' + @indexName + N' ON ' + @databaseName + N'.' 
                                    + @schemaName + N'.' + @objectName + N' REORGANIZE';

                /* If our index is partitioned, we should always REORGANIZE */
                IF @partitionCount > 1
                    SET @sqlCommand = @sqlCommand + N' PARTITION = ' 
                                    + CAST(@partitionNumber AS NVARCHAR(10));

            END
            /* If the index is heavily fragmented and doesn't contain any partitions or LOB's, 
               or if the index does not allow page locks, REBUILD it */
            ELSE IF (@fragmentation >= @rebuildThreshold OR @allowPageLocks <> 0)
                AND ISNULL(@containsLOB, 0) != 1 AND @partitionCount <= 1
            BEGIN

                /* Set online REBUILD options; requires Enterprise Edition */
                IF @onlineRebuild = 1 AND @editionCheck = 1 
                    SET @rebuildCommand = N' REBUILD WITH (ONLINE = ON';
                ELSE
                    SET @rebuildCommand = N' REBUILD WITH (ONLINE = Off';
                
                /* Set sort operation preferences */
                IF @sortInTempDB = 1 
                    SET @rebuildCommand = @rebuildCommand + N', SORT_IN_TEMPDB = ON';
                ELSE
                    SET @rebuildCommand = @rebuildCommand + N', SORT_IN_TEMPDB = Off';

                /* Set processor restriction options; requires Enterprise Edition */
                IF @maxDopRestriction IS NOT NULL AND @editionCheck = 1
                    SET @rebuildCommand = @rebuildCommand + N', MAXDOP = ' + CAST(@maxDopRestriction AS VARCHAR(2)) + N')';
                ELSE
                    SET @rebuildCommand = @rebuildCommand + N')';

                SET @sqlCommand = N'ALTER INDEX ' + @indexName + N' ON ' + @databaseName + N'.'
                                + @schemaName + N'.' + @objectName + @rebuildCommand;

            END
            ELSE
                /* Print an error message if any indexes happen to not meet the criteria above */
                IF @printCommands = 1 OR @debugMode = 1
                    RAISERROR('We are unable to defrag this index.', 0, 42) WITH NOWAIT;

            /* Are we executing the SQL?  IF so, do it */
            IF @executeSQL = 1
            BEGIN

                SET @debugMessage = 'Executing: ' + @sqlCommand;
                
                /* Print the commands we're executing if specified to do so */
                IF @printCommands = 1 OR @debugMode = 1
                    RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;

                /* Grab the time for logging purposes */
                SET @datetimestart  = GETDATE();

                /* Log our actions */
                INSERT INTO dbo.dba_indexDefragLog
                (
                      databaseID
                    , databaseName
                    , objectID
                    , objectName
                    , indexID
                    , indexName
                    , partitionNumber
                    , fragmentation
                    , page_count
                    , dateTimeStart
                    , sqlStatement
                )
                SELECT
                      @databaseID
                    , @databaseName
                    , @objectID
                    , @objectName
                    , @indexID
                    , @indexName
                    , @partitionNumber
                    , @fragmentation
                    , @pageCount
                    , @datetimestart
                    , @sqlCommand;

                SET @indexDefrag_id = SCOPE_IDENTITY();

                /* Wrap our execution attempt in a TRY/CATCH and log any errors that occur */
                BEGIN TRY

                    /* Execute our defrag! */
                    EXECUTE sp_executesql @sqlCommand;
                    SET @dateTimeEnd = GETDATE();
                    
                    /* Update our log with our completion time */
                    UPDATE dbo.dba_indexDefragLog
                    SET dateTimeEnd = @dateTimeEnd
                        , durationSeconds = DATEDIFF(second, @datetimestart, @dateTimeEnd)
                    WHERE indexDefrag_id = @indexDefrag_id;

                END TRY
                BEGIN CATCH

                    /* Update our log with our error message */
                    UPDATE dbo.dba_indexDefragLog
                    SET dateTimeEnd = GETDATE()
                        , durationSeconds = -1
                        , errorMessage = ERROR_MESSAGE()
                    WHERE indexDefrag_id = @indexDefrag_id;

                    IF @debugMode = 1 
                        RAISERROR('  An error has occurred executing this command! Please review the dba_indexDefragLog table for details.'
                            , 0, 42) WITH NOWAIT;

                END CATCH

                /* Just a little breather for the server */
                WAITFOR DELAY @defragDelay;

                UPDATE dbo.dba_indexDefragStatus
                SET defragDate = GETDATE()
                    , printStatus = 1
                WHERE databaseID       = @databaseID
                  AND objectID         = @objectID
                  AND indexID          = @indexID
                  AND partitionNumber  = @partitionNumber;

            END
            ELSE
            /* Looks like we're not executing, just printing the commands */
            BEGIN
                IF @debugMode = 1 RAISERROR('  Printing SQL statements...', 0, 42) WITH NOWAIT;
                
                IF @printCommands = 1 OR @debugMode = 1 
                    PRINT ISNULL(@sqlCommand, 'error!');

                UPDATE dbo.dba_indexDefragStatus
                SET printStatus = 1
                WHERE databaseID       = @databaseID
                  AND objectID         = @objectID
                  AND indexID          = @indexID
                  AND partitionNumber  = @partitionNumber;
            END

        END

        /* Do we want to output our fragmentation results? */
        IF @printFragmentation = 1
        BEGIN

            IF @debugMode = 1 RAISERROR('  Displaying a summary of our action...', 0, 42) WITH NOWAIT;

            SELECT databaseID
                , databaseName
                , objectID
                , objectName
                , indexID
                , indexName
                , partitionNumber
                , fragmentation
                , page_count
                , range_scan_count
            FROM dbo.dba_indexDefragStatus
            WHERE defragDate >= @startdatetime
            ORDER BY defragDate;

        END;

    END TRY
    BEGIN CATCH

        SET @debugMessage = ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS VARCHAR(10)) + ')';
        PRINT @debugMessage;

    END CATCH;

    /* When everything is said and done, make sure to get rid of our temp table */
    DROP TABLE #databaseList;
    DROP TABLE #processor;
    DROP TABLE #maxPartitionList;

    IF @debugMode = 1 RAISERROR('DONE!  Thank you for taking care of your indexes!  :)', 0, 42) WITH NOWAIT;

    SET NOCOUNT OFF;
    RETURN 0;
END


================================================
FILE: indexes/dba_indexLookup_sp.sql
================================================
If ObjectProperty(Object_ID('dbo.dba_indexLookup_sp'), N'IsProcedure') Is Null
Begin
    Execute ('Create Procedure dbo.dba_indexLookup_sp As Print ''Hello World!''')
    RaisError('Procedure dbo.dba_indexLookup_sp created.', 10, 1);
End;
Go

Set ANSI_Nulls On;
Set Ansi_Padding On;
Set Ansi_Warnings On;
Set ArithAbort On;
Set Concat_Null_Yields_Null On;
Set NoCount On;
Set Numeric_RoundAbort Off;
Set Quoted_Identifier On;
Go

Alter Procedure dbo.dba_indexLookup_sp

        /* Declare Parameters */
        @tableName  varchar(128)  =  Null
As
/**********************************************************************************************************

    NAME:           dba_indexLookup_sp

    SYNOPSIS:       Retrieves index information for the specified table name.

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    NOTES:          If the tableName is left null, it will return index information 
                    for all tables and indexes.

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2008-10-28
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;

Begin

    Declare @objectID int;

    If @tableName Is Not Null
        Set @objectID = Object_ID(@tableName);

    With indexCTE(partition_scheme_name
                , partition_function_name
                , data_space_id)
    As (
        Select sps.name
            , spf.name
            , sps.data_space_id
        From sys.partition_schemes As sps
        Join sys.partition_functions As spf
            On sps.function_id = spf.function_id
    )

    Select st.name As 'table_name'
        , IsNull(ix.name, '') As 'index_name'
        , ix.object_id
        , ix.index_id
		, Cast(
            Case When ix.index_id = 1 
                    Then 'clustered' 
                When ix.index_id =0
                    Then 'heap'
                Else 'nonclustered' End
			+ Case When ix.ignore_dup_key <> 0 
                Then ', ignore duplicate keys' 
                    Else '' End
			+ Case When ix.is_unique <> 0 
                Then ', unique' 
                    Else '' End
			+ Case When ix.is_primary_key <> 0 
                Then ', primary key' Else '' End As varchar(210)
            ) As 'index_description'
        , IsNull(Replace( Replace( Replace(
            (   
                Select c.name As 'columnName'
                From sys.index_columns As sic
                Join sys.columns As c 
                    On c.column_id = sic.column_id 
                    And c.object_id = sic.object_id
                Where sic.object_id = ix.object_id
                    And sic.index_id = ix.index_id
                    And is_included_column = 0
                Order By sic.index_column_id
                For XML Raw)
                , '"/><row columnName="', ', ')
                , '<row columnName="', '')
                , '"/>', ''), '')
            As 'indexed_columns'
        , IsNull(Replace( Replace( Replace(
            (   
                Select c.name As 'columnName'
                From sys.index_columns As sic
                Join sys.columns As c 
                    On c.column_id = sic.column_id 
                    And c.object_id = sic.object_id
                Where sic.object_id = ix.object_id
                    And sic.index_id = ix.index_id
                    And is_included_column = 1
                Order By sic.index_column_id
                For XML Raw)
                , '"/><row columnName="', ', ')
                , '<row columnName="', '')
                , '"/>', ''), '')
            As 'included_columns'
        , IsNull(cte.partition_scheme_name, '') As 'partition_scheme_name'
        , Count(partition_number) As 'partition_count'
        , Sum(rows) As 'row_count'
    From sys.indexes As ix
    Join sys.partitions As sp
        On ix.object_id = sp.object_id
        And ix.index_id = sp.index_id
    Join sys.tables As st
        On ix.object_id = st.object_id
    Left Join indexCTE As cte
        On ix.data_space_id = cte.data_space_id
    Where ix.object_id = IsNull(@objectID, ix.object_id)
    Group By st.name
        , IsNull(ix.name, '')
        , ix.object_id
        , ix.index_id
		, Cast(
            Case When ix.index_id = 1 
                    Then 'clustered' 
                When ix.index_id =0
                    Then 'heap'
                Else 'nonclustered' End
			+ Case When ix.ignore_dup_key <> 0 
                Then ', ignore duplicate keys' 
                    Else '' End
			+ Case When ix.is_unique <> 0 
                Then ', unique' 
                    Else '' End
			+ Case When ix.is_primary_key <> 0 
                Then ', primary key' Else '' End As varchar(210)
            )
        , IsNull(cte.partition_scheme_name, '')
        , IsNull(cte.partition_function_name, '')
    Order By table_name
        , index_id;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go


================================================
FILE: indexes/dba_indexStats_sp.sql
================================================
If ObjectProperty(Object_ID('dbo.dba_indexStats_sp'), N'IsProcedure') = 1
Begin
    Drop Procedure dbo.dba_indexStats_sp;
    Print 'Procedure dba_indexStats_sp dropped';
End;
Go

Set Quoted_Identifier On
Go
Set ANSI_Nulls On
Go

Create Procedure dbo.dba_indexStats_sp

        /* Declare Parameters */
          @databaseName         varchar(256)    = Null
        , @indexType            varchar(256)    = Null
        , @minRowCount          int             = Null
        , @maxRowCount          int             = Null
        , @minSeekScanLookup    int             = Null
        , @maxSeekScanLookup    int             = Null
As
/**********************************************************************************************************

    NAME:           dba_indexStats_sp

    SYNOPSIS:       Retrieves information regarding indexes; will return drop SQL
                    statement for non-clustered indexes.

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    NOTES:          @databaseName - optional, specify a specific database to interrogate;
                    by default, all user databases will be returned

                    @indexType - optional, valid options are: 
                                    Clustered
                                    NonClustered
                                    Unique Clustered
                                    Unique NonClustered
                                    Heap
    
                    @minRowCount - optional, specify a minimum number of rows an index
                                    must cover
    
                    @maxRowCount - optional, specify a maximum number of rows an index
                                    must cover
    
                    @minSeekScanLookup - optional, min sum aggregation of index scans, 
                                    seeks, and look-ups.  Useful for finding unused indexes
    
                    @minSeekScanLookup - optional, max sum aggregation of index scans,  
                                    seeks, and look-ups.  Useful for finding unused indexes

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2008-07-11
    
    VERSION:        1.0

    LICENSE:        Apache License v2
    
    USAGE:          EXEC dbo.dba_indexStats_sp
                      @databaseName         = 'your_db'
                    , @indexType            = 'NonClustered'
                    , @minSeekScanLookup    = 0
                    , @maxSeekScanLookup    = 1000
                    , @minRowCount          = 0
                    , @maxRowCount          = 10000000;

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;

Begin

    /* Declare Variables */
        Create Table #indexStats 
        (
              databaseName          varchar(256)
            , objectName            varchar(256)
            , indexName             varchar(256)
            , indexType             varchar(256)
            , user_seeks            int
            , user_scans            int
            , user_lookups          int
            , user_updates          int
            , total_seekScanLookup  int
            , rowCounts             int
            , SQL_DropStatement     varchar(2000)
        );

    /* Check for existing transactions;
       If one exists, exit with error. */
    If @@TranCount > 0 
    Begin

        /* Log the fact that there were open transactions */
        Execute dbo.dba_logError_sp @errorType = 'app'
            , @app_errorProcedure = 'dba_indexStats_sp'
            , @app_errorMessage = 'Open transaction exists; dba_indexStats_sp proc will not execute.';
          Print 'Open transactions exist!';

    End
    Else
    Begin
        Begin Try

        Execute sp_MSForEachDB 'Use [?]

        Declare @dbid int
            , @dbName varchar(100);

        Select @dbid = DB_ID()
            , @dbName = DB_Name();

        With indexSizeCTE (object_id, index_id, rowCounts) As
        (
            Select [object_id]
                , index_id
                , Sum([rows]) As ''rowCounts''
            From sys.partitions
            Group By [object_id]
                , index_id
        ) 

        Insert Into #indexStats
        Select  
                  @dbName
                , Object_Name(ix.[object_id]) as objectName
                , ix.name As ''indexName''
                , Case 
                    When ix.is_unique = 1 
                        Then ''UNIQUE ''
                    Else ''''
                  End + ix.type_desc As ''indexType''
                , ddius.user_seeks
                , ddius.user_scans
                , ddius.user_lookups
                , ddius.user_updates
                , ddius.user_seeks + ddius.user_scans + ddius.user_lookups
                , isc.rowCounts
                , Case 
                    When ix.type = 2 And ix.is_unique = 0
                        Then ''Drop Index '' + ix.name + '' On '' + @dbName + ''.dbo.'' + Object_Name(ddius.[object_id]) + '';''
                    When ix.type = 2 And ix.is_unique = 1
                        Then ''Alter Table '' + @dbName + ''.dbo.'' + Object_Name(ddius.[object_ID]) + '' Drop Constraint '' + ix.name + '';''
                    Else '' ''
                  End As ''SQL_DropStatement''
        From sys.indexes As ix
            Left Outer Join sys.dm_db_index_usage_stats ddius
                On ix.object_id = ddius.object_id
                    And ix.index_id = ddius.index_id
            Left Outer Join indexSizeCTE As isc
                On ix.object_id = isc.object_id
                    And ix.index_id = isc.index_id
        Where ddius.database_id = @dbid
            And ObjectProperty(ix.[object_id], N''IsUserTable'') = 1
        Order By (ddius.user_seeks + ddius.user_scans + ddius.user_lookups) Asc;
        '

        Select databaseName
            , objectName
            , indexName
            , indexType
            , user_seeks
            , user_scans
            , user_lookups
            , total_seekScanLookup
            , user_updates
            , rowCounts
            , SQL_DropStatement
        From #indexStats
        Where databaseName = IsNull(@databaseName, databaseName)
          And indexType = IsNull(@indexType, indexType)
          And rowCounts Between IsNull(@minRowCount, rowCounts) And IsNull(@maxRowCount, rowCounts)
          And total_seekScanLookup Between IsNull(@minSeekScanLookup, total_seekScanLookup) And IsNull(@maxSeekScanLookup, total_seekScanLookup)
          And databaseName Not In ('master', 'msdb', 'tempdb', 'model')
        Order By total_seekScanLookup;

        End Try
        Begin Catch

            /* Return an error message and log it */
              Execute dbo.dba_logError_sp;
              Print 'An error has occurred!';

        End Catch;
    End;

    /* Clean up! */
    Drop Table #indexStats;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go
Set ANSI_Nulls On;
Go

If ObjectProperty(Object_ID('dbo.dba_indexStats_sp'), N'IsProcedure') = 1 
    RaisError('Procedure dba_indexStats_sp was successfully created.', 10, 1);
Else
    RaisError('Procedure dba_indexStats_sp FAILED to create!', 16, 1);
Go

================================================
FILE: indexes/dba_missingIndexStoredProc_sp.sql
================================================
Use dbaTools;
Go

/* Create a stored procedure skeleton */
If ObjectProperty(Object_ID('dbo.dba_missingIndexStoredProc_sp'), N'IsProcedure') Is Null
Begin
    Execute ('Create Procedure dbo.dba_missingIndexStoredProc_sp As Print ''Hello World!''')
    RaisError('Procedure dba_missingIndexStoredProc_sp created.', 10, 1);
End;
Go

/* Drop our table if it already exists */
If Exists(Select Object_ID From sys.tables Where [name] = N'dba_missingIndexStoredProc')
Begin
    Drop Table dbo.dba_missingIndexStoredProc
    Print 'dba_missingIndexStoredProc table dropped!';
End

/* Create our table */
Create Table dbo.dba_missingIndexStoredProc
(
      missingIndexSP_id     int Identity(1,1)   Not Null
    , databaseName          varchar(128)        Not Null
    , databaseID            int                 Not Null
    , objectName            varchar(128)        Not Null
    , objectID              int                 Not Null
    , query_plan            xml                 Not Null
    , executionDate         smalldatetime       Not Null
    , statementExecutions   int                 Not Null
    
    Constraint PK_missingIndexStoredProc
        Primary Key Clustered(missingIndexSP_id)
);

Print 'dba_missingIndexStoredProc Table Created';

/* Configure our settings */
Set ANSI_Nulls On;
Set Quoted_Identifier On;
Go

Alter Procedure dbo.dba_missingIndexStoredProc_sp

        /* Declare Parameters */
            @lastExecuted_inDays    int = 7
          , @minExecutionCount      int = 1
          , @logResults             bit = 1
          , @displayResults         bit = 0

As
/**********************************************************************************************************

    NAME:           dba_missingIndexStoredProc_sp

    SYNOPSIS:       Retrieves stored procedures with missing indexes in their cached query plans.
                
                    @lastExecuted_inDays = number of days old the cached query plan
                                       can be to still appear in the results;
                                       the HIGHER the number, the longer the
                                       execution time.

                    @minExecutionCount = minimum number of executions the cached
                                     query plan can have to still appear 
                                     in the results; the LOWER the number,
                                     the longer the execution time.

                    @logResults = store results in dba_missingIndexStoredProc
                
                    @displayResults = return results to the caller

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    NOTES:          This is not 100% guaranteed to catch all missing indexes in
                    a stored procedure.  It will only catch it if the stored proc's
                    query plan is still in cache.  Run regularly to help minimize
                    the chance of missing a proc.

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2009-09-03
    
    VERSION:        1.0

    LICENSE:        Apache License v2
    
    USAGE:          Exec dbo.dba_missingIndexStoredProc_sp
                      @lastExecuted_inDays  = 30
                    , @minExecutionCount    = 5
                    , @logResults           = 1
                    , @displayResults       = 1;

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;
Set Ansi_Padding On;
Set Ansi_Warnings On;
Set ArithAbort On;
Set Concat_Null_Yields_Null On;
Set Numeric_RoundAbort Off;

Begin

    /* Declare Variables */
    Declare @currentDateTime smalldatetime;

    Set @currentDateTime = GetDate();

    Declare @plan_handles Table
    (
          plan_handle           varbinary(64)   Not Null
        , statementExecutions   int             Not Null
    );

    Create Table #missingIndexes
    (
          databaseID            int             Not Null
        , objectID              int             Not Null
        , query_plan            xml             Not Null
        , statementExecutions   int             Not Null
    );
    
    Create Clustered Index CIX_temp_missingIndexes
        On #missingIndexes(databaseID, objectID);
       
    Begin Try

        /* Perform some data validation */
        If @logResults = 0 And @displayResults = 0
        Begin

            /* Log the fact that there were open transactions */
            Execute dbo.dba_logError_sp
                  @errorType            = 'app'
                , @app_errorProcedure   = 'dba_missingIndexStoredProc_sp'
                , @app_errorMessage     = '@logResults = 0 and @displayResults = 0; no action taken, exiting stored proc.'
                , @forceExit            = 1
                , @returnError          = 1;  

        End;

        Begin Transaction;

        /* Retrieve distinct plan handles to minimize dm_exec_query_plan lookups */
        Insert Into @plan_handles
        Select plan_handle, Sum(execution_count) As 'executions'
        From sys.dm_exec_query_stats
        Where last_execution_time > DateAdd(day, -@lastExecuted_inDays, @currentDateTime)
        Group By plan_handle
        Having Sum(execution_count) > @minExecutionCount;

        With xmlNameSpaces (
            Default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan'
        )

        /* Retrieve our query plan's XML if there's a missing index */
        Insert Into #missingIndexes
        Select deqp.[dbid]
            , deqp.objectid
            , deqp.query_plan 
            , ph.statementExecutions
        From @plan_handles As ph
        Cross Apply sys.dm_exec_query_plan(ph.plan_handle) As deqp 
        Where deqp.query_plan.exist('//MissingIndex/@Database') = 1
            And deqp.objectid Is Not Null;

        /* Do we want to store the results of our process? */
        If @logResults = 1
        Begin
            Insert Into dbo.dba_missingIndexStoredProc
            Execute sp_msForEachDB 'Use ?; 
                                    Select ''?''
                                        , mi.databaseID
                                        , Object_Name(o.object_id)
                                        , o.object_id
                                        , mi.query_plan
                                        , GetDate()
                                        , mi.statementExecutions
                                    From sys.objects As o 
                                    Join #missingIndexes As mi 
                                        On o.object_id = mi.objectID 
                                    Where databaseID = DB_ID();';

        End
        /* We're not logging it, so let's display it */
        Else
        Begin
            Execute sp_msForEachDB 'Use ?; 
                                    Select ''?''
                                        , mi.databaseID
                                        , Object_Name(o.object_id)
                                        , o.object_id
                                        , mi.query_plan
                                        , GetDate()
                                        , mi.statementExecutions
                                    From sys.objects As o 
                                    Join #missingIndexes As mi 
                                        On o.object_id = mi.objectID 
                                    Where databaseID = DB_ID();';
        End;

        /* See above; this part will only work if we've 
           logged our data. */
        If @displayResults = 1 And @logResults = 1
        Begin
            Select *
            From dbo.dba_missingIndexStoredProc
            Where executionDate >= @currentDateTime;
        End;

        /* If you have an open transaction, commit it */
        If @@TranCount > 0
            Commit Transaction;

    End Try
    Begin Catch

        /* Whoops, there was an error... rollback! */
        If @@TranCount > 0
            Rollback Transaction;

        /* Return an error message and log it */
        Execute dbo.dba_logError_sp;

    End Catch;

    /* Clean-Up! */
    Drop Table #missingIndexes;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go

================================================
FILE: indexes/index_definition.sql
================================================
/**********************************************************************************************************

    NAME:           index_definition.sql

    SYNOPSIS:       Displays the definition of indexes; useful to audit indexes across servers & environments

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2012-10-15
    
    VERSION:        1.0

    LICENSE:        Apache License v2

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

-- Single database

WITH indexCTE AS
(
    SELECT st.object_id                                                                         AS objectID
        , st.name                                                                               AS tableName
        , si.index_id                                                                           AS indexID
        , si.name                                                                               AS indexName
        , si.type_desc                                                                          AS indexType
        , sc.column_id                                                                          AS columnID
        , sc.name + CASE WHEN sic.is_descending_key = 1 THEN ' DESC' ELSE '' END                AS columnName
        , sic.key_ordinal                                                                       AS ordinalPosition
        , CASE WHEN sic.is_included_column = 0 AND key_ordinal > 0 THEN sc.name ELSE NULL END   AS indexKeys
        , CASE WHEN sic.is_included_column = 1 THEN sc.name ELSE NULL END                       AS includedColumns
        , sic.partition_ordinal                                                                 AS partitionOrdinal
        , CASE WHEN sic.partition_ordinal > 0 THEN sc.name ELSE NULL END                        AS partitionColumns
        , si.is_primary_key                                                                     AS isPrimaryKey
        , si.is_unique                                                                          AS isUnique
        , si.is_unique_constraint                                                               AS isUniqueConstraint
        , si.has_filter                                                                         AS isFilteredIndex
        , COALESCE(si.filter_definition, '')                                                    AS filterDefinition
    FROM sys.tables                         AS st
    INNER JOIN sys.indexes                  AS si 
        ON si.object_id =   st.object_id
    INNER JOIN sys.index_columns            AS sic 
	    ON sic.object_id=si.object_id
        AND sic.index_id=si.index_id 
    INNER JOIN sys.columns                  AS sc 
	    ON sc.object_id = sic.object_id 
	    and sc.column_id = sic.column_id
) 

SELECT DISTINCT 
      @@SERVERNAME                                      AS ServerName
    , DB_NAME()                                         AS DatabaseName
    , tableName
    , indexName
    , indexType
    , STUFF((
            SELECT ', ' + indexKeys
                FROM indexCTE
            WHERE objectID = cte.objectID
                AND indexID = cte.indexID
                AND indexKeys IS NOT NULL 
            ORDER BY ordinalPosition
                FOR XML PATH(''), 
      TYPE).value('.','varchar(max)'),1,1,'')           AS indexKeys
    , COALESCE(STUFF((
            SELECT ', ' + includedColumns
                FROM indexCTE
            WHERE objectID = cte.objectID
                AND indexID = cte.indexID
                AND includedColumns IS NOT NULL 
            ORDER BY columnID
                FOR XML PATH(''), 
      TYPE).value('.','varchar(max)'),1,1,''), '')      AS includedColumns
    , COALESCE(STUFF((
            SELECT ', ' + partitionColumns
                FROM indexCTE
            WHERE objectID = cte.objectID
                AND indexID = cte.indexID
                AND partitionColumns IS NOT NULL 
            ORDER BY partitionOrdinal
                FOR XML PATH(''), 
      TYPE).value('.','varchar(max)'),1,1,''), '')      AS partitionKeys
    , isPrimaryKey
    , isUnique
    , isUniqueConstraint
    , isFilteredIndex
    , FilterDefinition
FROM indexCTE AS cte
WHERE tableName = 'your_example'
ORDER BY tableName
    , indexName;

/*********************************************************************************************************/

-- All databases
IF OBJECT_ID('tempdb..#IndexAudit') IS NOT NULL
    DROP TABLE #IndexAudit;

CREATE TABLE #IndexAudit
(
      serverName                SYSNAME
    , databaseName              SYSNAME
    , tableName                 VARCHAR(128)
    , indexName                 VARCHAR(128)
    , indexType                 NVARCHAR(60)
    , indexKeys                 VARCHAR(8000)
    , includedColumns           VARCHAR(8000)
    , partitionColumns          VARCHAR(8000)
    , isPrimaryKey              BIT
    , isUnique                  BIT
    , isUniqueConstraint        BIT
    , isFilteredIndex           BIT
    , FilterDefinition          VARCHAR(8000)
);

EXECUTE sp_foreachdb 'USE ?;
WITH indexCTE AS
(
    SELECT st.object_id                                                                         AS objectID
        , st.name                                                                               AS tableName
        , si.index_id                                                                           AS indexID
        , si.type_desc                                                                          AS indexType
        , si.name                                                                               AS indexName
        , sc.column_id                                                                          AS columnID
        , sc.name + CASE WHEN sic.is_descending_key = 1 THEN '' DESC'' ELSE '''' END            AS columnName
        , sic.key_ordinal                                                                       AS ordinalPosition
        , CASE WHEN sic.is_included_column = 0 AND key_ordinal > 0 THEN sc.name ELSE NULL END   AS indexKeys
        , CASE WHEN sic.is_included_column = 1 THEN sc.name ELSE NULL END                       AS includedColumns
        , sic.partition_ordinal                                                                 AS partitionOrdinal
        , CASE WHEN sic.partition_ordinal > 0 THEN sc.name ELSE NULL END                        AS partitionColumns
        , si.is_primary_key                                                                     AS isPrimaryKey
        , si.is_unique                                                                          AS isUnique
        , si.is_unique_constraint                                                               AS isUniqueConstraint
        , si.has_filter                                                                         AS isFilteredIndex
        , COALESCE(si.filter_definition, '''')                                                  AS filterDefinition
    FROM sys.tables                         AS st
    INNER JOIN sys.indexes                  AS si 
        ON si.object_id =   st.object_id
    INNER JOIN sys.index_columns            AS sic 
	    ON sic.object_id=si.object_id
        AND sic.index_id=si.index_id 
    INNER JOIN sys.columns                  AS sc 
	    ON sc.object_id = sic.object_id 
	    and sc.column_id = sic.column_id
) 

INSERT INTO #IndexAudit
SELECT DISTINCT 
      @@SERVERNAME                                              AS ServerName
    , DB_NAME()                                                 AS DatabaseName
    , tableName
    , indexName
    , indexType
    , STUFF((
            SELECT '', '' + indexKeys
                FROM indexCTE
            WHERE objectID = cte.objectID
                AND indexID = cte.indexID
                AND indexKeys IS NOT NULL 
            ORDER BY ordinalPosition
                FOR XML PATH(''''), 
      TYPE).value(''.'',''varchar(max)''),1,1,'''')             AS indexKeys
    , COALESCE(STUFF((
            SELECT '', '' + includedColumns
                FROM indexCTE
            WHERE objectID = cte.objectID
                AND indexID = cte.indexID
                AND includedColumns IS NOT NULL 
            ORDER BY columnID
                FOR XML PATH(''''), 
      TYPE).value(''.'',''varchar(max)''),1,1,''''), '''')      AS includedColumns
    , COALESCE(STUFF((
            SELECT '', '' + partitionColumns
                FROM indexCTE
            WHERE objectID = cte.objectID
                AND indexID = cte.indexID
                AND partitionColumns IS NOT NULL 
            ORDER BY partitionOrdinal
                FOR XML PATH(''''), 
      TYPE).value(''.'',''varchar(max)''),1,1,''''), '''')      AS partitionKeys
    , isPrimaryKey
    , isUnique
    , isUniqueConstraint
    , isFilteredIndex
    , FilterDefinition
FROM indexCTE AS cte
ORDER BY tableName
    , indexName;
';

-- For multi-server testing, dump results to a temp table and compare tables
SELECT *
FROM #IndexAudit
WHERE databaseName NOT IN ('tempdb', 'master', 'msdb', 'model')
ORDER BY serverName
    , databaseName
    , tableName
    , indexName;

================================================
FILE: indexes/missing.sql
================================================
/**********************************************************************************************************

    NAME:           missing.sql

    SYNOPSIS:       Displays potential missing indexes for a given database. Adding the indexes via the
                    provided CREATE scripts may improve server performance. 

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2014-04-08
    
    VERSION:        1.0

    LICENSE:        Apache License v2
    
    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20140408   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

SELECT 
      t.name AS 'affected_table'
    , 'CREATE NONCLUSTERED INDEX IX_' + t.name + '_missing_'
        + CONVERT(CHAR(8), GETDATE(), 112) + '_'
        + CAST(ROW_NUMBER() OVER (PARTITION BY t.name 
            ORDER BY CAST((ddmigs.user_seeks + ddmigs.user_scans) 
                * ddmigs.avg_user_impact AS BIGINT) DESC) AS VARCHAR(3))
        + ' ON ' + ddmid.statement 
        + ' (' + ISNULL(ddmid.equality_columns,'') 
        + CASE WHEN ddmid.equality_columns IS NOT NULL 
            AND ddmid.inequality_columns IS NOT NULL THEN ',' 
                ELSE '' END 
        + ISNULL(ddmid.inequality_columns, '')
        + ')' 
        + ISNULL(' INCLUDE (' + ddmid.included_columns + ');', ';'
        ) AS sql_statement
    , ddmigs.user_seeks
    , ddmigs.user_scans
    , CAST((ddmigs.user_seeks + ddmigs.user_scans) 
        * ddmigs.avg_user_impact AS BIGINT) AS 'est_impact'
    , ddmigs.last_user_seek
FROM sys.dm_db_missing_index_groups                                 AS ddmig
INNER JOIN sys.dm_db_missing_index_group_stats                      AS ddmigs
    ON ddmigs.group_handle = ddmig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details                          AS ddmid 
    ON ddmig.index_handle = ddmid.index_handle
INNER JOIN sys.tables                                               AS t
    ON ddmid.[object_id] = t.[object_id]
WHERE ddmid.database_id  =  DB_ID()                                                             ----> by default, only examines the current database
  AND CAST((ddmigs.user_seeks + ddmigs.user_scans) * ddmigs.avg_user_impact AS BIGINT)  >  100  ----> 100 is a starting point; update value as appropriate
ORDER BY CAST((ddmigs.user_seeks + ddmigs.user_scans) * ddmigs.avg_user_impact AS BIGINT) DESC;

================================================
FILE: indexes/unused.sql
================================================
/**********************************************************************************************************

    NAME:           unused.sql

    SYNOPSIS:       Displays potential unused indexes for the current database. Dropping these indexes 
                    may improve database performance. These statistics are reset each time the server 
                    is rebooted, so make sure to review the [sqlserver_start_time] value to ensure the 
                    statistics are captured for a meaningful time period.

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2014-04-08
    
    VERSION:        1.0

    LICENSE:        Apache License v2
    
    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20140408   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/


SELECT sqlserver_start_time FROM sys.dm_os_sys_info;

DECLARE @dbid INT
    , @dbName VARCHAR(100);

SELECT @dbid = DB_ID()
    , @dbName = DB_NAME();

WITH partitionCTE (object_id, index_id, row_count, partition_count) 
AS
(
    SELECT [object_id]
        , index_id
        , SUM([rows]) AS 'row_count'
        , COUNT(partition_id) AS 'partition_count'
    FROM sys.partitions
    GROUP BY [object_id]
        , index_id
)

SELECT OBJECT_NAME(i.[object_id]) AS objectName
        , i.name
        , CASE 
            WHEN i.is_unique = 1 
                THEN 'UNIQUE ' 
            ELSE '' 
          END + i.type_desc AS 'indexType'
        , ddius.user_seeks
        , ddius.user_scans
        , ddius.user_lookups
        , ddius.user_updates
        , cte.row_count
        , CASE WHEN partition_count > 1 THEN 'yes' 
            ELSE 'no' END AS 'partitioned?'
        , CASE 
            WHEN i.type = 2 AND i.is_unique = 0
                THEN 'Drop Index ' + i.name 
                    + ' On ' + @dbName 
                    + '.dbo.' + OBJECT_NAME(ddius.[object_id]) + ';'
            WHEN i.type = 2 AND i.is_unique = 1
                THEN 'Alter Table ' + @dbName 
                    + '.dbo.' + OBJECT_NAME(ddius.[object_ID]) 
                    + ' Drop Constraint ' + i.name + ';'
            ELSE '' 
          END AS 'SQL_DropStatement'
FROM sys.indexes                                                        AS i
INNER JOIN sys.dm_db_index_usage_stats                                  AS ddius
    ON i.object_id = ddius.object_id
        AND i.index_id = ddius.index_id
INNER JOIN partitionCTE                                                 AS cte
    ON i.object_id = cte.object_id
        AND i.index_id = cte.index_id
WHERE ddius.database_id = @dbid
    AND i.type = 2                                                      ----> retrieve nonclustered indexes only
    AND i.is_unique = 0                                                 ----> ignore unique indexes, we'll assume they're serving a necessary business use
    AND (ddius.user_seeks + ddius.user_scans + ddius.user_lookups) = 0  ----> starting point, update this value as needed; 0 retrieves completely unused indexes
ORDER BY user_updates DESC;



================================================
FILE: misc/dba_viewPageData_sp.sql
================================================
If ObjectProperty(Object_ID('dbo.dba_viewPageData_sp'), N'IsProcedure') Is Null
Begin
    Execute ('Create Procedure dbo.dba_viewPageData_sp As Print ''Hello World!''')
    RaisError('Procedure dba_viewPageData_sp created.', 10, 1);
End;
Go

Set ANSI_Nulls On;
Set Quoted_Identifier On;
Go

Alter Procedure dbo.dba_viewPageData_sp

        /* Declare Parameters */
          @databaseName varchar(128)
        , @tableName    varchar(128)    = Null -- database.schema.tableName
        , @indexName    varchar(128)    = Null
        , @fileNumber   int             = Null
        , @pageNumber   int             = Null
        , @printOption  int             = 3    -- 0, 1, 2, or 3
        , @pageType     char(4)         = 'Leaf' -- Leaf, Root, or IAM
        
As
/**********************************************************************************************************

    NAME:           dba_viewPageData_sp

    SYNOPSIS:       Retrieves page data for the specified table/page.

    DEPENDENCIES:   The following dependencies are required to execute this script:
                    - SQL Server 2005 or newer
                    
    NOTES:          Can pass either the table name or the pageID, but must pass one, or
                    you'll end up with no results. 
                    If the table name is passed, it will return the first page.
    
                    @tableName must be '<databaseName>.<schemaName>.<tableName>' in order to
                        function correctly.  When called within the same database, the database
                        prefix may be omitted.  
            
                    @printOption can be one of following values:
                        0 - print just the page header
                        1 - page header plus per-row hex dumps and a dump of the page slot array
                        2 - page header plus whole page hex dump
                        3 - page header plus detailed per-row interpretation
                        
                    Page Options borrowed from: 
                    https://blogs.msdn.com/sqlserverstorageengine/archive/2006/06/10/625659.aspx
            
                    @pageType must be one of the following values:
                        Leaf - returns the first page of the leaf level of your index or heap
                        Root - returns the root page of your index
                        IAM - returns the index allocation map chain for your index or heap
            
                    Conversions borrowed from:
                    http://sqlskills.com/blogs/paul/post/Inside-The-Storage-Engine-
                    sp_AllocationMetadata-putting-undocumented-system-catalog-views-to-work.aspx

    AUTHOR:         Michelle Ufford, http://sqlfool.com
    
    CREATED:        2009-05-06
    
    VERSION:        1.0

    LICENSE:        Apache License v2
    
    USAGE:          EXEC dbo.dba_viewPageData_sp
                      @databaseName = 'AdventureWorks'
                    , @tableName    = 'AdventureWorks.Sales.SalesOrderDetail'
                    , @indexName    = 'IX_SalesOrderDetail_ProductID'
                    --, @fileNumber   = 1
                    --, @pageNumber   = 38208
                    , @printOption  = 3
                    , @pageType     = 'Root';

    ----------------------------------------------------------------------------
    DISCLAIMER: 
    This code and information are provided "AS IS" without warranty of any kind,
    either expressed or implied, including but not limited to the implied 
    warranties or merchantability and/or fitness for a particular purpose.
    ----------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------------------------
 --  DATE       VERSION     AUTHOR                  DESCRIPTION                                        --
 ---------------------------------------------------------------------------------------------------------
     20150619   1.0         Michelle Ufford         Open Sourced on GitHub
**********************************************************************************************************/

Set NoCount On;
Set XACT_Abort On;
Set Ansi_Padding On;
Set Ansi_Warnings On;
Set ArithAbort On;
Set Concat_Null_Yields_Null On;
Set Numeric_RoundAbort Off;

Begin

    Declare @fileID         int
        , @pageID           int
        , @sqlStatement     nvarchar(1200)
        , @sqlParameters    nvarchar(255)
        , @errorMessage     varchar(100);

    Begin Try

        If @fileNumber Is Null And @pageNumber Is Null And @tableName Is Null
        Begin
            Set @errorMessage = 'You must provide either a file/page number, or a table name!';
            RaisError(@errorMessage, 16, 1);
        End;
            
        If @pageType Not In ('Leaf', 'Root', 'IAM')
        Begin
            Set @errorMessage = 'You have entered an invalid page type; valid options are "Leaf", "Root", or "IAM"';
            RaisError(@errorMessage, 16, 1);
        End;

        If @fileNumber Is Null Or @pageNumber Is Null
        Begin
        
            Set @sqlStatement = 
            Case When @pageType = 'Leaf' Then
                'Select Top 1 @p_fileID = Convert (varchar(6), Convert (int, 
                    SubString (au.first_page, 6, 1) +
                    SubString (au.first_page, 5, 1)))
                , @p_pageID = Convert (varchar(20), Convert (int, 
                     SubString (au.first_page, 4, 1) +
                     SubString (au.first_page, 3, 1) +
                     SubString (au.first_page, 2, 1) +
                     SubString (au.first_page, 1, 1)))'
            When @pageType = 'Root' Then
                'Select Top 1 @p_fileID = Convert (varchar(6), Convert (int, 
                    SubString (au.root_page, 6, 1) +
                    SubString (au.root_page, 5, 1)))
                , @p_pageID = Convert (varchar(20), Convert (int, 
                     SubString (au.root_page, 4, 1) +
                     SubString (au.root_page, 3, 1) +
                     SubString (au.root_page, 2, 1) +
                     SubString (au.root_page, 1, 1)))'
            When @pageType = 'IAM' Then
                'Select Top 1 @p_fileID = Convert (varchar(6), Convert (int, 
                    SubString (au.first_iam_page, 6, 1) +
                    SubString (au.first_iam_page, 5, 1)))
                , @p_pageID = Convert (varchar(20), Convert (int, 
                     SubString (au.first_iam_page, 4, 1) +
                     SubString (au.first_iam_page, 3, 1) +
                     SubString (au.first_iam_page, 2, 1) +
                     SubString (au.first_iam_page, 1, 1)))'
            End + 
            'From ' + QuoteName(ParseName(@databaseName, 1)) + '.sys.indexes AS i
            Join ' + QuoteName(ParseName(@databaseName, 1)) + '.sys.partitions AS p
                On i.[object_id] = p.[object_id]
                And i.index_id = p.index_id
            Join ' + QuoteName(ParseName(@databaseName, 1)) + '.sys.system_internals_allocation_units AS au
                On p.hobt_id = au.container_id
            Where p.[object_id] = Object_ID(@p_tableName)
                And au.first_page > 0x000000000000 ' 
                + Case When @indexName Is Null 
                    Then ';' 
                    Else 'And i.name = @p_indexName;' End;

            Set @sqlParameters = '@p_tableName varchar(128)
                                , @p_indexName varchar(128)
                                , @p_fileID int OUTPUT
                                , @p_pageID int OUTPUT';
            
            Execute sp_executeSQL @sqlStatement
                        , @sqlParameters
                        , @p_tableName = @tableName
                        , @p_indexName = @indexName
                        , @p_fileID = @fileID OUTPUT
                        , @p_pageID = @pageID OUTPUT;

            End
            Else
            Begin
                Select @fileID = @fileNumber
                    , @pageID = @pageNumber;
            End;

        DBCC TraceOn (3604);
        DBCC Page (@databaseName, @fileID, @pageID, @printOption);
        DBCC TraceOff (3604);

    End Try
    Begin Catch
    
        Print @errorMessage;
    
    End Catch;

    Set NoCount Off;
    Return 0;
End
Go

Set Quoted_Identifier Off;
Go
Download .txt
gitextract_ag5izxye/

├── LICENSE
├── README.md
├── admin/
│   ├── dba_findWastedSpace_sp.sql
│   ├── dba_recompile_sp.sql
│   ├── dba_replicationLatencyGet_sp.sql
│   ├── dba_replicationLatencyMonitor_sp.sql
│   └── sql_agent_job_history.sql
├── dev/
│   ├── bcp_script_generator.sql
│   ├── dba_parseString_udf.sql
│   ├── insert_statement_generator.sql
│   └── teradata_ddl_generator.sql
├── indexes/
│   ├── dba_indexDefrag_sp.sql
│   ├── dba_indexLookup_sp.sql
│   ├── dba_indexStats_sp.sql
│   ├── dba_missingIndexStoredProc_sp.sql
│   ├── index_definition.sql
│   ├── missing.sql
│   └── unused.sql
└── misc/
    └── dba_viewPageData_sp.sql
Download .txt
SYMBOL INDEX (9 symbols across 6 files)

FILE: admin/dba_recompile_sp.sql
  type databaseList (line 70) | Create Table ##databaseList
  type tableList (line 76) | Create Table ##tableList

FILE: admin/dba_replicationLatencyGet_sp.sql
  type tokenResults (line 91) | Create Table ##tokenResults

FILE: admin/dba_replicationLatencyMonitor_sp.sql
  type dbo (line 6) | Create Table dbo.dba_replicationMonitor

FILE: indexes/dba_indexStats_sp.sql
  type indexStats (line 90) | Create Table #indexStats

FILE: indexes/dba_missingIndexStoredProc_sp.sql
  type dbo (line 20) | Create Table dbo.dba_missingIndexStoredProc
  type missingIndexes (line 127) | Create Table #missingIndexes
  type CIX_temp_missingIndexes (line 135) | Create Clustered Index CIX_temp_missingIndexes

FILE: indexes/index_definition.sql
  type IndexAudit (line 112) | CREATE TABLE #IndexAudit
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (180K chars).
[
  {
    "path": "LICENSE",
    "chars": 11323,
    "preview": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licens"
  },
  {
    "path": "README.md",
    "chars": 2761,
    "preview": "sql-scripts\n===========\nRepo for sharing my SQL Server scripts and stored procedures. These have largely been tested on"
  },
  {
    "path": "admin/dba_findWastedSpace_sp.sql",
    "chars": 14916,
    "preview": "If ObjectProperty(Object_ID('dbo.dba_findWastedSpace_sp'), N'IsProcedure') Is Null\nBegin\n    Execute ('Create Procedure "
  },
  {
    "path": "admin/dba_recompile_sp.sql",
    "chars": 3951,
    "preview": "Use dbaTools;\nGo\n\nIf ObjectProperty(Object_ID('dbo.dba_recompile_sp'), N'IsProcedure') Is Null\nBegin\n    Execute ('Creat"
  },
  {
    "path": "admin/dba_replicationLatencyGet_sp.sql",
    "chars": 6107,
    "preview": "If ObjectProperty(Object_ID('dbo.dba_replicationLatencyGet_sp'), N'IsProcedure') = 1\nBegin\n    Drop Procedure dbo.dba_re"
  },
  {
    "path": "admin/dba_replicationLatencyMonitor_sp.sql",
    "chars": 9597,
    "preview": "Use DBAHoldings;\nGo\n\nIf Object_ID('dbo.dba_replicationMonitor') Is Null\nBegin\n    Create Table dbo.dba_replicationMonito"
  },
  {
    "path": "admin/sql_agent_job_history.sql",
    "chars": 10309,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "dev/bcp_script_generator.sql",
    "chars": 4214,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "dev/dba_parseString_udf.sql",
    "chars": 2214,
    "preview": "\n/* Let's create our parsing function... */\nCREATE FUNCTION dbo.dba_parseString_udf\n(\n          @stringToParse VARCHAR(8"
  },
  {
    "path": "dev/insert_statement_generator.sql",
    "chars": 6757,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "dev/teradata_ddl_generator.sql",
    "chars": 3995,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "indexes/dba_indexDefrag_sp.sql",
    "chars": 49267,
    "preview": "/*** Scroll down to the see important notes, disclaimers, and licensing information ***/\n\n/* Let's create our parsing fu"
  },
  {
    "path": "indexes/dba_indexLookup_sp.sql",
    "chars": 5989,
    "preview": "If ObjectProperty(Object_ID('dbo.dba_indexLookup_sp'), N'IsProcedure') Is Null\nBegin\n    Execute ('Create Procedure dbo."
  },
  {
    "path": "indexes/dba_indexStats_sp.sql",
    "chars": 8209,
    "preview": "If ObjectProperty(Object_ID('dbo.dba_indexStats_sp'), N'IsProcedure') = 1\nBegin\n    Drop Procedure dbo.dba_indexStats_sp"
  },
  {
    "path": "indexes/dba_missingIndexStoredProc_sp.sql",
    "chars": 9229,
    "preview": "Use dbaTools;\nGo\n\n/* Create a stored procedure skeleton */\nIf ObjectProperty(Object_ID('dbo.dba_missingIndexStoredProc_s"
  },
  {
    "path": "indexes/index_definition.sql",
    "chars": 10202,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "indexes/missing.sql",
    "chars": 3309,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "indexes/unused.sql",
    "chars": 3979,
    "preview": "/**********************************************************************************************************\n\n    NAME:  "
  },
  {
    "path": "misc/dba_viewPageData_sp.sql",
    "chars": 8447,
    "preview": "If ObjectProperty(Object_ID('dbo.dba_viewPageData_sp'), N'IsProcedure') Is Null\nBegin\n    Execute ('Create Procedure dbo"
  }
]

About this extraction

This page contains the full source code of the MichelleUfford/sql-scripts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (170.7 KB), approximately 37.2k tokens, and a symbol index with 9 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!