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