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) , '"/>', '') + 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(, ); 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(, ); 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) , '"/>', ''), '') 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) , '"/>', ''), '') 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 '..' 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