Getting Started with the SQL Server First Responder Kit --- transfer from ...

At first, this is not my article.   He did

 

 

But I want to tell you some are good and some are not necessary.
OK , Here we begin:


We need to generate sp_BlitzWho as all begin.


The first tool you generally use when something goes wrong. It will tell you who is connected, what they are running, and how badly it is affecting the database


SET ANSI_NULLS ON
GO


SET QUOTED_IDENTIFIER ON
GO


CREATE PROCEDURE [dbo].[sp_BlitzWho]
@Help TINYINT = 0 ,
@ShowSleepingSPIDs TINYINT = 0
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;




IF @Help = 1
PRINT '
sp_BlitzWho from FirstResponderKit.org
This script gives you a snapshot of everything currently executing on your SQL Server.
To learn more, visit FirstResponderKit.org where you can download new
versions for free, watch training videos on how it works, get more info on
the findings, contribute your own code, and more.
Known limitations of this version:
- Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000.

MIT License
Copyright (c) 2016 Brent Ozar Unlimited
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
';




/* Get the major and minor build numbers */
DECLARE @ProductVersion NVARCHAR(128)
,@ProductVersionMajor DECIMAL(10,2)
,@ProductVersionMinor DECIMAL(10,2)
,@EnhanceFlag BIT = 0
,@StringToExecute NVARCHAR(MAX)
,@EnhanceSQL NVARCHAR(MAX) =
N'[query_stats].last_dop,
[query_stats].min_dop,
[query_stats].max_dop,
[query_stats].last_grant_kb,
[query_stats].min_grant_kb,
[query_stats].max_grant_kb,
[query_stats].last_used_grant_kb,
[query_stats].min_used_grant_kb,
[query_stats].max_used_grant_kb,
[query_stats].last_ideal_grant_kb,
[query_stats].min_ideal_grant_kb,
[query_stats].max_ideal_grant_kb,
[query_stats].last_reserved_threads,
[query_stats].min_reserved_threads,
[query_stats].max_reserved_threads,
[query_stats].last_used_threads,
[query_stats].min_used_threads,
[query_stats].max_used_threads,'


SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ),
@ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2)


IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11
BEGIN
SET @StringToExecute = N'
SELECT GETDATE() AS [run_date] ,
CONVERT(VARCHAR, DATEADD(ms, [r].[total_elapsed_time], 0), 114) AS [elapsed_time] ,
[s].[session_id] ,
DB_NAME(r.database_id) AS database_name,
[wt].[wait_info] ,
[s].[status] ,
ISNULL(SUBSTRING([dest].[text],
( [query_stats].[statement_start_offset] / 2 ) + 1,
( ( CASE [query_stats].[statement_end_offset]
WHEN -1 THEN DATALENGTH([dest].[text])
ELSE [query_stats].[statement_end_offset]
END - [query_stats].[statement_start_offset] )
/ 2 ) + 1), [dest].[text]) AS [query_text] ,
[derp].[query_plan] ,
[qmg].[query_cost] ,
[r].[blocking_session_id] ,
[r].[cpu_time] AS [request_cpu_time],
[r].[logical_reads] AS [request_logical_reads],
[r].[writes] AS [request_writes],
[r].[reads] AS [request_physical_reads] ,
[s].[cpu_time] AS [session_cpu],
[s].[logical_reads] AS [session_logical_reads],
[s].[reads] AS [session_physical_reads] ,
[s].[writes] AS [session_writes],
[s].[memory_usage] ,
[r].[estimated_completion_time] ,
[r].[deadlock_priority] ,
CASE
WHEN [s].[transaction_isolation_level] = 0 THEN ''Unspecified''
WHEN [s].[transaction_isolation_level] = 1 THEN ''Read Uncommitted''
WHEN [s].[transaction_isolation_level] = 2 AND EXISTS (SELECT 1 FROM [sys].[dm_tran_active_snapshot_database_transactions] AS [trn] WHERE [s].[session_id] = [trn].[session_id] AND [is_snapshot] = 0 ) THEN ''Read Committed Snapshot Isolation''
WHEN [s].[transaction_isolation_level] = 2 AND NOT EXISTS (SELECT 1 FROM [sys].[dm_tran_active_snapshot_database_transactions] AS [trn] WHERE [s].[session_id] = [trn].[session_id] AND [is_snapshot] = 0 ) THEN ''Read Committed''
WHEN [s].[transaction_isolation_level] = 3 THEN ''Repeatable Read''
WHEN [s].[transaction_isolation_level] = 4 THEN ''Serializable''
WHEN [s].[transaction_isolation_level] = 5 THEN ''Snapshot''
ELSE ''WHAT HAVE YOU DONE?''
END AS [transaction_isolation_level] ,
[r].[open_transaction_count] ,
[qmg].[dop] AS [degree_of_parallelism] ,
[qmg].[request_time] ,
COALESCE(CAST([qmg].[grant_time] AS VARCHAR), ''N/A'') AS [grant_time] ,
[qmg].[requested_memory_kb] ,
[qmg].[granted_memory_kb] AS [grant_memory_kb],
CASE WHEN [qmg].[grant_time] IS NULL THEN ''N/A''
WHEN [qmg].[requested_memory_kb] < [qmg].[granted_memory_kb]
THEN ''Query Granted Less Than Query Requested''
ELSE ''Memory Request Granted''
END AS [is_request_granted] ,
[qmg].[required_memory_kb] ,
[qmg].[used_memory_kb] ,
[qmg].[ideal_memory_kb] ,
[qmg].[is_small] ,
[qmg].[timeout_sec] ,
[qmg].[resource_semaphore_id] ,
COALESCE(CAST([qmg].[wait_order] AS VARCHAR), ''N/A'') AS [wait_order] ,
COALESCE(CAST([qmg].[wait_time_ms] AS VARCHAR),
''N/A'') AS [wait_time_ms] ,
CASE [qmg].[is_next_candidate]
WHEN 0 THEN ''No''
WHEN 1 THEN ''Yes''
ELSE ''N/A''
END AS [next_candidate_for_memory_grant] ,
[qrs].[target_memory_kb] ,
COALESCE(CAST([qrs].[max_target_memory_kb] AS VARCHAR),
''Small Query Resource Semaphore'') AS [max_target_memory_kb] ,
[qrs].[total_memory_kb] ,
[qrs].[available_memory_kb] ,
[qrs].[granted_memory_kb] ,
[qrs].[used_memory_kb] ,
[qrs].[grantee_count] ,
[qrs].[waiter_count] ,
[qrs].[timeout_error_count] ,
COALESCE(CAST([qrs].[forced_grant_count] AS VARCHAR),
''Small Query Resource Semaphore'') AS [forced_grant_count],
[s].[nt_domain] ,
[s].[host_name] ,
[s].[login_name] ,
[s].[nt_user_name] ,
[s].[program_name] ,
[s].[client_interface_name] ,
[s].[login_time] ,
[r].[start_time]
FROM [sys].[dm_exec_sessions] AS [s]
INNER JOIN [sys].[dm_exec_requests] AS [r]
ON [r].[session_id] = [s].[session_id]
LEFT JOIN ( SELECT DISTINCT
[wait].[session_id] ,
( SELECT [waitwait].[wait_type] + N'' (''
+ CAST(SUM([waitwait].[wait_duration_ms]) AS NVARCHAR(128))
+ N'' ms) ''
FROM [sys].[dm_os_waiting_tasks] AS [waitwait]
WHERE [waitwait].[session_id] = [wait].[session_id]
GROUP BY [waitwait].[wait_type]
ORDER BY SUM([waitwait].[wait_duration_ms]) DESC
FOR
XML PATH('''') ) AS [wait_info]
FROM [sys].[dm_os_waiting_tasks] AS [wait] ) AS [wt]
ON [s].[session_id] = [wt].[session_id]
LEFT JOIN [sys].[dm_exec_query_stats] AS [query_stats]
ON [r].[sql_handle] = [query_stats].[sql_handle]
AND [r].[plan_handle] = [query_stats].[plan_handle]
AND [r].[statement_start_offset] = [query_stats].[statement_start_offset]
AND [r].[statement_end_offset] = [query_stats].[statement_end_offset]
LEFT JOIN [sys].[dm_exec_query_memory_grants] [qmg]
ON [r].[session_id] = [qmg].[session_id]
AND [r].[request_id] = [qmg].[request_id]
LEFT JOIN [sys].[dm_exec_query_resource_semaphores] [qrs]
ON [qmg].[resource_semaphore_id] = [qrs].[resource_semaphore_id]
AND [qmg].[pool_id] = [qrs].[pool_id]
OUTER APPLY [sys].[dm_exec_sql_text]([r].[sql_handle]) AS [dest]
OUTER APPLY [sys].[dm_exec_query_plan]([r].[plan_handle]) AS [derp]
WHERE [r].[session_id] <> @@SPID
AND (([s].[status] <> ''sleeping'' AND ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + ' = 0) OR ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + ' = 1)
ORDER BY 2 DESC;
'
END
IF @ProductVersionMajor >= 11
BEGIN
SELECT @EnhanceFlag =
CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1
WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1
WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1
ELSE 0
END


SELECT @StringToExecute = N'
SELECT GETDATE() AS [run_date] ,
CONVERT(VARCHAR, DATEADD(ms, [r].[total_elapsed_time], 0), 114) AS [elapsed_time] ,
[s].[session_id] ,
DB_NAME(r.database_id) AS database_name,
[wt].[wait_info] ,
[s].[status] ,
ISNULL(SUBSTRING([dest].[text],
( [query_stats].[statement_start_offset] / 2 ) + 1,
( ( CASE [query_stats].[statement_end_offset]
WHEN -1 THEN DATALENGTH([dest].[text])
ELSE [query_stats].[statement_end_offset]
END - [query_stats].[statement_start_offset] )
/ 2 ) + 1), [dest].[text]) AS [query_text] ,
[derp].[query_plan] ,
[qmg].[query_cost] ,
[r].[blocking_session_id] ,
[r].[cpu_time] AS [request_cpu_time],
[r].[logical_reads] AS [request_logical_reads],
[r].[writes] AS [request_writes],
[r].[reads] AS [request_physical_reads] ,
[s].[cpu_time] AS [session_cpu],
[s].[logical_reads] AS [session_logical_reads],
[s].[reads] AS [session_physical_reads] ,
[s].[writes] AS [session_writes],
[s].[memory_usage] ,
[r].[estimated_completion_time] ,
[r].[deadlock_priority] ,'
+
CASE @EnhanceFlag
WHEN 1 THEN @EnhanceSQL
ELSE N'' END +
N'CASE
WHEN [s].[transaction_isolation_level] = 0 THEN ''Unspecified''
WHEN [s].[transaction_isolation_level] = 1 THEN ''Read Uncommitted''
WHEN [s].[transaction_isolation_level] = 2 AND EXISTS (SELECT 1 FROM [sys].[dm_tran_active_snapshot_database_transactions] AS [trn] WHERE [s].[session_id] = [trn].[session_id] AND [is_snapshot] = 0 ) THEN ''Read Committed Snapshot Isolation''
WHEN [s].[transaction_isolation_level] = 2 AND NOT EXISTS (SELECT 1 FROM [sys].[dm_tran_active_snapshot_database_transactions] AS [trn] WHERE [s].[session_id] = [trn].[session_id] AND [is_snapshot] = 0 ) THEN ''Read Committed''
WHEN [s].[transaction_isolation_level] = 3 THEN ''Repeatable Read''
WHEN [s].[transaction_isolation_level] = 4 THEN ''Serializable''
WHEN [s].[transaction_isolation_level] = 5 THEN ''Snapshot''
ELSE ''WHAT HAVE YOU DONE?''
END AS [transaction_isolation_level] ,
[r].[open_transaction_count] ,
[qmg].[dop] AS [degree_of_parallelism] ,
[qmg].[request_time] ,
COALESCE(CAST([qmg].[grant_time] AS VARCHAR), ''Memory Not Granted'') AS [grant_time] ,
[qmg].[requested_memory_kb] ,
[qmg].[granted_memory_kb] AS [grant_memory_kb],
CASE WHEN [qmg].[grant_time] IS NULL THEN ''N/A''
WHEN [qmg].[requested_memory_kb] < [qmg].[granted_memory_kb]
THEN ''Query Granted Less Than Query Requested''
ELSE ''Memory Request Granted''
END AS [is_request_granted] ,
[qmg].[required_memory_kb] ,
[qmg].[used_memory_kb] ,
[qmg].[ideal_memory_kb] ,
[qmg].[is_small] ,
[qmg].[timeout_sec] ,
[qmg].[resource_semaphore_id] ,
COALESCE(CAST([qmg].[wait_order] AS VARCHAR), ''N/A'') AS [wait_order] ,
COALESCE(CAST([qmg].[wait_time_ms] AS VARCHAR),
''N/A'') AS [wait_time_ms] ,
CASE [qmg].[is_next_candidate]
WHEN 0 THEN ''No''
WHEN 1 THEN ''Yes''
ELSE ''N/A''
END AS [next_candidate_for_memory_grant] ,
[qrs].[target_memory_kb] ,
COALESCE(CAST([qrs].[max_target_memory_kb] AS VARCHAR),
''Small Query Resource Semaphore'') AS [max_target_memory_kb] ,
[qrs].[total_memory_kb] ,
[qrs].[available_memory_kb] ,
[qrs].[granted_memory_kb] ,
[qrs].[used_memory_kb] ,
[qrs].[grantee_count] ,
[qrs].[waiter_count] ,
[qrs].[timeout_error_count] ,
COALESCE(CAST([qrs].[forced_grant_count] AS VARCHAR),
''Small Query Resource Semaphore'') AS [forced_grant_count],
[s].[nt_domain] ,
[s].[host_name] ,
[s].[login_name] ,
[s].[nt_user_name] ,
[s].[program_name] ,
[s].[client_interface_name] ,
[s].[login_time] ,
[r].[start_time]
FROM [sys].[dm_exec_sessions] AS [s]
INNER JOIN [sys].[dm_exec_requests] AS [r]
ON [r].[session_id] = [s].[session_id]
LEFT JOIN ( SELECT DISTINCT
[wait].[session_id] ,
( SELECT [waitwait].[wait_type] + N'' (''
+ CAST(SUM([waitwait].[wait_duration_ms]) AS NVARCHAR(128))
+ N'' ms) ''
FROM [sys].[dm_os_waiting_tasks] AS [waitwait]
WHERE [waitwait].[session_id] = [wait].[session_id]
GROUP BY [waitwait].[wait_type]
ORDER BY SUM([waitwait].[wait_duration_ms]) DESC
FOR
XML PATH('''') ) AS [wait_info]
FROM [sys].[dm_os_waiting_tasks] AS [wait] ) AS [wt]
ON [s].[session_id] = [wt].[session_id]
LEFT JOIN [sys].[dm_exec_query_stats] AS [query_stats]
ON [r].[sql_handle] = [query_stats].[sql_handle]
AND [r].[plan_handle] = [query_stats].[plan_handle]
AND [r].[statement_start_offset] = [query_stats].[statement_start_offset]
AND [r].[statement_end_offset] = [query_stats].[statement_end_offset]
LEFT JOIN [sys].[dm_exec_query_memory_grants] [qmg]
ON [r].[session_id] = [qmg].[session_id]
AND [r].[request_id] = [qmg].[request_id]
LEFT JOIN [sys].[dm_exec_query_resource_semaphores] [qrs]
ON [qmg].[resource_semaphore_id] = [qrs].[resource_semaphore_id]
AND [qmg].[pool_id] = [qrs].[pool_id]
OUTER APPLY [sys].[dm_exec_sql_text]([r].[sql_handle]) AS [dest]
OUTER APPLY [sys].[dm_exec_query_plan]([r].[plan_handle]) AS [derp]
WHERE [r].[session_id] <> @@SPID
AND (([s].[status] <> ''sleeping'' AND ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + ' = 0) OR ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + ' = 1)
ORDER BY 2 DESC;
'
END
EXEC(@StringToExecute);


END
GO


sp_BlitzFirst – What are you waiting for?
It helps you discover what your database is waiting for. In the example, you can see the #1 problem is that things other than SQL Server are consuming too much of the CPU’s time.




SET ANSI_NULLS ON
GO


SET QUOTED_IDENTIFIER ON
GO


CREATE PROCEDURE [dbo].[sp_BlitzFirst]
@Question NVARCHAR(MAX) = NULL ,
@Help TINYINT = 0 ,
@AsOf DATETIMEOFFSET = NULL ,
@ExpertMode TINYINT = 0 ,
@Seconds INT = 5 ,
@OutputType VARCHAR(20) = 'TABLE' ,
@OutputServerName NVARCHAR(256) = NULL ,
@OutputDatabaseName NVARCHAR(256) = NULL ,
@OutputSchemaName NVARCHAR(256) = NULL ,
@OutputTableName NVARCHAR(256) = NULL ,
@OutputTableNameFileStats NVARCHAR(256) = NULL ,
@OutputTableNamePerfmonStats NVARCHAR(256) = NULL ,
@OutputTableNameWaitStats NVARCHAR(256) = NULL ,
@OutputXMLasNVARCHAR TINYINT = 0 ,
@FilterPlansByDatabase VARCHAR(MAX) = NULL ,
@CheckProcedureCache TINYINT = 0 ,
@FileLatencyThresholdMS INT = 100 ,
@SinceStartup TINYINT = 0 ,
@ShowSleepingSPIDs TINYINT = 0 ,
@VersionDate DATETIME = NULL OUTPUT
WITH EXECUTE AS CALLER, RECOMPILE
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET @VersionDate = '20170201'




IF @Help = 1 PRINT '
sp_BlitzFirst from FirstResponderKit.org


This script gives you a prioritized list of why your SQL Server is slow right now.




This is not an overall health check - for that, check out sp_Blitz.




To learn more, visit FirstResponderKit.org where you can download new
versions for free, watch training videos on how it works, get more info on
the findings, contribute your own code, and more.




Known limitations of this version:
- Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It
may work just fine on 2005, and if it does, hug your parents. Just don''t
file support issues if it breaks.
- If a temp table called #CustomPerfmonCounters exists for any other session,
but not our session, this stored proc will fail with an error saying the
temp table #CustomPerfmonCounters does not exist.
- @OutputServerName is not functional yet.




Unknown limitations of this version:
- None. Like Zombo.com, the only limit is yourself.




Changes - for the full list of improvements and fixes in this version, see:
github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit








MIT License




Copyright (c) 2016 Brent Ozar Unlimited




Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:




The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.




THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.




'








RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT;
DECLARE @StringToExecute NVARCHAR(MAX),
@ParmDefinitions NVARCHAR(4000),
@Parm1 NVARCHAR(4000),
@OurSessionID INT,
@LineFeed NVARCHAR(10),
@StockWarningHeader NVARCHAR(500),
@StockWarningFooter NVARCHAR(100),
@StockDetailsHeader NVARCHAR(100),
@StockDetailsFooter NVARCHAR(100),
@StartSampleTime DATETIMEOFFSET,
@FinishSampleTime DATETIMEOFFSET,
@FinishSampleTimeWaitFor DATETIME,
@ServiceName sysname,
@OutputTableNameFileStats_View NVARCHAR(256),
@OutputTableNamePerfmonStats_View NVARCHAR(256),
@OutputTableNameWaitStats_View NVARCHAR(256),
@ObjectFullName NVARCHAR(2000);




/* Sanitize our inputs */
SELECT
@OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'),
@OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'),
@OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas');




SELECT
@OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
@OutputSchemaName = QUOTENAME(@OutputSchemaName),
@OutputTableName = QUOTENAME(@OutputTableName),
@OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats),
@OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats),
@OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats),
@LineFeed = CHAR(13) + CHAR(10),
@StartSampleTime = SYSDATETIMEOFFSET(),
@FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()),
@FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()),
@OurSessionID = @@SPID;








IF @SinceStartup = 1
SELECT @Seconds = 0, @ExpertMode = 1;




IF @Seconds = 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'
SELECT @StartSampleTime = DATEADD(ms, AVG(-wait_time_ms), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET()
FROM sys.dm_os_wait_stats w
WHERE wait_type IN ('BROKER_TASK_STOP','DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP',
'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_DISPATCHER_WAIT','XE_TIMER_EVENT')
ELSE IF @Seconds = 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'
SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET()
FROM sys.databases
WHERE database_id = 2;
ELSE
SELECT @StartSampleTime = SYSDATETIMEOFFSET(), @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET());




IF @OutputType = 'SCHEMA'
BEGIN
SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'




END
ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL
BEGIN
/* They want to look into the past. */




SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,'
+ '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],'
+ '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableName
+ ' WHERE CheckDate >= DATEADD(mi, -15, ''' + CAST(@AsOf AS NVARCHAR(100)) + ''')'
+ ' AND CheckDate <= DATEADD(mi, 15, ''' + CAST(@AsOf AS NVARCHAR(100)) + ''')'
+ ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;';
EXEC(@StringToExecute);








END /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */
ELSE IF @Question IS NULL /* IF @OutputType = 'SCHEMA' */
BEGIN
/* What's running right now? This is the first and last result set. */
IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1
BEGIN
IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL
BEGIN
PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from FirstResponderKit.org'
END
ELSE
BEGIN
DECLARE @BlitzWho NVARCHAR(MAX) = 'EXEC [dbo].[sp_BlitzWho] @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs)
EXEC (@BlitzWho)
END
END /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 - What's running right now? This is the first and last result set. */





RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT;




/*
We start by creating #BlitzFirstResults. It's a temp table that will store
the results from our checks. Throughout the rest of this stored procedure,
we're running a series of checks looking for dangerous things inside the SQL
Server. When we find a problem, we insert rows into #BlitzResults. At the
end, we return these results to the end user.




#BlitzFirstResults has a CheckID field, but there's no Check table. As we do
checks, we insert data into this table, and we manually put in the CheckID.
We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can
download that from FirstResponderKit.org if you want to build
a tool that relies on the output of sp_BlitzFirst.
*/




IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL
DROP TABLE #BlitzFirstResults;
CREATE TABLE #BlitzFirstResults
(
ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
CheckID INT NOT NULL,
Priority TINYINT NOT NULL,
FindingsGroup VARCHAR(50) NOT NULL,
Finding VARCHAR(200) NOT NULL,
URL VARCHAR(200) NULL,
Details NVARCHAR(4000) NULL,
HowToStopIt NVARCHAR(MAX) NULL,
QueryPlan [XML] NULL,
QueryText NVARCHAR(MAX) NULL,
StartTime DATETIMEOFFSET NULL,
LoginName NVARCHAR(128) NULL,
NTUserName NVARCHAR(128) NULL,
OriginalLoginName NVARCHAR(128) NULL,
ProgramName NVARCHAR(128) NULL,
HostName NVARCHAR(128) NULL,
DatabaseID INT NULL,
DatabaseName NVARCHAR(128) NULL,
OpenTransactionCount INT NULL,
QueryStatsNowID INT NULL,
QueryStatsFirstID INT NULL,
PlanHandle VARBINARY(64) NULL,
DetailsInt INT NULL,
);




IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL
DROP TABLE #WaitStats;
CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET);




IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL
DROP TABLE #FileStats;
CREATE TABLE #FileStats (
ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
Pass TINYINT NOT NULL,
SampleTime DATETIMEOFFSET NOT NULL,
DatabaseID INT NOT NULL,
FileID INT NOT NULL,
DatabaseName NVARCHAR(256) ,
FileLogicalName NVARCHAR(256) ,
TypeDesc NVARCHAR(60) ,
SizeOnDiskMB BIGINT ,
io_stall_read_ms BIGINT ,
num_of_reads BIGINT ,
bytes_read BIGINT ,
io_stall_write_ms BIGINT ,
num_of_writes BIGINT ,
bytes_written BIGINT,
PhysicalName NVARCHAR(520) ,
avg_stall_read_ms INT ,
avg_stall_write_ms INT
);




IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL
DROP TABLE #QueryStats;
CREATE TABLE #QueryStats (
ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
Pass INT NOT NULL,
SampleTime DATETIMEOFFSET NOT NULL,
[sql_handle] VARBINARY(64),
statement_start_offset INT,
statement_end_offset INT,
plan_generation_num BIGINT,
plan_handle VARBINARY(64),
execution_count BIGINT,
total_worker_time BIGINT,
total_physical_reads BIGINT,
total_logical_writes BIGINT,
total_logical_reads BIGINT,
total_clr_time BIGINT,
total_elapsed_time BIGINT,
creation_time DATETIMEOFFSET,
query_hash BINARY(8),
query_plan_hash BINARY(8),
Points TINYINT
);




IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL
DROP TABLE #PerfmonStats;
CREATE TABLE #PerfmonStats (
ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
Pass TINYINT NOT NULL,
SampleTime DATETIMEOFFSET NOT NULL,
[object_name] NVARCHAR(128) NOT NULL,
[counter_name] NVARCHAR(128) NOT NULL,
[instance_name] NVARCHAR(128) NULL,
[cntr_value] BIGINT NULL,
[cntr_type] INT NOT NULL,
[value_delta] BIGINT NULL,
[value_per_second] DECIMAL(18,2) NULL
);




IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL
DROP TABLE #PerfmonCounters;
CREATE TABLE #PerfmonCounters (
ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
[object_name] NVARCHAR(128) NOT NULL,
[counter_name] NVARCHAR(128) NOT NULL,
[instance_name] NVARCHAR(128) NULL
);




IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL
DROP TABLE #FilterPlansByDatabase;
CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED);




IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL
DROP TABLE #MasterFiles;
CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT);
/* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */
IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'
SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'
ELSE
SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'
EXEC(@StringToExecute);




IF @FilterPlansByDatabase IS NOT NULL
BEGIN
IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER'
BEGIN
INSERT INTO #FilterPlansByDatabase (DatabaseID)
SELECT database_id
FROM sys.databases
WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb')
END
ELSE
BEGIN
SET @FilterPlansByDatabase = @FilterPlansByDatabase + ','
;WITH a AS
(
SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ
UNION ALL
SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1
FROM a
WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0
)
INSERT #FilterPlansByDatabase (DatabaseID)
SELECT SUBSTRING(@FilterPlansByDatabase, f, t - f)
FROM a
WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL
OPTION (MAXRECURSION 0)
END
END








SET @StockWarningHeader = '<?ClickToSeeCommmand -- ' + @LineFeed + @LineFeed
+ 'WARNING: Running this command may result in data loss or an outage.' + @LineFeed
+ 'This tool is meant as a shortcut to help generate scripts for DBAs.' + @LineFeed
+ 'It is not a substitute for database training and experience.' + @LineFeed
+ 'Now, having said that, here''s the details:' + @LineFeed + @LineFeed;




SELECT @StockWarningFooter = @LineFeed + @LineFeed + '-- ?>',
@StockDetailsHeader = '<?ClickToSeeDetails -- ' + @LineFeed,
@StockDetailsFooter = @LineFeed + ' -- ?>';




/* Get the instance name to use as a Perfmon counter prefix. */
IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'
SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1))
FROM sys.dm_os_performance_counters;
ELSE
BEGIN
SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'
EXEC(@StringToExecute);
SELECT @ServiceName = object_name FROM #PerfmonStats;
DELETE #PerfmonStats;
END




/* Build a list of queries that were run in the last 10 seconds.
We're looking for the death-by-a-thousand-small-cuts scenario
where a query is constantly running, and it doesn't have that
big of an impact individually, but it has a ton of impact
overall. We're going to build this list, and then after we
finish our @Seconds sample, we'll compare our plan cache to
this list to see what ran the most. */




/* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */
IF @CheckProcedureCache = 1
BEGIN
RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT;
IF @@VERSION LIKE 'Microsoft SQL Server 2005%'
BEGIN
IF @FilterPlansByDatabase IS NULL
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));';
END
ELSE
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr
INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID
WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()))
AND attr.attribute = ''dbid'';';
END
END
ELSE
BEGIN
IF @FilterPlansByDatabase IS NULL
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));';
END
ELSE
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr
INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID
WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()))
AND attr.attribute = ''dbid'';';
END
END
EXEC(@StringToExecute);




/* Get the totals for the entire plan cache */
INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time)
SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time)
FROM sys.dm_exec_query_stats qs;
END /*IF @CheckProcedureCache = 1 */








IF EXISTS (SELECT *
FROM tempdb.sys.all_objects obj
INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name'
INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name'
INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name'
WHERE obj.name LIKE '%CustomPerfmonCounters%')
BEGIN
SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'
EXEC(@StringToExecute);
END
ELSE
BEGIN
/* Add our default Perfmon counters */
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total')
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL)
/* Below counters added by Jefferson Elias */
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL)
END




/* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data.
After we finish doing our checks, we'll take another sample and compare them. */
RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT;
INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count)
SELECT
x.Pass,
x.SampleTime,
x.wait_type,
SUM(x.sum_wait_time_ms) AS sum_wait_time_ms,
SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms,
SUM(x.sum_waiting_tasks) AS sum_waiting_tasks
FROM (
SELECT
1 AS Pass,
CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime,
owt.wait_type,
CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id)
- CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms,
0 AS sum_signal_wait_time_ms,
0 AS sum_waiting_tasks
FROM sys.dm_os_waiting_tasks owt
WHERE owt.session_id > 50
AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END
UNION ALL
SELECT
1 AS Pass,
CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime,
os.wait_type,
CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms,
CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms,
CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks
FROM sys.dm_os_wait_stats os
) x
WHERE x.wait_type NOT IN (
'REQUEST_FOR_DEADLOCK_SEARCH',
'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_BUFFER_FLUSH',
'LAZYWRITER_SLEEP',
'XE_TIMER_EVENT',
'XE_DISPATCHER_WAIT',
'FT_IFTS_SCHEDULER_IDLE_WAIT',
'LOGMGR_QUEUE',
'CHECKPOINT_QUEUE',
'BROKER_TO_FLUSH',
'BROKER_TASK_STOP',
'BROKER_EVENTHANDLER',
'SLEEP_TASK',
'WAITFOR',
'DBMIRROR_DBM_MUTEX',
'DBMIRROR_EVENTS_QUEUE',
'DBMIRRORING_CMD',
'DISPATCHER_QUEUE_SEMAPHORE',
'BROKER_RECEIVE_WAITFOR',
'CLR_AUTO_EVENT',
'DIRTY_PAGE_POLL',
'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
'ONDEMAND_TASK_QUEUE',
'FT_IFTSHC_MUTEX',
'CLR_MANUAL_EVENT',
'CLR_SEMAPHORE',
'DBMIRROR_WORKER_QUEUE',
'DBMIRROR_DBM_EVENT',
'SP_SERVER_DIAGNOSTICS_SLEEP',
'HADR_CLUSAPI_CALL',
'HADR_LOGCAPTURE_WAIT',
'HADR_NOTIFICATION_DEQUEUE',
'HADR_TIMER_TASK',
'HADR_WORK_QUEUE',
'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
'RESOURCE_GOVERNOR_IDLE',
'QDS_ASYNC_QUEUE',
'QDS_SHUTDOWN_QUEUE',
'SLEEP_SYSTEMTASK',
'BROKER_TRANSMITTER',
'REDO_THREAD_PENDING_WORK',
'UCS_SESSION_REGISTRATION',
'PREEMPTIVE_XE_DISPATCHER',
'TRACEWRITE',
'OLEDB'
)
GROUP BY x.Pass, x.SampleTime, x.wait_type
ORDER BY sum_wait_time_ms DESC;








INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms ,
num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc)
SELECT
1 AS Pass,
CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime,
mf.[database_id],
mf.[file_id],
DB_NAME(vfs.database_id) AS [db_name],
mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name ,
CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb ,
CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END ,
CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END ,
CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END ,
CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END ,
CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END ,
CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END ,
mf.physical_name,
mf.type_desc
FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs
INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id
AND vfs.database_id = mf.database_id
WHERE vfs.num_of_reads > 0
OR vfs.num_of_writes > 0;




INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type])
SELECT 1 AS Pass,
CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type
FROM #PerfmonCounters counters
INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS
AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS
AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS)








RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT;








/* Maintenance Tasks Running - Backup Running - CheckID 1 */
IF @Seconds > 0
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount)
SELECT 1 AS CheckID,
1 AS Priority,
'Maintenance Tasks Running' AS FindingGroup,
'Backup Running' AS Finding,
'BrentOzar.com/askbrent/backups/' AS URL,
'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' AS Details,
'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt,
pl.query_plan AS QueryPlan,
r.start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
db.[resource_database_id] AS DatabaseID,
DB_NAME(db.resource_database_id) AS DatabaseName,
0 AS OpenTransactionCount
FROM sys.dm_exec_requests r
INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id
INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
INNER JOIN (
SELECT DISTINCT request_session_id, resource_database_id
FROM sys.dm_tran_locks
WHERE resource_type = N'DATABASE'
AND request_mode = N'S'
AND request_status = N'GRANT'
AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl
WHERE r.command LIKE 'BACKUP%';








/* If there's a backup running, add details explaining how long full backup has been taking in the last month. */
IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'
BEGIN
SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)';
EXEC(@StringToExecute);
END








/* Maintenance Tasks Running - DBCC Running - CheckID 2 */
IF @Seconds > 0
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount)
SELECT 2 AS CheckID,
1 AS Priority,
'Maintenance Tasks Running' AS FindingGroup,
'DBCC Running' AS Finding,
'BrentOzar.com/askbrent/dbcc/' AS URL,
'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' AS Details,
'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt,
pl.query_plan AS QueryPlan,
r.start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
db.[resource_database_id] AS DatabaseID,
DB_NAME(db.resource_database_id) AS DatabaseName,
0 AS OpenTransactionCount
FROM sys.dm_exec_requests r
INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id
INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id
FROM sys.dm_tran_locks l
INNER JOIN sys.databases d ON l.resource_database_id = d.database_id
WHERE l.resource_type = N'DATABASE'
AND l.request_mode = N'S'
AND l.request_status = N'GRANT'
AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl
WHERE r.command LIKE 'DBCC%';








/* Maintenance Tasks Running - Restore Running - CheckID 3 */
IF @Seconds > 0
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount)
SELECT 3 AS CheckID,
1 AS Priority,
'Maintenance Tasks Running' AS FindingGroup,
'Restore Running' AS Finding,
'BrentOzar.com/askbrent/backups/' AS URL,
'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' AS Details,
'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt,
pl.query_plan AS QueryPlan,
r.start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
db.[resource_database_id] AS DatabaseID,
DB_NAME(db.resource_database_id) AS DatabaseName,
0 AS OpenTransactionCount
FROM sys.dm_exec_requests r
INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id
INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
INNER JOIN (
SELECT DISTINCT request_session_id, resource_database_id
FROM sys.dm_tran_locks
WHERE resource_type = N'DATABASE'
AND request_mode = N'S'
AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl
WHERE r.command LIKE 'RESTORE%';








/* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */
IF @Seconds > 0
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount)
SELECT 4 AS CheckID,
1 AS Priority,
'SQL Server Internal Maintenance' AS FindingGroup,
'Database File Growing' AS Finding,
'BrentOzar.com/go/instant' AS URL,
'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details,
'Unfortunately, you can''t stop this, but you can prevent it next time. Check out BrentOzar.com/go/instant for details.' AS HowToStopIt,
pl.query_plan AS QueryPlan,
r.start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
NULL AS DatabaseID,
NULL AS DatabaseName,
0 AS OpenTransactionCount
FROM sys.dm_os_waiting_tasks t
INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id
INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id
INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl
WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER'


/* Query Problems - Long-Running Query Blocking Others - CheckID 5 */
IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000)
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount)
SELECT 5 AS CheckID,
1 AS Priority,
'Query Problems' AS FindingGroup,
'Long-Running Query Blocking Others' AS Finding,
'BrentOzar.com/go/blocking' AS URL,
'Query in ' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)),
(SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), '(Unknown)') + ' has a last request start time of ' + CAST(s.last_request_start_time AS NVARCHAR(100)) + '. Query follows:' + @LineFeed + @LineFeed
+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)),
(SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '') AS NVARCHAR(2000)) AS Details,
'KILL ' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + ';' AS HowToStopIt,
(SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan,
COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)),
(SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText,
r.start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
r.[database_id] AS DatabaseID,
DB_NAME(r.database_id) AS DatabaseName,
0 AS OpenTransactionCount
FROM sys.dm_os_waiting_tasks tBlocked
INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id
LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
WHERE tBlocked.wait_type LIKE 'LCK%' AND tBlocked.wait_duration_ms > 30000;




/* Query Problems - Plan Cache Erased Recently */
IF DATEADD(mi, -15, SYSDATETIMEOFFSET()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time)
BEGIN
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
SELECT TOP 1 7 AS CheckID,
50 AS Priority,
'Query Problems' AS FindingGroup,
'Plan Cache Erased Recently' AS Finding,
'BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL,
'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed
+ 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed
+ 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed
+ 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed
+ 'plans and put them in cache again. This causes high CPU loads.' AS Details,
'Find who did that, and stop them from doing it again.' AS HowToStopIt
FROM sys.dm_exec_query_stats
ORDER BY creation_time
END;








/* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */
IF @Seconds > 0
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount)
SELECT 8 AS CheckID,
50 AS Priority,
'Query Problems' AS FindingGroup,
'Sleeping Query with Open Transactions' AS Finding,
'brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL,
'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details,
'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt,
s.last_request_start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
db.[resource_database_id] AS DatabaseID,
DB_NAME(db.resource_database_id) AS DatabaseName,
(SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText,
sessions_with_transactions.open_transaction_count AS OpenTransactionCount
FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions
INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id
INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
INNER JOIN (
SELECT DISTINCT request_session_id, resource_database_id
FROM sys.dm_tran_locks
WHERE resource_type = N'DATABASE'
AND request_mode = N'S'
AND request_status = N'GRANT'
AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id
WHERE s.status = 'sleeping'
AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIMEOFFSET())
AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id
AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE'))








/* Query Problems - Query Rolling Back - CheckID 9 */
IF @Seconds > 0
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText)
SELECT 9 AS CheckID,
1 AS Priority,
'Query Problems' AS FindingGroup,
'Query Rolling Back' AS Finding,
'BrentOzar.com/askbrent/rollback/' AS URL,
'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details,
'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt,
r.start_time AS StartTime,
s.login_name AS LoginName,
s.nt_user_name AS NTUserName,
s.[program_name] AS ProgramName,
s.[host_name] AS HostName,
db.[resource_database_id] AS DatabaseID,
DB_NAME(db.resource_database_id) AS DatabaseName,
(SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText
FROM sys.dm_exec_sessions s
INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
LEFT OUTER JOIN (
SELECT DISTINCT request_session_id, resource_database_id
FROM sys.dm_tran_locks
WHERE resource_type = N'DATABASE'
AND request_mode = N'S'
AND request_status = N'GRANT'
AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id
WHERE r.status = 'rollback'


/* Server Performance - Page Life Expectancy Low - CheckID 10 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
SELECT 10 AS CheckID,
50 AS Priority,
'Server Performance' AS FindingGroup,
'Page Life Expectancy Low' AS Finding,
'BrentOzar.com/askbrent/page-life-expectancy/' AS URL,
'SQL Server Buffer Manager:Page life expectancy is ' + CAST(c.cntr_value AS NVARCHAR(10)) + ' seconds.' + @LineFeed
+ 'This means SQL Server can only keep data pages in memory for that many seconds after reading those pages in from storage.' + @LineFeed
+ 'This is a symptom, not a cause - it indicates very read-intensive queries that need an index, or insufficient server memory.' AS Details,
'Add more memory to the server, or find the queries reading a lot of data, and make them more efficient (or fix them with indexes).' AS HowToStopIt
FROM sys.dm_os_performance_counters c
WHERE object_name LIKE 'SQLServer:Buffer Manager%'
AND counter_name LIKE 'Page life expectancy%'
AND cntr_value < 300




/* Server Info - Database Size, Total GB - CheckID 21 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 21 AS CheckID,
251 AS Priority,
'Server Info' AS FindingGroup,
'Database Size, Total GB' AS Finding,
CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details,
SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt,
'BrentOzar.com/askbrent/' AS URL
FROM #MasterFiles
WHERE database_id > 4




/* Server Info - Database Count - CheckID 22 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 22 AS CheckID,
251 AS Priority,
'Server Info' AS FindingGroup,
'Database Count' AS Finding,
CAST(SUM(1) AS VARCHAR(100)) AS Details,
SUM (1) AS DetailsInt,
'BrentOzar.com/askbrent/' AS URL
FROM sys.databases
WHERE database_id > 4




/* Server Performance - High CPU Utilization CheckID 24 */
IF @Seconds < 30
BEGIN
/* If we're waiting less than 30 seconds, run this check now rather than wait til the end.
We get this data from the ring buffers, and it's only updated once per minute, so might
as well get it now - whereas if we're checking 30+ seconds, it might get updated by the
end of our sp_BlitzFirst session. */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'BrentOzar.com/go/cpu'
FROM (
SELECT record,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle
FROM (
SELECT TOP 1 CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%<SystemHealth>%'
ORDER BY timestamp DESC) AS rb
) AS y
WHERE 100 - SystemIdle >= 50




INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'BrentOzar.com/go/cpu'
FROM (
SELECT record,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle
FROM (
SELECT TOP 1 CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%<SystemHealth>%'
ORDER BY timestamp DESC) AS rb
) AS y


/* Highlight if non SQL processes are using >25% CPU */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'BrentOzar.com/go/cpu'
FROM (
SELECT record,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle
,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage
FROM (
SELECT TOP 1 CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%<SystemHealth>%'
ORDER BY timestamp DESC) AS rb
) AS y
WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25


END /* IF @Seconds < 30 */




RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT;








/* End of checks. If we haven't waited @Seconds seconds, wait. */
IF SYSDATETIMEOFFSET() < @FinishSampleTime
BEGIN
RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT;
WAITFOR TIME @FinishSampleTimeWaitFor;
END




RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT;
/* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */
INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count)
SELECT
x.Pass,
x.SampleTime,
x.wait_type,
SUM(x.sum_wait_time_ms) AS sum_wait_time_ms,
SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms,
SUM(x.sum_waiting_tasks) AS sum_waiting_tasks
FROM (
SELECT
2 AS Pass,
SYSDATETIMEOFFSET() AS SampleTime,
owt.wait_type,
SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id)
- CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms,
0 AS sum_signal_wait_time_ms,
CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks
FROM sys.dm_os_waiting_tasks owt
WHERE owt.session_id > 50
AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END
UNION ALL
SELECT
2 AS Pass,
SYSDATETIMEOFFSET() AS SampleTime,
os.wait_type,
SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms,
SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms,
SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks
FROM sys.dm_os_wait_stats os
) x
WHERE x.wait_type NOT IN (
'REQUEST_FOR_DEADLOCK_SEARCH',
'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_BUFFER_FLUSH',
'LAZYWRITER_SLEEP',
'XE_TIMER_EVENT',
'XE_DISPATCHER_WAIT',
'FT_IFTS_SCHEDULER_IDLE_WAIT',
'LOGMGR_QUEUE',
'CHECKPOINT_QUEUE',
'BROKER_TO_FLUSH',
'BROKER_TASK_STOP',
'BROKER_EVENTHANDLER',
'SLEEP_TASK',
'WAITFOR',
'DBMIRROR_DBM_MUTEX',
'DBMIRROR_EVENTS_QUEUE',
'DBMIRRORING_CMD',
'DISPATCHER_QUEUE_SEMAPHORE',
'BROKER_RECEIVE_WAITFOR',
'CLR_AUTO_EVENT',
'DIRTY_PAGE_POLL',
'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
'ONDEMAND_TASK_QUEUE',
'FT_IFTSHC_MUTEX',
'CLR_MANUAL_EVENT',
'CLR_SEMAPHORE',
'DBMIRROR_WORKER_QUEUE',
'DBMIRROR_DBM_EVENT',
'SP_SERVER_DIAGNOSTICS_SLEEP',
'HADR_CLUSAPI_CALL',
'HADR_LOGCAPTURE_WAIT',
'HADR_NOTIFICATION_DEQUEUE',
'HADR_TIMER_TASK',
'HADR_WORK_QUEUE',
'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
'RESOURCE_GOVERNOR_IDLE',
'QDS_ASYNC_QUEUE',
'QDS_SHUTDOWN_QUEUE',
'SLEEP_SYSTEMTASK',
'BROKER_TRANSMITTER',
'REDO_THREAD_PENDING_WORK',
'UCS_SESSION_REGISTRATION',
'PREEMPTIVE_XE_DISPATCHER',
'TRACEWRITE',
'OLEDB'
)
GROUP BY x.Pass, x.SampleTime, x.wait_type
ORDER BY sum_wait_time_ms DESC;




INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms ,
num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms)
SELECT 2 AS Pass,
SYSDATETIMEOFFSET() AS SampleTime,
mf.[database_id],
mf.[file_id],
DB_NAME(vfs.database_id) AS [db_name],
mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name ,
CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb ,
vfs.io_stall_read_ms ,
vfs.num_of_reads ,
vfs.[num_of_bytes_read],
vfs.io_stall_write_ms ,
vfs.num_of_writes ,
vfs.[num_of_bytes_written],
mf.physical_name,
mf.type_desc,
0,
0
FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs
INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id
AND vfs.database_id = mf.database_id
WHERE vfs.num_of_reads > 0
OR vfs.num_of_writes > 0;




INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type])
SELECT 2 AS Pass,
SYSDATETIMEOFFSET() AS SampleTime,
RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type
FROM #PerfmonCounters counters
INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS
AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS
AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS)




/* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */
UPDATE fNow
SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads))
FROM #FileStats fNow
INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms
WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0




UPDATE fNow
SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes))
FROM #FileStats fNow
INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms
WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0




UPDATE pNow
SET [value_delta] = pNow.cntr_value - pFirst.cntr_value,
[value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime))
FROM #PerfmonStats pNow
INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL))
AND pNow.ID > pFirst.ID
WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0;








/* If we're within 10 seconds of our projected finish time, do the plan cache analysis. */
IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1
BEGIN




INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'BrentOzar.com/go/topqueries',
'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.')




END
ELSE IF @CheckProcedureCache = 1
BEGIN








RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT;




/* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */
IF @@VERSION LIKE 'Microsoft SQL Server 2005%'
BEGIN
IF @FilterPlansByDatabase IS NULL
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= @StartSampleTimeText;';
END
ELSE
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr
INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID
WHERE qs.last_execution_time >= @StartSampleTimeText
AND attr.attribute = ''dbid'';';
END
END
ELSE
BEGIN
IF @FilterPlansByDatabase IS NULL
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= @StartSampleTimeText';
END
ELSE
BEGIN
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr
INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID
WHERE qs.last_execution_time >= @StartSampleTimeText
AND attr.attribute = ''dbid'';';
END
END
/* Old version pre-2016/06/13:
IF @@VERSION LIKE 'Microsoft SQL Server 2005%'
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= @StartSampleTimeText;';
ELSE
SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points)
SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0
FROM sys.dm_exec_query_stats qs
WHERE qs.last_execution_time >= @StartSampleTimeText;';
*/
SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)';
SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127);


EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1;
RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT;




/* Get the totals for the entire plan cache */
INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time)
SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time)
FROM sys.dm_exec_query_stats qs;


RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT;
/*
Pick the most resource-intensive queries to review. Update the Points field
in #QueryStats - if a query is in the top 10 for logical reads, CPU time,
duration, or execution, add 1 to its points.
*/
WITH qsTop AS (
SELECT TOP 10 qsNow.ID
FROM #QueryStats qsNow
INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1
WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time
AND qsNow.Pass = 2
AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */
ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC)
UPDATE #QueryStats
SET Points = Points + 1
FROM #QueryStats qs
INNER JOIN qsTop ON qs.ID = qsTop.ID;


WITH qsTop AS (
SELECT TOP 10 qsNow.ID
FROM #QueryStats qsNow
INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1
WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads
AND qsNow.Pass = 2
AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */
ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC)
UPDATE #QueryStats
SET Points = Points + 1
FROM #QueryStats qs
INNER JOIN qsTop ON qs.ID = qsTop.ID;


WITH qsTop AS (
SELECT TOP 10 qsNow.ID
FROM #QueryStats qsNow
INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1
WHERE qsNow.total_worker_time > qsFirst.total_worker_time
AND qsNow.Pass = 2
AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */
ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC)
UPDATE #QueryStats
SET Points = Points + 1
FROM #QueryStats qs
INNER JOIN qsTop ON qs.ID = qsTop.ID;


WITH qsTop AS (
SELECT TOP 10 qsNow.ID
FROM #QueryStats qsNow
INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1
WHERE qsNow.execution_count > qsFirst.execution_count
AND qsNow.Pass = 2
AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */
OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */
OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */)
ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC)
UPDATE #QueryStats
SET Points = Points + 1
FROM #QueryStats qs
INNER JOIN qsTop ON qs.ID = qsTop.ID;


/* Query Stats - CheckID 17 - Most Resource-Intensive Queries */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle)
SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'BrentOzar.com/go/topqueries',
'Query stats during the sample:' + @LineFeed +
'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed +
'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed +
'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed +
'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed +
'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed +
'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed +
@LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed +
'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) +
CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed +
'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) +
CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed +
'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) +
CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed +
'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) +
CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed +
'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) +
CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed +
'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) +
CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed +
--@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed +
--@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) +
@LineFeed AS Details,
'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt,
qp.query_plan,
QueryText = SUBSTRING(st.text,
(qsNow.statement_start_offset / 2) + 1,
((CASE qsNow.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qsNow.statement_end_offset
END - qsNow.statement_start_offset) / 2) + 1),
qsNow.ID AS QueryStatsNowID,
qsFirst.ID AS QueryStatsFirstID,
qsNow.plan_handle AS PlanHandle
FROM #QueryStats qsNow
INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0
LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1
CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st
CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp
WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL


UPDATE #BlitzFirstResults
SET DatabaseID = CAST(attr.value AS INT),
DatabaseName = DB_NAME(CAST(attr.value AS INT))
FROM #BlitzFirstResults
CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr
WHERE attr.attribute = 'dbid'


END /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */
RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT;


/* Wait Stats - CheckID 6 */
/* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt)
SELECT TOP 10 6 AS CheckID,
200 AS Priority,
'Wait Stats' AS FindingGroup,
wNow.wait_type AS Finding,
N'brentozar.com/sql/wait-stats/#' + wNow.wait_type AS URL,
'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details,
'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt,
((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt
FROM #WaitStats wNow
LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime
WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */
ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC;


/* Server Performance - Slow Data File Reads - CheckID 11 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName)
SELECT TOP 10 11 AS CheckID,
50 AS Priority,
'Server Performance' AS FindingGroup,
'Slow Data File Reads' AS Finding,
'BrentOzar.com/go/slow/' AS URL,
'File: ' + fNow.PhysicalName + @LineFeed
+ 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed
+ 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed
+ 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed
+ 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details,
'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt,
fNow.DatabaseID,
fNow.DatabaseName
FROM #FileStats fNow
INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000)
WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS
AND fNow.TypeDesc = 'ROWS'
ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC;


/* Server Performance - Slow Log File Writes - CheckID 12 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName)
SELECT TOP 10 12 AS CheckID,
50 AS Priority,
'Server Performance' AS FindingGroup,
'Slow Log File Writes' AS Finding,
'BrentOzar.com/go/slow/' AS URL,
'File: ' + fNow.PhysicalName + @LineFeed
+ 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed
+ 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed
+ 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed
+ 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details,
'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt,
fNow.DatabaseID,
fNow.DatabaseName
FROM #FileStats fNow
INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000)
WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS
AND fNow.TypeDesc = 'LOG'
ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC;


/* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
SELECT 13 AS CheckID,
1 AS Priority,
'SQL Server Internal Maintenance' AS FindingGroup,
'Log File Growing' AS Finding,
'BrentOzar.com/askbrent/file-growing/' AS URL,
'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed
+ 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details,
'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt
FROM #PerfmonStats ps
WHERE ps.Pass = 2
AND object_name = @ServiceName + ':Databases'
AND counter_name = 'Log Growths'
AND value_delta > 0


/* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
SELECT 14 AS CheckID,
1 AS Priority,
'SQL Server Internal Maintenance' AS FindingGroup,
'Log File Shrinking' AS Finding,
'BrentOzar.com/askbrent/file-shrinking/' AS URL,
'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed
+ 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details,
'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt
FROM #PerfmonStats ps
WHERE ps.Pass = 2
AND object_name = @ServiceName + ':Databases'
AND counter_name = 'Log Shrinks'
AND value_delta > 0




/* Query Problems - Compilations/Sec High - CheckID 15 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
SELECT 15 AS CheckID,
50 AS Priority,
'Query Problems' AS FindingGroup,
'Compilations/Sec High' AS Finding,
'BrentOzar.com/askbrent/compilations/' AS URL,
'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed
+ 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed
+ 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details,
'Find out why plans are not being reused, and consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt
FROM #PerfmonStats ps
INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0
WHERE ps.Pass = 2
AND ps.object_name = @ServiceName + ':SQL Statistics'
AND ps.counter_name = 'Batch Requests/sec'
AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */
AND (psComp.value_delta * 10) > ps.value_delta /* Compilations are more than 10% of batch requests per second */


/* Query Problems - Re-Compilations/Sec High - CheckID 16 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
SELECT 16 AS CheckID,
50 AS Priority,
'Query Problems' AS FindingGroup,
'Re-Compilations/Sec High' AS Finding,
'BrentOzar.com/askbrent/recompilations/' AS URL,
'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed
+ 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed
+ 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details,
'Find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt
FROM #PerfmonStats ps
INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0
WHERE ps.Pass = 2
AND ps.object_name = @ServiceName + ':SQL Statistics'
AND ps.counter_name = 'Batch Requests/sec'
AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */
AND (psComp.value_delta * 10) > ps.value_delta /* Recompilations are more than 10% of batch requests per second */




/* Server Info - Batch Requests per Sec - CheckID 19 */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt)
SELECT 19 AS CheckID,
250 AS Priority,
'Server Info' AS FindingGroup,
'Batch Requests per Sec' AS Finding,
'BrentOzar.com/go/measure' AS URL,
CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details,
ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt
FROM #PerfmonStats ps
INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1
WHERE ps.Pass = 2
AND ps.object_name = @ServiceName + ':SQL Statistics'
AND ps.counter_name = 'Batch Requests/sec';
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL)
INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL)


/* Server Info - SQL Compilations/sec - CheckID 25 */
IF @ExpertMode = 1
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt)
SELECT 25 AS CheckID,
250 AS Priority,
'Server Info' AS FindingGroup,
'SQL Compilations per Sec' AS Finding,
'BrentOzar.com/go/measure' AS URL,
CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details,
ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt
FROM #PerfmonStats ps
INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1
WHERE ps.Pass = 2
AND ps.object_name = @ServiceName + ':SQL Statistics'
AND ps.counter_name = 'SQL Compilations/sec';




/* Server Info - SQL Re-Compilations/sec - CheckID 26 */
IF @ExpertMode = 1
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt)
SELECT 26 AS CheckID,
250 AS Priority,
'Server Info' AS FindingGroup,
'SQL Re-Compilations per Sec' AS Finding,
'BrentOzar.com/go/measure' AS URL,
CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details,
ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt
FROM #PerfmonStats ps
INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1
WHERE ps.Pass = 2
AND ps.object_name = @ServiceName + ':SQL Statistics'
AND ps.counter_name = 'SQL Re-Compilations/sec';




/* Server Info - Wait Time per Core per Sec - CheckID 20 */
IF @Seconds > 0
BEGIN
WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime),
waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime),
cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1)
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt)
SELECT 19 AS CheckID,
250 AS Priority,
'Server Info' AS FindingGroup,
'Wait Time per Core per Sec' AS Finding,
'BrentOzar.com/go/measure' AS URL,
CAST((waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details,
(waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt
FROM cores i
CROSS JOIN waits1
CROSS JOIN waits2;
END




/* Server Performance - High CPU Utilization CheckID 24 */
IF @Seconds >= 30
BEGIN
/* If we're waiting 30+ seconds, run this check at the end.
We get this data from the ring buffers, and it's only updated once per minute, so might
as well get it now - whereas if we're checking 30+ seconds, it might get updated by the
end of our sp_BlitzFirst session. */
INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'BrentOzar.com/go/cpu'
FROM (
SELECT record,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle
FROM (
SELECT TOP 1 CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%<SystemHealth>%'
ORDER BY timestamp DESC) AS rb
) AS y
WHERE 100 - SystemIdle >= 50




INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'BrentOzar.com/go/cpu'
FROM (
SELECT record,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle
FROM (
SELECT TOP 1 CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%<SystemHealth>%'
ORDER BY timestamp DESC) AS rb
) AS y


END /* IF @Seconds < 30 */


RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT;


/* If we didn't find anything, apologize. */
IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250)
BEGIN


INSERT INTO #BlitzFirstResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
VALUES ( -1 ,
1 ,
'No Problems Found' ,
'From Your Community Volunteers' ,
'FirstResponderKit.org/' ,
'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. '
);


END /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */


/* Add credits for the nice folks who put so much time into building and maintaining this for free: */
INSERT INTO #BlitzFirstResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
VALUES ( -1 ,
255 ,
'Thanks!' ,
'From Your Community Volunteers' ,
'FirstResponderKit.org/' ,
'To get help or add your own contributions, join us at FirstResponderKit.org.'
);




INSERT INTO #BlitzFirstResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
VALUES ( -1 ,
0 ,
'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)),
'From Your Community Volunteers' ,
'FirstResponderKit.org/' ,
'We hope you found this tool useful.'
);


/* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old */
IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6
BEGIN
INSERT INTO #BlitzFirstResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT 27 AS CheckID ,
0 AS Priority ,
'Outdated sp_BlitzFirst' AS FindingsGroup ,
'sp_BlitzFirst is Over 6 Months Old' AS Finding ,
'FirstResponderKit.org/' AS URL ,
'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details
END


/* @OutputTableName lets us export the results to a permanent table */
IF @OutputDatabaseName IS NOT NULL
AND @OutputSchemaName IS NOT NULL
AND @OutputTableName IS NOT NULL
AND @OutputTableName NOT LIKE '#%'
AND EXISTS ( SELECT *
FROM sys.databases
WHERE QUOTENAME([name]) = @OutputDatabaseName)
BEGIN
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName
+ ''') AND NOT EXISTS (SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
+ @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
+ @OutputTableName + ''') CREATE TABLE '
+ @OutputSchemaName + '.'
+ @OutputTableName
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
CheckID INT NOT NULL,
Priority TINYINT NOT NULL,
FindingsGroup VARCHAR(50) NOT NULL,
Finding VARCHAR(200) NOT NULL,
URL VARCHAR(200) NOT NULL,
Details NVARCHAR(4000) NULL,
HowToStopIt [XML] NULL,
QueryPlan [XML] NULL,
QueryText NVARCHAR(MAX) NULL,
StartTime DATETIMEOFFSET NULL,
LoginName NVARCHAR(128) NULL,
NTUserName NVARCHAR(128) NULL,
OriginalLoginName NVARCHAR(128) NULL,
ProgramName NVARCHAR(128) NULL,
HostName NVARCHAR(128) NULL,
DatabaseID INT NULL,
DatabaseName NVARCHAR(128) NULL,
OpenTransactionCount INT NULL,
DetailsInt INT NULL,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
EXEC(@StringToExecute);
SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableName
+ ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + (CONVERT(NVARCHAR(100), @StartSampleTime, 127)) + ''', CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
BEGIN
SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
+ @OutputTableName
+ ''') IS NULL) CREATE TABLE '
+ @OutputTableName
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
CheckID INT NOT NULL,
Priority TINYINT NOT NULL,
FindingsGroup VARCHAR(50) NOT NULL,
Finding VARCHAR(200) NOT NULL,
URL VARCHAR(200) NOT NULL,
Details NVARCHAR(4000) NULL,
HowToStopIt [XML] NULL,
QueryPlan [XML] NULL,
QueryText NVARCHAR(MAX) NULL,
StartTime DATETIMEOFFSET NULL,
LoginName NVARCHAR(128) NULL,
NTUserName NVARCHAR(128) NULL,
OriginalLoginName NVARCHAR(128) NULL,
ProgramName NVARCHAR(128) NULL,
HostName NVARCHAR(128) NULL,
DatabaseID INT NULL,
DatabaseName NVARCHAR(128) NULL,
OpenTransactionCount INT NULL,
DetailsInt INT NULL,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
+ ' INSERT '
+ @OutputTableName
+ ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#')
BEGIN
RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0)
END


/* @OutputTableNameFileStats lets us export the results to a permanent table */
IF @OutputDatabaseName IS NOT NULL
AND @OutputSchemaName IS NOT NULL
AND @OutputTableNameFileStats IS NOT NULL
AND @OutputTableNameFileStats NOT LIKE '#%'
AND EXISTS ( SELECT *
FROM sys.databases
WHERE QUOTENAME([name]) = @OutputDatabaseName)
BEGIN
/* Create the table */
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName
+ ''') AND NOT EXISTS (SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
+ @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
+ @OutputTableNameFileStats + ''') CREATE TABLE '
+ @OutputSchemaName + '.'
+ @OutputTableNameFileStats
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
DatabaseID INT NOT NULL,
FileID INT NOT NULL,
DatabaseName NVARCHAR(256) ,
FileLogicalName NVARCHAR(256) ,
TypeDesc NVARCHAR(60) ,
SizeOnDiskMB BIGINT ,
io_stall_read_ms BIGINT ,
num_of_reads BIGINT ,
bytes_read BIGINT ,
io_stall_write_ms BIGINT ,
num_of_writes BIGINT ,
bytes_written BIGINT,
PhysicalName NVARCHAR(520) ,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
EXEC(@StringToExecute);




/* Create the view */
SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View;
IF OBJECT_ID(@ObjectFullName) IS NULL
BEGIN
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; EXEC (''CREATE VIEW '
+ @OutputSchemaName + '.'
+ @OutputTableNameFileStats_View + ' AS ' + @LineFeed
+ 'SELECT f.ServerName, f.CheckDate, f.DatabaseID, f.DatabaseName, f.FileID, f.FileLogicalName, f.TypeDesc, f.PhysicalName, f.SizeOnDiskMB' + @LineFeed
+ ', DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds' + @LineFeed
+ ', (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth' + @LineFeed
+ ', (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms' + @LineFeed
+ ', io_stall_read_ms_average = CASE WHEN (f.num_of_reads - fPrior.num_of_reads) = 0 THEN 0 ELSE (f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads) END' + @LineFeed
+ ', (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads' + @LineFeed
+ ', (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read' + @LineFeed
+ ', (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms' + @LineFeed
+ ', io_stall_write_ms_average = CASE WHEN (f.num_of_writes - fPrior.num_of_writes) = 0 THEN 0 ELSE (f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes) END' + @LineFeed
+ ', (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes' + @LineFeed
+ ', (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written' + @LineFeed
+ 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed
+ 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName AND f.DatabaseID = fPrior.DatabaseID AND f.FileID = fPrior.FileID AND f.CheckDate > fPrior.CheckDate' + @LineFeed
+ 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fMiddle ON f.ServerName = fMiddle.ServerName AND f.DatabaseID = fMiddle.DatabaseID AND f.FileID = fMiddle.FileID AND f.CheckDate > fMiddle.CheckDate AND fMiddle.CheckDate > fPrior.CheckDate' + @LineFeed
+ 'WHERE fMiddle.ID IS NULL;'')'
EXEC(@StringToExecute);
END




SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableNameFileStats
+ ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', '
+ 'DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##')
BEGIN
SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
+ @OutputTableNameFileStats
+ ''') IS NULL) CREATE TABLE '
+ @OutputTableNameFileStats
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
DatabaseID INT NOT NULL,
FileID INT NOT NULL,
DatabaseName NVARCHAR(256) ,
FileLogicalName NVARCHAR(256) ,
TypeDesc NVARCHAR(60) ,
SizeOnDiskMB BIGINT ,
io_stall_read_ms BIGINT ,
num_of_reads BIGINT ,
bytes_read BIGINT ,
io_stall_write_ms BIGINT ,
num_of_writes BIGINT ,
bytes_written BIGINT,
PhysicalName NVARCHAR(520) ,
DetailsInt INT NULL,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
+ ' INSERT '
+ @OutputTableNameFileStats
+ ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', '
+ 'DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#')
BEGIN
RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0)
END








/* @OutputTableNamePerfmonStats lets us export the results to a permanent table */
IF @OutputDatabaseName IS NOT NULL
AND @OutputSchemaName IS NOT NULL
AND @OutputTableNamePerfmonStats IS NOT NULL
AND @OutputTableNamePerfmonStats NOT LIKE '#%'
AND EXISTS ( SELECT *
FROM sys.databases
WHERE QUOTENAME([name]) = @OutputDatabaseName)
BEGIN
/* Create the table */
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName
+ ''') AND NOT EXISTS (SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
+ @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
+ @OutputTableNamePerfmonStats + ''') CREATE TABLE '
+ @OutputSchemaName + '.'
+ @OutputTableNamePerfmonStats
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
[object_name] NVARCHAR(128) NOT NULL,
[counter_name] NVARCHAR(128) NOT NULL,
[instance_name] NVARCHAR(128) NULL,
[cntr_value] BIGINT NULL,
[cntr_type] INT NOT NULL,
[value_delta] BIGINT NULL,
[value_per_second] DECIMAL(18,2) NULL,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
EXEC(@StringToExecute);




/* Create the view */
SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View;
IF OBJECT_ID(@ObjectFullName) IS NULL
BEGIN
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; EXEC (''CREATE VIEW '
+ @OutputSchemaName + '.'
+ @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed
+ 'SELECT p.ServerName, p.CheckDate, p.object_name, p.counter_name, p.instance_name' + @LineFeed
+ ', DATEDIFF(ss, pPrior.CheckDate, p.CheckDate) AS ElapsedSeconds' + @LineFeed
+ ', p.cntr_value' + @LineFeed
+ ', p.cntr_type' + @LineFeed
+ ', (p.cntr_value - pPrior.cntr_value) AS cntr_delta' + @LineFeed
+ 'FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats + ' p' + @LineFeed
+ 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats + ' pPrior ON p.ServerName = pPrior.ServerName AND p.object_name = pPrior.object_name AND p.counter_name = pPrior.counter_name AND p.instance_name = pPrior.instance_name AND p.CheckDate > pPrior.CheckDate' + @LineFeed
+ 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats + ' pMiddle ON p.ServerName = pMiddle.ServerName AND p.object_name = pMiddle.object_name AND p.counter_name = pMiddle.counter_name AND p.instance_name = pMiddle.instance_name AND p.CheckDate > pMiddle.CheckDate AND pMiddle.CheckDate > pPrior.CheckDate' + @LineFeed
+ 'WHERE pMiddle.ID IS NULL;'')'
EXEC(@StringToExecute);
END;




SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableNamePerfmonStats
+ ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', '
+ 'object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2';
EXEC(@StringToExecute);




END
ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##')
BEGIN
SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
+ @OutputTableNamePerfmonStats
+ ''') IS NULL) CREATE TABLE '
+ @OutputTableNamePerfmonStats
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
[object_name] NVARCHAR(128) NOT NULL,
[counter_name] NVARCHAR(128) NOT NULL,
[instance_name] NVARCHAR(128) NULL,
[cntr_value] BIGINT NULL,
[cntr_type] INT NOT NULL,
[value_delta] BIGINT NULL,
[value_per_second] DECIMAL(18,2) NULL,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
+ ' INSERT '
+ @OutputTableNamePerfmonStats
+ ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', '
+ 'object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#')
BEGIN
RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0)
END








/* @OutputTableNameWaitStats lets us export the results to a permanent table */
IF @OutputDatabaseName IS NOT NULL
AND @OutputSchemaName IS NOT NULL
AND @OutputTableNameWaitStats IS NOT NULL
AND @OutputTableNameWaitStats NOT LIKE '#%'
AND EXISTS ( SELECT *
FROM sys.databases
WHERE QUOTENAME([name]) = @OutputDatabaseName)
BEGIN
/* Create the table */
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName
+ ''') AND NOT EXISTS (SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
+ @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
+ @OutputTableNameWaitStats + ''') CREATE TABLE '
+ @OutputSchemaName + '.'
+ @OutputTableNameWaitStats
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
wait_type NVARCHAR(60),
wait_time_ms BIGINT,
signal_wait_time_ms BIGINT,
waiting_tasks_count BIGINT ,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
EXEC(@StringToExecute);




/* Create the view */
SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View;
IF OBJECT_ID(@ObjectFullName) IS NULL
BEGIN
SET @StringToExecute = 'USE '
+ @OutputDatabaseName
+ '; EXEC (''CREATE VIEW '
+ @OutputSchemaName + '.'
+ @OutputTableNameWaitStats_View + ' AS ' + @LineFeed
+ 'SELECT w.ServerName, w.CheckDate, w.wait_type' + @LineFeed
+ ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed
+ ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed
+ ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed
+ ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed
+ 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed
+ 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND w.CheckDate > wPrior.CheckDate' + @LineFeed
+ 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wMiddle ON w.ServerName = wMiddle.ServerName AND w.wait_type = wMiddle.wait_type AND w.CheckDate > wMiddle.CheckDate AND wMiddle.CheckDate > wPrior.CheckDate' + @LineFeed
+ 'WHERE wMiddle.ID IS NULL;'')'
EXEC(@StringToExecute);
END








SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableNameWaitStats
+ ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', '
+ 'wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##')
BEGIN
SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
+ @OutputTableNameWaitStats
+ ''') IS NULL) CREATE TABLE '
+ @OutputTableNameWaitStats
+ ' (ID INT IDENTITY(1,1) NOT NULL,
ServerName NVARCHAR(128),
CheckDate DATETIMEOFFSET,
wait_type NVARCHAR(60),
wait_time_ms BIGINT,
signal_wait_time_ms BIGINT,
waiting_tasks_count BIGINT ,
CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
+ ' INSERT '
+ @OutputTableNameWaitStats
+ ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT '''
+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', ''' + CONVERT(NVARCHAR(100), @StartSampleTime, 127) + ''', '
+ 'wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0';
EXEC(@StringToExecute);
END
ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#')
BEGIN
RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0)
END


DECLARE @separator AS VARCHAR(1);
IF @OutputType = 'RSV'
SET @separator = CHAR(31);
ELSE
SET @separator = ',';




IF @OutputType = 'COUNT' AND @SinceStartup = 0
BEGIN
SELECT COUNT(*) AS Warnings
FROM #BlitzFirstResults
END
ELSE
IF @OutputType = 'Opserver1' AND @SinceStartup = 0
BEGIN


SELECT r.[Priority] ,
r.[FindingsGroup] ,
r.[Finding] ,
r.[URL] ,
r.[Details],
r.[HowToStopIt] ,
r.[CheckID] ,
r.[StartTime],
r.[LoginName],
r.[NTUserName],
r.[OriginalLoginName],
r.[ProgramName],
r.[HostName],
r.[DatabaseID],
r.[DatabaseName],
r.[OpenTransactionCount],
r.[QueryPlan],
r.[QueryText],
qsNow.plan_handle AS PlanHandle,
qsNow.sql_handle AS SqlHandle,
qsNow.statement_start_offset AS StatementStartOffset,
qsNow.statement_end_offset AS StatementEndOffset,
[Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)),
[ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)),
[Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)),
[DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)),
[CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)),
[CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)),
[Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)),
[ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)),
[PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121),
[TotalExecutions] = qsNow.execution_count,
[TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)),
[TotalDuration] = qsNow.total_elapsed_time,
[TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)),
[TotalCPU] = qsNow.total_worker_time,
[TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)),
[TotalReads] = qsNow.total_logical_reads,
[TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)),
r.[DetailsInt]
FROM #BlitzFirstResults r
LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0
LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1
LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID
LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID
ORDER BY r.Priority ,
r.FindingsGroup ,
CASE
WHEN r.CheckID = 6 THEN DetailsInt
ELSE 0
END DESC,
r.Finding,
r.ID;
END
ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0
BEGIN




SELECT Result = CAST([Priority] AS NVARCHAR(100))
+ @separator + CAST(CheckID AS NVARCHAR(100))
+ @separator + COALESCE([FindingsGroup],
'(N/A)') + @separator
+ COALESCE([Finding], '(N/A)') + @separator
+ COALESCE(DatabaseName, '(N/A)') + @separator
+ COALESCE([URL], '(N/A)') + @separator
+ COALESCE([Details], '(N/A)')
FROM #BlitzFirstResults
ORDER BY Priority ,
FindingsGroup ,
CASE
WHEN CheckID = 6 THEN DetailsInt
ELSE 0
END DESC,
Finding,
Details;
END
ELSE IF @ExpertMode = 0 AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0
BEGIN
SELECT [Priority] ,
[FindingsGroup] ,
[Finding] ,
[URL] ,
CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details,
CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt,
[QueryText],
[QueryPlan]
FROM #BlitzFirstResults
WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */
ORDER BY Priority ,
FindingsGroup ,
CASE
WHEN CheckID = 6 THEN DetailsInt
ELSE 0
END DESC,
Finding,
ID;
END
ELSE IF @ExpertMode = 0 AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0
BEGIN
SELECT [Priority] ,
[FindingsGroup] ,
[Finding] ,
[URL] ,
CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details,
CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt,
CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText,
CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan
FROM #BlitzFirstResults
WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */
ORDER BY Priority ,
FindingsGroup ,
CASE
WHEN CheckID = 6 THEN DetailsInt
ELSE 0
END DESC,
Finding,
ID;
END
ELSE IF @ExpertMode = 1
BEGIN
IF @SinceStartup = 0
SELECT r.[Priority] ,
r.[FindingsGroup] ,
r.[Finding] ,
r.[URL] ,
CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details,
CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt,
r.[CheckID] ,
r.[StartTime],
r.[LoginName],
r.[NTUserName],
r.[OriginalLoginName],
r.[ProgramName],
r.[HostName],
r.[DatabaseID],
r.[DatabaseName],
r.[OpenTransactionCount],
r.[QueryPlan],
r.[QueryText],
qsNow.plan_handle AS PlanHandle,
qsNow.sql_handle AS SqlHandle,
qsNow.statement_start_offset AS StatementStartOffset,
qsNow.statement_end_offset AS StatementEndOffset,
[Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)),
[ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)),
[Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)),
[DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)),
[CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)),
[CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)),
[Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)),
[ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)),
[PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121),
[TotalExecutions] = qsNow.execution_count,
[TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)),
[TotalDuration] = qsNow.total_elapsed_time,
[TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)),
[TotalCPU] = qsNow.total_worker_time,
[TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)),
[TotalReads] = qsNow.total_logical_reads,
[TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)),
r.[DetailsInt]
FROM #BlitzFirstResults r
LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0
LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1
LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID
LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID
WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */
ORDER BY r.Priority ,
r.FindingsGroup ,
CASE
WHEN r.CheckID = 6 THEN DetailsInt
ELSE 0
END DESC,
r.Finding,
r.ID;
-------------------------
--What happened: #WaitStats
-------------------------
IF @Seconds = 0
BEGIN
/* Measure waits in hours */
;WITH max_batch AS (
SELECT MAX(SampleTime) AS SampleTime
FROM #WaitStats
)
SELECT
'WAIT STATS' AS Pattern,
b.SampleTime AS [Sample Ended],
CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample],
wd1.wait_type,
CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)],
CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / 60 / 60 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour],
CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)],
CASE WHEN c.[Wait Time (Seconds)] > 0
THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1))
ELSE 0 END AS [Percent Signal Waits],
(wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits],
CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0
THEN
CAST((wd2.wait_time_ms-wd1.wait_time_ms)/
(1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1))
ELSE 0 END AS [Avg ms Per Wait],
N'brentozar.com/sql/wait-stats/#' + wd1.wait_type AS URL
FROM max_batch b
JOIN #WaitStats wd2 ON
wd2.SampleTime =b.SampleTime
JOIN #WaitStats wd1 ON
wd1.wait_type=wd2.wait_type AND
wd2.SampleTime > wd1.SampleTime
CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores
CROSS APPLY (SELECT
CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)],
CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c
WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0
AND wd2.wait_time_ms-wd1.wait_time_ms > 0
ORDER BY [Wait Time (Seconds)] DESC;
END
ELSE
BEGIN
/* Measure waits in seconds */
;WITH max_batch AS (
SELECT MAX(SampleTime) AS SampleTime
FROM #WaitStats
)
SELECT
'WAIT STATS' AS Pattern,
b.SampleTime AS [Sample Ended],
DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample],
wd1.wait_type,
c.[Wait Time (Seconds)],
CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second],
c.[Signal Wait Time (Seconds)],
CASE WHEN c.[Wait Time (Seconds)] > 0
THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1))
ELSE 0 END AS [Percent Signal Waits],
(wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits],
CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0
THEN
CAST((wd2.wait_time_ms-wd1.wait_time_ms)/
(1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1))
ELSE 0 END AS [Avg ms Per Wait],
N'brentozar.com/sql/wait-stats/#' + wd1.wait_type AS URL
FROM max_batch b
JOIN #WaitStats wd2 ON
wd2.SampleTime =b.SampleTime
JOIN #WaitStats wd1 ON
wd1.wait_type=wd2.wait_type AND
wd2.SampleTime > wd1.SampleTime
CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores
CROSS APPLY (SELECT
CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)],
CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c
WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0
AND wd2.wait_time_ms-wd1.wait_time_ms > 0
ORDER BY [Wait Time (Seconds)] DESC;
END;
-------------------------
--What happened: #FileStats
-------------------------
WITH readstats AS (
SELECT 'PHYSICAL READS' AS Pattern,
ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank,
wd2.SampleTime AS [Sample Time],
DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)],
wd1.DatabaseName ,
wd1.FileLogicalName AS [File Name],
UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] ,
wd1.SizeOnDiskMB ,
( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes],
CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0
THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1))
ELSE 0
END AS [MB Read/Written],
wd2.avg_stall_read_ms AS [Avg Stall (ms)],
wd1.PhysicalName AS [file physical name]
FROM #FileStats wd2
JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime
AND wd1.DatabaseID = wd2.DatabaseID
AND wd1.FileID = wd2.FileID
),
writestats AS (
SELECT
'PHYSICAL WRITES' AS Pattern,
ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank,
wd2.SampleTime AS [Sample Time],
DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)],
wd1.DatabaseName ,
wd1.FileLogicalName AS [File Name],
UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] ,
wd1.SizeOnDiskMB ,
( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes],
CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0
THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1))
ELSE 0
END AS [MB Read/Written],
wd2.avg_stall_write_ms AS [Avg Stall (ms)],
wd1.PhysicalName AS [file physical name]
FROM #FileStats wd2
JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime
AND wd1.DatabaseID = wd2.DatabaseID
AND wd1.FileID = wd2.FileID
)
SELECT
Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name]
FROM readstats
WHERE StallRank <=5 AND [MB Read/Written] > 0
UNION ALL
SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name]
FROM writestats
WHERE StallRank <=5 AND [MB Read/Written] > 0;


-------------------------
--What happened: #PerfmonStats
-------------------------


SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name,
pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue,
pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue,
pLast.cntr_value - pFirst.cntr_value AS ValueDelta,
((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond
FROM #PerfmonStats pLast
INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL))
AND pLast.ID > pFirst.ID
WHERE (pLast.cntr_value - pFirst.cntr_value) > 0
ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name
-------------------------
--What happened: #QueryStats
-------------------------
IF @CheckProcedureCache = 1
BEGIN


SELECT qsNow.*, qsFirst.*
FROM #QueryStats qsNow
INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1
WHERE qsNow.Pass = 2
END
ELSE
BEGIN
SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]
END
END


DROP TABLE #BlitzFirstResults;


/* What's running right now? This is the first and last result set. */
IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1
BEGIN
IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NOT NULL
BEGIN
EXEC [dbo].[sp_BlitzWho]
END
END /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 - What's running right now? This is the first and last result set. */


END /* IF @Question IS NULL */
ELSE IF @Question IS NOT NULL


/* We're playing Magic SQL 8 Ball, so give them an answer. */
BEGIN
IF OBJECT_ID('tempdb..#BlitzFirstAnswers') IS NOT NULL
DROP TABLE #BlitzFirstAnswers;
CREATE TABLE #BlitzFirstAnswers(Answer VARCHAR(200) NOT NULL);
INSERT INTO #BlitzFirstAnswers VALUES ('It sounds like a SAN problem.');
INSERT INTO #BlitzFirstAnswers VALUES ('You know what you need? Bacon.');
INSERT INTO #BlitzFirstAnswers VALUES ('Talk to the developers about that.');
INSERT INTO #BlitzFirstAnswers VALUES ('Let''s post that on StackOverflow.com and find out.');
INSERT INTO #BlitzFirstAnswers VALUES ('Have you tried adding an index?');
INSERT INTO #BlitzFirstAnswers VALUES ('Have you tried dropping an index?');
INSERT INTO #BlitzFirstAnswers VALUES ('You can''t prove anything.');
INSERT INTO #BlitzFirstAnswers VALUES ('Please phrase the question in the form of an answer.');
INSERT INTO #BlitzFirstAnswers VALUES ('Outlook not so good. Access even worse.');
INSERT INTO #BlitzFirstAnswers VALUES ('Did you try asking the rubber duck? codinghorror.com/blog/2012/03/rubber-duck-problem-solving.html');
INSERT INTO #BlitzFirstAnswers VALUES ('Oooo, I read about that once.');
INSERT INTO #BlitzFirstAnswers VALUES ('I feel your pain.');
INSERT INTO #BlitzFirstAnswers VALUES ('LMGTFY.com');
INSERT INTO #BlitzFirstAnswers VALUES ('No comprende Ingles, senor.');
INSERT INTO #BlitzFirstAnswers VALUES ('I don''t have that problem on my Mac.');
INSERT INTO #BlitzFirstAnswers VALUES ('Is Priority Boost on?');
INSERT INTO #BlitzFirstAnswers VALUES ('Have you tried rebooting your machine?');
INSERT INTO #BlitzFirstAnswers VALUES ('Try defragging your cursors.');
INSERT INTO #BlitzFirstAnswers VALUES ('Why are you wearing that? Do you have a job interview later or something?');
INSERT INTO #BlitzFirstAnswers VALUES ('I''m ashamed that you don''t know the answer to that question.');
INSERT INTO #BlitzFirstAnswers VALUES ('Duh, Debra.');
INSERT INTO #BlitzFirstAnswers VALUES ('Have you tried restoring TempDB?');
SELECT TOP 1 Answer FROM #BlitzFirstAnswers ORDER BY NEWID();
END


END /* ELSE IF @OutputType = 'SCHEMA' */


SET NOCOUNT OFF;
GO


sp_BlitzCache – Which queries should be tuned?
This looks at SQL Servers query plan cache to determine which queries are having the biggest impact on the database over time






SET ANSI_NULLS ON
GO




SET QUOTED_IDENTIFIER ON
GO




CREATE PROCEDURE [dbo].[sp_BlitzCache]
@Help BIT = 0,
@Top INT = NULL,
@SortOrder VARCHAR(50) = 'CPU',
@UseTriggersAnyway BIT = NULL,
@ExportToExcel BIT = 0,
@ExpertMode TINYINT = 0,
@OutputServerName NVARCHAR(256) = NULL ,
@OutputDatabaseName NVARCHAR(256) = NULL ,
@OutputSchemaName NVARCHAR(256) = NULL ,
@OutputTableName NVARCHAR(256) = NULL ,
@ConfigurationDatabaseName NVARCHAR(128) = NULL ,
@ConfigurationSchemaName NVARCHAR(256) = NULL ,
@ConfigurationTableName NVARCHAR(256) = NULL ,
@DurationFilter DECIMAL(38,4) = NULL ,
@HideSummary BIT = 0 ,
@IgnoreSystemDBs BIT = 1 ,
@OnlyQueryHashes VARCHAR(MAX) = NULL ,
@IgnoreQueryHashes VARCHAR(MAX) = NULL ,
@OnlySqlHandles VARCHAR(MAX) = NULL ,
@IgnoreSqlHandles VARCHAR(MAX) = NULL ,
@QueryFilter VARCHAR(10) = 'ALL' ,
@DatabaseName NVARCHAR(128) = NULL ,
@StoredProcName NVARCHAR(128) = NULL,
@Reanalyze BIT = 0 ,
@SkipAnalysis BIT = 0 ,
@BringThePain BIT = 0, /* This will forcibly set @Top to 2,147,483,647 */
@MinimumExecutionCount INT = 0
WITH RECOMPILE
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;




DECLARE @Version VARCHAR(30);
DECLARE @VersionDate VARCHAR(30);
SET @Version = '4.3';
SET @VersionDate = '20170201';




IF @Help = 1 PRINT '
sp_BlitzCache from FirstResponderKit.org


This script displays your most resource-intensive queries from the plan cache,
and points to ways you can tune these queries to make them faster.








To learn more, visit FirstResponderKit.org where you can download new
versions for free, watch training videos on how it works, get more info on
the findings, contribute your own code, and more.




Known limitations of this version:
- This query will not run on SQL Server 2005.
- SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is
excluded by default.
- @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes
with no spaces between the hash values.
- @OutputServerName is not functional yet.




Unknown limitations of this version:
- May or may not be vulnerable to the wick effect.




Changes - for the full list of improvements and fixes in this version, see:
github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit












MIT License




Copyright (c) 2016 Brent Ozar Unlimited




Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:




The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.




THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'




DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ;




IF @Help = 1
BEGIN
SELECT N'@Help' AS [Parameter Name] ,
N'BIT' AS [Data Type] ,
N'Displays this help message.' AS [Parameter Description]




UNION ALL
SELECT N'@Top',
N'INT',
N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.'




UNION ALL
SELECT N'@SortOrder',
N'VARCHAR(10)',
N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.'




UNION ALL
SELECT N'@UseTriggersAnyway',
N'BIT',
N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.'




UNION ALL
SELECT N'@ExportToExcel',
N'BIT',
N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.'




UNION ALL
SELECT N'@ExpertMode',
N'TINYINT',
N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.'




UNION ALL
SELECT N'@OutputDatabaseName',
N'NVARCHAR(128)',
N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.'




UNION ALL
SELECT N'@OutputSchemaName',
N'NVARCHAR(256)',
N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.'




UNION ALL
SELECT N'@OutputTableName',
N'NVARCHAR(256)',
N'The output table. If this does not exist, it will be created for you.'




UNION ALL
SELECT N'@DurationFilter',
N'DECIMAL(38,4)',
N'Excludes queries with an average duration (in seconds) less than @DurationFilter.'




UNION ALL
SELECT N'@HideSummary',
N'BIT',
N'Hides the findings summary result set.'




UNION ALL
SELECT N'@IgnoreSystemDBs',
N'BIT',
N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)'




UNION ALL
SELECT N'@OnlyQueryHashes',
N'VARCHAR(MAX)',
N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.'




UNION ALL
SELECT N'@IgnoreQueryHashes',
N'VARCHAR(MAX)',
N'A list of query hashes to ignore.'

UNION ALL
SELECT N'@OnlySqlHandles',
N'VARCHAR(MAX)',
N'One or more sql_handles to use for filtering results.'




UNION ALL
SELECT N'@DatabaseName',
N'NVARCHAR(128)',
N'A database name which is used for filtering results.'




UNION ALL
SELECT N'@BringThePain',
N'BIT',
N'This forces sp_BlitzCache to examine the entire plan cache. Be careful running this on servers with a lot of memory or a large execution plan cache.'




UNION ALL
SELECT N'@QueryFilter',
N'VARCHAR(10)',
N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', or ''all'' (any variation in capitalization is acceptable).'




UNION ALL
SELECT N'@Reanalyze',
N'BIT',
N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results'

UNION ALL
SELECT N'@MinimumExecutionCount',
N'INT',
N'Queries with fewer than this number of executions will be omitted from results.';




/* Column definitions */
SELECT N'# Executions' AS [Column Name],
N'BIGINT' AS [Data Type],
N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description]




UNION ALL
SELECT N'Executions / Minute',
N'MONEY',
N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.'




UNION ALL
SELECT N'Execution Weight',
N'MONEY',
N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.'




UNION ALL
SELECT N'Database',
N'sysname',
N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.'




UNION ALL
SELECT N'Total CPU',
N'BIGINT',
N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.'




UNION ALL
SELECT N'Avg CPU',
N'BIGINT',
N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.'




UNION ALL
SELECT N'CPU Weight',
N'MONEY',
N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.'








UNION ALL
SELECT N'Total Duration',
N'BIGINT',
N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.'




UNION ALL
SELECT N'Avg Duration',
N'BIGINT',
N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.'




UNION ALL
SELECT N'Duration Weight',
N'MONEY',
N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.'




UNION ALL
SELECT N'Total Reads',
N'BIGINT',
N'Total logical reads performed by this query since last compilation.'




UNION ALL
SELECT N'Average Reads',
N'BIGINT',
N'Average logical reads performed by each execution of this query since the last compilation.'




UNION ALL
SELECT N'Read Weight',
N'MONEY',
N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.'




UNION ALL
SELECT N'Total Writes',
N'BIGINT',
N'Total logical writes performed by this query since last compilation.'




UNION ALL
SELECT N'Average Writes',
N'BIGINT',
N'Average logical writes performed by each execution this query since last compilation.'




UNION ALL
SELECT N'Write Weight',
N'MONEY',
N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.'




UNION ALL
SELECT N'Query Type',
N'NVARCHAR(256)',
N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".'




UNION ALL
SELECT N'Query Text',
N'NVARCHAR(4000)',
N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.'




UNION ALL
SELECT N'% Executions (Type)',
N'MONEY',
N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.'




UNION ALL
SELECT N'% CPU (Type)',
N'MONEY',
N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.'




UNION ALL
SELECT N'% Duration (Type)',
N'MONEY',
N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.'




UNION ALL
SELECT N'% Reads (Type)',
N'MONEY',
N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.'




UNION ALL
SELECT N'% Writes (Type)',
N'MONEY',
N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.'




UNION ALL
SELECT N'Total Rows',
N'BIGINT',
N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.'




UNION ALL
SELECT N'Average Rows',
N'MONEY',
N'Average number of rows returned by each execution of the query.'




UNION ALL
SELECT N'Min Rows',
N'BIGINT',
N'The minimum number of rows returned by any execution of this query.'




UNION ALL
SELECT N'Max Rows',
N'BIGINT',
N'The maximum number of rows returned by any execution of this query.'




UNION ALL
SELECT N'MinGrantKB',
N'BIGINT',
N'The minimum memory grant the query received in kb.'




UNION ALL
SELECT N'MaxGrantKB',
N'BIGINT',
N'The maximum memory grant the query received in kb.'




UNION ALL
SELECT N'MinUsedGrantKB',
N'BIGINT',
N'The minimum used memory grant the query received in kb.'




UNION ALL
SELECT N'MaxUsedGrantKB',
N'BIGINT',
N'The maximum used memory grant the query received in kb.'




UNION ALL
SELECT N'PercentMemoryGrantUsed',
N'MONEY',
N'Result of dividing the maximum grant used by the minimum granted.'




UNION ALL
SELECT N'AvgMaxMemoryGrant',
N'MONEY',
N'The average maximum memory grant for a query.'




UNION ALL
SELECT N'# Plans',
N'INT',
N'The total number of execution plans found that match a given query.'




UNION ALL
SELECT N'# Distinct Plans',
N'INT',
N'The number of distinct execution plans that match a given query. '
+ NCHAR(13) + NCHAR(10)
+ N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.'




UNION ALL
SELECT N'Created At',
N'DATETIME',
N'Time that the execution plan was last compiled.'




UNION ALL
SELECT N'Last Execution',
N'DATETIME',
N'The last time that this query was executed.'




UNION ALL
SELECT N'Query Plan',
N'XML',
N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.'




UNION ALL
SELECT N'Plan Handle',
N'VARBINARY(64)',
N'An arbitrary identifier referring to the compiled plan this query is a part of.'




UNION ALL
SELECT N'SQL Handle',
N'VARBINARY(64)',
N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.'




UNION ALL
SELECT N'Query Hash',
N'BINARY(8)',
N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.'




UNION ALL
SELECT N'Warnings',
N'VARCHAR(MAX)',
N'A list of individual warnings generated by this query.' ;









/* Configuration table description */
SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] ,
N'100' AS [Default Value] ,
N'Executions / Minute' AS [Unit of Measure] ,
N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description]




UNION ALL
SELECT N'Parameter Sniffing Variance Percent' ,
N'30' ,
N'Percent' ,
N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.'




UNION ALL
SELECT N'Parameter Sniffing IO Threshold' ,
N'100,000' ,
N'Logical reads' ,
N'Minimum number of average logical reads before parameter sniffing checks are evaluated.'




UNION ALL
SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] ,
N'10' ,
N'Percent' ,
N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.'




UNION ALL
SELECT N'Long Running Query Warning' AS [Configuration Parameter] ,
N'300' ,
N'Seconds' ,
N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.'




UNION ALL
SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] ,
N'10' ,
N'Percent' ,
N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'
RETURN
END




/*Validate version*/
IF (
SELECT
CASE
WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) like '8%' THEN 0
WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) like '9%' THEN 0
ELSE 1
END
) = 0
BEGIN
DECLARE @version_msg VARCHAR(8000)
SELECT @version_msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933);
PRINT @version_msg;
RETURN;
END




/* Set @Top based on sort */
IF (
@Top IS NULL
AND LOWER(@SortOrder) IN ( 'all', 'all sort' )
)
BEGIN
SET @Top = 5;
END;




IF (
@Top IS NULL
AND LOWER(@SortOrder) NOT IN ( 'all', 'all sort' )
)
BEGIN
SET @Top = 10;
END;




/* validate user inputs */
IF @Top IS NULL
OR @SortOrder IS NULL
OR @QueryFilter IS NULL
OR @Reanalyze IS NULL
BEGIN
RAISERROR(N'Several parameters (@Top, @SortOrder, @QueryFilter, @renalyze) are required. Do not set them to NULL. Please try again.', 16, 1) WITH NOWAIT;
RETURN;
END




RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT;




IF OBJECT_ID('tempdb.dbo.##bou_BlitzCacheResults') IS NULL
BEGIN
CREATE TABLE ##bou_BlitzCacheResults (
SPID INT,
ID INT IDENTITY(1,1),
CheckID INT,
Priority TINYINT,
FindingsGroup VARCHAR(50),
Finding VARCHAR(200),
URL VARCHAR(200),
Details VARCHAR(4000)
);
END




IF OBJECT_ID('tempdb.dbo.##bou_BlitzCacheProcs') IS NULL
BEGIN
CREATE TABLE ##bou_BlitzCacheProcs (
SPID INT ,
QueryType NVARCHAR(256),
DatabaseName sysname,
AverageCPU DECIMAL(38,4),
AverageCPUPerMinute DECIMAL(38,4),
TotalCPU DECIMAL(38,4),
PercentCPUByType MONEY,
PercentCPU MONEY,
AverageDuration DECIMAL(38,4),
TotalDuration DECIMAL(38,4),
PercentDuration MONEY,
PercentDurationByType MONEY,
AverageReads BIGINT,
TotalReads BIGINT,
PercentReads MONEY,
PercentReadsByType MONEY,
ExecutionCount BIGINT,
PercentExecutions MONEY,
PercentExecutionsByType MONEY,
ExecutionsPerMinute MONEY,
TotalWrites BIGINT,
AverageWrites MONEY,
PercentWrites MONEY,
PercentWritesByType MONEY,
WritesPerMinute MONEY,
PlanCreationTime DATETIME,
PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()),
LastExecutionTime DATETIME,
PlanHandle VARBINARY(64),
[Remove Plan Handle From Cache] AS
CASE WHEN [PlanHandle] IS NOT NULL
THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');'
ELSE 'N/A' END,
SqlHandle VARBINARY(64),
[Remove SQL Handle From Cache] AS
CASE WHEN [SqlHandle] IS NOT NULL
THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');'
ELSE 'N/A' END,
[SQL Handle More Info] AS
CASE WHEN [SqlHandle] IS NOT NULL
THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; '
ELSE 'N/A' END,
QueryHash BINARY(8),
[Query Hash More Info] AS
CASE WHEN [QueryHash] IS NOT NULL
THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; '
ELSE 'N/A' END,
QueryPlanHash BINARY(8),
StatementStartOffset INT,
StatementEndOffset INT,
MinReturnedRows BIGINT,
MaxReturnedRows BIGINT,
AverageReturnedRows MONEY,
TotalReturnedRows BIGINT,
LastReturnedRows BIGINT,
MinGrantKB BIGINT,
MaxGrantKB BIGINT,
MinUsedGrantKB BIGINT,
MaxUsedGrantKB BIGINT,
PercentMemoryGrantUsed MONEY,
AvgMaxMemoryGrant MONEY,
QueryText NVARCHAR(MAX),
QueryPlan XML,
/* these next four columns are the total for the type of query.
don't actually use them for anything apart from math by type.
*/
TotalWorkerTimeForType BIGINT,
TotalElapsedTimeForType BIGINT,
TotalReadsForType BIGINT,
TotalExecutionCountForType BIGINT,
TotalWritesForType BIGINT,
NumberOfPlans INT,
NumberOfDistinctPlans INT,
SerialDesiredMemory FLOAT,
SerialRequiredMemory FLOAT,
CachedPlanSize FLOAT,
CompileTime FLOAT,
CompileCPU FLOAT ,
CompileMemory FLOAT ,
min_worker_time BIGINT,
max_worker_time BIGINT,
is_forced_plan BIT,
is_forced_parameterized BIT,
is_cursor BIT,
is_optimistic_cursor BIT,
is_forward_only_cursor BIT,
is_parallel BIT,
is_forced_serial BIT,
is_key_lookup_expensive BIT,
key_lookup_cost FLOAT,
is_remote_query_expensive BIT,
remote_query_cost FLOAT,
frequent_execution BIT,
parameter_sniffing BIT,
unparameterized_query BIT,
near_parallel BIT,
plan_warnings BIT,
plan_multiple_plans BIT,
long_running BIT,
downlevel_estimator BIT,
implicit_conversions BIT,
busy_loops BIT,
tvf_join BIT,
tvf_estimate BIT,
compile_timeout BIT,
compile_memory_limit_exceeded BIT,
warning_no_join_predicate BIT,
QueryPlanCost FLOAT,
missing_index_count INT,
unmatched_index_count INT,
min_elapsed_time BIGINT,
max_elapsed_time BIGINT,
age_minutes MONEY,
age_minutes_lifetime MONEY,
is_trivial BIT,
trace_flags_session VARCHAR(1000),
is_unused_grant BIT,
function_count INT,
clr_function_count INT,
is_table_variable BIT,
no_stats_warning BIT,
relop_warnings BIT,
is_table_scan BIT,
backwards_scan BIT,
forced_index BIT,
forced_seek BIT,
forced_scan BIT,
columnstore_row_mode BIT,
is_computed_scalar BIT ,
is_sort_expensive BIT,
sort_cost FLOAT,
is_computed_filter BIT,
op_name VARCHAR(100) NULL,
index_insert_count INT NULL,
index_update_count INT NULL,
index_delete_count INT NULL,
cx_insert_count INT NULL,
cx_update_count INT NULL,
cx_delete_count INT NULL,
table_insert_count INT NULL,
table_update_count INT NULL,
table_delete_count INT NULL,
index_ops AS (index_insert_count + index_update_count + index_delete_count +
cx_insert_count + cx_update_count + cx_delete_count +
table_insert_count + table_update_count + table_delete_count),
is_row_level BIT,
SetOptions VARCHAR(MAX),
Warnings VARCHAR(MAX)
);
END




DECLARE @DurationFilter_i INT,
@MinMemoryPerQuery INT,
@msg NVARCHAR(4000) ;








IF @BringThePain = 1
BEGIN
RAISERROR(N'You have chosen to bring the pain. Setting top to 2147483647.', 0, 1) WITH NOWAIT;
SET @Top = 2147483647;
END




/* Change duration from seconds to milliseconds */
IF @DurationFilter IS NOT NULL
BEGIN
RAISERROR(N'Converting Duration Filter to milliseconds', 0, 1) WITH NOWAIT;
SET @DurationFilter_i = CAST((@DurationFilter * 1000.0) AS INT)
END




RAISERROR(N'Checking database validity', 0, 1) WITH NOWAIT;
SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)) ;
IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> ''
BEGIN
RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1);
RETURN;
END
IF (SELECT DATABASEPROPERTYEX(@DatabaseName, 'Status')) <> 'ONLINE'
BEGIN
RAISERROR('The database you specified is not readable. Please check the name and try again. Better yet, check your server.', 16, 1);
RETURN;
END




SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)';




SET @SortOrder = LOWER(@SortOrder);
SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', '');
SET @SortOrder = REPLACE(@SortOrder, 'executions per minute', 'avg executions');
SET @SortOrder = REPLACE(@SortOrder, 'executions / minute', 'avg executions');
SET @SortOrder = REPLACE(@SortOrder, 'xpm', 'avg executions');
SET @SortOrder = REPLACE(@SortOrder, 'recent compilations', 'compiles');




RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT;
IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes',
'duration', 'avg duration', 'executions', 'avg executions',
'compiles', 'memory grant', 'avg memory grant',
'all', 'all avg')
BEGIN
RAISERROR(N'Invalid sort order chosen, reverting to cpu', 0, 1) WITH NOWAIT;
SET @SortOrder = 'cpu';
END




SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
@OutputSchemaName = QUOTENAME(@OutputSchemaName),
@OutputTableName = QUOTENAME(@OutputTableName);




SET @QueryFilter = LOWER(@QueryFilter);




IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro')
BEGIN
RAISERROR(N'Invalid query filter chosen. Reverting to all.', 0, 1) WITH NOWAIT;
SET @QueryFilter = 'all';
END




IF @SkipAnalysis = 1
BEGIN
RAISERROR(N'Skip Analysis set to 1, hiding Summary', 0, 1) WITH NOWAIT;
SET @HideSummary = 1;
END




IF @Reanalyze = 1 AND OBJECT_ID('tempdb..##bou_BlitzCacheResults') IS NULL
BEGIN
RAISERROR(N'##bou_BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT;
SET @Reanalyze = 0;
END




IF @Reanalyze = 0
BEGIN
RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT;
DELETE ##bou_BlitzCacheResults
WHERE SPID = @@SPID;
RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT;
DELETE ##bou_BlitzCacheProcs
WHERE SPID = @@SPID;
END




IF @Reanalyze = 1
BEGIN
RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT;
GOTO Results
END




IF @SortOrder IN ('all', 'all avg')
BEGIN
RAISERROR(N'Checking all sort orders, please be patient', 0, 1) WITH NOWAIT;
GOTO AllSorts
END








RAISERROR(N'Creating temp tables for internal processing', 0, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#only_query_hashes') IS NOT NULL
DROP TABLE #only_query_hashes ;




IF OBJECT_ID('tempdb..#ignore_query_hashes') IS NOT NULL
DROP TABLE #ignore_query_hashes ;




IF OBJECT_ID('tempdb..#only_sql_handles') IS NOT NULL
DROP TABLE #only_sql_handles ;




IF OBJECT_ID('tempdb..#ignore_sql_handles') IS NOT NULL
DROP TABLE #ignore_sql_handles ;

IF OBJECT_ID('tempdb..#p') IS NOT NULL
DROP TABLE #p;




IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL
DROP TABLE #checkversion;




IF OBJECT_ID ('tempdb..#configuration') IS NOT NULL
DROP TABLE #configuration;




CREATE TABLE #only_query_hashes (
query_hash BINARY(8)
);




CREATE TABLE #ignore_query_hashes (
query_hash BINARY(8)
);




CREATE TABLE #only_sql_handles (
sql_handle VARBINARY(64)
);




CREATE TABLE #ignore_sql_handles (
sql_handle VARBINARY(64)
);




CREATE TABLE #p (
SqlHandle VARBINARY(64),
TotalCPU BIGINT,
TotalDuration BIGINT,
TotalReads BIGINT,
TotalWrites BIGINT,
ExecutionCount BIGINT
);




CREATE TABLE #checkversion (
version NVARCHAR(128),
common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ),
major AS PARSENAME(CONVERT(VARCHAR(32), version), 4),
minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3),
build AS PARSENAME(CONVERT(VARCHAR(32), version), 2),
revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1)
);




CREATE TABLE #configuration (
parameter_name VARCHAR(100),
value DECIMAL(38,0)
);




RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT;
WITH x AS (
SELECT SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) < 24 THEN 1 ELSE 0 END) AS [plans_24],
SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) < 4 THEN 1 ELSE 0 END) AS [plans_4],
COUNT(deqs.creation_time) AS [total_plans]
FROM sys.dm_exec_query_stats AS deqs
)
SELECT CONVERT(DECIMAL(3,2), x.plans_24 / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24],
CONVERT(DECIMAL(3,2), x.plans_4 / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4],
@@SPID AS SPID
INTO #plan_creation
FROM x








RAISERROR(N'Checking plan stub count', 0, 1) WITH NOWAIT;
SELECT CONVERT(DECIMAL(9, 2), ( CAST(COUNT(*) AS DECIMAL(9, 2)) / ( SELECT COUNT (*) FROM sys.dm_exec_cached_plans ) )) AS plan_stubs_percent,
COUNT(*) AS total_plan_stubs,
( SELECT COUNT (*) FROM sys.dm_exec_cached_plans ) AS total_plans,
ISNULL(AVG(DATEDIFF(HOUR, qs.creation_time, GETDATE())), 0) AS avg_plan_age,
@@SPID AS SPID
INTO #plan_stubs_warning
FROM sys.dm_exec_cached_plans cp
LEFT JOIN sys.dm_exec_query_stats qs
ON cp.plan_handle = qs.plan_handle
WHERE cp.cacheobjtype = 'Compiled Plan Stub';








RAISERROR(N'Checking single use plan count', 0, 1) WITH NOWAIT;
SELECT CONVERT(DECIMAL(9, 2), ( CAST(COUNT(*) AS DECIMAL(9, 2)) / ( SELECT COUNT (*) FROM sys.dm_exec_cached_plans ) )) AS single_use_plans_percent,
COUNT(*) AS total_single_use_plans,
( SELECT COUNT (*) FROM sys.dm_exec_cached_plans ) AS total_plans,
ISNULL(AVG(DATEDIFF(HOUR, qs.creation_time, GETDATE())), 0) AS avg_plan_age,
@@SPID AS SPID
INTO #single_use_plans_warning
FROM sys.dm_exec_cached_plans cp
LEFT JOIN sys.dm_exec_query_stats qs
ON cp.plan_handle = qs.plan_handle
WHERE cp.usecounts = 1
AND cp.cacheobjtype = 'Compiled Plan';












SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ;
SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ;
SET @IgnoreQueryHashes = LTRIM(RTRIM(@IgnoreQueryHashes)) ;




DECLARE @individual VARCHAR(100) ;




IF (@OnlySqlHandles IS NOT NULL AND @IgnoreSqlHandles IS NOT NULL)
BEGIN
RAISERROR('You shouldn''t need to ignore and filter on SqlHandle at the same time.', 0, 1) WITH NOWAIT
RETURN;
END




IF (@StoredProcName IS NOT NULL AND (@OnlySqlHandles IS NOT NULL OR @IgnoreSqlHandles IS NOT NULL))
BEGIN
RAISERROR('You can''t filter on stored procedure name and SQL Handle.', 0, 1) WITH NOWAIT
RETURN;
END




IF @OnlySqlHandles IS NOT NULL
AND LEN(@OnlySqlHandles) > 0
BEGIN
RAISERROR(N'Processing SQL Handles', 0, 1) WITH NOWAIT;
SET @individual = '';




WHILE LEN(@OnlySqlHandles) > 0
BEGIN
IF PATINDEX('%,%', @OnlySqlHandles) > 0
BEGIN
SET @individual = SUBSTRING(@OnlySqlHandles, 0, PATINDEX('%,%',@OnlySqlHandles)) ;

INSERT INTO #only_sql_handles
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)

--SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8));




SET @OnlySqlHandles = SUBSTRING(@OnlySqlHandles, LEN(@individual + ',') + 1, LEN(@OnlySqlHandles)) ;
END
ELSE
BEGIN
SET @individual = @OnlySqlHandles
SET @OnlySqlHandles = NULL




INSERT INTO #only_sql_handles
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)




--SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ;
END
END
END




IF @IgnoreSqlHandles IS NOT NULL
AND LEN(@IgnoreSqlHandles) > 0
BEGIN
RAISERROR(N'Processing SQL Handles To Ignore', 0, 1) WITH NOWAIT;
SET @individual = '';




WHILE LEN(@IgnoreSqlHandles) > 0
BEGIN
IF PATINDEX('%,%', @IgnoreSqlHandles) > 0
BEGIN
SET @individual = SUBSTRING(@IgnoreSqlHandles, 0, PATINDEX('%,%',@IgnoreSqlHandles)) ;

INSERT INTO #ignore_sql_handles
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)

--SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8));




SET @IgnoreSqlHandles = SUBSTRING(@IgnoreSqlHandles, LEN(@individual + ',') + 1, LEN(@IgnoreSqlHandles)) ;
END
ELSE
BEGIN
SET @individual = @IgnoreSqlHandles
SET @IgnoreSqlHandles = NULL




INSERT INTO #ignore_sql_handles
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)




--SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ;
END
END
END




IF @StoredProcName IS NOT NULL AND @StoredProcName <> N''




BEGIN
RAISERROR(N'Setting up filter for stored procedure name', 0, 1) WITH NOWAIT;
INSERT #only_sql_handles
( sql_handle )
SELECT ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),''))
FROM sys.dm_exec_procedure_stats AS deps
WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName




END












IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0)
OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0))
AND LEFT(@QueryFilter, 3) = 'pro'
BEGIN
RAISERROR('You cannot limit by query hash and filter by stored procedure', 16, 1);
RETURN;
END




/* If the user is attempting to limit by query hash, set up the
#only_query_hashes temp table. This will be used to narrow down
results.




Just a reminder: Using @OnlyQueryHashes will ignore stored
procedures and triggers.
*/
IF @OnlyQueryHashes IS NOT NULL
AND LEN(@OnlyQueryHashes) > 0
BEGIN
RAISERROR(N'Setting up filter for Query Hashes', 0, 1) WITH NOWAIT;
SET @individual = '';




WHILE LEN(@OnlyQueryHashes) > 0
BEGIN
IF PATINDEX('%,%', @OnlyQueryHashes) > 0
BEGIN
SET @individual = SUBSTRING(@OnlyQueryHashes, 0, PATINDEX('%,%',@OnlyQueryHashes)) ;

INSERT INTO #only_query_hashes
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)

--SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8));




SET @OnlyQueryHashes = SUBSTRING(@OnlyQueryHashes, LEN(@individual + ',') + 1, LEN(@OnlyQueryHashes)) ;
END
ELSE
BEGIN
SET @individual = @OnlyQueryHashes
SET @OnlyQueryHashes = NULL




INSERT INTO #only_query_hashes
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)




--SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ;
END
END
END




/* If the user is setting up a list of query hashes to ignore, those
values will be inserted into #ignore_query_hashes. This is used to
exclude values from query results.




Just a reminder: Using @IgnoreQueryHashes will ignore stored
procedures and triggers.
*/
IF @IgnoreQueryHashes IS NOT NULL
AND LEN(@IgnoreQueryHashes) > 0
BEGIN
RAISERROR(N'Setting up filter to ignore query hashes', 0, 1) WITH NOWAIT;
SET @individual = '' ;




WHILE LEN(@IgnoreQueryHashes) > 0
BEGIN
IF PATINDEX('%,%', @IgnoreQueryHashes) > 0
BEGIN
SET @individual = SUBSTRING(@IgnoreQueryHashes, 0, PATINDEX('%,%',@IgnoreQueryHashes)) ;

INSERT INTO #ignore_query_hashes
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) ;

SET @IgnoreQueryHashes = SUBSTRING(@IgnoreQueryHashes, LEN(@individual + ',') + 1, LEN(@IgnoreQueryHashes)) ;
END
ELSE
BEGIN
SET @individual = @IgnoreQueryHashes ;
SET @IgnoreQueryHashes = NULL ;




INSERT INTO #ignore_query_hashes
SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) ;
END
END
END




IF @ConfigurationDatabaseName IS NOT NULL
BEGIN
RAISERROR(N'Reading values from Configuration Database', 0, 1) WITH NOWAIT;
DECLARE @config_sql NVARCHAR(MAX) = N'INSERT INTO #configuration SELECT parameter_name, value FROM '
+ QUOTENAME(@ConfigurationDatabaseName)
+ '.' + QUOTENAME(@ConfigurationSchemaName)
+ '.' + QUOTENAME(@ConfigurationTableName)
+ ' ; ' ;
EXEC(@config_sql);
END




RAISERROR(N'Setting up variables', 0, 1) WITH NOWAIT;
DECLARE @sql NVARCHAR(MAX) = N'',
@insert_list NVARCHAR(MAX) = N'',
@plans_triggers_select_list NVARCHAR(MAX) = N'',
@body NVARCHAR(MAX) = N'',
@body_where NVARCHAR(MAX) = N'WHERE 1 = 1 ' + @nl,
@body_order NVARCHAR(MAX) = N'ORDER BY #sortable# DESC OPTION (RECOMPILE) ',

@q NVARCHAR(1) = N'''',
@pv VARCHAR(20),
@pos TINYINT,
@v DECIMAL(6,2),
@build INT;








RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT;




INSERT INTO #checkversion (version)
SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128))
OPTION (RECOMPILE);








SELECT @v = common_version ,
@build = build
FROM #checkversion
OPTION (RECOMPILE);




IF (@SortOrder IN ('memory grant', 'avg memory grant'))
AND ((@v < 11)
OR (@v = 11 AND @build < 6020)
OR (@v = 12 AND @build < 5000)
OR (@v = 13 AND @build < 1601))
BEGIN
RAISERROR('Your version of SQL does not support sorting by memory grant or average memory grant. Please use another sort order.', 16, 1);
RETURN;
END




RAISERROR (N'Creating dynamic SQL based on SQL Server version.',0,1) WITH NOWAIT;




SET @insert_list += N'
INSERT INTO ##bou_BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType,
PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount,
ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime,
LastExecutionTime, StatementStartOffset, StatementEndOffset, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows,
LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant,
QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType,
TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash,
min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime) ' ;




SET @body += N'
FROM (SELECT TOP (@Top) x.*, xpa.*,
CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1
THEN DATEDIFF(mi, cached_time, GETDATE())
ELSE NULL END) as MONEY) as age_minutes,
CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1
THEN DATEDIFF(mi, cached_time, last_execution_time)
ELSE Null END) as MONEY) as age_minutes_lifetime
FROM sys.#view# x
CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa
WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ;




SET @body += N' WHERE 1 = 1 ' + @nl ;








IF @IgnoreSystemDBs = 1
BEGIN
RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT;
SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ;
END




IF @DatabaseName IS NOT NULL OR @DatabaseName <> ''
BEGIN
RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT;
SET @body += N' AND CAST(xpa.value AS BIGINT) = DB_ID('
+ QUOTENAME(@DatabaseName, N'''')
+ N') ' + @nl;
END




IF (SELECT COUNT(*) FROM #only_sql_handles) > 0
BEGIN
RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT;
SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ;
END




IF (SELECT COUNT(*) FROM #ignore_sql_handles) > 0
BEGIN
RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT;
SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ;
END




IF (SELECT COUNT(*) FROM #only_query_hashes) > 0
AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0
AND (SELECT COUNT(*) FROM #only_sql_handles) = 0
AND (SELECT COUNT(*) FROM #ignore_sql_handles) = 0
BEGIN
RAISERROR(N'Including only chosen Query Hashes', 0, 1) WITH NOWAIT;
SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_query_hashes q WHERE q.query_hash = x.query_hash) ' + @nl ;
END




/* filtering for query hashes */
IF (SELECT COUNT(*) FROM #ignore_query_hashes) > 0
AND (SELECT COUNT(*) FROM #only_query_hashes) = 0
BEGIN
RAISERROR(N'Excluding chosen Query Hashes', 0, 1) WITH NOWAIT;
SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_query_hashes iq WHERE iq.query_hash = x.query_hash) ' + @nl ;
END
/* end filtering for query hashes */








IF @DurationFilter IS NOT NULL
BEGIN
RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT;
SET @body += N' AND (total_elapsed_time / 1000.0) / execution_count > @min_duration ' + @nl ;
END








/* Apply the sort order here to only grab relevant plans.
This should make it faster to process since we'll be pulling back fewer
plans for processing.
*/
RAISERROR(N'Applying chosen sort order', 0, 1) WITH NOWAIT;
SELECT @body += N' ORDER BY ' +
CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time'
WHEN N'reads' THEN N'total_logical_reads'
WHEN N'writes' THEN N'total_logical_writes'
WHEN N'duration' THEN N'total_elapsed_time'
WHEN N'executions' THEN N'execution_count'
WHEN N'compiles' THEN N'cached_time'
WHEN N'memory grant' THEN N'max_grant_kb'
/* And now the averages */
WHEN N'avg cpu' THEN N'total_worker_time / execution_count'
WHEN N'avg reads' THEN N'total_logical_reads / execution_count'
WHEN N'avg writes' THEN N'total_logical_writes / execution_count'
WHEN N'avg duration' THEN N'total_elapsed_time / execution_count'
WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END'
WHEN N'avg executions' THEN 'CASE WHEN execution_count = 0 THEN 0
WHEN COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1
THEN DATEDIFF(mi, cached_time, GETDATE())
ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1
THEN DATEDIFF(mi, cached_time, last_execution_time)
ELSE Null END) as MONEY), 0) = 0 THEN 0
ELSE CAST((1.00 * execution_count / COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1
THEN DATEDIFF(mi, cached_time, GETDATE())
ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1
THEN DATEDIFF(mi, cached_time, last_execution_time)
ELSE Null END) as MONEY))) AS money)
END '
END + N' DESC ' + @nl ;









SET @body += N') AS qs
CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs,
SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed,
SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker,
SUM(CAST(total_logical_reads AS BIGINT)) AS t_TotalReads,
SUM(CAST(total_logical_writes AS BIGINT)) AS t_TotalWrites
FROM sys.#view#) AS t
CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp ' + @nl ;




SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q) + @nl ;












SET @plans_triggers_select_list += N'
SELECT TOP (@Top)
@@SPID ,
''Procedure: '' + COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''') AS QueryType,
COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), ''-- N/A --'') AS DatabaseName,
(total_worker_time / 1000.0) / execution_count AS AvgCPU ,
(total_worker_time / 1000.0) AS TotalCPU ,
CASE WHEN total_worker_time = 0 THEN 0
WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0
ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time)) AS MONEY)
END AS AverageCPUPerMinute ,
CASE WHEN t.t_TotalWorker = 0 THEN 0
ELSE CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY)
END AS PercentCPUByType,
CASE WHEN t.t_TotalElapsed = 0 THEN 0
ELSE CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY)
END AS PercentDurationByType,
CASE WHEN t.t_TotalReads = 0 THEN 0
ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY)
END AS PercentReadsByType,
CASE WHEN t.t_TotalExecs = 0 THEN 0
ELSE CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY)
END AS PercentExecutionsByType,
(total_elapsed_time / 1000.0) / execution_count AS AvgDuration ,
(total_elapsed_time / 1000.0) AS TotalDuration ,
total_logical_reads / execution_count AS AvgReads ,
total_logical_reads AS TotalReads ,
execution_count AS ExecutionCount ,
CASE WHEN execution_count = 0 THEN 0
WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0
ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time))) AS money)
END AS ExecutionsPerMinute ,
total_logical_writes AS TotalWrites ,
total_logical_writes / execution_count AS AverageWrites ,
CASE WHEN t.t_TotalWrites = 0 THEN 0
ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY)
END AS PercentWritesByType,
CASE WHEN total_logical_writes = 0 THEN 0
WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0
ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0)) AS money)
END AS WritesPerMinute,
qs.cached_time AS PlanCreationTime,
qs.last_execution_time AS LastExecutionTime,
NULL AS StatementStartOffset,
NULL AS StatementEndOffset,
NULL AS MinReturnedRows,
NULL AS MaxReturnedRows,
NULL AS AvgReturnedRows,
NULL AS TotalReturnedRows,
NULL AS LastReturnedRows,
NULL AS MinGrantKB,
NULL AS MaxGrantKB,
NULL AS MinUsedGrantKB,
NULL AS MaxUsedGrantKB,
NULL AS PercentMemoryGrantUsed,
NULL AS AvgMaxMemoryGrant,
st.text AS QueryText ,
query_plan AS QueryPlan,
t.t_TotalWorker,
t.t_TotalElapsed,
t.t_TotalReads,
t.t_TotalExecs,
t.t_TotalWrites,
qs.sql_handle AS SqlHandle,
qs.plan_handle AS PlanHandle,
NULL AS QueryHash,
NULL AS QueryPlanHash,
qs.min_worker_time / 1000.0,
qs.max_worker_time / 1000.0,
CASE WHEN qp.query_plan.value(''declare namespace p="schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END,
qs.min_elapsed_time / 1000.0,
qs.max_elapsed_time / 1000.0,
age_minutes,
age_minutes_lifetime '








IF LEFT(@QueryFilter, 3) IN ('all', 'sta')
BEGIN
SET @sql += @insert_list;

SET @sql += N'
SELECT TOP (@Top)
@@SPID ,
''Statement'' AS QueryType,
COALESCE(DB_NAME(CAST(pa.value AS INT)), ''-- N/A --'') AS DatabaseName,
(total_worker_time / 1000.0) / execution_count AS AvgCPU ,
(total_worker_time / 1000.0) AS TotalCPU ,
CASE WHEN total_worker_time = 0 THEN 0
WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0
ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time)) AS MONEY)
END AS AverageCPUPerMinute ,
CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY) AS PercentCPUByType,
CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY) AS PercentDurationByType,
CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) AS PercentReadsByType,
CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) AS PercentExecutionsByType,
(total_elapsed_time / 1000.0) / execution_count AS AvgDuration ,
(total_elapsed_time / 1000.0) AS TotalDuration ,
total_logical_reads / execution_count AS AvgReads ,
total_logical_reads AS TotalReads ,
execution_count AS ExecutionCount ,
CASE WHEN execution_count = 0 THEN 0
WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0
ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time))) AS money)
END AS ExecutionsPerMinute ,
total_logical_writes AS TotalWrites ,
total_logical_writes / execution_count AS AverageWrites ,
CASE WHEN t.t_TotalWrites = 0 THEN 0
ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY)
END AS PercentWritesByType,
CASE WHEN total_logical_writes = 0 THEN 0
WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0
ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0)) AS money)
END AS WritesPerMinute,
qs.creation_time AS PlanCreationTime,
qs.last_execution_time AS LastExecutionTime,
qs.statement_start_offset AS StatementStartOffset,
qs.statement_end_offset AS StatementEndOffset, '

IF (@v >= 11) OR (@v >= 10.5 AND @build >= 2500)
BEGIN
RAISERROR(N'Adding additional info columns for newer versions of SQL', 0, 1) WITH NOWAIT;
SET @sql += N'
qs.min_rows AS MinReturnedRows,
qs.max_rows AS MaxReturnedRows,
CAST(qs.total_rows as MONEY) / execution_count AS AvgReturnedRows,
qs.total_rows AS TotalReturnedRows,
qs.last_rows AS LastReturnedRows, ' ;
END
ELSE
BEGIN
RAISERROR(N'Substituting NULLs for more info columns in older versions of SQL', 0, 1) WITH NOWAIT;
SET @sql += N'
NULL AS MinReturnedRows,
NULL AS MaxReturnedRows,
NULL AS AvgReturnedRows,
NULL AS TotalReturnedRows,
NULL AS LastReturnedRows, ' ;
END




IF (@v = 11 AND @build >= 6020) OR (@v = 12 AND @build >= 5000) OR (@v = 13 AND @build >= 1601)




BEGIN
RAISERROR(N'Getting memory grant information for newer versions of SQL', 0, 1) WITH NOWAIT;
SET @sql += N'
min_grant_kb AS MinGrantKB,
max_grant_kb AS MaxGrantKB,
min_used_grant_kb AS MinUsedGrantKB,
max_used_grant_kb AS MaxUsedGrantKB,
CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed,
CAST(ISNULL(NULLIF(( max_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, ';
END
ELSE
BEGIN
RAISERROR(N'Substituting NULLs for memory grant columns in older versions of SQL', 0, 1) WITH NOWAIT;
SET @sql += N'
NULL AS MinGrantKB,
NULL AS MaxGrantKB,
NULL AS MinUsedGrantKB,
NULL AS MaxUsedGrantKB,
NULL AS PercentMemoryGrantUsed,
NULL AS AvgMaxMemoryGrant, ' ;
END





SET @sql += N'
SUBSTRING(st.text, ( qs.statement_start_offset / 2 ) + 1, ( ( CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset ) / 2 ) + 1) AS QueryText ,
query_plan AS QueryPlan,
t.t_TotalWorker,
t.t_TotalElapsed,
t.t_TotalReads,
t.t_TotalExecs,
t.t_TotalWrites,
qs.sql_handle AS SqlHandle,
qs.plan_handle AS PlanHandle,
qs.query_hash AS QueryHash,
qs.query_plan_hash AS QueryPlanHash,
qs.min_worker_time / 1000.0,
qs.max_worker_time / 1000.0,
CASE WHEN qp.query_plan.value(''declare namespace p="schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END,
qs.min_elapsed_time / 1000.0,
qs.max_worker_time / 1000.0,
age_minutes,
age_minutes_lifetime ';

SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ;

SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ;

SET @sql += @body_order + @nl + @nl + @nl;




IF @SortOrder = 'compiles'
BEGIN
RAISERROR(N'Sorting by compiles', 0, 1) WITH NOWAIT;
SET @sql = REPLACE(@sql, '#sortable#', 'creation_time');
END
END








IF (@QueryFilter = 'all'
AND (SELECT COUNT(*) FROM #only_query_hashes) = 0
AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0)
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant'))
OR (LEFT(@QueryFilter, 3) = 'pro')
BEGIN
SET @sql += @insert_list;
SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Stored Procedure') ;




SET @sql += REPLACE(@body, '#view#', 'dm_exec_procedure_stats') ;
SET @sql += @body_where ;




IF @IgnoreSystemDBs = 1
SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ;




SET @sql += @body_order + @nl + @nl + @nl ;
END












/*******************************************************************************
*
* Because the trigger execution count in SQL Server 2008R2 and earlier is not
* correct, we ignore triggers for these versions of SQL Server. If you'd like
* to include trigger numbers, just know that the ExecutionCount,
* PercentExecutions, and ExecutionsPerMinute are wildly inaccurate for
* triggers on these versions of SQL Server.
*
* This is why we can't have nice things.
*
******************************************************************************/
IF (@UseTriggersAnyway = 1 OR @v >= 11)
AND (SELECT COUNT(*) FROM #only_query_hashes) = 0
AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0
AND (@QueryFilter = 'all')
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant'))
BEGIN
RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT;




/* Trigger level information from the plan cache */
SET @sql += @insert_list ;




SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Trigger') ;




SET @sql += REPLACE(@body, '#view#', 'dm_exec_trigger_stats') ;




SET @sql += @body_where ;




IF @IgnoreSystemDBs = 1
SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ;

SET @sql += @body_order + @nl + @nl + @nl ;
END




DECLARE @sort NVARCHAR(MAX);




SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time'
WHEN N'reads' THEN N'total_logical_reads'
WHEN N'writes' THEN N'total_logical_writes'
WHEN N'duration' THEN N'total_elapsed_time'
WHEN N'executions' THEN N'execution_count'
WHEN N'compiles' THEN N'cached_time'
WHEN N'memory grant' THEN N'max_grant_kb'
/* And now the averages */
WHEN N'avg cpu' THEN N'total_worker_time / execution_count'
WHEN N'avg reads' THEN N'total_logical_reads / execution_count'
WHEN N'avg writes' THEN N'total_logical_writes / execution_count'
WHEN N'avg duration' THEN N'total_elapsed_time / execution_count'
WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END'
WHEN N'avg executions' THEN N'CASE WHEN execution_count = 0 THEN 0
WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0
ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money)
END'
END ;




SELECT @sql = REPLACE(@sql, '#sortable#', @sort);




SET @sql += N'
INSERT INTO #p (SqlHandle, TotalCPU, TotalReads, TotalDuration, TotalWrites, ExecutionCount)
SELECT SqlHandle,
TotalCPU,
TotalReads,
TotalDuration,
TotalWrites,
ExecutionCount
FROM (SELECT SqlHandle,
TotalCPU,
TotalReads,
TotalDuration,
TotalWrites,
ExecutionCount,
ROW_NUMBER() OVER (PARTITION BY SqlHandle ORDER BY #sortable# DESC) AS rn
FROM ##bou_BlitzCacheProcs) AS x
WHERE x.rn = 1
OPTION (RECOMPILE);
';




SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU'
WHEN N'reads' THEN N'TotalReads'
WHEN N'writes' THEN N'TotalWrites'
WHEN N'duration' THEN N'TotalDuration'
WHEN N'executions' THEN N'ExecutionCount'
WHEN N'compiles' THEN N'PlanCreationTime'
WHEN N'memory grant' THEN N'MaxGrantKB'
WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount'
WHEN N'avg reads' THEN N'TotalReads / ExecutionCount'
WHEN N'avg writes' THEN N'TotalWrites / ExecutionCount'
WHEN N'avg duration' THEN N'TotalDuration / ExecutionCount'
WHEN N'avg memory grant' THEN N'AvgMaxMemoryGrant'
WHEN N'avg executions' THEN N'CASE WHEN ExecutionCount = 0 THEN 0
WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0
ELSE CAST((1.00 * ExecutionCount / COALESCE(age_minutes, age_minutes_lifetime)) AS money)
END'
END ;




SELECT @sql = REPLACE(@sql, '#sortable#', @sort);




IF @Reanalyze = 0
BEGIN
RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT;




EXEC sp_executesql @sql, N'@Top INT, @min_duration INT', @Top, @DurationFilter_i;
END




/*
--Debugging section
SELECT DATALENGTH(@sql)
PRINT SUBSTRING(@sql, 0, 4000)
PRINT SUBSTRING(@sql, 4000, 8000)
PRINT SUBSTRING(@sql, 8000, 12000)
PRINT SUBSTRING(@sql, 16000, 24000)
PRINT SUBSTRING(@sql, 24000, 28000)
PRINT SUBSTRING(@sql, 28000, 32000)
PRINT SUBSTRING(@sql, 32000, 36000)
PRINT SUBSTRING(@sql, 36000, 40000)
*/




/* Update ##bou_BlitzCacheProcs to get Stored Proc info
* This should get totals for all statements in a Stored Proc
*/
RAISERROR(N'Attempting to aggregate stored proc info from separate statements', 0, 1) WITH NOWAIT;
;WITH agg AS (
SELECT
b.SqlHandle,
SUM(b.MinReturnedRows) AS MinReturnedRows,
SUM(b.MaxReturnedRows) AS MaxReturnedRows,
SUM(b.AverageReturnedRows) AS AverageReturnedRows,
SUM(b.TotalReturnedRows) AS TotalReturnedRows,
SUM(b.LastReturnedRows) AS LastReturnedRows
FROM ##bou_BlitzCacheProcs b
WHERE b.QueryHash IS NOT NULL
AND b.SPID = @@SPID
GROUP BY b.SqlHandle
)
UPDATE b
SET
b.MinReturnedRows = b2.MinReturnedRows,
b.MaxReturnedRows = b2.MaxReturnedRows,
b.AverageReturnedRows = b2.AverageReturnedRows,
b.TotalReturnedRows = b2.TotalReturnedRows,
b.LastReturnedRows = b2.LastReturnedRows
FROM ##bou_BlitzCacheProcs b
JOIN agg b2
ON b2.SqlHandle = b.SqlHandle
WHERE b.QueryHash IS NULL
AND b.SPID = @@SPID
OPTION (RECOMPILE) ;




/* Compute the total CPU, etc across our active set of the plan cache.
* Yes, there's a flaw - this doesn't include anything outside of our @Top
* metric.
*/
RAISERROR('Computing CPU, duration, read, and write metrics', 0, 1) WITH NOWAIT;
DECLARE @total_duration BIGINT,
@total_cpu BIGINT,
@total_reads BIGINT,
@total_writes BIGINT,
@total_execution_count BIGINT;




SELECT @total_cpu = SUM(TotalCPU),
@total_duration = SUM(TotalDuration),
@total_reads = SUM(TotalReads),
@total_writes = SUM(TotalWrites),
@total_execution_count = SUM(ExecutionCount)
FROM #p
OPTION (RECOMPILE) ;




DECLARE @cr NVARCHAR(1) = NCHAR(13);
DECLARE @lf NVARCHAR(1) = NCHAR(10);
DECLARE @tab NVARCHAR(1) = NCHAR(9);




/* Update CPU percentage for stored procedures */
RAISERROR(N'Update CPU percentage for stored procedures', 0, 1) WITH NOWAIT;
UPDATE ##bou_BlitzCacheProcs
SET PercentCPU = y.PercentCPU,
PercentDuration = y.PercentDuration,
PercentReads = y.PercentReads,
PercentWrites = y.PercentWrites,
PercentExecutions = y.PercentExecutions,
ExecutionsPerMinute = y.ExecutionsPerMinute,
/* Strip newlines and tabs. Tabs are replaced with multiple spaces
so that the later whitespace trim will completely eliminate them
*/
QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ')
FROM (
SELECT PlanHandle,
CASE @total_cpu WHEN 0 THEN 0
ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU,
CASE @total_duration WHEN 0 THEN 0
ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration,
CASE @total_reads WHEN 0 THEN 0
ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads,
CASE @total_writes WHEN 0 THEN 0
ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites,
CASE @total_execution_count WHEN 0 THEN 0
ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions,
CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime)
WHEN 0 THEN 0
ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY)
END AS ExecutionsPerMinute
FROM (
SELECT PlanHandle,
TotalCPU,
TotalDuration,
TotalReads,
TotalWrites,
ExecutionCount,
PlanCreationTime,
LastExecutionTime
FROM ##bou_BlitzCacheProcs
WHERE PlanHandle IS NOT NULL
AND SPID = @@SPID
GROUP BY PlanHandle,
TotalCPU,
TotalDuration,
TotalReads,
TotalWrites,
ExecutionCount,
PlanCreationTime,
LastExecutionTime
) AS x
) AS y
WHERE ##bou_BlitzCacheProcs.PlanHandle = y.PlanHandle
AND ##bou_BlitzCacheProcs.PlanHandle IS NOT NULL
AND ##bou_BlitzCacheProcs.SPID = @@SPID
OPTION (RECOMPILE) ;








RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT;
UPDATE ##bou_BlitzCacheProcs
SET PercentCPU = y.PercentCPU,
PercentDuration = y.PercentDuration,
PercentReads = y.PercentReads,
PercentWrites = y.PercentWrites,
PercentExecutions = y.PercentExecutions,
ExecutionsPerMinute = y.ExecutionsPerMinute,
/* Strip newlines and tabs. Tabs are replaced with multiple spaces
so that the later whitespace trim will completely eliminate them
*/
QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ')
FROM (
SELECT DatabaseName,
SqlHandle,
QueryHash,
CASE @total_cpu WHEN 0 THEN 0
ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU,
CASE @total_duration WHEN 0 THEN 0
ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration,
CASE @total_reads WHEN 0 THEN 0
ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads,
CASE @total_writes WHEN 0 THEN 0
ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites,
CASE @total_execution_count WHEN 0 THEN 0
ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions,
CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime)
WHEN 0 THEN 0
ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY)
END AS ExecutionsPerMinute
FROM (
SELECT DatabaseName,
SqlHandle,
QueryHash,
TotalCPU,
TotalDuration,
TotalReads,
TotalWrites,
ExecutionCount,
PlanCreationTime,
LastExecutionTime
FROM ##bou_BlitzCacheProcs
WHERE SPID = @@SPID
GROUP BY DatabaseName,
SqlHandle,
QueryHash,
TotalCPU,
TotalDuration,
TotalReads,
TotalWrites,
ExecutionCount,
PlanCreationTime,
LastExecutionTime
) AS x
) AS y
WHERE ##bou_BlitzCacheProcs.SqlHandle = y.SqlHandle
AND ##bou_BlitzCacheProcs.QueryHash = y.QueryHash
AND ##bou_BlitzCacheProcs.DatabaseName = y.DatabaseName
AND ##bou_BlitzCacheProcs.PlanHandle IS NULL
OPTION (RECOMPILE) ;












/* Testing using XML nodes to speed up processing */
RAISERROR(N'Begin XML nodes processing', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
SELECT QueryHash ,
SqlHandle ,
PlanHandle,
q.n.query('.') AS statement
INTO #statements
FROM ##bou_BlitzCacheProcs p
CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n)
WHERE p.SPID = @@SPID
OPTION (RECOMPILE) ;




WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
INSERT #statements
SELECT QueryHash ,
SqlHandle ,
PlanHandle,
q.n.query('.') AS statement
FROM ##bou_BlitzCacheProcs p
CROSS APPLY p.QueryPlan.nodes('//p:StmtCursor') AS q(n)
OPTION (RECOMPILE) ;




WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
SELECT QueryHash ,
SqlHandle ,
q.n.query('.') AS query_plan
INTO #query_plan
FROM #statements p
CROSS APPLY p.statement.nodes('//p:QueryPlan') AS q(n)
OPTION (RECOMPILE) ;




WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
SELECT QueryHash ,
SqlHandle ,
q.n.query('.') AS relop
INTO #relop
FROM #query_plan p
CROSS APPLY p.query_plan.nodes('//p:RelOp') AS q(n)
OPTION (RECOMPILE) ;












-- high level plan stuff
RAISERROR(N'Gathering high level plan information', 0, 1) WITH NOWAIT;
UPDATE ##bou_BlitzCacheProcs
SET NumberOfDistinctPlans = distinct_plan_count,
NumberOfPlans = number_of_plans ,
plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN 1 END
FROM (
SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count,
COUNT(QueryHash) AS number_of_plans,
QueryHash
FROM ##bou_BlitzCacheProcs
WHERE SPID = @@SPID
GROUP BY QueryHash
) AS x
WHERE ##bou_BlitzCacheProcs.QueryHash = x.QueryHash
OPTION (RECOMPILE) ;




-- statement level checks
RAISERROR(N'Performing statement level checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET QueryPlanCost = CASE WHEN QueryType LIKE '%Stored Procedure%' THEN
statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float')
ELSE
statement.value('sum(/p:StmtSimple[xs:hexBinary(substring(@QueryPlanHash, 3)) = xs:hexBinary(sql:column("QueryPlanHash"))]/@StatementSubTreeCost)', 'float')
END ,
compile_timeout = CASE WHEN statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 THEN 1 END ,
compile_memory_limit_exceeded = CASE WHEN statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 THEN 1 END ,
unmatched_index_count = statement.value('count(//p:UnmatchedIndexes/Parameterization/Object)', 'int') ,
is_trivial = CASE WHEN statement.exist('/p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 THEN 1 END ,
unparameterized_query = CASE WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 1 AND
statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList/p:ColumnReference') = 0 THEN 1
WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 0 AND
statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/*/p:RelOp/descendant::p:ScalarOperator/p:Identifier/p:ColumnReference[contains(@Column, "@")]') = 1 THEN 1
END
FROM #statements s
WHERE s.QueryHash = ##bou_BlitzCacheProcs.QueryHash
AND SPID = @@SPID
OPTION (RECOMPILE);




--Gather Stored Proc costs
RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT;
;WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
, QueryCost AS (
SELECT
DISTINCT
statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') AS SubTreeCost,
s.PlanHandle,
s.SqlHandle
FROM #statements AS s
WHERE PlanHandle IS NOT NULL
)
, QueryCostUpdate AS (
SELECT
DISTINCT
SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery,
qc.PlanHandle,
qc.SqlHandle
FROM QueryCost qc
WHERE qc.SubTreeCost > 0
)
UPDATE b
SET b.QueryPlanCost =
CASE WHEN
b.QueryType LIKE '%Procedure%' THEN
(SELECT TOP 1 PlanTotalQuery FROM QueryCostUpdate qcu WHERE qcu.PlanHandle = b.PlanHandle ORDER BY PlanTotalQuery DESC)
ELSE
b.QueryPlanCost
END
FROM QueryCostUpdate qcu
JOIN ##bou_BlitzCacheProcs AS b
ON qcu.SqlHandle = b.SqlHandle
AND b.SPID = @@SPID
OPTION (RECOMPILE);




-- query level checks
RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET missing_index_count = query_plan.value('count(/p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') ,
SerialDesiredMemory = query_plan.value('sum(/p:QueryPlan/p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') ,
SerialRequiredMemory = query_plan.value('sum(/p:QueryPlan/p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'),
CachedPlanSize = query_plan.value('sum(/p:QueryPlan/@CachedPlanSize)', 'float') ,
CompileTime = query_plan.value('sum(/p:QueryPlan/@CompileTime)', 'float') ,
CompileCPU = query_plan.value('sum(/p:QueryPlan/@CompileCPU)', 'float') ,
CompileMemory = query_plan.value('sum(/p:QueryPlan/@CompileMemory)', 'float') ,
implicit_conversions = CASE WHEN query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 THEN 1 END ,
plan_warnings = CASE WHEN query_plan.value('count(/p:QueryPlan/p:Warnings)', 'int') > 0 THEN 1 END,
is_forced_serial = CASE WHEN query_plan.value('count(/p:QueryPlan/@NonParallelPlanReason)', 'int') > 0 THEN 1 END
FROM #query_plan qp
WHERE qp.QueryHash = ##bou_BlitzCacheProcs.QueryHash
AND SPID = @@SPID
OPTION (RECOMPILE);




-- operator level checks
RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE p
SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END
FROM ##bou_BlitzCacheProcs p
JOIN (
SELECT qs.SqlHandle,
relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows ,
relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions
FROM #relop qs
) AS x ON p.SqlHandle = x.SqlHandle
WHERE SPID = @@SPID
OPTION (RECOMPILE);




RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE p
SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END
FROM ##bou_BlitzCacheProcs p
JOIN (
SELECT r.SqlHandle,
1 AS tvf_join
FROM #relop AS r
WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1
AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1
) AS x ON p.SqlHandle = x.SqlHandle
WHERE SPID = @@SPID
OPTION (RECOMPILE);








RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
, x AS (
SELECT r.SqlHandle,
c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate,
c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning ,
c.n.exist('//p:Warnings') AS relop_warnings
FROM #relop AS r
CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n)
)
UPDATE p
SET p.warning_no_join_predicate = x.warning_no_join_predicate,
p.no_stats_warning = x.no_stats_warning,
p.relop_warnings = x.relop_warnings
FROM ##bou_BlitzCacheProcs AS p
JOIN x ON x.SqlHandle = p.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);








RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
, x AS (
SELECT r.SqlHandle,
c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char
FROM #relop r
CROSS APPLY r.relop.nodes('//p:Object') AS c(n)
)
UPDATE p
SET is_table_variable = CASE WHEN x.first_char = '@' THEN 1 END
FROM ##bou_BlitzCacheProcs AS p
JOIN x ON x.SqlHandle = p.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);




RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
, x AS (
SELECT qs.QueryHash,
n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count,
n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count
FROM #relop qs
CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn)
)
UPDATE p
SET p.function_count = x.function_count,
p.clr_function_count = x.clr_function_count
FROM ##bou_BlitzCacheProcs AS p
JOIN x ON x.QueryHash = p.QueryHash
AND SPID = @@SPID
OPTION (RECOMPILE);








RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET key_lookup_cost = x.key_lookup_cost
FROM (
SELECT
qs.SqlHandle,
relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float') AS key_lookup_cost
FROM #relop qs
WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1
) AS x
WHERE ##bou_BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE) ;








RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET remote_query_cost = x.remote_query_cost
FROM (
SELECT
qs.SqlHandle,
relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float') AS remote_query_cost
FROM #relop qs
WHERE [relop].exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1
) AS x
WHERE ##bou_BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE) ;




RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET sort_cost = (x.sort_io + x.sort_cpu)
FROM (
SELECT
qs.SqlHandle,
relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io,
relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu
FROM #relop qs
WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1
) AS x
WHERE ##bou_BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE) ;




RAISERROR(N'Checking for icky cursors', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_optimistic_cursor = CASE WHEN n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 THEN 1 END,
b.is_forward_only_cursor = CASE WHEN n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 THEN 1 ELSE 0 END
FROM ##bou_BlitzCacheProcs b
JOIN #statements AS qs
ON b.QueryHash = qs.QueryHash
CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn)
WHERE SPID = @@SPID
OPTION (RECOMPILE) ;








RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT;
;WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET
b.is_table_scan = x.is_table_scan,
b.backwards_scan = x.backwards_scan,
b.forced_index = x.forced_index,
b.forced_seek = x.forced_seek,
b.forced_scan = x.forced_scan
FROM ##bou_BlitzCacheProcs b
JOIN (
SELECT
qs.SqlHandle,
0 AS is_table_scan,
q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan,
q.n.value('@ForcedIndex', 'bit') AS forced_index,
q.n.value('@ForceSeek', 'bit') AS forced_seek,
q.n.value('@ForceScan', 'bit') AS forced_scan
FROM #relop qs
CROSS APPLY qs.relop.nodes('//p:IndexScan') AS q(n)
UNION ALL
SELECT
qs.SqlHandle,
1 AS is_table_scan,
q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan,
q.n.value('@ForcedIndex', 'bit') AS forced_index,
q.n.value('@ForceSeek', 'bit') AS forced_seek,
q.n.value('@ForceScan', 'bit') AS forced_scan
FROM #relop qs
CROSS APPLY qs.relop.nodes('//p:TableScan') AS q(n)
) AS x ON b.SqlHandle = x.SqlHandle
WHERE SPID = @@SPID
OPTION (RECOMPILE) ;


RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET columnstore_row_mode = x.is_row_mode
FROM (
SELECT
qs.SqlHandle,
relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode
FROM #relop qs
WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1
) AS x
WHERE ##bou_BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE) ;








RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET is_computed_scalar = x.computed_column_function
FROM (
SELECT qs.SqlHandle,
n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function
FROM #relop qs
CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn)
WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1
) AS x
WHERE ##bou_BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE)


RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET is_computed_filter = x.filter_function
FROM (
SELECT
r.SqlHandle,
c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function
FROM #relop AS r
CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n)
) x
WHERE ##bou_BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE)




RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
IndexOps AS
(
SELECT
r.SqlHandle,
c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name,
c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii,
c.n.exist('@PhysicalOp[.="Index Update"]') AS iu,
c.n.exist('@PhysicalOp[.="Index Delete"]') AS id,
c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii,
c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu,
c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid,
c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti,
c.n.exist('@PhysicalOp[.="Table Update"]') AS tu,
c.n.exist('@PhysicalOp[.="Table Delete"]') AS td
FROM #relop AS r
CROSS APPLY r.relop.nodes('/p:RelOp') c(n)
OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n)
OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n)
OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n)
), iops AS
(
SELECT ios.SqlHandle,
SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count,
SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count,
SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count,
SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count,
SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count,
SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count,
SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count,
SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count,
SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count
FROM IndexOps AS ios
WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update',
'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update',
'Table Insert', 'Table Delete', 'Table Update')
GROUP BY ios.SqlHandle)
UPDATE b
SET b.index_insert_count = iops.index_insert_count,
b.index_update_count = iops.index_update_count,
b.index_delete_count = iops.index_delete_count,
b.cx_insert_count = iops.cx_insert_count,
b.cx_update_count = iops.cx_update_count,
b.cx_delete_count = iops.cx_delete_count,
b.table_insert_count = iops.table_insert_count,
b.table_update_count = iops.table_update_count,
b.table_delete_count = iops.table_delete_count
FROM ##bou_BlitzCacheProcs AS b
JOIN iops ON iops.SqlHandle = b.SqlHandle
WHERE SPID = @@SPID
OPTION(RECOMPILE);




IF @v >= 12
BEGIN
RAISERROR('Checking for downlevel cardinality estimators being used on SQL Server 2014.', 0, 1) WITH NOWAIT;




WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE p
SET downlevel_estimator = CASE WHEN statement.value('min(//p:StmtSimple/@CardinalityEstimationModelVersion)', 'int') < (@v * 10) THEN 1 END
FROM ##bou_BlitzCacheProcs p
JOIN #statements s ON p.QueryHash = s.QueryHash
WHERE SPID = @@SPID
OPTION (RECOMPILE) ;
END ;




IF @v >= 13
BEGIN
RAISERROR('Checking for row level security in 2016 only', 0, 1) WITH NOWAIT;




WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE p
SET p.is_row_level = 1
FROM ##bou_BlitzCacheProcs p
JOIN #statements s ON p.QueryHash = s.QueryHash
WHERE SPID = @@SPID
AND statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1
OPTION (RECOMPILE) ;
END ;




/* END Testing using XML nodes to speed up processing */
RAISERROR(N'Gathering additional plan level information', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET NumberOfDistinctPlans = distinct_plan_count,
NumberOfPlans = number_of_plans,
QueryPlanCost = CASE WHEN QueryType LIKE '%Procedure%' THEN
QueryPlanCost
ELSE
QueryPlan.value('sum(//p:StmtSimple[xs:hexBinary(substring(@QueryPlanHash, 3)) = xs:hexBinary(sql:column("QueryPlanHash"))]/@StatementSubTreeCost)', 'float')
END,
missing_index_count = QueryPlan.value('count(//p:MissingIndexGroup)', 'int') ,
unmatched_index_count = QueryPlan.value('count(//p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') ,
plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN 1 END ,
is_trivial = CASE WHEN QueryPlan.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 THEN 1 END ,
SerialDesiredMemory = QueryPlan.value('sum(//p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') ,
SerialRequiredMemory = QueryPlan.value('sum(//p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'),
CachedPlanSize = QueryPlan.value('sum(//p:QueryPlan/@CachedPlanSize)', 'float') ,
CompileTime = QueryPlan.value('sum(//p:QueryPlan/@CompileTime)', 'float') ,
CompileCPU = QueryPlan.value('sum(//p:QueryPlan/@CompileCPU)', 'float') ,
CompileMemory = QueryPlan.value('sum(//p:QueryPlan/@CompileMemory)', 'float')
FROM (
SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count,
COUNT(QueryHash) AS number_of_plans,
QueryHash
FROM ##bou_BlitzCacheProcs
WHERE SPID = @@SPID
GROUP BY QueryHash
) AS x
WHERE ##bou_BlitzCacheProcs.QueryHash = x.QueryHash
OPTION (RECOMPILE) ;




/* Update to grab stored procedure name for individual statements */
RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT;
UPDATE p
SET QueryType = QueryType + ' (parent ' +
+ QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id))
+ '.'
+ QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')'
FROM ##bou_BlitzCacheProcs p
JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle
WHERE QueryType = 'Statement'
AND SPID = @@SPID




/* Trace Flag Checks 2014 SP2 and 2016 SP1 only)*/
RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT;
;WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
, tf_pretty AS (
SELECT qp.QueryHash,
qp.SqlHandle,
q.n.value('@Value', 'INT') AS trace_flag,
q.n.value('@Scope', 'VARCHAR(10)') AS scope
FROM #query_plan qp
CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n)
)
SELECT DISTINCT tf1.SqlHandle , tf1.QueryHash,
STUFF((
SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag)
FROM tf_pretty AS tf2
WHERE tf1.SqlHandle = tf2.SqlHandle
AND tf1.QueryHash = tf2.QueryHash
AND tf2.scope = 'Global'
FOR XML PATH(N'')), 1, 2, N''
) AS global_trace_flags,
STUFF((
SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag)
FROM tf_pretty AS tf2
WHERE tf1.SqlHandle = tf2.SqlHandle
AND tf1.QueryHash = tf2.QueryHash
AND tf2.scope = 'Session'
FOR XML PATH(N'')), 1, 2, N''
) AS session_trace_flags
INTO #trace_flags
FROM tf_pretty AS tf1
OPTION (RECOMPILE);




UPDATE p
SET p.trace_flags_session = tf.session_trace_flags
FROM ##bou_BlitzCacheProcs p
JOIN #trace_flags tf ON tf.QueryHash = p.QueryHash
WHERE SPID = @@SPID
OPTION(RECOMPILE);




IF @SkipAnalysis = 1
BEGIN
RAISERROR(N'Skipping analysis, going to results', 0, 1) WITH NOWAIT;
GOTO Results ;
END


/* Set configuration values */
RAISERROR(N'Setting configuration values', 0, 1) WITH NOWAIT;
DECLARE @execution_threshold INT = 1000 ,
@parameter_sniffing_warning_pct TINYINT = 30,
/* This is in average reads */
@parameter_sniffing_io_threshold BIGINT = 100000 ,
@ctp_threshold_pct TINYINT = 10,
@long_running_query_warning_seconds BIGINT = 300 * 1000 ,
@memory_grant_warning_percent INT = 10;




IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'frequent execution threshold' = LOWER(parameter_name))
BEGIN
SELECT @execution_threshold = CAST(value AS INT)
FROM #configuration
WHERE 'frequent execution threshold' = LOWER(parameter_name) ;




SET @msg = ' Setting "frequent execution threshold" to ' + CAST(@execution_threshold AS VARCHAR(10)) ;




RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing variance percent' = LOWER(parameter_name))
BEGIN
SELECT @parameter_sniffing_warning_pct = CAST(value AS TINYINT)
FROM #configuration
WHERE 'parameter sniffing variance percent' = LOWER(parameter_name) ;




SET @msg = ' Setting "parameter sniffing variance percent" to ' + CAST(@parameter_sniffing_warning_pct AS VARCHAR(3)) ;




RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing io threshold' = LOWER(parameter_name))
BEGIN
SELECT @parameter_sniffing_io_threshold = CAST(value AS BIGINT)
FROM #configuration
WHERE 'parameter sniffing io threshold' = LOWER(parameter_name) ;




SET @msg = ' Setting "parameter sniffing io threshold" to ' + CAST(@parameter_sniffing_io_threshold AS VARCHAR(10));




RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name))
BEGIN
SELECT @ctp_threshold_pct = CAST(value AS TINYINT)
FROM #configuration
WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name) ;




SET @msg = ' Setting "cost threshold for parallelism warning" to ' + CAST(@ctp_threshold_pct AS VARCHAR(3));




RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'long running query warning (seconds)' = LOWER(parameter_name))
BEGIN
SELECT @long_running_query_warning_seconds = CAST(value * 1000 AS BIGINT)
FROM #configuration
WHERE 'long running query warning (seconds)' = LOWER(parameter_name) ;




SET @msg = ' Setting "long running query warning (seconds)" to ' + CAST(@long_running_query_warning_seconds AS VARCHAR(10));




RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'unused memory grant' = LOWER(parameter_name))
BEGIN
SELECT @memory_grant_warning_percent = CAST(value AS INT)
FROM #configuration
WHERE 'unused memory grant' = LOWER(parameter_name) ;




SET @msg = ' Setting "unused memory grant" to ' + CAST(@memory_grant_warning_percent AS VARCHAR(10));




RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




DECLARE @ctp INT ;




SELECT @ctp = NULLIF(CAST(value AS INT), 0)
FROM sys.configurations
WHERE name = 'cost threshold for parallelism'
OPTION (RECOMPILE);




/* Update to populate checks columns */
RAISERROR('Checking for query level SQL Server issues.', 0, 1) WITH NOWAIT;




WITH XMLNAMESPACES('schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##bou_BlitzCacheProcs
SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END ,
parameter_sniffing = CASE WHEN AverageReads > @parameter_sniffing_io_threshold
AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1
WHEN AverageReads > @parameter_sniffing_io_threshold
AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1
WHEN AverageReads > @parameter_sniffing_io_threshold
AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1
WHEN AverageReads > @parameter_sniffing_io_threshold
AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END ,
near_parallel = CASE WHEN QueryPlanCost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END,
long_running = CASE WHEN AverageDuration > @long_running_query_warning_seconds THEN 1
WHEN max_worker_time > @long_running_query_warning_seconds THEN 1
WHEN max_elapsed_time > @long_running_query_warning_seconds THEN 1 END,
is_key_lookup_expensive = CASE WHEN QueryPlanCost > (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END,
is_sort_expensive = CASE WHEN QueryPlanCost > (@ctp / 2) AND sort_cost >= QueryPlanCost * .5 THEN 1 END,
is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END,
is_forced_serial = CASE WHEN is_forced_serial = 1 AND QueryPlanCost > (@ctp / 2) THEN 1 END,
is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END
WHERE SPID = @@SPID
OPTION (RECOMPILE) ;


RAISERROR('Checking for forced parameterization and cursors.', 0, 1) WITH NOWAIT;


/* Set options checks */
UPDATE p
SET is_forced_parameterized = CASE WHEN (CAST(pa.value AS INT) & 131072 = 131072) THEN 1
END ,
is_forced_plan = CASE WHEN (CAST(pa.value AS INT) & 4 = 4) THEN 1
END ,
SetOptions = SUBSTRING(
CASE WHEN (CAST(pa.value AS INT) & 1 = 1) THEN ', ANSI_PADDING' ELSE '' END +
CASE WHEN (CAST(pa.value AS INT) & 8 = 8) THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
CASE WHEN (CAST(pa.value AS INT) & 16 = 16) THEN ', ANSI_WARNINGS' ELSE '' END +
CASE WHEN (CAST(pa.value AS INT) & 32 = 32) THEN ', ANSI_NULLS' ELSE '' END +
CASE WHEN (CAST(pa.value AS INT) & 64 = 64) THEN ', QUOTED_IDENTIFIER' ELSE '' END +
CASE WHEN (CAST(pa.value AS INT) & 4096 = 4096) THEN ', ARITH_ABORT' ELSE '' END +
CASE WHEN (CAST(pa.value AS INT) & 8192 = 8191) THEN ', NUMERIC_ROUNDABORT' ELSE '' END
, 2, 200000)
FROM ##bou_BlitzCacheProcs p
CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa
WHERE pa.attribute = 'set_options'
AND SPID = @@SPID
OPTION (RECOMPILE) ;




/* Cursor checks */
UPDATE p
SET is_cursor = CASE WHEN CAST(pa.value AS INT) <> 0 THEN 1 END
FROM ##bou_BlitzCacheProcs p
CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa
WHERE pa.attribute LIKE '%cursor%'
AND SPID = @@SPID
OPTION (RECOMPILE) ;
RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT;


/* Populate warnings */
UPDATE ##bou_BlitzCacheProcs
SET Warnings = CASE WHEN QueryPlan IS NULL THEN 'We couldn''t find a plan for this query. Possible reasons for this include dynamic SQL, RECOMPILE hints, and encrypted code.' ELSE
SUBSTRING(
CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END +
CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END +
CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END +
CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END +
CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END +
CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END +
CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END +
CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CAST(missing_index_count AS VARCHAR(3)) + ')' ELSE '' END +
CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(unmatched_index_count AS VARCHAR(3)) + ')' ELSE '' END +
CASE WHEN is_cursor = 1 THEN ', Cursor'
+ CASE WHEN is_optimistic_cursor = 1 THEN ' with optimistic' ELSE '' END
+ CASE WHEN is_forward_only_cursor = 0 THEN ' with forward only' ELSE '' END
ELSE '' END +
CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END +
CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END +
CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END +
CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END +
CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END +
CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END +
CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END +
CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END +
CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END +
CASE WHEN plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END +
CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END +
CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END +
CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END +
CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END +
CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END +
CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END +
CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), function_count) + ' function(s)' ELSE '' END +
CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), clr_function_count) + ' CLR function(s)' ELSE '' END +
CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END +
CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END +
CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END +
CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END +
CASE WHEN is_table_scan = 1 THEN ', Table Scans' ELSE '' END +
CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END +
CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END +
CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END +
CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END +
CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END +
CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END +
CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END +
CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END +
CASE WHEN index_ops >= 5 THEN ', ' + CONVERT(VARCHAR(10), index_ops) + ' Indexes Modified' ELSE '' END +
CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END
, 2, 200000)
END
WHERE SPID = @@SPID
OPTION (RECOMPILE) ;




UPDATE ##bou_BlitzCacheProcs
SET Warnings = 'No warnings detected.'
WHERE Warnings = '' OR Warnings IS NULL


Results:
IF @OutputDatabaseName IS NOT NULL
AND @OutputSchemaName IS NOT NULL
AND @OutputTableName IS NOT NULL
BEGIN
RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT;




/* send results to a table */
DECLARE @insert_sql NVARCHAR(MAX) = N'' ;




SET @insert_sql = 'USE '
+ @OutputDatabaseName
+ '; IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName
+ ''') AND NOT EXISTS (SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
+ @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
+ @OutputTableName + ''') CREATE TABLE '
+ @OutputSchemaName + '.'
+ @OutputTableName
+ N'(ID bigint NOT NULL IDENTITY(1,1),
ServerName nvarchar(256),
CheckDate DATETIMEOFFSET,
Version nvarchar(256),
QueryType nvarchar(256),
Warnings varchar(max),
DatabaseName sysname,
SerialDesiredMemory float,
SerialRequiredMemory float,
AverageCPU bigint,
TotalCPU bigint,
PercentCPUByType money,
CPUWeight money,
AverageDuration bigint,
TotalDuration bigint,
DurationWeight money,
PercentDurationByType money,
AverageReads bigint,
TotalReads bigint,
ReadWeight money,
PercentReadsByType money,
AverageWrites bigint,
TotalWrites bigint,
WriteWeight money,
PercentWritesByType money,
ExecutionCount bigint,
ExecutionWeight money,
PercentExecutionsByType money,' + N'
ExecutionsPerMinute money,
PlanCreationTime datetime,
PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()),
LastExecutionTime datetime,
PlanHandle varbinary(64),
[Remove Plan Handle From Cache] AS
CASE WHEN [PlanHandle] IS NOT NULL
THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''
ELSE ''N/A'' END,
SqlHandle varbinary(64),
[Remove SQL Handle From Cache] AS
CASE WHEN [SqlHandle] IS NOT NULL
THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''
ELSE ''N/A'' END,
[SQL Handle More Info] AS
CASE WHEN [SqlHandle] IS NOT NULL
THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''
ELSE ''N/A'' END,
QueryHash binary(8),
[Query Hash More Info] AS
CASE WHEN [QueryHash] IS NOT NULL
THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''
ELSE ''N/A'' END,
QueryPlanHash binary(8),
StatementStartOffset int,
StatementEndOffset int,
MinReturnedRows bigint,
MaxReturnedRows bigint,
AverageReturnedRows money,
TotalReturnedRows bigint,
QueryText nvarchar(max),
QueryPlan xml,
NumberOfPlans int,
NumberOfDistinctPlans int,
MinGrantKB BIGINT,
MaxGrantKB BIGINT,
MinUsedGrantKB BIGINT,
MaxUsedGrantKB BIGINT,
PercentMemoryGrantUsed MONEY,
AvgMaxMemoryGrant MONEY,
QueryPlanCost FLOAT,
CONSTRAINT [PK_' +CAST(NEWID() AS NCHAR(36)) + '] PRIMARY KEY CLUSTERED(ID))';




EXEC sp_executesql @insert_sql ;


SET @insert_sql =N' IF EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + N''') '
+ 'INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableName
+ N' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, '
+ N' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, '
+ N' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryHash, StatementStartOffset, StatementEndOffset, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
+ N' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, QueryPlanCost ) '
+ N'SELECT TOP (@Top) '
+ QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), N'''') + N', SYSDATETIMEOFFSET(),'
+ QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), N'''') + ', '
+ N' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, '
+ N' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, '
+ N' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryHash, StatementStartOffset, StatementEndOffset, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
+ N' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, QueryPlanCost '
+ N' FROM ##bou_BlitzCacheProcs '
+ N' WHERE 1=1 '
IF @MinimumExecutionCount IS NOT NULL
SET @insert_sql += N' AND ExecutionCount >= @minimumExecutionCount '
+ N' AND SPID = @@SPID '

SELECT @insert_sql += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU '
WHEN 'reads' THEN N' TotalReads '
WHEN 'writes' THEN N' TotalWrites '
WHEN 'duration' THEN N' TotalDuration '
WHEN 'executions' THEN N' ExecutionCount '
WHEN 'compiles' THEN N' PlanCreationTime '
WHEN 'memory grant' THEN N' MaxGrantKB'
WHEN 'avg cpu' THEN N' AverageCPU'
WHEN 'avg reads' THEN N' AverageReads'
WHEN 'avg writes' THEN N' AverageWrites'
WHEN 'avg duration' THEN N' AverageDuration'
WHEN 'avg executions' THEN N' ExecutionsPerMinute'
WHEN 'avg memory grant' THEN N' AvgMaxMemoryGrant'
END + N' DESC '




SET @insert_sql += N' OPTION (RECOMPILE) ; '

EXEC sp_executesql @insert_sql, N'@Top INT, @minimumExecutionCount INT', @Top, @MinimumExecutionCount;




RETURN
END
ELSE IF @ExportToExcel = 1
BEGIN
RAISERROR('Displaying results with Excel formatting (no plans).', 0, 1) WITH NOWAIT;




/* excel output */
UPDATE ##bou_BlitzCacheProcs
SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),' ','<>'),'><',''),'<>',' '), 1, 32000);




SET @sql = N'
SELECT TOP (@Top)
DatabaseName AS [Database Name],
QueryPlanCost AS [Cost],
QueryText,
QueryType AS [Query Type],
Warnings,
ExecutionCount,
ExecutionsPerMinute AS [Executions / Minute],
PercentExecutions AS [Execution Weight],
PercentExecutionsByType AS [% Executions (Type)],
SerialDesiredMemory AS [Serial Desired Memory],
SerialRequiredMemory AS [Serial Required Memory],
TotalCPU AS [Total CPU (ms)],
AverageCPU AS [Avg CPU (ms)],
PercentCPU AS [CPU Weight],
PercentCPUByType AS [% CPU (Type)],
TotalDuration AS [Total Duration (ms)],
AverageDuration AS [Avg Duration (ms)],
PercentDuration AS [Duration Weight],
PercentDurationByType AS [% Duration (Type)],
TotalReads AS [Total Reads],
AverageReads AS [Average Reads],
PercentReads AS [Read Weight],
PercentReadsByType AS [% Reads (Type)],
TotalWrites AS [Total Writes],
AverageWrites AS [Average Writes],
PercentWrites AS [Write Weight],
PercentWritesByType AS [% Writes (Type)],
TotalReturnedRows,
AverageReturnedRows,
MinReturnedRows,
MaxReturnedRows,
MinGrantKB,
MaxGrantKB,
MinUsedGrantKB,
MaxUsedGrantKB,
PercentMemoryGrantUsed,
AvgMaxMemoryGrant,
NumberOfPlans,
NumberOfDistinctPlans,
PlanCreationTime AS [Created At],
LastExecutionTime AS [Last Execution],
StatementStartOffset,
StatementEndOffset,
PlanHandle AS [Plan Handle],
SqlHandle AS [SQL Handle],
QueryHash,
QueryPlanHash,
COALESCE(SetOptions, '''') AS [SET Options]
FROM ##bou_BlitzCacheProcs
WHERE 1 = 1
AND SPID = @@SPID ' + @nl




IF @MinimumExecutionCount IS NOT NULL
SET @sql += N' AND ExecutionCount >= @minimumExecutionCount '
SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN ' TotalCPU '
WHEN 'reads' THEN ' TotalReads '
WHEN 'writes' THEN ' TotalWrites '
WHEN 'duration' THEN ' TotalDuration '
WHEN 'executions' THEN ' ExecutionCount '
WHEN 'compiles' THEN ' PlanCreationTime '
WHEN 'memory grant' THEN 'MaxGrantKB'
WHEN 'avg cpu' THEN 'AverageCPU'
WHEN 'avg reads' THEN 'AverageReads'
WHEN 'avg writes' THEN 'AverageWrites'
WHEN 'avg duration' THEN 'AverageDuration'
WHEN 'avg executions' THEN 'ExecutionsPerMinute'
WHEN 'avg memory grant' THEN 'AvgMaxMemoryGrant'
END + N' DESC '


SET @sql += N' OPTION (RECOMPILE) ; '


EXEC sp_executesql @sql, N'@Top INT, @minimumExecutionCount INT', @Top, @MinimumExecutionCount;
END


RAISERROR('Displaying analysis of plan cache.', 0, 1) WITH NOWAIT;


DECLARE @columns NVARCHAR(MAX) = N'' ;


IF @ExpertMode = 0
BEGIN
RAISERROR(N'Returning ExpertMode = 0', 0, 1) WITH NOWAIT;
SET @columns = N' DatabaseName AS [Database],
QueryPlanCost AS [Cost],
QueryText AS [Query Text],
QueryType AS [Query Type],
Warnings AS [Warnings],
ExecutionCount AS [# Executions],
ExecutionsPerMinute AS [Executions / Minute],
PercentExecutions AS [Execution Weight],
TotalCPU AS [Total CPU (ms)],
AverageCPU AS [Avg CPU (ms)],
PercentCPU AS [CPU Weight],
TotalDuration AS [Total Duration (ms)],
AverageDuration AS [Avg Duration (ms)],
PercentDuration AS [Duration Weight],
TotalReads AS [Total Reads],
AverageReads AS [Avg Reads],
PercentReads AS [Read Weight],
TotalWrites AS [Total Writes],
AverageWrites AS [Avg Writes],
PercentWrites AS [Write Weight],
AverageReturnedRows AS [Average Rows],
MinGrantKB AS [Minimum Memory Grant KB],
MaxGrantKB AS [Maximum Memory Grant KB],
MinUsedGrantKB AS [Minimum Used Grant KB],
MaxUsedGrantKB AS [Maximum Used Grant KB],
AvgMaxMemoryGrant AS [Average Max Memory Grant],
PlanCreationTime AS [Created At],
LastExecutionTime AS [Last Execution],
PlanHandle AS [Plan Handle],
SqlHandle AS [SQL Handle],
QueryPlan AS [Query Plan],
COALESCE(SetOptions, '''') AS [SET Options] ';
END
ELSE
BEGIN
SET @columns = N' DatabaseName AS [Database],
QueryText AS [Query Text],
QueryType AS [Query Type],
Warnings AS [Warnings], ' + @nl




IF @ExpertMode = 2 /* Opserver */
BEGIN
RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT;
SET @columns += N'
CASE WHEN QueryPlan IS NULL THEN ''We couldn''''t find a plan for this query. Possible reasons for this include dynamic SQL, RECOMPILE hints, and encrypted code.'' ELSE
SUBSTRING(
CASE WHEN warning_no_join_predicate = 1 THEN '', 20'' ELSE '''' END +
CASE WHEN compile_timeout = 1 THEN '', 18'' ELSE '''' END +
CASE WHEN compile_memory_limit_exceeded = 1 THEN '', 19'' ELSE '''' END +
CASE WHEN busy_loops = 1 THEN '', 16'' ELSE '''' END +
CASE WHEN is_forced_plan = 1 THEN '', 3'' ELSE '''' END +
CASE WHEN is_forced_parameterized > 0 THEN '', 5'' ELSE '''' END +
CASE WHEN unparameterized_query = 1 THEN '', 23'' ELSE '''' END +
CASE WHEN missing_index_count > 0 THEN '', 10'' ELSE '''' END +
CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END +
CASE WHEN is_cursor = 1 THEN '', 4'' ELSE '''' END +
CASE WHEN is_parallel = 1 THEN '', 6'' ELSE '''' END +
CASE WHEN near_parallel = 1 THEN '', 7'' ELSE '''' END +
CASE WHEN frequent_execution = 1 THEN '', 1'' ELSE '''' END +
CASE WHEN plan_warnings = 1 THEN '', 8'' ELSE '''' END +
CASE WHEN parameter_sniffing = 1 THEN '', 2'' ELSE '''' END +
CASE WHEN long_running = 1 THEN '', 9'' ELSE '''' END +
CASE WHEN downlevel_estimator = 1 THEN '', 13'' ELSE '''' END +
CASE WHEN implicit_conversions = 1 THEN '', 14'' ELSE '''' END +
CASE WHEN tvf_join = 1 THEN '', 17'' ELSE '''' END +
CASE WHEN plan_multiple_plans = 1 THEN '', 21'' ELSE '''' END +
CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END +
CASE WHEN is_trivial = 1 THEN '', 24'' ELSE '''' END +
CASE WHEN is_forced_serial = 1 THEN '', 25'' ELSE '''' END +
CASE WHEN is_key_lookup_expensive = 1 THEN '', 26'' ELSE '''' END +
CASE WHEN is_remote_query_expensive = 1 THEN '', 28'' ELSE '''' END +
CASE WHEN trace_flags_session IS NOT NULL THEN '', 29'' ELSE '''' END +
CASE WHEN is_unused_grant = 1 THEN '', 30'' ELSE '''' END +
CASE WHEN function_count > 0 IS NOT NULL THEN '', 31'' ELSE '''' END +
CASE WHEN clr_function_count > 0 THEN '', 32'' ELSE '''' END +
CASE WHEN PlanCreationTimeHours <= 4 THEN '', 33'' ELSE '''' END +
CASE WHEN is_table_variable = 1 THEN '', 34'' ELSE '''' END +
CASE WHEN no_stats_warning = 1 THEN '', 35'' ELSE '''' END +
CASE WHEN relop_warnings = 1 THEN '', 36'' ELSE '''' END +
CASE WHEN is_table_scan = 1 THEN '', 37'' ELSE '''' END +
CASE WHEN backwards_scan = 1 THEN '', 38'' ELSE '''' END +
CASE WHEN forced_index = 1 THEN '', 39'' ELSE '''' END +
CASE WHEN forced_seek = 1 OR forced_scan = 1 THEN '', 40'' ELSE '''' END +
CASE WHEN columnstore_row_mode = 1 THEN '', 41 '' ELSE '' END +
CASE WHEN is_computed_scalar = 1 THEN '', 42 '' ELSE '' END +
CASE WHEN is_sort_expensive = 1 THEN '', 43'' ELSE '''' END +
CASE WHEN is_computed_filter = 1 THEN '', 44'' ELSE '''' END +
CASE WHEN index_ops >= 5 THEN '', 45'' ELSE '''' END +
CASE WHEN is_row_level = 1 THEN '', 46'' ELSE '''' END
, 2, 200000) END AS opserver_warning , ' + @nl ;
END

SET @columns += N' ExecutionCount AS [# Executions],
ExecutionsPerMinute AS [Executions / Minute],
PercentExecutions AS [Execution Weight],
SerialDesiredMemory AS [Serial Desired Memory],
SerialRequiredMemory AS [Serial Required Memory],
TotalCPU AS [Total CPU (ms)],
AverageCPU AS [Avg CPU (ms)],
PercentCPU AS [CPU Weight],
TotalDuration AS [Total Duration (ms)],
AverageDuration AS [Avg Duration (ms)],
PercentDuration AS [Duration Weight],
TotalReads AS [Total Reads],
AverageReads AS [Average Reads],
PercentReads AS [Read Weight],
TotalWrites AS [Total Writes],
AverageWrites AS [Average Writes],
PercentWrites AS [Write Weight],
PercentExecutionsByType AS [% Executions (Type)],
PercentCPUByType AS [% CPU (Type)],
PercentDurationByType AS [% Duration (Type)],
PercentReadsByType AS [% Reads (Type)],
PercentWritesByType AS [% Writes (Type)],
TotalReturnedRows AS [Total Rows],
AverageReturnedRows AS [Avg Rows],
MinReturnedRows AS [Min Rows],
MaxReturnedRows AS [Max Rows],
MinGrantKB AS [Minimum Memory Grant KB],
MaxGrantKB AS [Maximum Memory Grant KB],
MinUsedGrantKB AS [Minimum Used Grant KB],
MaxUsedGrantKB AS [Maximum Used Grant KB],
AvgMaxMemoryGrant AS [Average Max Memory Grant],
NumberOfPlans AS [# Plans],
NumberOfDistinctPlans AS [# Distinct Plans],
PlanCreationTime AS [Created At],
LastExecutionTime AS [Last Execution],
QueryPlanCost AS [Query Plan Cost],
QueryPlan AS [Query Plan],
CachedPlanSize AS [Cached Plan Size (KB)],
CompileTime AS [Compile Time (ms)],
CompileCPU AS [Compile CPU (ms)],
CompileMemory AS [Compile memory (KB)],
COALESCE(SetOptions, '''') AS [SET Options],
PlanHandle AS [Plan Handle],
SqlHandle AS [SQL Handle],
[SQL Handle More Info],
QueryHash AS [Query Hash],
[Query Hash More Info],
QueryPlanHash AS [Query Plan Hash],
StatementStartOffset,
StatementEndOffset,
[Remove Plan Handle From Cache],
[Remove SQL Handle From Cache] ';
END


SET @sql = N'
SELECT TOP (@Top) ' + @columns + @nl + N'
FROM ##bou_BlitzCacheProcs
WHERE SPID = @spid ' + @nl




IF @MinimumExecutionCount IS NOT NULL
SET @sql += N' AND ExecutionCount >= @minimumExecutionCount '
SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU '
WHEN 'reads' THEN N' TotalReads '
WHEN 'writes' THEN N' TotalWrites '
WHEN 'duration' THEN N' TotalDuration '
WHEN 'executions' THEN N' ExecutionCount '
WHEN 'compiles' THEN N' PlanCreationTime '
WHEN 'memory grant' THEN N' MaxGrantKB'
WHEN 'avg cpu' THEN N' AverageCPU'
WHEN 'avg reads' THEN N' AverageReads'
WHEN 'avg writes' THEN N' AverageWrites'
WHEN 'avg duration' THEN N' AverageDuration'
WHEN 'avg executions' THEN N' ExecutionsPerMinute'
WHEN 'avg memory grant' THEN N' AvgMaxMemoryGrant'
END + N' DESC '
SET @sql += N' OPTION (RECOMPILE) ; '








EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT', @Top, @@SPID, @MinimumExecutionCount ;




IF @HideSummary = 0 AND @ExportToExcel = 0
BEGIN
IF @Reanalyze = 0
BEGIN
RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT;




/* Build summary data */
IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE frequent_execution = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
1,
100,
'Execution Pattern',
'Frequently Executed Queries',
'brentozar.com/blitzcache/frequently-executed-queries/',
'Queries are being executed more than '
+ CAST (@execution_threshold AS VARCHAR(5))
+ ' times per minute. This can put additional load on the server, even when queries are lightweight.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE parameter_sniffing = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
2,
50,
'Parameterization',
'Parameter Sniffing',
'brentozar.com/blitzcache/parameter-sniffing/',
'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ;




/* Forced execution plans */
IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE is_forced_plan = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
3,
5,
'Parameterization',
'Forced Plans',
'brentozar.com/blitzcache/forced-plans/',
'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE is_cursor = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
4,
200,
'Cursors',
'Cursors',
'brentozar.com/blitzcache/cursors-found-slow-queries/',
'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE is_cursor = 1
AND is_optimistic_cursor = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
4,
200,
'Cursors',
'Optimistic Cursors',
'brentozar.com/blitzcache/cursors-found-slow-queries/',
'There are optimistic cursors in the plan cache, which can harm performance.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE is_cursor = 1
AND is_forward_only_cursor = 0
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
4,
200,
'Cursors',
'Non-forward Only Cursors',
'brentozar.com/blitzcache/cursors-found-slow-queries/',
'There are non-forward only cursors in the plan cache, which can harm performance.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE is_forced_parameterized = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
5,
50,
'Parameterization',
'Forced Parameterization',
'brentozar.com/blitzcache/forced-parameterization/',
'Execution plans have been compiled with forced parameterization.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_parallel = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
6,
200,
'Execution Plans',
'Parallelism',
'brentozar.com/blitzcache/parallel-plans-detected/',
'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE near_parallel = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
7,
200,
'Execution Plans',
'Nearly Parallel',
'brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/',
'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE plan_warnings = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
8,
50,
'Execution Plans',
'Query Plan Warnings',
'brentozar.com/blitzcache/query-plan-warnings/',
'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE long_running = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
9,
50,
'Performance',
'Long Running Queries',
'brentozar.com/blitzcache/long-running-queries/',
'Long running queries have been found. These are queries with an average duration longer than '
+ CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5))
+ ' second(s). These queries should be investigated for additional tuning options') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.missing_index_count > 0
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
10,
50,
'Performance',
'Missing Index Request',
'brentozar.com/blitzcache/missing-index-request/',
'Queries found with missing indexes.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.downlevel_estimator = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
13,
200,
'Cardinality',
'Legacy Cardinality Estimator in Use',
'brentozar.com/blitzcache/legacy-cardinality-estimator/',
'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE implicit_conversions = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
14,
50,
'Performance',
'Implicit Conversions',
'brentozar.com/go/implicit',
'One or more queries are comparing two fields that are not of the same data type.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE busy_loops = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
16,
10,
'Performance',
'Frequently executed operators',
'brentozar.com/blitzcache/busy-loops/',
'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE tvf_join = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
17,
50,
'Performance',
'Joining to table valued functions',
'brentozar.com/blitzcache/tvf-join/',
'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE compile_timeout = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
18,
50,
'Execution Plans',
'Compilation timeout',
'brentozar.com/blitzcache/compilation-timeout/',
'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE compile_memory_limit_exceeded = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
19,
50,
'Execution Plans',
'Compilation memory limit exceeded',
'brentozar.com/blitzcache/compile-memory-limit-exceeded/',
'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE warning_no_join_predicate = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
20,
10,
'Execution Plans',
'No join predicate',
'brentozar.com/blitzcache/no-join-predicate/',
'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE plan_multiple_plans = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
21,
200,
'Execution Plans',
'Multiple execution plans',
'brentozar.com/blitzcache/multiple-plans/',
'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count/');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE unmatched_index_count > 0
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
22,
100,
'Performance',
'Unmatched indexes',
'brentozar.com/blitzcache/unmatched-indexes',
'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE unparameterized_query = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
23,
100,
'Parameterization',
'Unparameterized queries',
'brentozar.com/blitzcache/unparameterized-queries',
'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".');




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs
WHERE is_trivial = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
24,
100,
'Execution Plans',
'Trivial Plans',
'brentozar.com/blitzcache/trivial-plans',
'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.');

IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_forced_serial= 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
25,
10,
'Execution Plans',
'Forced Serialization',
'brentozar.com/blitzcache/forced-serialization/',
'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_key_lookup_expensive= 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
26,
100,
'Execution Plans',
'Expensive Key Lookups',
'brentozar.com/blitzcache/expensive-key-lookups/',
'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_remote_query_expensive= 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
28,
100,
'Execution Plans',
'Expensive Remote Query',
'brentozar.com/blitzcache/expensive-remote-query/',
'There''s a remote query in your plan that costs >=50% of the total plan cost.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.trace_flags_session IS NOT NULL
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
29,
100,
'Trace Flags',
'Session Level Trace Flags Enabled',
'brentozar.com/blitz/trace-flags-enabled-globally/',
'Someone is enabling session level Trace Flags in a query.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_unused_grant IS NOT NULL
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
30,
100,
'Unused memory grants',
'Queries are asking for more memory than they''re using',
'brentozar.com/blitzcache/unused-memory-grants/',
'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.function_count > 0
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
31,
100,
'Compute Scalar That References A Function',
'This could be trouble if you''re using Scalar Functions or MSTVFs',
'brentozar.com/blitzcache/compute-scalar-functions/',
'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.clr_function_count > 0
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
32,
100,
'Compute Scalar That References A CLR Function',
'This could be trouble if your CLR functions perform data access',
'brentozar.com/blitzcache/compute-scalar-functions/',
'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates') ;








IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_table_variable = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
33,
100,
'Table Variables detected',
'Beware nasty side effects',
'brentozar.com/blitzcache/table-variables/',
'All modifications are single threaded, and selects have really low row estimates.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.no_stats_warning = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
35,
100,
'Columns with no statistics',
'Poor cardinality estimates may ensue',
'brentozar.com/blitzcache/columns-no-statistics/',
'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.relop_warnings = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
36,
100,
'Operator Warnings',
'SQL is throwing operator level plan warnings',
'brentozar.com/blitzcache/query-plan-warnings/',
'Check the plan for more details.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_table_scan = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
37,
100,
'Table Scans',
'Your database has HEAPs',
'brentozar.com/archive/2012/05/video-heaps/',
'This may not be a problem. Run sp_BlitzIndex for more information.') ;

IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.backwards_scan = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
38,
100,
'Backwards Scans',
'Indexes are being read backwards',
'brentozar.com/blitzcache/backwards-scans/',
'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.forced_index = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
39,
100,
'Index forcing',
'Someone is using hints to force index usage',
'brentozar.com/blitzcache/optimizer-forcing/',
'This can cause inefficient plans, and will prevent missing index requests.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.forced_seek = 1
OR p.forced_scan = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
40,
100,
'Seek/Scan forcing',
'Someone is using hints to force index seeks/scans',
'brentozar.com/blitzcache/optimizer-forcing/',
'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.columnstore_row_mode = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
41,
100,
'ColumnStore indexes operating in Row Mode',
'Batch Mode is optimal for ColumnStore indexes',
'brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/',
'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_computed_scalar = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
42,
50,
'Computed Columns Referencing Scalar UDFs',
'This makes a whole lot of stuff run serially',
'brentozar.com/blitzcache/computed-columns-referencing-functions/',
'This can cause a whole mess of bad serializartion problems.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_sort_expensive = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
43,
100,
'Execution Plans',
'Expensive Sort',
'brentozar.com/blitzcache/expensive-sorts/',
'There''s a sort in your plan that costs >=50% of the total plan cost.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_computed_filter = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
44,
50,
'Filters Referencing Scalar UDFs',
'Expensive Sort',
'brentozar.com/blitzcache/compute-scalar-functions/',
'Someone put a Scalar UDF in the WHERE clause!') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.index_ops >= 5
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
45,
100,
'Many Indexes Modified',
'Write Queries Are Hitting >= 5 Indexes',
'No URL yet',
'This can cause lots of hidden I/O -- Run sp_BLitzIndex for more information.') ;




IF EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheProcs p
WHERE p.is_row_level = 1
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
46,
100,
'Plan Confusion',
'Row Level Security is in use',
'No URL yet',
'You may see a lot of confusing junk in your query plan') ;




IF EXISTS (SELECT 1/0
FROM #plan_creation p
WHERE (p.percent_24 > 0 OR p.percent_4 > 0)
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
SELECT SPID,
999,
254,
'Plan Cache Information',
'You have ' + CONVERT(NVARCHAR(10), p.percent_24) + '% plans created in the past 24 hours, and ' + CONVERT(NVARCHAR(10), p.percent_4) + '% created in the past 4 hours.',
'',
'If these percentages are high, it may be a sign of memory pressure or plan cache instability.'
FROM #plan_creation p ;




IF EXISTS (SELECT 1/0
FROM #single_use_plans_warning p
WHERE p.total_plans >= 1000
AND p.single_use_plans_percent >= 10.
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
SELECT SPID,
999,
255,
'Plan Cache Information',
'Your plan cache is ' + CONVERT(NVARCHAR(10), p.single_use_plans_percent) + '% single use plans with an average age of ' + CONVERT(NVARCHAR(10), p.avg_plan_age) + ' minutes.',
'',
'Having a lot of single use plans indicates plan cache bloat. This can be cause by non-parameterized dynamic SQL and EF code, or lots of ad hoc queries.'
FROM #single_use_plans_warning p ;




IF EXISTS (SELECT 1/0
FROM #plan_stubs_warning p
WHERE p.total_plans >= 1000
AND p.plan_stubs_percent >= 30.
AND p.total_plan_stubs >= (40009 * 4)
AND SPID = @@SPID)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
SELECT SPID,
999,
255,
'Plan Cache Information',
'Your plan cache has ' + CONVERT(NVARCHAR(10), p.total_plan_stubs) + ' plan stubs, with an average age of ' + CONVERT(NVARCHAR(10), p.avg_plan_age) + ' minutes.',
'brentozar.com/blitz/poison-wait-detected/',
'A high number of plan stubs may result in CMEMTHREAD waits, which you have '
+ CONVERT(VARCHAR(10), (SELECT CONVERT(DECIMAL(9,0), (dows.wait_time_ms / 60000.)) FROM sys.dm_os_wait_stats AS dows WHERE dows.wait_type = 'CMEMTHREAD')) + ' minutes of.'
FROM #plan_stubs_warning p ;


IF EXISTS (SELECT 1/0
FROM #trace_flags AS tf
WHERE tf.global_trace_flags IS NOT NULL
)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
1000,
255,
'Global Trace Flags Enabled',
'You have Global Trace Flags enabled on your server',
'brentozar.com/blitz/trace-flags-enabled-globally/',
'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ;




IF NOT EXISTS (SELECT 1/0
FROM ##bou_BlitzCacheResults AS bcr
WHERE bcr.Priority = 2147483647
)
INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
VALUES (@@SPID,
2147483647,
255,
'Thanks for using sp_BlitzCache!' ,
'From Your Community Volunteers',
'FirstResponderKit.org',
'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + @VersionDate);


END

SELECT Priority,
FindingsGroup,
Finding,
URL,
Details,
CheckID
FROM ##bou_BlitzCacheResults
WHERE SPID = @@SPID
GROUP BY Priority,
FindingsGroup,
Finding,
URL,
Details,
CheckID
ORDER BY Priority ASC, CheckID ASC
OPTION (RECOMPILE);
END




/*Begin code to sort by all*/
AllSorts:
RAISERROR('Beginning all sort loop', 0, 1) WITH NOWAIT;








IF (
@Top > 10
AND @BringThePain = 0
)
BEGIN
RAISERROR(
'
You''ve chosen a value greater than 10 to sort the whole plan cache by.
That can take a long time and harm performance.
Please choose a number <= 10, or set @BringThePain = 1 to signify you understand this might be a bad idea.
', 0, 1) WITH NOWAIT;
RETURN;
END;








IF OBJECT_ID('tempdb..#checkversion_allsort') IS NULL
BEGIN
CREATE TABLE #checkversion_allsort
(
version NVARCHAR(128),
common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1),
major AS PARSENAME(CONVERT(VARCHAR(32), version), 4),
minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3),
build AS PARSENAME(CONVERT(VARCHAR(32), version), 2),
revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1)
);




INSERT INTO #checkversion_allsort
(version)
SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128))
OPTION ( RECOMPILE );
END;








SELECT @v = common_version,
@build = build
FROM #checkversion_allsort
OPTION ( RECOMPILE );




IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL
BEGIN
CREATE TABLE #bou_allsort
(
Id INT IDENTITY(1, 1),
DatabaseName VARCHAR(128),
Cost FLOAT,
QueryText NVARCHAR(MAX),
QueryType NVARCHAR(256),
Warnings VARCHAR(MAX),
ExecutionCount BIGINT,
ExecutionsPerMinute MONEY,
ExecutionWeight MONEY,
TotalCPU BIGINT,
AverageCPU BIGINT,
CPUWeight MONEY,
TotalDuration BIGINT,
AverageDuration BIGINT,
DurationWeight MONEY,
TotalReads BIGINT,
AverageReads BIGINT,
ReadWeight MONEY,
TotalWrites BIGINT,
AverageWrites BIGINT,
WriteWeight MONEY,
AverageReturnedRows MONEY,
MinGrantKB BIGINT,
MaxGrantKB BIGINT,
MinUsedGrantKB BIGINT,
MaxUsedGrantKB BIGINT,
AvgMaxMemoryGrant MONEY,
PlanCreationTime DATETIME,
LastExecutionTime DATETIME,
PlanHandle VARBINARY(64),
SqlHandle VARBINARY(64),
QueryPlan XML,
SetOptions VARCHAR(MAX),
Pattern NVARCHAR(20)
);
END;




DECLARE @AllSortSql NVARCHAR(MAX) = N'';
DECLARE @MemGrant BIT;
SELECT @MemGrant = CASE WHEN (
( @v < 11 )
OR (
@v = 11
AND @build < 6020
)
OR (
@v = 12
AND @build < 5000
)
OR (
@v = 13
AND @build < 1601
)
) THEN 0
ELSE 1
END;








IF LOWER(@SortOrder) = 'all'
BEGIN
RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT;
SET @AllSortSql += N'
DECLARE @ISH NVARCHAR(MAX) = N''''




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''reads'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''writes'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''duration'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE);

'


IF @MemGrant = 0
BEGIN
SET @AllSortSql += N' SELECT *
FROM #bou_allsort
ORDER BY Id
OPTION(RECOMPILE); '
END


IF @MemGrant = 1
BEGIN
SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);


INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);


SELECT *
FROM #bou_allsort
ORDER BY Id
OPTION(RECOMPILE); '
END


END








IF LOWER(@SortOrder) = 'all avg'
BEGIN
RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT;
SET @AllSortSql += N'
DECLARE @ISH NVARCHAR(MAX) = N''''


INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )


EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''avg cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''avg reads'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )


EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''avg writes'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''avg duration'' WHERE Pattern IS NULL OPTION(RECOMPILE);




SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE);




INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE);

'

IF @MemGrant = 0
BEGIN
SET @AllSortSql += N' SELECT *
FROM #bou_allsort
ORDER BY Id
OPTION(RECOMPILE); '
END

IF @MemGrant = 1
BEGIN
SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') OPTION(RECOMPILE);

INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, ExecutionCount, ExecutionsPerMinute, ExecutionWeight,
TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads,
ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB,
MaxUsedGrantKB, AvgMaxMemoryGrant, PlanCreationTime, LastExecutionTime, PlanHandle, SqlHandle, QueryPlan, SetOptions )

EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName;

UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);


SELECT *
FROM #bou_allsort
ORDER BY Id
OPTION(RECOMPILE); '
END
END


EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT', @i_DatabaseName = @DatabaseName, @i_Top = @Top


/*End of AllSort section*/
END /*Final End*/


GO


sp_BlitzIndex – How are my indexes doing?
can tell you when more time is being spent updating an index than actually using it.




SET ANSI_NULLS ON
GO




SET QUOTED_IDENTIFIER ON
GO








CREATE PROCEDURE [dbo].[sp_BlitzIndex]
@DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/
@SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/
@TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/
@Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/
/*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/
@Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */
/*Note:@Filter doesn't do anything unless @Mode=0*/
@SkipPartitions BIT = 0,
@SkipStatistics BIT = 1,
@GetAllDatabases BIT = 0,
@BringThePain BIT = 0,
@ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */,
@OutputServerName NVARCHAR(256) = NULL ,
@OutputDatabaseName NVARCHAR(256) = NULL ,
@OutputSchemaName NVARCHAR(256) = NULL ,
@OutputTableName NVARCHAR(256) = NULL ,
@Help TINYINT = 0,
@VersionDate DATETIME = NULL OUTPUT
WITH RECOMPILE
AS
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
DECLARE @Version VARCHAR(30);
SET @Version = '4.8';
SET @VersionDate = '20170201';
IF @Help = 1 PRINT '
/*
sp_BlitzIndex from FirstResponderKit.org


This script analyzes the design and performance of your indexes.
To learn more, visit FirstResponderKit.org where you can download new
versions for free, watch training videos on how it works, get more info on
the findings, contribute your own code, and more.
Known limitations of this version:
- Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000.
- The @OutputDatabaseName parameters are not functional yet. To check the
status of this enhancement request, visit:
github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/221
- Does not analyze columnstore, spatial, XML, or full text indexes. If you
would like to contribute code to analyze those, head over to Github and
check out the issues list: FirstResponderKit.org
- Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor.
-- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important
for the user to understand if it is going to be offline and not just run a script.
-- Example 2: they do not include all the options the index may have been created with (padding, compression
filegroup/partition scheme etc.)
-- (The compression and filegroup index create syntax is not trivial because it is set at the partition
level and is not trivial to code.)
- Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.)
Unknown limitations of this version:
- We knew them once, but we forgot.
Changes - for the full list of improvements and fixes in this version, see:
github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/milestone/4?closed=1
MIT License
Copyright (c) 2016 Brent Ozar Unlimited
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'








DECLARE @ScriptVersionName NVARCHAR(50);
DECLARE @DaysUptime NUMERIC(23,2);
DECLARE @DatabaseID INT;
DECLARE @ObjectID INT;
DECLARE @dsql NVARCHAR(MAX);
DECLARE @params NVARCHAR(MAX);
DECLARE @msg NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
DECLARE @Rowcount BIGINT;
DECLARE @SQLServerProductVersion NVARCHAR(128);
DECLARE @SQLServerEdition INT;
DECLARE @FilterMB INT;
DECLARE @collation NVARCHAR(256);
DECLARE @NumDatabases INT;
DECLARE @LineFeed NVARCHAR(5);




SET @LineFeed = CHAR(13) + CHAR(10);
SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/
SET @FilterMB=250;
SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate)




RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT;




IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL
DROP TABLE #IndexSanity;




IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL
DROP TABLE #IndexPartitionSanity;




IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL
DROP TABLE #IndexSanitySize;




IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL
DROP TABLE #IndexColumns;




IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL
DROP TABLE #MissingIndexes;




IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL
DROP TABLE #ForeignKeys;




IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL
DROP TABLE #BlitzIndexResults;

IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL
DROP TABLE #IndexCreateTsql;




IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL
DROP TABLE #DatabaseList;




IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL
DROP TABLE #Statistics;




IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL
DROP TABLE #PartitionCompressionInfo;




IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL
DROP TABLE #ComputedColumns;


IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL
DROP TABLE #TraceStatus;




RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT;
CREATE TABLE #BlitzIndexResults
(
blitz_result_id INT IDENTITY PRIMARY KEY,
check_id INT NOT NULL,
index_sanity_id INT NULL,
Priority INT NULL,
findings_group VARCHAR(4000) NOT NULL,
finding VARCHAR(200) NOT NULL,
[database_name] VARCHAR(200) NULL,
URL VARCHAR(200) NOT NULL,
details NVARCHAR(4000) NOT NULL,
index_definition NVARCHAR(MAX) NOT NULL,
secret_columns NVARCHAR(MAX) NULL,
index_usage_summary NVARCHAR(MAX) NULL,
index_size_summary NVARCHAR(MAX) NULL,
create_tsql NVARCHAR(MAX) NULL,
more_info NVARCHAR(MAX)NULL
);




CREATE TABLE #IndexSanity
(
[index_sanity_id] INT IDENTITY PRIMARY KEY,
[database_id] SMALLINT NOT NULL ,
[object_id] INT NOT NULL ,
[index_id] INT NOT NULL ,
[index_type] TINYINT NOT NULL,
[database_name] NVARCHAR(128) NOT NULL ,
[schema_name] NVARCHAR(128) NOT NULL ,
[object_name] NVARCHAR(128) NOT NULL ,
index_name NVARCHAR(128) NULL ,
key_column_names NVARCHAR(MAX) NULL ,
key_column_names_with_sort_order NVARCHAR(MAX) NULL ,
key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL ,
count_key_columns INT NULL ,
include_column_names NVARCHAR(MAX) NULL ,
include_column_names_no_types NVARCHAR(MAX) NULL ,
count_included_columns INT NULL ,
partition_key_column_name NVARCHAR(MAX) NULL,
filter_definition NVARCHAR(MAX) NOT NULL ,
is_indexed_view BIT NOT NULL ,
is_unique BIT NOT NULL ,
is_primary_key BIT NOT NULL ,
is_XML BIT NOT NULL,
is_spatial BIT NOT NULL,
is_NC_columnstore BIT NOT NULL,
is_CX_columnstore BIT NOT NULL,
is_disabled BIT NOT NULL ,
is_hypothetical BIT NOT NULL ,
is_padded BIT NOT NULL ,
fill_factor SMALLINT NOT NULL ,
user_seeks BIGINT NOT NULL ,
user_scans BIGINT NOT NULL ,
user_lookups BIGINT NOT NULL ,
user_updates BIGINT NULL ,
last_user_seek DATETIME NULL ,
last_user_scan DATETIME NULL ,
last_user_lookup DATETIME NULL ,
last_user_update DATETIME NULL ,
is_referenced_by_foreign_key BIT DEFAULT(0),
secret_columns NVARCHAR(MAX) NULL,
count_secret_columns INT NULL,
create_date DATETIME NOT NULL,
modify_date DATETIME NOT NULL,
[db_schema_object_name] AS [schema_name] + '.' + [object_name] ,
[db_schema_object_indexid] AS [schema_name] + '.' + [object_name]
+ CASE WHEN [index_name] IS NOT NULL THEN '.' + index_name
ELSE ''
END + ' (' + CAST(index_id AS NVARCHAR(20)) + ')' ,
first_key_column_name AS CASE WHEN count_key_columns > 1
THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1)
ELSE key_column_names
END ,
index_definition AS
CASE WHEN partition_key_column_name IS NOT NULL
THEN N'[PARTITIONED BY:' + partition_key_column_name + N']'
ELSE ''
END +
CASE index_id
WHEN 0 THEN N'[HEAP] '
WHEN 1 THEN N'[CX] '
ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN '[VIEW] '
ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] '
ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] '
ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] '
ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] '
ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] '
ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] '
ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] '
ELSE N'' END + CASE WHEN count_key_columns > 0 THEN
N'[' + CAST(count_key_columns AS VARCHAR(10)) + N' KEY'
+ CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END
+ N'] ' + LTRIM(key_column_names_with_sort_order)
ELSE N'' END + CASE WHEN count_included_columns > 0 THEN
N' [' + CAST(count_included_columns AS VARCHAR(10)) + N' INCLUDE' +
+ CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END
+ N'] ' + include_column_names
ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition
ELSE N'' END ,
[total_reads] AS user_seeks + user_scans + user_lookups,
[reads_per_write] AS CAST(CASE WHEN user_updates > 0
THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates)
ELSE 0 END AS MONEY) ,
[index_usage_summary] AS N'Reads: ' +
REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), '.00', '')
+ CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN
N' ('
+ RTRIM(
CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), '.00', '') + N' seek ' ELSE N'' END
+ CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), '.00', '') + N' scan ' ELSE N'' END
+ CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), '.00', '') + N' lookup' ELSE N'' END
)
+ N') '
ELSE N' ' END
+ N'Writes:' +
REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), '.00', ''),
[more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') +
N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([object_name],'''') + N';'
);








CREATE TABLE #IndexPartitionSanity
(
[index_partition_sanity_id] INT IDENTITY PRIMARY KEY ,
[index_sanity_id] INT NULL ,
[database_id] INT NOT NULL ,
[object_id] INT NOT NULL ,
[index_id] INT NOT NULL ,
[partition_number] INT NOT NULL ,
row_count BIGINT NOT NULL ,
reserved_MB NUMERIC(29,2) NOT NULL ,
reserved_LOB_MB NUMERIC(29,2) NOT NULL ,
reserved_row_overflow_MB NUMERIC(29,2) NOT NULL ,
leaf_insert_count BIGINT NULL ,
leaf_delete_count BIGINT NULL ,
leaf_update_count BIGINT NULL ,
range_scan_count BIGINT NULL ,
singleton_lookup_count BIGINT NULL ,
forwarded_fetch_count BIGINT NULL ,
lob_fetch_in_pages BIGINT NULL ,
lob_fetch_in_bytes BIGINT NULL ,
row_overflow_fetch_in_pages BIGINT NULL ,
row_overflow_fetch_in_bytes BIGINT NULL ,
row_lock_count BIGINT NULL ,
row_lock_wait_count BIGINT NULL ,
row_lock_wait_in_ms BIGINT NULL ,
page_lock_count BIGINT NULL ,
page_lock_wait_count BIGINT NULL ,
page_lock_wait_in_ms BIGINT NULL ,
index_lock_promotion_attempt_count BIGINT NULL ,
index_lock_promotion_count BIGINT NULL,
data_compression_desc VARCHAR(60) NULL
);




CREATE TABLE #IndexSanitySize
(
[index_sanity_size_id] INT IDENTITY NOT NULL ,
[index_sanity_id] INT NULL ,
[database_id] INT NOT NULL,
partition_count INT NOT NULL ,
total_rows BIGINT NOT NULL ,
total_reserved_MB NUMERIC(29,2) NOT NULL ,
total_reserved_LOB_MB NUMERIC(29,2) NOT NULL ,
total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL ,
total_leaf_delete_count BIGINT NULL,
total_leaf_update_count BIGINT NULL,
total_range_scan_count BIGINT NULL,
total_singleton_lookup_count BIGINT NULL,
total_forwarded_fetch_count BIGINT NULL,
total_row_lock_count BIGINT NULL ,
total_row_lock_wait_count BIGINT NULL ,
total_row_lock_wait_in_ms BIGINT NULL ,
avg_row_lock_wait_in_ms BIGINT NULL ,
total_page_lock_count BIGINT NULL ,
total_page_lock_wait_count BIGINT NULL ,
total_page_lock_wait_in_ms BIGINT NULL ,
avg_page_lock_wait_in_ms BIGINT NULL ,
total_index_lock_promotion_attempt_count BIGINT NULL ,
total_index_lock_promotion_count BIGINT NULL ,
data_compression_desc VARCHAR(8000) NULL,
index_size_summary AS ISNULL(
CASE WHEN partition_count > 1
THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] '
ELSE N''
END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; '
+ CASE WHEN total_reserved_MB > 1024 THEN
CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB'
ELSE
CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB'
END
+ CASE WHEN total_reserved_LOB_MB > 1024 THEN
N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB LOB'
WHEN total_reserved_LOB_MB > 0 THEN
N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB LOB'
ELSE ''
END
+ CASE WHEN total_reserved_row_overflow_MB > 1024 THEN
N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow'
WHEN total_reserved_row_overflow_MB > 0 THEN
N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow'
ELSE ''
END ,
N'Error- NULL in computed column'),
index_op_stats AS ISNULL(
(
REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; '
+ REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; '
+ REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; '
+ REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; '
+ CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN
REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; '
ELSE N'' END




/* rows will only be in this dmv when data is in memory for the table */
), N'Table metadata not in memory'),
index_lock_wait_summary AS ISNULL(
CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND
total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits.'
ELSE
CASE WHEN total_row_lock_wait_count > 0 THEN
N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'')
+ N'; total duration: ' +
CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/
REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; '
ELSE
REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; '
END
+ N'avg duration: ' +
CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/
REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; '
ELSE
REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; '
END
ELSE N''
END +
CASE WHEN total_page_lock_wait_count > 0 THEN
N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'')
+ N'; total duration: ' +
CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/
REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; '
ELSE
REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; '
END
+ N'avg duration: ' +
CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/
REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; '
ELSE
REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; '
END
ELSE N''
END +
CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN
N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'')
+ N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') + N'.'
ELSE N''
END
END
,'Error- NULL in computed column')
);




CREATE TABLE #IndexColumns
(
[database_id] INT NOT NULL,
[object_id] INT NOT NULL ,
[index_id] INT NOT NULL ,
[key_ordinal] INT NULL ,
is_included_column BIT NULL ,
is_descending_key BIT NULL ,
[partition_ordinal] INT NULL ,
column_name NVARCHAR(256) NOT NULL ,
system_type_name NVARCHAR(256) NOT NULL,
max_length SMALLINT NOT NULL,
[precision] TINYINT NOT NULL,
[scale] TINYINT NOT NULL,
collation_name NVARCHAR(256) NULL,
is_nullable BIT NULL,
is_identity BIT NULL,
is_computed BIT NULL,
is_replicated BIT NULL,
is_sparse BIT NULL,
is_filestream BIT NULL,
seed_value BIGINT NULL,
increment_value INT NULL ,
last_value BIGINT NULL,
is_not_for_replication BIT NULL
);




CREATE TABLE #MissingIndexes
([object_id] INT NOT NULL,
[database_name] NVARCHAR(128) NOT NULL ,
[schema_name] NVARCHAR(128) NOT NULL ,
[table_name] NVARCHAR(128),
[statement] NVARCHAR(512) NOT NULL,
magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact),
avg_total_user_cost NUMERIC(29,4) NOT NULL,
avg_user_impact NUMERIC(29,1) NOT NULL,
user_seeks BIGINT NOT NULL,
user_scans BIGINT NOT NULL,
unique_compiles BIGINT NULL,
equality_columns NVARCHAR(4000),
inequality_columns NVARCHAR(4000),
included_columns NVARCHAR(4000),
[index_estimated_impact] AS
REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(
(user_seeks + user_scans)
AS BIGINT) AS MONEY), 1), '.00', '') + N' use'
+ CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END
+N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30))
+ N'%; Avg query cost: '
+ CAST(avg_total_user_cost AS NVARCHAR(30)),
[missing_index_details] AS
CASE WHEN equality_columns IS NOT NULL THEN N'EQUALITY: ' + equality_columns + N' '
ELSE N''
END + CASE WHEN inequality_columns IS NOT NULL THEN N'INEQUALITY: ' + inequality_columns + N' '
ELSE N''
END + CASE WHEN included_columns IS NOT NULL THEN N'INCLUDES: ' + included_columns + N' '
ELSE N''
END,
[create_tsql] AS N'CREATE INDEX [ix_' + table_name + N'_'
+ REPLACE(REPLACE(REPLACE(REPLACE(
ISNULL(equality_columns,N'')+
CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END
+ ISNULL(inequality_columns,''),',','')
,'[',''),']',''),' ','_')
+ CASE WHEN included_columns IS NOT NULL THEN N'_includes' ELSE N'' END + N'] ON '
+ [statement] + N' (' + ISNULL(equality_columns,N'')
+ CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END
+ CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END +
') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END
+ N' WITH ('
+ N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?'
+ N')'
+ N';'
,
[more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') +
N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';'
);




CREATE TABLE #ForeignKeys (
[database_name] NVARCHAR(128) NOT NULL ,
foreign_key_name NVARCHAR(256),
parent_object_id INT,
parent_object_name NVARCHAR(256),
referenced_object_id INT,
referenced_object_name NVARCHAR(256),
is_disabled BIT,
is_not_trusted BIT,
is_not_for_replication BIT,
parent_fk_columns NVARCHAR(MAX),
referenced_fk_columns NVARCHAR(MAX),
update_referential_action_desc NVARCHAR(16),
delete_referential_action_desc NVARCHAR(60)
)

CREATE TABLE #IndexCreateTsql (
index_sanity_id INT NOT NULL,
create_tsql NVARCHAR(MAX) NOT NULL
)




CREATE TABLE #DatabaseList (
DatabaseName NVARCHAR(256)
)




CREATE TABLE #PartitionCompressionInfo (
[index_sanity_id] INT NULL,
[partition_compression_detail] VARCHAR(8000) NULL
)




CREATE TABLE #Statistics (
database_name NVARCHAR(256) NOT NULL,
table_name NVARCHAR(128) NULL,
schema_name NVARCHAR(128) NULL,
index_name NVARCHAR(128) NULL,
column_names NVARCHAR(4000) NULL,
statistics_name NVARCHAR(128) NULL,
last_statistics_update DATETIME NULL,
days_since_last_stats_update INT NULL,
rows BIGINT NULL,
rows_sampled BIGINT NULL,
percent_sampled DECIMAL(18, 1) NULL,
histogram_steps INT NULL,
modification_counter BIGINT NULL,
percent_modifications DECIMAL(18, 1) NULL,
modifications_before_auto_update INT NULL,
index_type_desc NVARCHAR(128) NULL,
table_create_date DATETIME NULL,
table_modify_date DATETIME NULL,
no_recompute BIT NULL,
has_filter BIT NULL,
filter_definition NVARCHAR(MAX) NULL
);




CREATE TABLE #ComputedColumns
(
index_sanity_id INT IDENTITY(1, 1) NOT NULL,
database_name NVARCHAR(128) NULL,
table_name NVARCHAR(128) NOT NULL,
schema_name NVARCHAR(128) NOT NULL,
column_name NVARCHAR(128) NULL,
is_nullable BIT NULL,
definition NVARCHAR(MAX) NULL,
uses_database_collation BIT NOT NULL,
is_persisted BIT NOT NULL,
is_computed BIT NOT NULL,
is_function INT NOT NULL,
column_definition NVARCHAR(MAX) NULL
);


CREATE TABLE #TraceStatus
(
TraceFlag VARCHAR(10) ,
status BIT ,
Global BIT ,
Session BIT
);








IF @GetAllDatabases = 1
BEGIN
INSERT INTO #DatabaseList (DatabaseName)
SELECT DB_NAME(database_id)
FROM sys.databases
WHERE user_access_desc='MULTI_USER'
AND state_desc = 'ONLINE'
AND database_id > 4
AND DB_NAME(database_id) NOT LIKE 'ReportServer%'
AND is_distributor = 0;
END
ELSE
BEGIN
INSERT INTO #DatabaseList
( DatabaseName )
SELECT CASE WHEN @DatabaseName IS NULL OR @DatabaseName = N'' THEN DB_NAME()
ELSE @DatabaseName END
END




SET @NumDatabases = @@ROWCOUNT;




/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */




BEGIN TRY
IF @NumDatabases >= 50 AND @BringThePain != 1
BEGIN




INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( -1, 0 ,
@ScriptVersionName,
CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END,
N'From Your Community Volunteers' , N'BrentOzar.com/BlitzIndex' ,
N''
, N'',N''
);
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( 1, 0 ,
N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ',
N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.',
N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.',
'FirstResponderKit.org', '', '', '', ''
);


SELECT bir.blitz_result_id,
bir.check_id,
bir.index_sanity_id,
bir.Priority,
bir.findings_group,
bir.finding,
bir.database_name,
bir.URL,
bir.details,
bir.index_definition,
bir.secret_columns,
bir.index_usage_summary,
bir.index_size_summary,
bir.create_tsql,
bir.more_info
FROM #BlitzIndexResults AS bir




RETURN;




END
END TRY
BEGIN CATCH
RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT;




SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();




RAISERROR (@msg,
@ErrorSeverity,
@ErrorState
);

WHILE @@trancount > 0
ROLLBACK;




RETURN;
END CATCH;




/* Permission granted or unnecessary? Ok, let's go! */




DECLARE c1 CURSOR
LOCAL FAST_FORWARD
FOR
SELECT DatabaseName FROM #DatabaseList ORDER BY DatabaseName




OPEN c1
FETCH NEXT FROM c1 INTO @DatabaseName
WHILE @@FETCH_STATUS = 0
BEGIN

RAISERROR (@LineFeed, 0, 1) WITH NOWAIT;
RAISERROR (@LineFeed, 0, 1) WITH NOWAIT;
RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT;




SELECT @DatabaseID = [database_id]
FROM sys.databases
WHERE [name] = @DatabaseName
AND user_access_desc='MULTI_USER'
AND state_desc = 'ONLINE';




/* Last startup */
SELECT @DaysUptime = CAST(DATEDIFF(hh,create_date,GETDATE())/24. AS NUMERIC (23,2))
FROM sys.databases
WHERE database_id = 2;




IF @DaysUptime = 0 SET @DaysUptime = .01;




----------------------------------------
--STEP 1: OBSERVE THE PATIENT
--This step puts index information into temp tables.
----------------------------------------
BEGIN TRY
BEGIN




--Validate SQL Server Verson




IF (SELECT LEFT(@SQLServerProductVersion,
CHARINDEX('.',@SQLServerProductVersion,0)-1
)) <= 9
BEGIN
SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion;
RAISERROR(@msg,16,1);
END




--Short circuit here if database name does not exist.
IF @DatabaseName IS NULL OR @DatabaseID IS NULL
BEGIN
SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'
RAISERROR(@msg,16,1);
END




--Validate parameters.
IF (@Mode NOT IN (0,1,2,3,4))
BEGIN
SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail';
RAISERROR(@msg,16,1);
END




IF (@Mode <> 0 AND @TableName IS NOT NULL)
BEGIN
SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.';
RAISERROR(@msg,16,1);
END




IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0)
BEGIN
SET @msg=N'@Filter only appies when @Mode=0 and @TableName is not specified. Please try again.';
RAISERROR(@msg,16,1);
END




IF (@SchemaName IS NOT NULL AND @TableName IS NULL)
BEGIN
SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'
RAISERROR(@msg,16,1);
END








IF (@TableName IS NOT NULL AND @SchemaName IS NULL)
BEGIN
SET @SchemaName=N'dbo'
SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'
RAISERROR(@msg,1,1) WITH NOWAIT;
END




--If a table is specified, grab the object id.
--Short circuit if it doesn't exist.
IF @TableName IS NOT NULL
BEGIN
SET @dsql = N'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT @ObjectID= OBJECT_ID
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on
so.schema_id=sc.schema_id
where so.type in (''U'', ''V'')
and so.name=' + QUOTENAME(@TableName,'''')+ N'
and sc.name=' + QUOTENAME(@SchemaName,'''')+ N'
/*Has a row in sys.indexes. This lets us get indexed views.*/
and exists (
SELECT si.name
FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si
WHERE so.object_id=si.object_id)
OPTION (RECOMPILE);';




SET @params='@ObjectID INT OUTPUT'




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT;

IF @ObjectID IS NULL
BEGIN
SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) +
N'Please check your parameters.'
RAISERROR(@msg,1,1);
RETURN;
END
END




--set @collation
SELECT @collation=collation_name
FROM sys.databases
WHERE database_id=@DatabaseID;




--insert columns for clustered indexes and heaps
--collect info on identity columns for this one
SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ',
si.object_id,
si.index_id,
sc.key_ordinal,
sc.is_included_column,
sc.is_descending_key,
sc.partition_ordinal,
c.name as column_name,
st.name as system_type_name,
c.max_length,
c.[precision],
c.[scale],
c.collation_name,
c.is_nullable,
c.is_identity,
c.is_computed,
c.is_replicated,
' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N',
' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N',
CAST(ic.seed_value AS BIGINT),
CAST(ic.increment_value AS INT),
CAST(ic.last_value AS BIGINT),
ic.is_not_for_replication
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON
si.object_id=c.object_id
LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON
sc.object_id = si.object_id
and sc.index_id=si.index_id
AND sc.column_id=c.column_id
LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON
c.object_id=ic.object_id and
c.column_id=ic.column_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON
c.system_type_id=st.system_type_id
AND c.user_type_id=st.user_type_id
WHERE si.index_id in (0,1) '
+ CASE WHEN @ObjectID IS NOT NULL
THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30))
ELSE N'' END
+ N'OPTION (RECOMPILE);';




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT;
INSERT #IndexColumns ( database_id, object_id, index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal,
column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed,
is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication )
EXEC sp_executesql @dsql;




--insert columns for nonclustered indexes
--this uses a full join to sys.index_columns
--We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table.
SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ',
si.object_id,
si.index_id,
sc.key_ordinal,
sc.is_included_column,
sc.is_descending_key,
sc.partition_ordinal,
c.name as column_name,
st.name as system_type_name,
c.max_length,
c.[precision],
c.[scale],
c.collation_name,
c.is_nullable,
c.is_identity,
c.is_computed,
c.is_replicated,
' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N',
' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N'
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON
si.object_id=c.object_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON
sc.object_id = si.object_id
and sc.index_id=si.index_id
AND sc.column_id=c.column_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON
c.system_type_id=st.system_type_id
AND c.user_type_id=st.user_type_id
WHERE si.index_id not in (0,1) '
+ CASE WHEN @ObjectID IS NOT NULL
THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30))
ELSE N'' END
+ N'OPTION (RECOMPILE);';




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT;
INSERT #IndexColumns ( database_id, object_id, index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal,
column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed,
is_replicated, is_sparse, is_filestream )
EXEC sp_executesql @dsql;

SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + ' AS database_id,
so.object_id,
si.index_id,
si.type,
' + QUOTENAME(@DatabaseName, '''') + ' AS database_name,
COALESCE(sc.NAME, ''Unknown'') AS [schema_name],
COALESCE(so.name, ''Unknown'') AS [object_name],
COALESCE(si.name, ''Unknown'') AS [index_name],
CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END,
si.is_unique,
si.is_primary_key,
CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML,
CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial,
CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore,
CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore,
si.is_disabled,
si.is_hypothetical,
si.is_padded,
si.fill_factor,'
+ CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN '
CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition
ELSE ''''
END AS filter_definition' ELSE ''''' AS filter_definition' END + '
, ISNULL(us.user_seeks, 0), ISNULL(us.user_scans, 0),
ISNULL(us.user_lookups, 0), ISNULL(us.user_updates, 0), us.last_user_seek, us.last_user_scan,
us.last_user_lookup, us.last_user_update,
so.create_date, so.modify_date
FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si WITH (NOLOCK)
JOIN ' + QUOTENAME(@DatabaseName) + '.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id
AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/
AND so.type <> ''TF'' /*Exclude table valued functions*/
JOIN ' + QUOTENAME(@DatabaseName) + '.sys.schemas sc ON so.schema_id = sc.schema_id
LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id]
AND si.index_id = us.index_id
AND us.database_id = '+ CAST(@DatabaseID AS NVARCHAR(10)) + '
WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 )
/* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' +
CASE WHEN @TableName IS NOT NULL THEN ' and so.name=' + QUOTENAME(@TableName,'''') + ' ' ELSE '' END +
'OPTION ( RECOMPILE );
';
IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT;
INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name],
index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore,
is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans,
user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update,
create_date, modify_date )
EXEC sp_executesql @dsql;




RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET key_column_names = D1.key_column_names
FROM #IndexSanity si
CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name
+ N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}'
AS col_definition
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
AND c.is_included_column = 0 /*Just Keys*/
AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/
ORDER BY c.object_id, c.index_id, c.key_ordinal
FOR XML PATH('') ,TYPE).value('.', 'varchar(max)'), 1, 1, ''))
) D1 ( key_column_names )




RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET partition_key_column_name = D1.partition_key_column_name
FROM #IndexSanity si
CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/
ORDER BY c.object_id, c.index_id, c.key_ordinal
FOR XML PATH('') , TYPE).value('.', 'varchar(max)'), 1, 1,''))) D1
( partition_key_column_name )




RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order
FROM #IndexSanity si
CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key
WHEN 1 THEN N' DESC'
ELSE N''
+ N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}'
END AS col_definition
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
AND c.is_included_column = 0 /*Just Keys*/
AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/
ORDER BY c.object_id, c.index_id, c.key_ordinal
FOR XML PATH('') , TYPE).value('.', 'varchar(max)'), 1, 1, ''))
) D2 ( key_column_names_with_sort_order )




RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types
FROM #IndexSanity si
CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key
WHEN 1 THEN N' [DESC]'
ELSE N''
END AS col_definition
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
AND c.is_included_column = 0 /*Just Keys*/
AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/
ORDER BY c.object_id, c.index_id, c.key_ordinal
FOR XML PATH('') , TYPE).value('.', 'varchar(max)'), 1, 1, ''))
) D2 ( key_column_names_with_sort_order_no_types )




RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET include_column_names = D3.include_column_names
FROM #IndexSanity si
CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name
+ N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}'
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
AND c.is_included_column = 1 /*Just includes*/
ORDER BY c.column_name /*Order doesn't matter in includes,
this is here to make rows easy to compare.*/
FOR XML PATH('') , TYPE).value('.', 'varchar(max)'), 1, 1, ''))
) D3 ( include_column_names );




RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET include_column_names_no_types = D3.include_column_names_no_types
FROM #IndexSanity si
CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name)
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
AND c.is_included_column = 1 /*Just includes*/
ORDER BY c.column_name /*Order doesn't matter in includes,
this is here to make rows easy to compare.*/
FOR XML PATH('') , TYPE).value('.', 'varchar(max)'), 1, 1, ''))
) D3 ( include_column_names_no_types );




RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET count_included_columns = D4.count_included_columns,
count_key_columns = D4.count_key_columns
FROM #IndexSanity si
CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1
ELSE 0
END) AS count_included_columns,
SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1
ELSE 0
END) AS count_key_columns
FROM #IndexColumns c
WHERE c.database_id= si.database_id
AND c.object_id = si.object_id
AND c.index_id = si.index_id
) AS D4 ( count_included_columns, count_key_columns );




IF (@SkipPartitions = 0)
BEGIN
IF (SELECT LEFT(@SQLServerProductVersion,
CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here
BEGIN

RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT;




--NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819
--This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow.
SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + ' AS database_id,
ps.object_id,
ps.index_id,
ps.partition_number,
ps.row_count,
ps.reserved_page_count * 8. / 1024. AS reserved_MB,
ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB,
ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB,
os.leaf_insert_count,
os.leaf_delete_count,
os.leaf_update_count,
os.range_scan_count,
os.singleton_lookup_count,
os.forwarded_fetch_count,
os.lob_fetch_in_pages,
os.lob_fetch_in_bytes,
os.row_overflow_fetch_in_pages,
os.row_overflow_fetch_in_bytes,
os.row_lock_count,
os.row_lock_wait_count,
os.row_lock_wait_in_ms,
os.page_lock_count,
os.page_lock_wait_count,
os.page_lock_wait_in_ms,
os.index_lock_promotion_attempt_count,
os.index_lock_promotion_count,
' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN 'par.data_compression_desc ' ELSE 'null as data_compression_desc' END + '
FROM ' + QUOTENAME(@DatabaseName) + '.sys.dm_db_partition_stats AS ps
JOIN ' + QUOTENAME(@DatabaseName) + '.sys.partitions AS par on ps.partition_id=par.partition_id
JOIN ' + QUOTENAME(@DatabaseName) + '.sys.objects AS so ON ps.object_id = so.object_id
AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/
AND so.type <> ''TF'' /*Exclude table valued functions*/
LEFT JOIN ' + QUOTENAME(@DatabaseName) + '.sys.dm_db_index_operational_stats('
+ CAST(@DatabaseID AS NVARCHAR(10)) + ', NULL, NULL,NULL) AS os ON
ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number
WHERE 1=1
' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + '
' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + '
ORDER BY ps.object_id, ps.index_id, ps.partition_number
OPTION ( RECOMPILE );
';
END
ELSE
BEGIN
RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT;
--This is the syntax that will be used if you change 2147483647 to 11 on line ~819.
--If you have a lot of paritions and this suddenly starts running for a long time, change it back.
SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + ' AS database_id,
ps.object_id,
ps.index_id,
ps.partition_number,
ps.row_count,
ps.reserved_page_count * 8. / 1024. AS reserved_MB,
ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB,
ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB,
os.leaf_insert_count,
os.leaf_delete_count,
os.leaf_update_count,
os.range_scan_count,
os.singleton_lookup_count,
os.forwarded_fetch_count,
os.lob_fetch_in_pages,
os.lob_fetch_in_bytes,
os.row_overflow_fetch_in_pages,
os.row_overflow_fetch_in_bytes,
os.row_lock_count,
os.row_lock_wait_count,
os.row_lock_wait_in_ms,
os.page_lock_count,
os.page_lock_wait_count,
os.page_lock_wait_in_ms,
os.index_lock_promotion_attempt_count,
os.index_lock_promotion_count,
' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N'
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id
AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/
AND so.type <> ''TF'' /*Exclude table valued functions*/
OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('
+ CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os
WHERE 1=1
' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N'
' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + '
ORDER BY ps.object_id, ps.index_id, ps.partition_number
OPTION ( RECOMPILE );
';
END;




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT;
INSERT #IndexPartitionSanity ( [database_id],
[object_id],
index_id,
partition_number,
row_count,
reserved_MB,
reserved_LOB_MB,
reserved_row_overflow_MB,
leaf_insert_count,
leaf_delete_count,
leaf_update_count,
range_scan_count,
singleton_lookup_count,
forwarded_fetch_count,
lob_fetch_in_pages,
lob_fetch_in_bytes,
row_overflow_fetch_in_pages,
row_overflow_fetch_in_bytes,
row_lock_count,
row_lock_wait_count,
row_lock_wait_in_ms,
page_lock_count,
page_lock_wait_count,
page_lock_wait_in_ms,
index_lock_promotion_attempt_count,
index_lock_promotion_count,
data_compression_desc )
EXEC sp_executesql @dsql;

RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT;
UPDATE #IndexPartitionSanity
SET index_sanity_id = i.index_sanity_id
FROM #IndexPartitionSanity ps
JOIN #IndexSanity i ON ps.[object_id] = i.[object_id]
AND ps.index_id = i.index_id
AND i.database_id = ps.database_id
END; --End Check For @SkipPartitions = 0








RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT;
INSERT #IndexSanitySize ( [index_sanity_id], [database_id], partition_count, total_rows, total_reserved_MB,
total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_range_scan_count,
total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count,
total_forwarded_fetch_count,total_row_lock_count,
total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms,
total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms,
avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count,
total_index_lock_promotion_count, data_compression_desc )
SELECT index_sanity_id, ipp.database_id, COUNT(*), SUM(row_count), SUM(reserved_MB), SUM(reserved_LOB_MB),
SUM(reserved_row_overflow_MB),
SUM(range_scan_count),
SUM(singleton_lookup_count),
SUM(leaf_delete_count),
SUM(leaf_update_count),
SUM(forwarded_fetch_count),
SUM(row_lock_count),
SUM(row_lock_wait_count),
SUM(row_lock_wait_in_ms),
CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN
SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count))
ELSE 0 END AS avg_row_lock_wait_in_ms,
SUM(page_lock_count),
SUM(page_lock_wait_count),
SUM(page_lock_wait_in_ms),
CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN
SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count))
ELSE 0 END AS avg_page_lock_wait_in_ms,
SUM(index_lock_promotion_attempt_count),
SUM(index_lock_promotion_count),
LEFT(MAX(data_compression_info.data_compression_rollup),8000)
FROM #IndexPartitionSanity ipp
/* individual partitions can have distinct compression settings, just roll them into a list here*/
OUTER APPLY (SELECT STUFF((
SELECT N', ' + data_compression_desc
FROM #IndexPartitionSanity ipp2
WHERE ipp.[object_id]=ipp2.[object_id]
AND ipp.[index_id]=ipp2.[index_id]
AND ipp.database_id = @DatabaseID
ORDER BY ipp2.partition_number
FOR XML PATH(''),TYPE).value('.', 'varchar(max)'), 1, 1, ''))
data_compression_info(data_compression_rollup)
WHERE ipp.database_id = @DatabaseID
GROUP BY index_sanity_id, ipp.database_id
ORDER BY index_sanity_id
OPTION ( RECOMPILE );




RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT;
IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id')
CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id);




RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT;
SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT id.object_id, ' + QUOTENAME(@DatabaseName,'''') + N', sc.[name], so.[name], id.statement , gs.avg_total_user_cost,
gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles,id.equality_columns,
id.inequality_columns,id.included_columns
FROM sys.dm_db_missing_index_groups ig
JOIN sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle
JOIN sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on
id.object_id=so.object_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on
so.schema_id=sc.schema_id
WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + '
' + CASE WHEN @ObjectID IS NULL THEN N''
ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30))
END +
N'OPTION (RECOMPILE);'




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);
INSERT #MissingIndexes ( [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost,
avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns,
inequality_columns,included_columns)
EXEC sp_executesql @dsql;




SET @dsql = N'
SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS [database_name],
fk_object.name AS foreign_key_name,
parent_object.[object_id] AS parent_object_id,
parent_object.name AS parent_object_name,
referenced_object.[object_id] AS referenced_object_id,
referenced_object.name AS referenced_object_name,
fk.is_disabled,
fk.is_not_trusted,
fk.is_not_for_replication,
parent.fk_columns,
referenced.fk_columns,
[update_referential_action_desc],
[delete_referential_action_desc]
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id
CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id]
AND fkc.parent_column_id=c_parent.column_id
WHERE fk.parent_object_id=fkc.parent_object_id
AND fk.[object_id]=fkc.constraint_object_id
ORDER BY fkc.constraint_column_id
FOR XML PATH('''') ,
TYPE).value(''.'', ''varchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns )
CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns
FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id]
AND fkc.referenced_column_id=c_referenced.column_id
WHERE fk.referenced_object_id=fkc.referenced_object_id
and fk.[object_id]=fkc.constraint_object_id
ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/
FOR XML PATH('''') ,
TYPE).value(''.'', ''varchar(max)''), 1, 1, '''') ) referenced ( fk_columns )
' + CASE WHEN @ObjectID IS NOT NULL THEN
'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' '
ELSE N' ' END + '
ORDER BY parent_object_name, foreign_key_name
OPTION (RECOMPILE);';
IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT;
INSERT #ForeignKeys ( [database_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name,
is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns,
[update_referential_action_desc], [delete_referential_action_desc] )
EXEC sp_executesql @dsql;




RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT;
UPDATE #IndexSanity
SET is_referenced_by_foreign_key=1
FROM #IndexSanity s
JOIN #ForeignKeys fk ON
s.object_id=fk.referenced_object_id
AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns




RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT;
UPDATE nc
SET secret_columns=
N'[' +
CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS VARCHAR(10)) END +
CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END +
CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END +
CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) +
/* Uniquifiers only needed on non-unique clustereds-- not heaps */
CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END
END
, count_secret_columns=
CASE tb.index_id WHEN 0 THEN 1 ELSE
tb.count_key_columns +
CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END
END
FROM #IndexSanity AS nc
JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id
AND tb.index_id IN (0,1)
WHERE nc.index_id > 1;




RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT;
UPDATE tb
SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END
, count_secret_columns = 1
FROM #IndexSanity AS tb
WHERE tb.index_id = 0 /*Heaps-- these have the RID */
OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */








RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT;
INSERT #IndexCreateTsql (index_sanity_id, create_tsql)
SELECT
index_sanity_id,
ISNULL (
/* Script drops for disabled non-clustered indexes*/
CASE WHEN is_disabled = 1 AND index_id <> 1
THEN N'--DROP INDEX ' + QUOTENAME([index_name]) + N' ON '
+ QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name])
ELSE
CASE index_id WHEN 0 THEN N'--I''m a Heap!'
ELSE
CASE WHEN is_XML = 1 OR is_spatial=1 THEN N'' /* Not even trying for these just yet...*/
ELSE
CASE WHEN is_primary_key=1 THEN
N'ALTER TABLE ' + QUOTENAME([schema_name]) +
N'.' + QUOTENAME([object_name]) +
N' ADD CONSTRAINT [' +
index_name +
N'] PRIMARY KEY ' +
CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END +
key_column_names_with_sort_order_no_types + N' )'
WHEN is_CX_columnstore= 1 THEN
N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([schema_name]) + '.' + QUOTENAME([object_name])
ELSE /*Else not a PK or cx columnstore */
N'CREATE ' +
CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END +
CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END +
CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE '
ELSE N'' END +
N'INDEX ['
+ index_name + N'] ON ' +
QUOTENAME([schema_name]) + '.' + QUOTENAME([object_name]) +
CASE WHEN is_NC_columnstore=1 THEN
N' (' + ISNULL(include_column_names_no_types,'') + N' )'
ELSE /*Else not colunnstore */
N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )'
+ CASE WHEN include_column_names_no_types IS NOT NULL THEN
N' INCLUDE (' + include_column_names_no_types + N')'
ELSE N''
END
END /*End non-colunnstore case */
+ CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END
END /*End Non-PK index CASE */
+ CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN
N' WITH ('
+ N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', '
+ N'ONLINE=?, SORT_IN_TEMPDB=?'
+ N')'
ELSE N'' END
+ N';'
END /*End non-spatial and non-xml CASE */
END
END, '[Unknown Error]')
AS create_tsql
FROM #IndexSanity
WHERE database_id = @DatabaseID;

RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT;
;WITH [maps]
AS ( SELECT
index_sanity_id,
partition_number,
data_compression_desc,
partition_number - ROW_NUMBER() OVER (PARTITION BY ips.index_sanity_id, data_compression_desc ORDER BY partition_number ) AS [rN]
FROM #IndexPartitionSanity ips
),
[grps]
AS ( SELECT MIN([maps].[partition_number]) AS [MinKey] ,
MAX([maps].[partition_number]) AS [MaxKey] ,
index_sanity_id,
maps.data_compression_desc
FROM [maps]
GROUP BY [maps].[rN], index_sanity_id, maps.data_compression_desc)
INSERT #PartitionCompressionInfo
(index_sanity_id, partition_compression_detail)
SELECT DISTINCT grps.index_sanity_id , SUBSTRING(( STUFF((SELECT ', ' + ' Partition'
+ CASE WHEN [grps2].[MinKey] < [grps2].[MaxKey]
THEN +'s '
+ CAST([grps2].[MinKey] AS VARCHAR)
+ ' - '
+ CAST([grps2].[MaxKey] AS VARCHAR)
+ ' use ' + grps2.data_compression_desc
ELSE ' '
+ CAST([grps2].[MinKey] AS VARCHAR)
+ ' uses ' + grps2.data_compression_desc
END AS [Partitions]
FROM [grps] AS grps2
WHERE grps2.index_sanity_id = grps.index_sanity_id
ORDER BY grps2.MinKey, grps2.MaxKey
FOR XML PATH('') ,
TYPE
).[value]('.', 'VARCHAR(MAX)'), 1, 1, '') ), 0, 8000) AS [partition_compression_detail]
FROM grps;


RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT;
UPDATE sz
SET sz.data_compression_desc = pci.partition_compression_detail
FROM #IndexSanitySize sz
JOIN #PartitionCompressionInfo AS pci
ON pci.index_sanity_id = sz.index_sanity_id;













IF @SkipStatistics = 0
BEGIN
IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12)
OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000)
OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500))
BEGIN
RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT;
SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name,
obj.name AS table_name,
sch.name AS schema_name,
ISNULL(i.name, ''System Or User Statistic'') AS index_name,
ca.column_names AS column_names,
s.name AS statistics_name,
CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update,
DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update,
ddsp.rows,
ddsp.rows_sampled,
CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled,
ddsp.steps AS histogram_steps,
ddsp.modification_counter,
CASE WHEN ddsp.modification_counter > 0
THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1))
ELSE ddsp.modification_counter
END AS percent_modifications,
CASE WHEN ddsp.rows < 500 THEN 500
ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT)
END AS modifications_before_auto_update,
ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc,
CONVERT(DATETIME, obj.create_date) AS table_create_date,
CONVERT(DATETIME, obj.modify_date) AS table_modify_date,
s.no_recompute,
s.has_filter,
s.filter_definition
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj
ON s.object_id = obj.object_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch
ON sch.schema_id = obj.schema_id
LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i
ON i.object_id = s.object_id
AND i.index_id = s.stats_id
OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp
CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c
ON sc.column_id = c.column_id AND sc.object_id = c.object_id
WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id
ORDER BY sc.stats_column_id
FOR XML PATH(''''), TYPE).value(''.'', ''varchar(max)''), 1, 2, '''')
) ca (column_names)
WHERE obj.is_ms_shipped = 0
OPTION (RECOMPILE);'


IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT;
INSERT #Statistics ( database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update,
days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter,
percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date,
no_recompute, has_filter, filter_definition)


EXEC sp_executesql @dsql;
END
ELSE
BEGIN
RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT;
SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name,
obj.name AS table_name,
sch.name AS schema_name,
ISNULL(i.name, ''System Or User Statistic'') AS index_name,
ca.column_names AS column_names,
s.name AS statistics_name,
CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update,
DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update,
si.rowcnt,
si.rowmodctr,
CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1))
ELSE si.rowmodctr
END AS percent_modifications,
CASE WHEN si.rowcnt < 500 THEN 500
ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT)
END AS modifications_before_auto_update,
ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc,
CONVERT(DATETIME, obj.create_date) AS table_create_date,
CONVERT(DATETIME, obj.modify_date) AS table_modify_date,
s.no_recompute,
'
+ CASE WHEN @SQLServerProductVersion NOT LIKE '9%'
THEN N's.has_filter,
s.filter_definition'
ELSE N'NULL AS has_filter,
NULL AS filter_definition' END
+ N'
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si
ON si.name = s.name
INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj
ON s.object_id = obj.object_id
INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch
ON sch.schema_id = obj.schema_id
LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i
ON i.object_id = s.object_id
AND i.index_id = s.stats_id
CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c
ON sc.column_id = c.column_id AND sc.object_id = c.object_id
WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id
ORDER BY sc.stats_column_id
FOR XML PATH(''''), TYPE).value(''.'', ''varchar(max)''), 1, 2, '''')
) ca (column_names)
WHERE obj.is_ms_shipped = 0
AND si.rowcnt > 0
OPTION (RECOMPILE);'




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT;
INSERT #Statistics(database_name, table_name, schema_name, index_name, column_names, statistics_name,
last_statistics_update, days_since_last_stats_update, rows, modification_counter,
percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date,
no_recompute, has_filter, filter_definition)


EXEC sp_executesql @dsql;
END




END




IF (PARSENAME(@SQLServerProductVersion, 4) >= 10)
BEGIN
RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT;
SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS DatabaseName,
t.name AS table_name,
s.name AS schema_name,
c.name AS column_name,
cc.is_nullable,
cc.definition,
cc.uses_database_collation,
cc.is_persisted,
cc.is_computed,
CASE WHEN cc.definition LIKE ''%.%'' THEN 1 ELSE 0 END AS is_function,
''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) +
'' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition +
CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition]
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c
ON cc.object_id = c.object_id
AND cc.column_id = c.column_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t
ON t.object_id = cc.object_id
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s
ON s.schema_id = t.schema_id
OPTION (RECOMPILE);'




IF @dsql IS NULL
RAISERROR('@dsql is null',16,1);




INSERT #ComputedColumns
( database_name, table_name, schema_name, column_name, is_nullable, definition,
uses_database_collation, is_persisted, is_computed, is_function, column_definition )
EXEC sp_executesql @dsql;




END


RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT;
INSERT #TraceStatus
EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS')


END
END TRY
BEGIN CATCH
RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT;




IF @dsql IS NOT NULL
BEGIN
SET @msg= 'Last @dsql: ' + @dsql;
RAISERROR(@msg, 0, 1) WITH NOWAIT;
END




SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT;


WHILE @@trancount > 0
ROLLBACK;




RETURN;
END CATCH;
FETCH NEXT FROM c1 INTO @DatabaseName
END
DEALLOCATE c1;




----------------------------------------
--STEP 2: DIAGNOSE THE PATIENT
--EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY.
----------------------------------------
BEGIN TRY
----------------------------------------
--If @TableName is specified, just return information for that table.
--The @Mode parameter doesn't matter if you're looking at a specific table.
----------------------------------------
IF @TableName IS NOT NULL
BEGIN
RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT;




--We do a left join here in case this is a disabled NC.
--In that case, it won't have any size info/pages allocated.



WITH table_mode_cte AS (
SELECT
s.db_schema_object_indexid,
s.key_column_names,
s.index_definition,
ISNULL(s.secret_columns,N'') AS secret_columns,
s.fill_factor,
s.index_usage_summary,
sz.index_op_stats,
ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary,
partition_compression_detail ,
ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary,
s.is_referenced_by_foreign_key,
(SELECT COUNT(*)
FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id
AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index,
s.last_user_seek,
s.last_user_scan,
s.last_user_lookup,
s.last_user_update,
s.create_date,
s.modify_date,
ct.create_tsql,
1 AS display_order
FROM #IndexSanity s
LEFT JOIN #IndexSanitySize sz ON
s.index_sanity_id=sz.index_sanity_id
LEFT JOIN #IndexCreateTsql ct ON
s.index_sanity_id=ct.index_sanity_id
LEFT JOIN #PartitionCompressionInfo pci ON
pci.index_sanity_id = s.index_sanity_id
WHERE s.[object_id]=@ObjectID
UNION ALL
SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) +
N' (' + @ScriptVersionName + ')' ,
N'SQL Server First Responder Kit' ,
N'FirstResponderKit.org' ,
N'From Your Community Volunteers',
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
0 AS display_order
)
SELECT
db_schema_object_indexid AS [Details: db_schema.table.index(indexid)],
index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}],
secret_columns AS [Secret Columns],
fill_factor AS [Fillfactor],
index_usage_summary AS [Usage Stats],
index_op_stats AS [Op Stats],
index_size_summary AS [Size],
partition_compression_detail AS [Compression Type],
index_lock_wait_summary AS [Lock Waits],
is_referenced_by_foreign_key AS [Referenced by FK?],
FKs_covered_by_index AS [FK Covered by Index?],
last_user_seek AS [Last User Seek],
last_user_scan AS [Last User Scan],
last_user_lookup AS [Last User Lookup],
last_user_update AS [Last User Write],
create_date AS [Created],
modify_date AS [Last Modified],
create_tsql AS [Create TSQL]
FROM table_mode_cte
ORDER BY display_order ASC, key_column_names ASC
OPTION ( RECOMPILE );




IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL
BEGIN
SELECT N'Missing index.' AS Finding ,
N'BrentOzar.com/go/Indexaphobia' AS URL ,
mi.[statement] +
' Est. Benefit: '
+ CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477'
ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(
(magic_benefit_number/@DaysUptime)
AS BIGINT) AS MONEY), 1), '.00', '')
END AS [Estimated Benefit],
missing_index_details AS [Missing Index Request] ,
index_estimated_impact AS [Estimated Impact],
create_tsql AS [Create TSQL]
FROM #MissingIndexes mi
WHERE [object_id] = @ObjectID
/* Minimum benefit threshold = 100k/day of uptime */
AND (magic_benefit_number/@DaysUptime) >= 100000
ORDER BY magic_benefit_number DESC
OPTION ( RECOMPILE );
END
ELSE
SELECT 'No missing indexes.' AS finding;




SELECT
column_name AS [Column Name],
(SELECT COUNT(*)
FROM #IndexColumns c2
WHERE c2.column_name=c.column_name
AND c2.key_ordinal IS NOT NULL)
+ CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN
-1+ (SELECT COUNT(DISTINCT index_id)
FROM #IndexColumns c3
WHERE c3.index_id NOT IN (0,1))
ELSE 0 END
AS [Found In],
system_type_name +
CASE max_length WHEN -1 THEN N' (max)' ELSE
CASE
WHEN system_type_name IN (N'char',N'nchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')'
WHEN system_type_name IN (N'varchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')'
ELSE ''
END
END
AS [Type],
CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?],
max_length AS [Length (max bytes)],
[precision] AS [Prec],
[scale] AS [Scale],
CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?],
CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?],
CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?],
CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?],
CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?],
collation_name AS [Collation]
FROM #IndexColumns AS c
WHERE index_id IN (0,1);




IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL
BEGIN
SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key],
parent_fk_columns AS [Foreign Key Columns],
referenced_object_name AS [Referenced Table],
referenced_fk_columns AS [Referenced Table Columns],
is_disabled AS [Is Disabled?],
is_not_trusted AS [Not Trusted?],
is_not_for_replication [Not for Replication?],
[update_referential_action_desc] AS [Cascading Updates?],
[delete_referential_action_desc] AS [Cascading Deletes?]
FROM #ForeignKeys
ORDER BY [Foreign Key]
OPTION ( RECOMPILE );
END
ELSE
SELECT 'No foreign keys.' AS finding;
END




--If @TableName is NOT specified...
--Act based on the @Mode and @Filter. (@Filter applies only when @Mode=0 "diagnose")
ELSE
BEGIN;
IF @Mode IN (0, 4) /* DIAGNOSE*/
BEGIN;
RAISERROR(N'@Mode=0 or 4, we are diagnosing.', 0,1) WITH NOWAIT;




----------------------------------------
--Multiple Index Personalities: Check_id 0-10
----------------------------------------
BEGIN;




--SELECT [object_id], key_column_names, database_id
-- FROM #IndexSanity
-- WHERE index_type IN (1,2) /* Clustered, NC only*/
-- AND is_hypothetical = 0
-- AND is_disabled = 0
-- GROUP BY [object_id], key_column_names, database_id
-- HAVING COUNT(*) > 1








RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT;
WITH duplicate_indexes
AS ( SELECT [object_id], key_column_names, database_id
FROM #IndexSanity
WHERE index_type IN (1,2) /* Clustered, NC only*/
AND is_hypothetical = 0
AND is_disabled = 0
AND is_primary_key = 0
GROUP BY [object_id], key_column_names, database_id
HAVING COUNT(*) > 1)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 1 AS check_id,
ip.index_sanity_id,
50 AS Priority,
'Multiple Index Personalities' AS findings_group,
'Duplicate keys' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/duplicateindex' AS URL,
N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details,
ip.index_definition,
ip.secret_columns,
ip.index_usage_summary,
ips.index_size_summary
FROM duplicate_indexes di
JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id]
AND ip.database_id = di.database_id
AND di.key_column_names = ip.key_column_names
JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id AND ip.database_id = ips.database_id
/* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */
WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END
AND ip.is_primary_key = 0
ORDER BY ip.object_id, ip.key_column_names_with_sort_order
OPTION ( RECOMPILE );




RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT;
WITH borderline_duplicate_indexes
AS ( SELECT DISTINCT [object_id], first_key_column_name, key_column_names,
COUNT([object_id]) OVER ( PARTITION BY [object_id], first_key_column_name ) AS number_dupes
FROM #IndexSanity
WHERE index_type IN (1,2) /* Clustered, NC only*/
AND is_hypothetical=0
AND is_disabled=0
AND is_primary_key = 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 2 AS check_id,
ip.index_sanity_id,
60 AS Priority,
'Multiple Index Personalities' AS findings_group,
'Borderline duplicate keys' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/duplicateindex' AS URL,
ip.db_schema_object_indexid AS details,
ip.index_definition,
ip.secret_columns,
ip.index_usage_summary,
ips.index_size_summary
FROM #IndexSanity AS ip
JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id
WHERE EXISTS (
SELECT di.[object_id]
FROM borderline_duplicate_indexes AS di
WHERE di.[object_id] = ip.[object_id] AND
di.first_key_column_name = ip.first_key_column_name AND
di.key_column_names <> ip.key_column_names AND
di.number_dupes > 1
)
AND ip.is_primary_key = 0
/* WHERE clause skips near-duplicate indexes when getting all databases or using PainRelief mode */
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)

ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names
OPTION ( RECOMPILE );




END
----------------------------------------
--Aggressive Indexes: Check_id 10-19
----------------------------------------
BEGIN;




RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 11 AS check_id,
i.index_sanity_id,
10 AS Priority,
N'Aggressive Indexes' AS findings_group,
N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AggressiveIndexes' AS URL,
i.db_schema_object_indexid + N': ' +
sz.index_lock_wait_summary + N' NC indexes on table: ' +
CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END)
AS NVARCHAR(30)) AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000
AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) > 5000
GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id
OPTION ( RECOMPILE );




RAISERROR(N'check_id 12: Total lock wait time > 5 minutes (row + page) with short average waits', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 12 AS check_id,
i.index_sanity_id,
10 AS Priority,
N'Aggressive Indexes' AS findings_group,
N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AggressiveIndexes' AS URL,
i.db_schema_object_indexid + N': ' +
sz.index_lock_wait_summary + N' NC indexes on table: ' +
CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END)
AS NVARCHAR(30)) AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000
AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) < 5000
GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id
OPTION ( RECOMPILE );




END




----------------------------------------
--Index Hoarder: Check_id 20-29
----------------------------------------
BEGIN
RAISERROR(N'check_id 20: >=7 NC indexes on any given table. Yes, 7 is an arbitrary number.', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 20 AS check_id,
MAX(i.index_sanity_id) AS index_sanity_id,
100 AS Priority,
'Index Hoarder' AS findings_group,
'Many NC indexes on a single table' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details,
i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition,
'' AS secret_columns,
REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); '
+ REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ',
REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)'
+ CASE WHEN SUM(total_reserved_MB) > 1024 THEN
N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)'
WHEN SUM(total_reserved_MB) > 0 THEN
N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)'
ELSE ''
END AS index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
WHERE index_id NOT IN ( 0, 1 )
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
GROUP BY db_schema_object_name, [i].[database_name]
HAVING COUNT(*) >= 7
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );




IF @Filter = 1 /*@Filter=1 is "ignore unusued" */
BEGIN
RAISERROR(N'Skipping checks on unused indexes (21 and 22) because @Filter=1', 0,1) WITH NOWAIT;
END
ELSE /*Otherwise, go ahead and do the checks*/
BEGIN
RAISERROR(N'check_id 21: >=5 percent of indexes are unused. Yes, 5 is an arbitrary number.', 0,1) WITH NOWAIT;
DECLARE @percent_NC_indexes_unused NUMERIC(29,1);
DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1);




SELECT @percent_NC_indexes_unused =( 100.00 * SUM(CASE WHEN total_reads = 0 THEN 1
ELSE 0
END) ) / COUNT(*) ,
@NC_indexes_unused_reserved_MB = SUM(CASE WHEN total_reads = 0 THEN sz.total_reserved_MB
ELSE 0
END)
FROM #IndexSanity i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE index_id NOT IN ( 0, 1 )
AND i.is_unique = 0
/*Skipping tables created in the last week, or modified in past 2 days*/
AND i.create_date >= DATEADD(dd,-7,GETDATE())
AND i.modify_date > DATEADD(dd,-2,GETDATE())
OPTION ( RECOMPILE );




IF @percent_NC_indexes_unused >= 5
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 21 AS check_id,
MAX(i.index_sanity_id) AS index_sanity_id,
150 AS Priority,
N'Index Hoarder' AS findings_group,
N'More than 5 percent NC indexes are unused' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' +
N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details,
i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition,
'' AS secret_columns,
CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); '
+ CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary,

REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)'
+ CASE WHEN SUM(total_reserved_MB) > 1024 THEN
N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)'
WHEN SUM(total_reserved_MB) > 0 THEN
N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)'
ELSE ''
END AS index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE index_id NOT IN ( 0, 1 )
AND i.is_unique = 0
AND total_reads = 0
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
/*Skipping tables created in the last week, or modified in past 2 days*/
AND i.create_date >= DATEADD(dd,-7,GETDATE())
AND i.modify_date > DATEADD(dd,-2,GETDATE())
GROUP BY i.database_name
OPTION ( RECOMPILE );




RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 22 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Index Hoarder' AS findings_group,
N'Unused NC index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
N'0 reads: ' + i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.total_reads=0
AND i.index_id NOT IN (0,1) /*NCs only*/
AND i.is_unique = 0
AND sz.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE sz.total_reserved_MB END
ORDER BY i.db_schema_object_indexid
OPTION ( RECOMPILE );
END /*end checks only run when @Filter <> 1*/




RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 23 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Index Hoarder' AS findings_group,
N'Borderline: Wide indexes (7 or more columns)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on '
+ i.db_schema_object_indexid AS details, i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE ( count_key_columns + count_included_columns ) >= 7
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );




RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT;
WITH count_columns AS (
SELECT [object_id],
SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length
FROM #IndexColumns ic
WHERE index_id IN (1,0) /*Heap or clustered only*/
AND key_ordinal > 0
GROUP BY object_id
)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 24 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Index Hoarder' AS findings_group,
N'Wide clustered index (> 3 columns OR > 16 bytes)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of '
+ CAST(cc.sum_max_length AS NVARCHAR(10))
+ N' bytes in clustered index:' + i.db_schema_object_name
+ N'. ' +
(SELECT CAST(COUNT(*) AS NVARCHAR(23)) FROM #IndexSanity i2
WHERE i2.[object_id]=i.[object_id] AND i2.index_id <> 1
AND i2.is_disabled=0 AND i2.is_hypothetical=0)
+ N' NC indexes on the table.'
AS details,
i.index_definition,
secret_columns,
i.index_usage_summary,
ip.index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
JOIN count_columns AS cc ON i.[object_id]=cc.[object_id]
WHERE index_id = 1 /* clustered only */
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND
(count_key_columns > 3 /*More than three key columns.*/
OR cc.sum_max_length > 16 /*More than 16 bytes in key */)
AND i.is_CX_columnstore = 0
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );




RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT;
WITH count_columns AS (
SELECT [object_id],
SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns,
COUNT(*) AS total_columns
FROM #IndexColumns ic
WHERE index_id IN (1,0) /*Heap or clustered only*/
GROUP BY object_id
)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 25 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Index Hoarder' AS findings_group,
N'Addicted to nulls' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
i.db_schema_object_name
+ N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10))
+ N' of ' + CAST(total_columns AS NVARCHAR(10))
+ N' columns.' AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
JOIN count_columns AS cc ON i.[object_id]=cc.[object_id]
WHERE i.index_id IN (1,0)
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND cc.non_nullable_columns < 2
AND cc.total_columns > 3
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );




RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT;
WITH count_columns AS (
SELECT [object_id],
SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns,
SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length,
COUNT(*) AS total_columns
FROM #IndexColumns ic
WHERE index_id IN (1,0) /*Heap or clustered only*/
GROUP BY object_id
)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 26 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Index Hoarder' AS findings_group,
N'Wide tables: 35+ cols or > 2000 non-LOB bytes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
i.db_schema_object_name
+ N' has ' + CAST((total_columns) AS NVARCHAR(10))
+ N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10))
+ N' bytes.' +
CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10))
+ ' columns are LOB types.' ELSE ''
END
AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
JOIN count_columns AS cc ON i.[object_id]=cc.[object_id]
WHERE i.index_id IN (1,0)
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND
(cc.total_columns >= 35 OR
cc.sum_max_length >= 2000)
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );

RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT;
WITH count_columns AS (
SELECT [object_id],
SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns,
COUNT(*) AS total_columns
FROM #IndexColumns ic
WHERE index_id IN (1,0) /*Heap or clustered only*/
GROUP BY object_id
)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 27 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Index Hoarder' AS findings_group,
N'Addicted to strings' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
i.db_schema_object_name
+ N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10))
+ N' of ' + CAST(total_columns AS NVARCHAR(10))
+ N' columns. Check if data types are valid.' AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
JOIN count_columns AS cc ON i.[object_id]=cc.[object_id]
CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1
WHERE i.index_id IN (1,0)
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND calc1.non_string_or_lob_columns <= 1
AND cc.total_columns > 3
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );




RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 28 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Index Hoarder' AS findings_group,
N'Non-Unique clustered index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexHoarder' AS URL,
N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name
+ N' and all NC indexes. ' +
(SELECT CAST(COUNT(*) AS NVARCHAR(23)) FROM #IndexSanity i2
WHERE i2.[object_id]=i.[object_id] AND i2.index_id <> 1
AND i2.is_disabled=0 AND i2.is_hypothetical=0)
+ N' NC indexes on the table.'
AS details,
i.index_definition,
secret_columns,
i.index_usage_summary,
ip.index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
WHERE index_id = 1 /* clustered only */
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND is_unique=0 /* not unique */
AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE )












END
----------------------------------------
--Feature-Phobic Indexes: Check_id 30-39
----------------------------------------
BEGIN
RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT;




DECLARE @number_indexes_with_includes INT;
DECLARE @percent_indexes_with_includes NUMERIC(10, 1);




SELECT @number_indexes_with_includes = SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END),
@percent_indexes_with_includes = 100.*
SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) )
FROM #IndexSanity;




IF @number_indexes_with_includes = 0 AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 30 AS check_id,
NULL AS index_sanity_id,
250 AS Priority,
N'Feature-Phobic Indexes' AS findings_group,
N'No indexes use includes' AS finding, 'BrentOzar.com/go/IndexFeatures' AS URL,
N'No indexes use includes' AS details,
@DatabaseName + N' (Entire database)' AS index_definition,
N'' AS secret_columns,
N'N/A' AS index_usage_summary,
N'N/A' AS index_size_summary OPTION ( RECOMPILE );




RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT;
IF @percent_indexes_with_includes <= 3 AND @number_indexes_with_includes > 0 AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 31 AS check_id,
NULL AS index_sanity_id,
150 AS Priority,
N'Feature-Phobic Indexes' AS findings_group,
N'Borderline: Includes are used in < 3% of indexes' AS findings,
@DatabaseName AS [Database Name],
N'BrentOzar.com/go/IndexFeatures' AS URL,
N'Only ' + CAST(@percent_indexes_with_includes AS NVARCHAR(10)) + '% of indexes have includes' AS details,
N'Entire database' AS index_definition,
N'' AS secret_columns,
N'N/A' AS index_usage_summary,
N'N/A' AS index_size_summary OPTION ( RECOMPILE );




RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT;
DECLARE @count_filtered_indexes INT;
DECLARE @count_indexed_views INT;




SELECT @count_filtered_indexes=COUNT(*)
FROM #IndexSanity
WHERE filter_definition <> '' OPTION ( RECOMPILE );




SELECT @count_indexed_views=COUNT(*)
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE is_indexed_view = 1 OPTION ( RECOMPILE );




IF @count_filtered_indexes = 0 AND @count_indexed_views=0 AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 32 AS check_id,
NULL AS index_sanity_id,
250 AS Priority,
N'Feature-Phobic Indexes' AS findings_group,
N'Borderline: No filtered indexes or indexed views exist' AS finding,
@DatabaseName AS [Database Name],
N'BrentOzar.com/go/IndexFeatures' AS URL,
N'These are NOT always needed-- but do you know when you would use them?' AS details,
@DatabaseName + N' (Entire database)' AS index_definition,
N'' AS secret_columns,
N'N/A' AS index_usage_summary,
N'N/A' AS index_size_summary OPTION ( RECOMPILE );
END;




RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT;




INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 33 AS check_id,
i.index_sanity_id AS index_sanity_id,
250 AS Priority,
N'Feature-Phobic Indexes' AS findings_group,
N'Potential filtered index (based on column name)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/IndexFeatures' AS URL,
N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexColumns ic
JOIN #IndexSanity i ON
ic.[object_id]=i.[object_id] AND
ic.[index_id]=i.[index_id] AND
i.[index_id] > 1 /* non-clustered index */
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE (column_name LIKE 'is%'
OR column_name LIKE '%archive%'
OR column_name LIKE '%active%'
OR column_name LIKE '%flag%')
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );

----------------------------------------
--Self Loathing Indexes : Check_id 40-49
----------------------------------------
BEGIN

RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 40 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Low Fill Factor: nonclustered index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+
CASE WHEN (last_user_update IS NULL OR user_updates < 1)
THEN N'No writes have been made.'
ELSE
N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' +
CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.'
END
AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE index_id > 1
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE );




RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 40 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Low Fill Factor: clustered index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+
CASE WHEN (last_user_update IS NULL OR user_updates < 1)
THEN N'No writes have been made.'
ELSE
N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' +
CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.'
END
AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
WHERE index_id = 1
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE );








RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 41 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Hypothetical Index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
N'Hypothetical Index: ' + db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
N'' AS index_usage_summary,
N'' AS index_size_summary
FROM #IndexSanity AS i
WHERE is_hypothetical = 1
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );








RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT;
--Note: disabled NC indexes will have O rows in #IndexSanitySize!
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 42 AS check_id,
index_sanity_id,
150 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Disabled Index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
N'Disabled Index:' + db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
'DISABLED' AS index_size_summary
FROM #IndexSanity AS i
WHERE is_disabled = 1
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );




RAISERROR(N'check_id 43: Heaps with forwarded records or deletes', 0,1) WITH NOWAIT;
WITH heaps_cte
AS ( SELECT [object_id],
SUM(forwarded_fetch_count) AS forwarded_fetch_count,
SUM(leaf_delete_count) AS leaf_delete_count
FROM #IndexPartitionSanity
GROUP BY [object_id]
HAVING SUM(forwarded_fetch_count) > 0
OR SUM(leaf_delete_count) > 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 43 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Heaps with forwarded records or deletes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + ' forwarded fetches, '
+ CAST(h.leaf_delete_count AS NVARCHAR(256)) + ' deletes against heap:'
+ db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity i
JOIN heaps_cte h ON i.[object_id] = h.[object_id]
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.index_id = 0
AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END
OPTION ( RECOMPILE );




RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT;
WITH heaps_cte
AS ( SELECT [object_id], SUM(forwarded_fetch_count) AS forwarded_fetch_count,
SUM(leaf_delete_count) AS leaf_delete_count
FROM #IndexPartitionSanity
GROUP BY [object_id]
HAVING SUM(forwarded_fetch_count) > 0
OR SUM(leaf_delete_count) > 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 44 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Large Active heap' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
N'Should this table be a heap? ' + db_schema_object_indexid AS details,
i.index_definition,
'N/A' AS secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity i
LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id]
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.index_id = 0
AND
(i.total_reads > 0 OR i.user_updates > 0)
AND sz.total_rows >= 100000
AND h.[object_id] IS NULL /*don't duplicate the prior check.*/
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );




RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT;
WITH heaps_cte
AS ( SELECT [object_id], SUM(forwarded_fetch_count) AS forwarded_fetch_count,
SUM(leaf_delete_count) AS leaf_delete_count
FROM #IndexPartitionSanity
GROUP BY [object_id]
HAVING SUM(forwarded_fetch_count) > 0
OR SUM(leaf_delete_count) > 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 45 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Medium Active heap' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
N'Should this table be a heap? ' + db_schema_object_indexid AS details,
i.index_definition,
'N/A' AS secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity i
LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id]
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.index_id = 0
AND
(i.total_reads > 0 OR i.user_updates > 0)
AND sz.total_rows >= 10000 AND sz.total_rows < 100000
AND h.[object_id] IS NULL /*don't duplicate the prior check.*/
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );




RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT;
WITH heaps_cte
AS ( SELECT [object_id], SUM(forwarded_fetch_count) AS forwarded_fetch_count,
SUM(leaf_delete_count) AS leaf_delete_count
FROM #IndexPartitionSanity
GROUP BY [object_id]
HAVING SUM(forwarded_fetch_count) > 0
OR SUM(leaf_delete_count) > 0)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 46 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Small Active heap' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
N'Should this table be a heap? ' + db_schema_object_indexid AS details,
i.index_definition,
'N/A' AS secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity i
LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id]
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.index_id = 0
AND
(i.total_reads > 0 OR i.user_updates > 0)
AND sz.total_rows < 10000
AND h.[object_id] IS NULL /*don't duplicate the prior check.*/
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );




RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 47 AS check_id,
i.index_sanity_id,
100 AS Priority,
N'Self Loathing Indexes' AS findings_group,
N'Heap with a Nonclustered Primary Key' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/SelfLoathing' AS URL,
db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
sz.index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.index_type = 2 AND i.is_primary_key = 1 AND i.secret_columns LIKE '%RID%'
OPTION ( RECOMPILE );




END;
----------------------------------------
--Indexaphobia
--Missing indexes with value >= 5 million: : Check_id 50-59
----------------------------------------
BEGIN
RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT;
WITH index_size_cte
AS ( SELECT i.database_id,
i.[object_id],
MAX(i.index_sanity_id) AS index_sanity_id,
ISNULL (
CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END)
AS NVARCHAR(30))+ N' NC indexes exist (' +
CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024
THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024.




AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); '
ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END)
AS NVARCHAR(30)) + N'MB); '
END +
CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477'
ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '')
END +
+ N' Estimated Rows;'
,N'') AS index_size_summary
FROM #IndexSanity AS i
LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id
WHERE i.is_hypothetical = 0
AND i.is_disabled = 0
GROUP BY i.database_id, i.[object_id])
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
index_usage_summary, index_size_summary, create_tsql, more_info )

SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition],
index_estimated_impact, t.index_size_summary, create_tsql, more_info
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum,
50 AS check_id,
sz.index_sanity_id,
10 AS Priority,
N'Indexaphobia' AS findings_group,
N'High value missing index' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/Indexaphobia' AS URL,
mi.[statement] +
N' Est. benefit per day: ' +
CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477'
ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(
(magic_benefit_number/@DaysUptime)
AS BIGINT) AS MONEY), 1), '.00', '')
END AS details,
missing_index_details AS [definition],
index_estimated_impact,
sz.index_size_summary,
mi.create_tsql,
mi.more_info,
magic_benefit_number
FROM #MissingIndexes mi
LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id AND DB_ID(mi.database_name) = sz.database_id
/* Minimum benefit threshold = 100k/day of uptime */
WHERE ( @Mode = 4 AND (magic_benefit_number/@DaysUptime) >= 100000 ) OR (magic_benefit_number/@DaysUptime) >= 100000
) AS t
WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END
ORDER BY magic_benefit_number DESC








END
----------------------------------------
--Abnormal Psychology : Check_id 60-79
----------------------------------------
BEGIN
RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 60 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'XML Indexes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
N'' AS index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.is_XML = 1 OPTION ( RECOMPILE );




RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 61 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
CASE WHEN i.is_NC_columnstore=1
THEN N'NC Columnstore Index'
ELSE N'Clustered Columnstore Index'
END AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1
OPTION ( RECOMPILE );








RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 62 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Spatial indexes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.is_spatial = 1 OPTION ( RECOMPILE );




RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 63 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Compressed indexes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' OPTION ( RECOMPILE );




RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 64 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Partitioned indexes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.partition_key_column_name IS NOT NULL OPTION ( RECOMPILE );




RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 65 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Non-Aligned index on a partitioned table' AS finding,
i.[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanity AS iParent ON
i.[object_id]=iParent.[object_id]
AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */
AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.partition_key_column_name IS NULL
OPTION ( RECOMPILE );




RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 66 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Recently created tables/indexes (1 week)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid + N' was created on ' +
CONVERT(NVARCHAR(16),i.create_date,121) +
N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.'
AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.create_date >= DATEADD(dd,-7,GETDATE())
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
OPTION ( RECOMPILE );




RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 67 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Recently modified tables/indexes (2 days)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_indexid + N' was modified on ' +
CONVERT(NVARCHAR(16),i.modify_date,121) +
N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.'
AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.modify_date > DATEADD(dd,-2,GETDATE())
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
AND /*Exclude recently created tables.*/
i.create_date < DATEADD(dd,-7,GETDATE())
OPTION ( RECOMPILE );




RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT;
-- Allowed Ranges:
--int -2,147,483,648 to 2,147,483,647
--smallint -32,768 to 32,768
--tinyint 0 to 255




INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 68 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Identity column within ' +
CAST (calc1.percent_remaining AS NVARCHAR(256))
+ N' percent end of range' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name)
+ N' is an identity with type ' + ic.system_type_name
+ N', last value of '
+ ISNULL(REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(ic.last_value AS BIGINT) AS MONEY), 1), '.00', ''),N'NULL')
+ N', seed of '
+ ISNULL(REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(ic.seed_value AS BIGINT) AS MONEY), 1), '.00', ''),N'NULL')
+ N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256))
+ N', and range of ' +
CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647'
WHEN 'smallint' THEN N'+/- 32,768'
WHEN 'tinyint' THEN N'0 to 255'
END
AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexColumns ic ON
i.object_id=ic.object_id
AND i.index_id IN (0,1) /* heaps and cx only */
AND ic.is_identity=1
AND ic.system_type_name IN ('tinyint', 'smallint', 'int')
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
CROSS APPLY (
SELECT CAST(CASE WHEN ic.increment_value >= 0
THEN
CASE ic.system_type_name
WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100
WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100
WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100
ELSE 999
END
ELSE --ic.increment_value is negative
CASE ic.system_type_name
WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100
WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100
WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100
ELSE -1
END
END AS NUMERIC(5,1)) AS percent_remaining
) AS calc1
WHERE i.index_id IN (1,0)
AND calc1.percent_remaining <= 30
UNION ALL
SELECT 68 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Identity column using a negative seed or increment other than 1' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name)
+ N' is an identity with type ' + ic.system_type_name
+ N', last value of '
+ ISNULL(REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(ic.last_value AS BIGINT) AS MONEY), 1), '.00', ''),N'NULL')
+ N', seed of '
+ ISNULL(REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(ic.seed_value AS BIGINT) AS MONEY), 1), '.00', ''),N'NULL')
+ N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256))
+ N', and range of ' +
CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647'
WHEN 'smallint' THEN N'+/- 32,768'
WHEN 'tinyint' THEN N'0 to 255'
END
AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexColumns ic ON
i.object_id=ic.object_id
AND i.index_id IN (0,1) /* heaps and cx only */
AND ic.is_identity=1
AND ic.system_type_name IN ('tinyint', 'smallint', 'int')
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
WHERE i.index_id IN (1,0)
AND (ic.seed_value < 0 OR ic.increment_value <> 1)
ORDER BY finding, details DESC OPTION ( RECOMPILE );




RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT;
WITH count_columns AS (
SELECT [object_id],
COUNT(*) AS column_count
FROM #IndexColumns ic
WHERE index_id IN (1,0) /*Heap or clustered only*/
AND collation_name <> @collation
GROUP BY object_id
)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 69 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Column collation does not match database collation' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_name
+ N' has ' + CAST(column_count AS NVARCHAR(20))
+ N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END
+ N' with a different collation than the db collation of '
+ @collation AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
JOIN count_columns AS cc ON i.[object_id]=cc.[object_id]
WHERE i.index_id IN (1,0)
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );




RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT;
WITH count_columns AS (
SELECT [object_id],
COUNT(*) AS column_count,
SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count
FROM #IndexColumns ic
WHERE index_id IN (1,0) /*Heap or clustered only*/
GROUP BY object_id
)
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 70 AS check_id,
i.index_sanity_id,
200 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Replicated columns' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
i.db_schema_object_name
+ N' has ' + CAST(replicated_column_count AS NVARCHAR(20))
+ N' out of ' + CAST(column_count AS NVARCHAR(20))
+ N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END
+ N' in one or more publications.'
AS details,
i.index_definition,
secret_columns,
ISNULL(i.index_usage_summary,''),
ISNULL(ip.index_size_summary,'')
FROM #IndexSanity i
JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id
JOIN count_columns AS cc ON i.[object_id]=cc.[object_id]
WHERE i.index_id IN (1,0)
AND replicated_column_count > 0
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE );




RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary, more_info )
SELECT 71 AS check_id,
NULL AS index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
N'Cascading Updates or Deletes' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/AbnormalPsychology' AS URL,
N'Foreign Key ' + foreign_key_name +
N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')'
+ N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')'
+ N' has settings:'
+ CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END
+ CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END
AS details,
[fk].[database_name]
AS index_definition,
N'N/A' AS secret_columns,
N'N/A' AS index_usage_summary,
N'N/A' AS index_size_summary,
(SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id)
AS more_info
FROM #ForeignKeys fk
WHERE ([delete_referential_action_desc] <> N'NO_ACTION'
OR [update_referential_action_desc] <> N'NO_ACTION')
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)




RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT;
IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6))
AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1)
BEGIN
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 72 AS check_id,
i.index_sanity_id,
150 AS Priority,
N'Abnormal Psychology' AS findings_group,
'Columnstore Indexes are being used in conjunction with trace flag 834. Visit the link to see why this can be a bad idea' AS finding,
[database_name] AS [Database Name],
N'support.microsoft.com/en-us/kb/3210239' AS URL,
i.db_schema_object_indexid AS details,
i.index_definition,
i.secret_columns,
i.index_usage_summary,
ISNULL(sz.index_size_summary,'') AS index_size_summary
FROM #IndexSanity AS i
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
WHERE i.index_type IN (5,6)
OPTION ( RECOMPILE )
END




END




----------------------------------------
--Workaholics: Check_id 80-89
----------------------------------------
BEGIN




RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )




--Workaholics according to index_usage_stats
--This isn't perfect: it mentions the number of scans present in a plan
--A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got.
--in the case of things like indexed views, the operator might be in the plan but never executed
SELECT TOP 5
80 AS check_id,
i.index_sanity_id AS index_sanity_id,
200 AS Priority,
N'Workaholics' AS findings_group,
N'Scan-a-lots (index_usage_stats)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/Workaholics' AS URL,
REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','')
+ N' scans against ' + i.db_schema_object_indexid
+ N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. '
+ N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details,
ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition,
ISNULL(i.secret_columns,'') AS secret_columns,
i.index_usage_summary AS index_usage_summary,
iss.index_size_summary AS index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id
WHERE ISNULL(i.user_scans,0) > 0
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
ORDER BY i.user_scans * iss.total_reserved_MB DESC;




RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
--Workaholics according to index_operational_stats
--This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops
--But this can help bubble up some most-accessed tables
SELECT TOP 5
81 AS check_id,
i.index_sanity_id AS index_sanity_id,
200 AS Priority,
N'Workaholics' AS findings_group,
N'Top recent accesses (index_op_stats)' AS finding,
[database_name] AS [Database Name],
N'BrentOzar.com/go/Workaholics' AS URL,
ISNULL(REPLACE(
CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1),
N'.00',N'')
+ N' uses of ' + i.db_schema_object_indexid + N'. '
+ REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. '
+ REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. '
+ N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details,
ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition,
ISNULL(i.secret_columns,'') AS secret_columns,
i.index_usage_summary AS index_usage_summary,
iss.index_size_summary AS index_size_summary
FROM #IndexSanity i
JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id
WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0)
AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC;








END




----------------------------------------
--Statistics Info: Check_id 90-99
----------------------------------------
BEGIN




RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 90 AS check_id,
200 AS Priority,
'Functioning Statistaholics' AS findings_group,
'Statistic Abandonment Issues',
s.database_name,
'' AS URL,
'Statistics on this table were last updated ' +
CASE s.last_statistics_update WHEN NULL THEN N' NEVER '
ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) +
' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) +
' modifications in that time, which is ' +
CONVERT(NVARCHAR(100), s.percent_modifications) +
'% of the table.'
END AS details,
QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #Statistics AS s
WHERE s.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 7)
AND s.percent_modifications >= 10.
AND s.rows >= 10000




RAISERROR(N'check_id 91: Statistics with a low sample rate', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 91 AS check_id,
200 AS Priority,
'Functioning Statistaholics' AS findings_group,
'Antisocial Samples',
s.database_name,
'' AS URL,
'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' AS details,
QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #Statistics AS s
WHERE s.rows_sampled < 1.
AND s.rows >= 10000




RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 92 AS check_id,
200 AS Priority,
'Functioning Statistaholics' AS findings_group,
'Cyberphobic Samples',
s.database_name,
'' AS URL,
'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' AS details,
QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #Statistics AS s
WHERE s.no_recompute = 1




RAISERROR(N'check_id 93: Statistics with filters', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 93 AS check_id,
200 AS Priority,
'Functioning Statistaholics' AS findings_group,
'Filter Fixation',
s.database_name,
'' AS URL,
'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' AS details,
QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #Statistics AS s
WHERE s.has_filter = 1




END




----------------------------------------
--Computed Column Info: Check_id 90-99
----------------------------------------
BEGIN




RAISERROR(N'check_id 99: Computed Columns That Reference Functions', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 99 AS check_id,
50 AS Priority,
'Cold Calculators' AS findings_group,
'Serial Forcer' AS finding,
cc.database_name,
'' AS URL,
'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition
+ '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details,
cc.column_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #ComputedColumns AS cc
WHERE cc.is_function = 1




RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 100 AS check_id,
200 AS Priority,
'Cold Calculators' AS findings_group,
'Definition Defeatists' AS finding,
cc.database_name,
'' AS URL,
'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' +
'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name +
' ADD PERSISTED' AS details,
cc.column_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #ComputedColumns AS cc
WHERE cc.is_persisted = 0




END


RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT;
IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6
BEGIN
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( -1, 0 ,
'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'FirstResponderKit.org/',
'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.',
N'',N'',N''
);
END




IF EXISTS(SELECT * FROM #BlitzIndexResults)
BEGIN
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( -1, 0 ,
@ScriptVersionName,
CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END,
N'From Your Community Volunteers' , N'FirstResponderKit.org' ,
N''
, N'',N''
);
END
ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4)
BEGIN
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( -1, 0 ,
@ScriptVersionName,
CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END,
N'From Your Community Volunteers' , N'FirstResponderKit.org' ,
N''
, N'',N''
);
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( 1, 0 ,
'No Major Problems Found',
'Nice Work!',
'FirstResponderKit.org', 'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', 'The new default Mode 0 only looks for very serious index issues.', '', ''
);




END
ELSE
BEGIN
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( -1, 0 ,
@ScriptVersionName,
CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END,
N'From Your Community Volunteers' , N'BrentOzar.com/BlitzIndex' ,
N''
, N'',N''
);
INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
index_usage_summary, index_size_summary )
VALUES ( 1, 0 ,
'No Problems Found',
'Nice job! Or more likely, you have a nearly empty database.',
'FirstResponderKit.org', 'Time to go read some blog posts.', '', '', ''
);




END




RAISERROR(N'Returning results.', 0,1) WITH NOWAIT;

/*Return results.*/
IF (@Mode = 0)
BEGIN




SELECT Priority, ISNULL(br.findings_group,N'') +
CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END
+ br.finding AS [Finding],
br.[database_name] AS [Database Name],
br.details AS [Details: schema.table.index(indexid)],
br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}],
ISNULL(br.secret_columns,'') AS [Secret Columns],
br.index_usage_summary AS [Usage],
br.index_size_summary AS [Size],
COALESCE(br.more_info,sn.more_info,'') AS [More Info],
br.URL,
COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL]
FROM #BlitzIndexResults br
LEFT JOIN #IndexSanity sn ON
br.index_sanity_id=sn.index_sanity_id
LEFT JOIN #IndexCreateTsql ts ON
br.index_sanity_id=ts.index_sanity_id
WHERE br.check_id IN (0, 1, 11, 22, 43, 68, 50, 60, 61, 62, 63, 64, 65, 72)
ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC
OPTION (RECOMPILE);




END
ELSE IF (@Mode = 4)
SELECT Priority, ISNULL(br.findings_group,N'') +
CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END
+ br.finding AS [Finding],
br.[database_name] AS [Database Name],
br.details AS [Details: schema.table.index(indexid)],
br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}],
ISNULL(br.secret_columns,'') AS [Secret Columns],
br.index_usage_summary AS [Usage],
br.index_size_summary AS [Size],
COALESCE(br.more_info,sn.more_info,'') AS [More Info],
br.URL,
COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL]
FROM #BlitzIndexResults br
LEFT JOIN #IndexSanity sn ON
br.index_sanity_id=sn.index_sanity_id
LEFT JOIN #IndexCreateTsql ts ON
br.index_sanity_id=ts.index_sanity_id
ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC
OPTION (RECOMPILE);




END; /* End @Mode=0 or 4 (diagnose)*/
ELSE IF @Mode=1 /*Summarize*/
BEGIN
--This mode is to give some overall stats on the database.
RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT;




SELECT DB_NAME(i.database_id) AS [Database Name],
CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects],
CAST(CAST(SUM(sz.total_reserved_MB)/
1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB],
CAST(CAST(SUM(sz.total_reserved_LOB_MB)/
1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB],
CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/
1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB],
CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables],
CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END)
/1024. AS NUMERIC(29,1)) AS [Clustered Tables GB],
SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes],
CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END)
/1024. AS NUMERIC(29,1)) AS [NC Indexes GB],
CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN
CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END)
/ SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1))
ELSE 0 END AS [ratio table: NC Indexes],
SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps],
CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END)
/1024. AS NUMERIC(29,1)) AS [Heaps GB],
SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables],
SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs],
CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB],
SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes],
SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views],
MAX(total_rows) AS [Max Row Count],
CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END)
/1024. AS NUMERIC(29,1)) AS [Max Table GB],
CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END)
/1024. AS NUMERIC(29,1)) AS [Max NC Index GB],
SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB],
SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB],
SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB],
SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB],
SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB],
SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB],
MIN(create_date) AS [Oldest Create Date],
MAX(create_date) AS [Most Recent Create Date],
MAX(modify_date) AS [Most Recent Modify Date],
1 AS [Display Order]
FROM #IndexSanity AS i
--left join here so we don't lose disabled nc indexes
LEFT JOIN #IndexSanitySize AS sz
ON i.index_sanity_id=sz.index_sanity_id
GROUP BY DB_NAME(i.database_id)
UNION ALL
SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END,
@ScriptVersionName,
N'From Your Community Volunteers' ,
N'FirstResponderKit.org' ,
N'',
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
NULL,NULL,0 AS display_order
ORDER BY [Display Order] ASC
OPTION (RECOMPILE);

END /* End @Mode=1 (summarize)*/
ELSE IF @Mode=2 /*Index Detail*/
BEGIN
--This mode just spits out all the detail without filters.
--This supports slicing AND dicing in Excel
RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT;




SELECT [database_name] AS [Database Name],
[schema_name] AS [Schema Name],
[object_name] AS [Object Name],
ISNULL(index_name, '') AS [Index Name],
CAST(index_id AS VARCHAR(10))AS [Index ID],
db_schema_object_indexid AS [Details: schema.table.index(indexid)],
CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE'
ELSE 'NonClustered'
END AS [Object Type],
index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}],
ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort],
ISNULL(count_key_columns, 0) AS [Count Key Columns],
ISNULL(include_column_names, '') AS [Include Column Names],
ISNULL(count_included_columns,0) AS [Count Included Columns],
ISNULL(secret_columns,'') AS [Secret Column Names],
ISNULL(count_secret_columns,0) AS [Count Secret Columns],
ISNULL(partition_key_column_name, '') AS [Partition Key Column Name],
ISNULL(filter_definition, '') AS [Filter Definition],
is_indexed_view AS [Is Indexed View],
is_primary_key AS [Is Primary Key],
is_XML AS [Is XML],
is_spatial AS [Is Spatial],
is_NC_columnstore AS [Is NC Columnstore],
is_CX_columnstore AS [Is CX Columnstore],
is_disabled AS [Is Disabled],
is_hypothetical AS [Is Hypothetical],
is_padded AS [Is Padded],
fill_factor AS [Fill Factor],
is_referenced_by_foreign_key AS [Is Reference by Foreign Key],
last_user_seek AS [Last User Seek],
last_user_scan AS [Last User Scan],
last_user_lookup AS [Last User Lookup],
last_user_update AS [Last User Update],
total_reads AS [Total Reads],
user_updates AS [User Updates],
reads_per_write AS [Reads Per Write],
index_usage_summary AS [Index Usage],
sz.partition_count AS [Partition Count],
sz.total_rows AS [Rows],
sz.total_reserved_MB AS [Reserved MB],
sz.total_reserved_LOB_MB AS [Reserved LOB MB],
sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB],
sz.index_size_summary AS [Index Size],
sz.total_row_lock_count AS [Row Lock Count],
sz.total_row_lock_wait_count AS [Row Lock Wait Count],
sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms],
sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms],
sz.total_page_lock_count AS [Page Lock Count],
sz.total_page_lock_wait_count AS [Page Lock Wait Count],
sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms],
sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms],
sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts],
sz.total_index_lock_promotion_count AS [Lock Escalations],
sz.data_compression_desc AS [Data Compression],
i.create_date AS [Create Date],
i.modify_date AS [Modify Date],
more_info AS [More Info],
1 AS [Display Order]
FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes
LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID]
OPTION (RECOMPILE);












END /* End @Mode=2 (index detail)*/
ELSE IF @Mode=3 /*Missing index Detail*/
BEGIN
SELECT
database_name AS [Database Name],
[schema_name] AS [Schema],
table_name AS [Table],
CAST((magic_benefit_number/@DaysUptime) AS BIGINT)
AS [Magic Benefit Number],
missing_index_details AS [Missing Index Details],
avg_total_user_cost AS [Avg Query Cost],
avg_user_impact AS [Est Index Improvement],
user_seeks AS [Seeks],
user_scans AS [Scans],
unique_compiles AS [Compiles],
equality_columns AS [Equality Columns],
inequality_columns AS [Inequality Columns],
included_columns AS [Included Columns],
index_estimated_impact AS [Estimated Impact],
create_tsql AS [Create TSQL],
more_info AS [More Info],
1 AS [Display Order]
FROM #MissingIndexes
/* Minimum benefit threshold = 100k/day of uptime */
WHERE (magic_benefit_number/@DaysUptime) >= 100000
UNION ALL
SELECT
@ScriptVersionName,
N'From Your Community Volunteers' ,
N'FirstResponderKit.org' ,
100000000000,
N'',
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
NULL, 0 AS display_order
ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC
OPTION (RECOMPILE);




END /* End @Mode=3 (index detail)*/
END
END TRY




BEGIN CATCH
RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT;




SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();




RAISERROR (@msg,
@ErrorSeverity,
@ErrorState
);

WHILE @@trancount > 0
ROLLBACK;


RETURN;
END CATCH;


GO




That is enought even as an processional DBA .


You can find more at

 

posted @ 2020-02-16 19:44  怕提神  阅读(640)  评论(0)    收藏  举报