IF OBJECT_ID('dbo.sp_Blitz') IS NULL
  EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;');
GO

ALTER PROCEDURE [dbo].[sp_Blitz]
    @Help TINYINT = 0 ,
    @CheckUserDatabaseObjects TINYINT = 1 ,
    @CheckProcedureCache TINYINT = 0 ,
    @OutputType VARCHAR(20) = 'TABLE' ,
    @OutputProcedureCache TINYINT = 0 ,
    @CheckProcedureCacheFilter VARCHAR(10) = NULL ,
    @CheckServerInfo TINYINT = 0 ,
    @SkipChecksServer NVARCHAR(256) = NULL ,
    @SkipChecksDatabase NVARCHAR(256) = NULL ,
    @SkipChecksSchema NVARCHAR(256) = NULL ,
    @SkipChecksTable NVARCHAR(256) = NULL ,
    @IgnorePrioritiesBelow INT = NULL ,
    @IgnorePrioritiesAbove INT = NULL ,
    @OutputServerName NVARCHAR(256) = NULL ,
    @OutputDatabaseName NVARCHAR(256) = NULL ,
    @OutputSchemaName NVARCHAR(256) = NULL ,
    @OutputTableName NVARCHAR(256) = NULL ,
    @OutputXMLasNVARCHAR TINYINT = 0 ,
    @EmailRecipients VARCHAR(MAX) = NULL ,
    @EmailProfile sysname = NULL ,
    @SummaryMode TINYINT = 0 ,
    @BringThePain TINYINT = 0 ,
    @UsualDBOwner sysname = NULL ,
	@UsualOwnerOfJobs sysname = NULL , -- This is to set the owner of Jobs is you have a different account than SA that you use as Default
	@SkipBlockingChecks TINYINT = 1 ,
    @Debug TINYINT = 0 ,
    @Version     VARCHAR(30) = NULL OUTPUT,
	@VersionDate DATETIME = NULL OUTPUT,
    @VersionCheckMode BIT = 0
WITH RECOMPILE
AS
    SET NOCOUNT ON;
	SET STATISTICS XML OFF;
	SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
	

	SELECT @Version = '8.29', @VersionDate = '20260203';
	SET @OutputType = UPPER(@OutputType);

    IF(@VersionCheckMode = 1)
	BEGIN
		RETURN;
	END;

	IF @Help = 1 
	BEGIN
	PRINT '
	/*
	sp_Blitz from http://FirstResponderKit.org
	
	This script checks the health of your SQL Server and gives you a prioritized
	to-do list of the most urgent things you should consider fixing.

	To learn more, visit http://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.
	 - If a database name has a question mark in it, some tests will fail. Gotta
	   love that unsupported sp_MSforeachdb.
	 - If you have offline databases, sp_Blitz fails the first time you run it,
	   but does work the second time. (Hoo, boy, this will be fun to debug.)
      - @OutputServerName will output QueryPlans as NVARCHAR(MAX) since Microsoft
	    has refused to support XML columns in Linked Server queries. The bug is now
		16 years old! *~ \o/ ~*

	Unknown limitations of this version:
	 - None.  (If we knew them, they would be known. Duh.)

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

	Parameter explanations:

	@CheckUserDatabaseObjects	1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects.
	@CheckServerInfo			1=show server info like CPUs, memory, virtualization
	@CheckProcedureCache		1=top 20-50 resource-intensive cache plans and analyze them for common performance issues.
	@OutputProcedureCache		1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm
	@CheckProcedureCacheFilter	''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount''
	@OutputType					''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none
	@IgnorePrioritiesBelow		50=ignore priorities below 50
	@IgnorePrioritiesAbove		50=ignore priorities above 50
	@Debug						0=silent (Default) | 1=messages per step | 2=outputs dynamic queries
	For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details.

    MIT License
	
	Copyright for portions of sp_Blitz are held by Microsoft as part of project
	tigertoolbox and are provided under the MIT license:
	https://github.com/Microsoft/tigertoolbox
	
	All other copyrights for sp_Blitz are held by Brent Ozar Unlimited.

	Copyright (c) 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.

	*/';
	RETURN;
	END;    /* @Help = 1 */

	ELSE IF @OutputType = 'SCHEMA'
	BEGIN
		SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT';

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

		DECLARE @StringToExecute NVARCHAR(4000)
			,@curr_tracefilename NVARCHAR(500)
			,@base_tracefilename NVARCHAR(500)
			,@indx int
			,@query_result_separator CHAR(1)
			,@EmailSubject NVARCHAR(255)
			,@EmailBody NVARCHAR(MAX)
			,@EmailAttachmentFilename NVARCHAR(255)
			,@ProductVersion NVARCHAR(128)
			,@ProductVersionMajor DECIMAL(10,2)
			,@ProductVersionMinor DECIMAL(10,2)
			,@CurrentName NVARCHAR(128)
			,@CurrentDefaultValue NVARCHAR(200)
			,@CurrentCheckID INT
			,@CurrentPriority INT
			,@CurrentFinding VARCHAR(200)
			,@CurrentURL VARCHAR(200)
			,@CurrentDetails NVARCHAR(4000)
			,@MsSinceWaitsCleared DECIMAL(38,0)
			,@CpuMsSinceWaitsCleared DECIMAL(38,0)
			,@ResultText NVARCHAR(MAX)
			,@crlf NVARCHAR(2)
			,@Processors int
			,@NUMANodes int
			,@MinServerMemory bigint
			,@MaxServerMemory bigint
			,@ColumnStoreIndexesInUse bit
		    ,@QueryStoreInUse bit
			,@TraceFileIssue bit
			-- Flag for Windows OS to help with Linux support
			,@IsWindowsOperatingSystem BIT
			,@DaysUptime NUMERIC(23,2)
            /* For First Responder Kit consistency check:*/
            ,@spBlitzFullName                VARCHAR(1024)
            ,@BlitzIsOutdatedComparedToOthers BIT
            ,@tsql                           NVARCHAR(MAX)
            ,@VersionCheckModeExistsTSQL     NVARCHAR(MAX)
            ,@BlitzProcDbName                VARCHAR(256)
            ,@ExecRet                        INT
            ,@InnerExecRet                   INT
            ,@TmpCnt                         INT
            ,@PreviousComponentName          VARCHAR(256)
            ,@PreviousComponentFullPath      VARCHAR(1024)
            ,@CurrentStatementId             INT
            ,@CurrentComponentSchema         VARCHAR(256)
            ,@CurrentComponentName           VARCHAR(256)
            ,@CurrentComponentType           VARCHAR(256)
            ,@CurrentComponentVersionDate    DATETIME2
            ,@CurrentComponentFullName       VARCHAR(1024)
            ,@CurrentComponentMandatory      BIT
            ,@MaximumVersionDate             DATETIME
            ,@StatementCheckName             VARCHAR(256)
            ,@StatementOutputsCounter        BIT
            ,@OutputCounterExpectedValue     INT
            ,@StatementOutputsExecRet        BIT
            ,@StatementOutputsDateTime       BIT
            ,@CurrentComponentMandatoryCheckOK       BIT
            ,@CurrentComponentVersionCheckModeOK     BIT
            ,@canExitLoop                            BIT
            ,@frkIsConsistent                        BIT
			,@NeedToTurnNumericRoundabortBackOn      BIT
			,@sa bit = 1
			,@SUSER_NAME sysname = SUSER_SNAME()
			,@SkipDBCC bit = 0
			,@SkipTrace bit = 0
			,@SkipXPRegRead bit = 0
			,@SkipXPFixedDrives bit = 0
			,@SkipXPCMDShell bit = 0
			,@SkipMaster bit = 0
			,@SkipMSDB_objs bit = 0
            ,@SkipMSDB_jobs bit = 0
			,@SkipModel bit = 0
			,@SkipTempDB bit = 0
			,@SkipValidateLogins bit = 0
      ,@SkipGetAlertInfo bit = 0

			DECLARE
			    @db_perms table
            (
			    database_name sysname,
				permission_name sysname
			);

			INSERT
			    @db_perms
			(
			    database_name,
			    permission_name
			)
            SELECT
                database_name =
				    DB_NAME(d.database_id),
                fmp.permission_name
            FROM sys.databases AS d
            CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp
            WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/
            
            /* End of declarations for First Responder Kit consistency check:*/
        ;

        /* Create temp table for check 73 */
        IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL
			EXEC sp_executesql N'DROP TABLE #AlertInfo;';

        CREATE TABLE #AlertInfo
		(
		    FailSafeOperator NVARCHAR(255) ,
		    NotificationMethod INT ,
		    ForwardingServer NVARCHAR(255) ,
		    ForwardingSeverity INT ,
		    PagerToTemplate NVARCHAR(255) ,
		    PagerCCTemplate NVARCHAR(255) ,
		    PagerSubjectTemplate NVARCHAR(255) ,
		    PagerSendSubjectOnly NVARCHAR(255) ,
		    ForwardAlways INT
		);

		/* Create temp table for check 2301 */
		IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL
			EXEC sp_executesql N'DROP TABLE #InvalidLogins;';
								 
		CREATE TABLE #InvalidLogins
		(
			LoginSID    varbinary(85),
			LoginName   VARCHAR(256)
		);

		/*Starting permissions checks here, but only if we're not a sysadmin*/
		IF
        (
            SELECT 
                sa = 
                    ISNULL
                    (
                        IS_SRVROLEMEMBER(N'sysadmin'), 
                        0
                    )
        ) = 0
        BEGIN
			IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT;
		    
			SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/

		    IF NOT EXISTS
		    (
		        SELECT 
                    1/0 
                FROM sys.fn_my_permissions(NULL, NULL) AS fmp 
                WHERE fmp.permission_name = N'VIEW SERVER STATE'
		    )
			BEGIN
			    RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT;
				RETURN;
			END; /*If we don't have this, we can't do anything at all.*/

            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM fn_my_permissions(NULL, NULL) AS fmp
                WHERE fmp.permission_name = N'ALTER TRACE'
            )
            BEGIN
                SET @SkipTrace = 1;
            END; /*We need this permission to execute trace stuff, apparently*/

            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp
                WHERE fmp.permission_name = N'EXECUTE'
            )
            BEGIN
                SET @SkipXPFixedDrives = 1;
            END; /*Need execute on xp_fixeddrives*/

            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp
                WHERE fmp.permission_name = N'EXECUTE'
            )
            BEGIN
                SET @SkipXPCMDShell = 1;
            END; /*Need execute on xp_cmdshell*/

			IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/
			BEGIN
			    BEGIN TRY
			        /* Try to fill the table for check 2301 */
					INSERT INTO #InvalidLogins
			    	(
			    		[LoginSID]
			    		,[LoginName]
			    	)
			    	EXEC sp_validatelogins;
			
			    	SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/
			    END TRY
			    BEGIN CATCH
			    	SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/
			    END CATCH;
			END; /*Need execute on sp_validatelogins*/
            
			IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM fn_my_permissions(N'[master].[dbo].[sp_MSgetalertinfo]', N'OBJECT') AS fmp
                WHERE fmp.permission_name = N'EXECUTE'
            )
            BEGIN
			    SET @SkipGetAlertInfo = 1;
			END; /*Need execute on sp_MSgetalertinfo*/

			IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/
			BEGIN
				IF EXISTS
            	(
            	    SELECT 1/0
            	    FROM @db_perms
            	    WHERE database_name = N'model'
            	)
            	BEGIN
            	    BEGIN TRY
            	        IF EXISTS
            	        (
            	            SELECT 1/0
            	            FROM model.sys.objects
            	        )
            	        BEGIN
                            SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/
            	        END;
            	    END TRY
            	    BEGIN CATCH
            	        SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/
            	    END CATCH;
            	END;
            	ELSE
            	BEGIN
            	    SET @SkipModel = 1; /*We don't have read permissions in the model database*/
            	END;
			END;

			IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/
			BEGIN
				IF EXISTS
				(
					SELECT	1/0
            	    FROM	@db_perms
            	    WHERE	database_name = N'msdb'
				)
				BEGIN
					BEGIN TRY
						IF EXISTS
						(
            	            SELECT	1/0
            	            FROM	msdb.sys.objects
						)
						BEGIN
							SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/
						END;
					END TRY
					BEGIN CATCH
						SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/
					END CATCH;
				END;
				ELSE
				BEGIN
					SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/
				END;
			END;

            IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/
			BEGIN
				IF EXISTS
				(
					SELECT	1/0
            	    FROM	@db_perms
            	    WHERE	database_name = N'msdb'
				)
				BEGIN
					BEGIN TRY
						IF EXISTS
						(
            	            SELECT	1/0
            	            FROM	msdb.dbo.sysjobs
						)
						BEGIN
							SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/
						END;
					END TRY
					BEGIN CATCH
						SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/
					END CATCH;
				END;
				ELSE
				BEGIN
					SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/
				END;
			END;
		END;

		SET @crlf = NCHAR(13) + NCHAR(10);
		SET @ResultText = 'sp_Blitz Results: ' + @crlf;

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

		/* 
		Set the session state of Numeric_RoundAbort to off if any databases have Numeric Round-Abort enabled.  
		Stops arithmetic overflow errors during data conversion. See Github issue #2302 for more info.
		*/
		IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on, so we may need to turn it off temporarily */
			BEGIN
			IF EXISTS (SELECT 1 
							FROM sys.databases
							WHERE is_numeric_roundabort_on = 1) /* A database has it turned on */
				BEGIN
				SET @NeedToTurnNumericRoundabortBackOn = 1;
				SET NUMERIC_ROUNDABORT OFF;
				END;
			END;
	



		/*
		--TOURSTOP01--
		See https://www.BrentOzar.com/go/blitztour for a guided tour.

		We start by creating #BlitzResults. It's a temp table that will store all of
		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.

		#BlitzResults 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.
		For a list of checks, visit http://FirstResponderKit.org.
		*/
		IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL
			DROP TABLE #BlitzResults;
		CREATE TABLE #BlitzResults
			(
			  ID INT IDENTITY(1, 1) ,
			  CheckID INT ,
			  DatabaseName NVARCHAR(128) ,
			  Priority TINYINT ,
			  FindingsGroup VARCHAR(50) ,
			  Finding VARCHAR(200) ,
			  URL VARCHAR(200) ,
			  Details NVARCHAR(4000) ,
			  QueryPlan [XML] NULL ,
			  QueryPlanFiltered [NVARCHAR](MAX) NULL
			);

		IF OBJECT_ID('tempdb..#TemporaryDatabaseResults') IS NOT NULL
			DROP TABLE #TemporaryDatabaseResults;
		CREATE TABLE #TemporaryDatabaseResults
			(
			  DatabaseName NVARCHAR(128) ,
			  Finding NVARCHAR(128)
			);

        /* First Responder Kit consistency (temporary tables) */
        
        IF(OBJECT_ID('tempdb..#FRKObjects') IS NOT NULL)
        BEGIN
            EXEC sp_executesql N'DROP TABLE #FRKObjects;';
        END;    

        -- this one represents FRK objects
        CREATE TABLE #FRKObjects (
            DatabaseName                VARCHAR(256)    NOT NULL,
            ObjectSchemaName            VARCHAR(256)    NULL,
            ObjectName                  VARCHAR(256)    NOT NULL,
            ObjectType                  VARCHAR(256)    NOT NULL,
            MandatoryComponent          BIT             NOT NULL
        );
        
        
        IF(OBJECT_ID('tempdb..#StatementsToRun4FRKVersionCheck') IS NOT NULL)
        BEGIN
            EXEC sp_executesql N'DROP TABLE #StatementsToRun4FRKVersionCheck;';
        END;


        -- This one will contain the statements to be executed
        -- order: 1- Mandatory, 2- VersionCheckMode, 3- VersionCheck

        CREATE TABLE #StatementsToRun4FRKVersionCheck (
            StatementId                 INT IDENTITY(1,1),
            CheckName                   VARCHAR(256),
            SubjectName                 VARCHAR(256),
            SubjectFullPath             VARCHAR(1024),
            StatementText               NVARCHAR(MAX),
            StatementOutputsCounter     BIT,
            OutputCounterExpectedValue  INT,
            StatementOutputsExecRet     BIT,
            StatementOutputsDateTime    BIT 
        );

        /* End of First Responder Kit consistency (temporary tables) */
        
            
		/*
		You can build your own table with a list of checks to skip. For example, you
		might have some databases that you don't care about, or some checks you don't
		want to run. Then, when you run sp_Blitz, you can specify these parameters:
		@SkipChecksDatabase = 'DBAtools',
		@SkipChecksSchema = 'dbo',
		@SkipChecksTable = 'BlitzChecksToSkip'
		Pass in the database, schema, and table that contains the list of checks you
		want to skip. This part of the code checks those parameters, gets the list,
		and then saves those in a temp table. As we run each check, we'll see if we
		need to skip it.
		*/
		/* --TOURSTOP07-- */
		IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL
			DROP TABLE #SkipChecks;
		CREATE TABLE #SkipChecks
			(
			  DatabaseName NVARCHAR(128) ,
			  CheckID INT ,
			  ServerName NVARCHAR(128)
			);
		CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName);

        INSERT INTO #SkipChecks
		    (DatabaseName)
        SELECT
		    DB_NAME(d.database_id)
        FROM sys.databases AS d
        WHERE LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance', 'rdsadmin')
		OPTION(RECOMPILE);

		/*Skip checks for database where we don't have read permissions*/
		INSERT INTO
		    #SkipChecks
		(
		    DatabaseName
		)
		SELECT
		    DB_NAME(d.database_id)
		FROM sys.databases AS d
		WHERE NOT EXISTS
		(
		    SELECT
			    1/0
			FROM @db_perms AS dp
			WHERE dp.database_name = DB_NAME(d.database_id)
		);

		/*Skip individial checks where we don't have permissions*/
        INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
        SELECT
            v.*
        FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/
        WHERE @SkipModel = 1;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
			v.*
		FROM (VALUES(NULL,  28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/
		WHERE @SkipMSDB_objs = 1;

        INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
			v.*
		FROM (VALUES
					/*sysjobs checks*/
					(NULL,   6, NULL), /*Jobs Owned By Users*/
					(NULL,  57, NULL), /*SQL Agent Job Runs at Startup*/
					(NULL,  79, NULL), /*Shrink Database Job*/
					(NULL,  94, NULL), /*Agent Jobs Without Failure Emails*/
					(NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/
					(NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/
					(NULL, 181, NULL), /*Repetitive Maintenance Tasks*/
					
					/*sysalerts checks*/
					(NULL,  30, NULL), /*Not All Alerts Configured*/
					(NULL,  59, NULL), /*Alerts Configured without Follow Up*/
                    (NULL,  61, NULL), /*No Alerts for Sev 19-25*/
					(NULL,  96, NULL), /*No Alerts for Corruption*/
					(NULL,  98, NULL), /*Alerts Disabled*/
					(NULL, 219, NULL), /*Alerts Without Event Descriptions*/

					/*sysoperators*/
					(NULL,  31, NULL)  /*No Operators Configured/Enabled*/
            ) AS v (DatabaseName, CheckID, ServerName)
		WHERE @SkipMSDB_jobs = 1;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/
		WHERE @sa = 0;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/
		WHERE @sa = 0;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/
		WHERE @SkipXPFixedDrives = 1;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/
		WHERE @SkipTrace = 1;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/
		WHERE @sa = 0;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/
		WHERE @SkipXPCMDShell = 1;

		INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 2301, NULL))	AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/
		WHERE @SkipValidateLogins = 1;

        INSERT #SkipChecks (DatabaseName, CheckID, ServerName)
		SELECT
		    v.*
		FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/
		WHERE @SkipGetAlertInfo = 1;

		IF @sa = 0
		BEGIN
			INSERT INTO #BlitzResults
			( CheckID ,
			  Priority ,
			  FindingsGroup ,
			  Finding ,
			  URL ,
			  Details
			)
			SELECT 223 AS CheckID ,
			         0 AS Priority ,
			         'Informational' AS FindingsGroup ,
			         'Some Checks Skipped' AS Finding ,
			         '' AS URL ,
			         'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details;
		END;
		/*End of SkipsChecks added due to permissions*/

		IF @SkipChecksTable IS NOT NULL
			AND @SkipChecksSchema IS NOT NULL
			AND @SkipChecksDatabase IS NOT NULL
			BEGIN
				
				IF @Debug IN (1, 2) RAISERROR('Inserting SkipChecks', 0, 1) WITH NOWAIT;
				
				SET @StringToExecute = N'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName )
				SELECT DISTINCT DatabaseName, CheckID, ServerName
				FROM '
				IF LTRIM(RTRIM(@SkipChecksServer)) <> ''
					BEGIN
					SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.';
					END
				SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable)
					+ N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);';
				EXEC(@StringToExecute);
			END;

		-- Flag for Windows OS to help with Linux support
		IF EXISTS ( SELECT  1
						FROM    sys.all_objects
						WHERE   name = 'dm_os_host_info' )
			BEGIN
				SELECT @IsWindowsOperatingSystem = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info ;
			END;
			ELSE
			BEGIN
				SELECT @IsWindowsOperatingSystem = 1 ;
			END;


			IF NOT EXISTS ( SELECT  1
							FROM    #SkipChecks
							WHERE   DatabaseName IS NULL AND CheckID = 106 )
							AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1
			BEGIN

					select @curr_tracefilename = [path] from sys.traces where is_default = 1 ;
					set @curr_tracefilename = reverse(@curr_tracefilename);

					-- Set the trace file path separator based on underlying OS
					IF (@IsWindowsOperatingSystem = 1) AND @curr_tracefilename IS NOT NULL
					BEGIN
						select @indx = patindex('%\%', @curr_tracefilename) ;
						set @curr_tracefilename = reverse(@curr_tracefilename) ;
						set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ;
					END;
					ELSE
					BEGIN
						select @indx = patindex('%/%', @curr_tracefilename) ;
						set @curr_tracefilename = reverse(@curr_tracefilename) ;
						set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '/log.trc' ;
					END;

			END;

		/* If the server has any databases on Antiques Roadshow, skip the checks that would break due to CTEs. */
		IF @CheckUserDatabaseObjects = 1 AND EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90)
		BEGIN
			SET @CheckUserDatabaseObjects = 0;
			PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.';
			PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.';
			PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:';
			PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;';
			INSERT  INTO #BlitzResults
			( CheckID ,
				Priority ,
				FindingsGroup ,
				Finding ,
				URL ,
				Details
			)
			SELECT 204 AS CheckID ,
					0 AS Priority ,
					'Informational' AS FindingsGroup ,
					'@CheckUserDatabaseObjects Disabled' AS Finding ,
					'https://www.BrentOzar.com/blitz/' AS URL ,
					'Since you have databases with compatibility_level < 90, we can''t run @CheckUserDatabaseObjects = 1. To find them: SELECT * FROM sys.databases WHERE compatibility_level < 90' AS Details;
		END;

		/* --TOURSTOP08-- */
		/* If the server is Amazon RDS, skip checks that it doesn't allow */
		IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
		   AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
		   AND db_id('rdsadmin') IS NOT NULL
		   AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change'))
			BEGIN
						INSERT INTO #SkipChecks (CheckID) VALUES (6); /* Security - Jobs Owned By Users per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
						INSERT INTO #SkipChecks (CheckID) VALUES (29); /* tables in model database created by users - not allowed */
						INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */
						INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */
						INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */
						INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */
						INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */
						INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */
						INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */
						INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */
						INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/
						INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/

						-- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check
						--INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */

						INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */
						INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */
						INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */

						-- Following are skipped due to limited permissions in msdb/SQLAgent in RDS
						INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */
						INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */
						INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */
						INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */
						INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/
						INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */
						INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */
						INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */
						INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */
						INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */
						INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */
			            INSERT  INTO #BlitzResults
			            ( CheckID ,
				            Priority ,
				            FindingsGroup ,
				            Finding ,
				            URL ,
				            Details
			            )
			            SELECT 223 AS CheckID ,
					            0 AS Priority ,
					            'Informational' AS FindingsGroup ,
					            'Some Checks Skipped' AS Finding ,
					            'https://aws.amazon.com/rds/sqlserver/' AS URL ,
					            'Amazon RDS detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details;
			END; /* Amazon RDS skipped checks */

		/* If the server is ExpressEdition, skip checks that it doesn't allow */
		IF CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%'
			BEGIN
						INSERT INTO #SkipChecks (CheckID) VALUES (30); /* Alerts not configured */
						INSERT INTO #SkipChecks (CheckID) VALUES (31); /* Operators not configured */
						INSERT INTO #SkipChecks (CheckID) VALUES (61); /* Agent alerts 19-25 */
						INSERT INTO #SkipChecks (CheckID) VALUES (73); /* Failsafe operator */
						INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */
			            INSERT  INTO #BlitzResults
			            ( CheckID ,
				            Priority ,
				            FindingsGroup ,
				            Finding ,
				            URL ,
				            Details
			            )
			            SELECT 223 AS CheckID ,
					            0 AS Priority ,
					            'Informational' AS FindingsGroup ,
					            'Some Checks Skipped' AS Finding ,
					            'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS URL ,
					            'Express Edition detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details;
			END; /* Express Edition skipped checks */

		/* If the server is an Azure Managed Instance, skip checks that it doesn't allow */
		IF SERVERPROPERTY('EngineEdition') = 8
			BEGIN
						INSERT INTO #SkipChecks (CheckID) VALUES (1);  /* Full backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
						INSERT INTO #SkipChecks (CheckID) VALUES (2);  /* Log backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
						INSERT INTO #SkipChecks (CheckID) VALUES (6);  /* Security - Jobs Owned By Users per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
						INSERT INTO #SkipChecks (CheckID) VALUES (21);  /* Informational - Database Encrypted per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
						INSERT INTO #SkipChecks (CheckID) VALUES (24);  /* File Configuration - System Database on C Drive per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
						INSERT INTO #SkipChecks (CheckID) VALUES (30);  /* SQL Agent Alerts cannot be configured on MI */
						INSERT INTO #SkipChecks (CheckID) VALUES (50);  /* Max Server Memory Set Too High - because they max it out */
						INSERT INTO #SkipChecks (CheckID) VALUES (55);  /* Security - Database Owner <> sa per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
						INSERT INTO #SkipChecks (CheckID) VALUES (61);  /* SQL Agent Alerts cannot be configured on MI */
						INSERT INTO #SkipChecks (CheckID) VALUES (73);  /* SQL Agent Failsafe Operator cannot be configured on MI */
						INSERT INTO #SkipChecks (CheckID) VALUES (74);  /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */
						INSERT INTO #SkipChecks (CheckID) VALUES (96);  /* SQL Agent Alerts cannot be configured on MI */
						INSERT INTO #SkipChecks (CheckID) VALUES (97);  /* Unusual SQL Server Edition */
						INSERT INTO #SkipChecks (CheckID) VALUES (100);  /* Remote DAC disabled - but it's working anyway, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
						INSERT INTO #SkipChecks (CheckID) VALUES (186);  /* MSDB Backup History Purged Too Frequently */
						INSERT INTO #SkipChecks (CheckID) VALUES (192);  /* IFI can not be set for data files and is always used for log files in MI */
						INSERT INTO #SkipChecks (CheckID) VALUES (199);  /* Default trace, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
						INSERT INTO #SkipChecks (CheckID) VALUES (211);  /*Power Plan */
						INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'master');  /* Max file size set */
						INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model');  /* Max file size set */
						INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb');  /* Max file size set */
						INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb');  /* Max file size set */
						INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */
						INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */
						INSERT INTO #SkipChecks (CheckID) VALUES (258);/* CheckID 258 - Security - SQL Server service is running as LocalSystem or NT AUTHORITY\SYSTEM */
						INSERT INTO #SkipChecks (CheckID) VALUES (259);/* CheckID 259 - Security - SQL Server Agent service is running as LocalSystem or NT AUTHORITY\SYSTEM */
						INSERT INTO #SkipChecks (CheckID) VALUES (260); /* CheckID 260 - Security - SQL Server service account is member of Administrators */
						INSERT INTO #SkipChecks (CheckID) VALUES (261); /*CheckID 261 - Security - SQL Server Agent service account is member of Administrators */
			            INSERT  INTO #BlitzResults
			            ( CheckID ,
				            Priority ,
				            FindingsGroup ,
				            Finding ,
				            URL ,
				            Details
			            )
			            SELECT 223 AS CheckID ,
					            0 AS Priority ,
					            'Informational' AS FindingsGroup ,
					            'Some Checks Skipped' AS Finding ,
					            'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-index' AS URL ,
					            'Managed Instance detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details;
            END; /* Azure Managed Instance skipped checks */

		/*
		That's the end of the SkipChecks stuff.
		The next several tables are used by various checks later.
		*/
		IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL
			DROP TABLE #ConfigurationDefaults;
		CREATE TABLE #ConfigurationDefaults
			(
			  name NVARCHAR(128) ,
			  DefaultValue BIGINT,
			  CheckID INT
			);

        IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL
            DROP TABLE #Recompile;
        CREATE TABLE #Recompile(
            DBName varchar(200),
            ProcName varchar(300),
            RecompileFlag varchar(1),
            SPSchema varchar(50)
        );

		IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL
			DROP TABLE #DatabaseDefaults;
		CREATE TABLE #DatabaseDefaults
			(
				name NVARCHAR(128) ,
				DefaultValue NVARCHAR(200),
				CheckID INT,
		        Priority INT,
		        Finding VARCHAR(200),
		        URL VARCHAR(200),
		        Details NVARCHAR(4000)
			);

		IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT NULL
			DROP TABLE #DatabaseScopedConfigurationDefaults;
		CREATE TABLE #DatabaseScopedConfigurationDefaults
			(ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60), default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, );

		IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL
			DROP TABLE #DBCCs;
		CREATE TABLE #DBCCs
			(
			  ID INT IDENTITY(1, 1)
					 PRIMARY KEY ,
			  ParentObject VARCHAR(255) ,
			  Object VARCHAR(255) ,
			  Field VARCHAR(255) ,
			  Value VARCHAR(255) ,
			  DbName NVARCHAR(128) NULL
			);

		IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL
			DROP TABLE #LogInfo2012;
		CREATE TABLE #LogInfo2012
			(
			  recoveryunitid INT ,
			  FileID SMALLINT ,
			  FileSize BIGINT ,
			  StartOffset BIGINT ,
			  FSeqNo BIGINT ,
			  [Status] TINYINT ,
			  Parity TINYINT ,
			  CreateLSN NUMERIC(38)
			);

		IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL
			DROP TABLE #LogInfo;
		CREATE TABLE #LogInfo
			(
			  FileID SMALLINT ,
			  FileSize BIGINT ,
			  StartOffset BIGINT ,
			  FSeqNo BIGINT ,
			  [Status] TINYINT ,
			  Parity TINYINT ,
			  CreateLSN NUMERIC(38)
			);

		IF OBJECT_ID('tempdb..#partdb') IS NOT NULL
			DROP TABLE #partdb;
		CREATE TABLE #partdb
			(
			  dbname NVARCHAR(128) ,
			  objectname NVARCHAR(200) ,
			  type_desc NVARCHAR(128)
			);

		IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL
			DROP TABLE #TraceStatus;
		CREATE TABLE #TraceStatus
			(
			  TraceFlag VARCHAR(10) ,
			  status BIT ,
			  Global BIT ,
			  Session BIT
			);

		IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL
			DROP TABLE #driveInfo;
		CREATE TABLE #driveInfo
			(
			  drive NVARCHAR(2),
              logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT
			  available_MB DECIMAL(18, 0),
              total_MB DECIMAL(18, 0),
              used_percent DECIMAL(18, 2)
			);

		IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL
			DROP TABLE #dm_exec_query_stats;
		CREATE TABLE #dm_exec_query_stats
			(
			  [id] [int] NOT NULL
						 IDENTITY(1, 1) ,
			  [sql_handle] [varbinary](64) NOT NULL ,
			  [statement_start_offset] [int] NOT NULL ,
			  [statement_end_offset] [int] NOT NULL ,
			  [plan_generation_num] [bigint] NOT NULL ,
			  [plan_handle] [varbinary](64) NOT NULL ,
			  [creation_time] [datetime] NOT NULL ,
			  [last_execution_time] [datetime] NOT NULL ,
			  [execution_count] [bigint] NOT NULL ,
			  [total_worker_time] [bigint] NOT NULL ,
			  [last_worker_time] [bigint] NOT NULL ,
			  [min_worker_time] [bigint] NOT NULL ,
			  [max_worker_time] [bigint] NOT NULL ,
			  [total_physical_reads] [bigint] NOT NULL ,
			  [last_physical_reads] [bigint] NOT NULL ,
			  [min_physical_reads] [bigint] NOT NULL ,
			  [max_physical_reads] [bigint] NOT NULL ,
			  [total_logical_writes] [bigint] NOT NULL ,
			  [last_logical_writes] [bigint] NOT NULL ,
			  [min_logical_writes] [bigint] NOT NULL ,
			  [max_logical_writes] [bigint] NOT NULL ,
			  [total_logical_reads] [bigint] NOT NULL ,
			  [last_logical_reads] [bigint] NOT NULL ,
			  [min_logical_reads] [bigint] NOT NULL ,
			  [max_logical_reads] [bigint] NOT NULL ,
			  [total_clr_time] [bigint] NOT NULL ,
			  [last_clr_time] [bigint] NOT NULL ,
			  [min_clr_time] [bigint] NOT NULL ,
			  [max_clr_time] [bigint] NOT NULL ,
			  [total_elapsed_time] [bigint] NOT NULL ,
			  [last_elapsed_time] [bigint] NOT NULL ,
			  [min_elapsed_time] [bigint] NOT NULL ,
			  [max_elapsed_time] [bigint] NOT NULL ,
			  [query_hash] [binary](8) NULL ,
			  [query_plan_hash] [binary](8) NULL ,
			  [query_plan] [xml] NULL ,
			  [query_plan_filtered] [nvarchar](MAX) NULL ,
			  [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS
									 NULL ,
			  [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS
											  NULL
			);

		IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL
			DROP TABLE #ErrorLog;
		CREATE TABLE #ErrorLog
			(
			  LogDate DATETIME ,
			  ProcessInfo NVARCHAR(20) ,
			  [Text] NVARCHAR(1000)
			);

		IF OBJECT_ID('tempdb..#fnTraceGettable') IS NOT NULL
			DROP TABLE #fnTraceGettable;
		CREATE TABLE #fnTraceGettable
			(
			  TextData NVARCHAR(4000) ,
			  DatabaseName NVARCHAR(256) ,
			  EventClass INT ,
			  Severity INT ,
			  StartTime DATETIME ,
			  EndTime DATETIME ,
			  Duration BIGINT ,
			  NTUserName NVARCHAR(256) ,
			  NTDomainName NVARCHAR(256) ,
			  HostName NVARCHAR(256) ,
			  ApplicationName NVARCHAR(256) ,
			  LoginName NVARCHAR(256) ,
			  DBUserName NVARCHAR(256)
			 );

		IF OBJECT_ID('tempdb..#Instances') IS NOT NULL
			DROP TABLE #Instances;
		CREATE TABLE #Instances
            (
              Instance_Number NVARCHAR(MAX) ,
              Instance_Name NVARCHAR(MAX) ,
              Data_Field NVARCHAR(MAX)
            );

		IF OBJECT_ID('tempdb..#IgnorableWaits') IS NOT NULL
			DROP TABLE #IgnorableWaits;
		CREATE TABLE #IgnorableWaits (wait_type NVARCHAR(60));
		INSERT INTO #IgnorableWaits VALUES ('BROKER_EVENTHANDLER');
		INSERT INTO #IgnorableWaits VALUES ('BROKER_RECEIVE_WAITFOR');
		INSERT INTO #IgnorableWaits VALUES ('BROKER_TASK_STOP');
		INSERT INTO #IgnorableWaits VALUES ('BROKER_TO_FLUSH');
		INSERT INTO #IgnorableWaits VALUES ('BROKER_TRANSMITTER');
		INSERT INTO #IgnorableWaits VALUES ('CHECKPOINT_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT');
		INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT');
		INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE');
		INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT');
		INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX');
		INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_WORKER_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('DBMIRRORING_CMD');
		INSERT INTO #IgnorableWaits VALUES ('DIRTY_PAGE_POLL');
		INSERT INTO #IgnorableWaits VALUES ('DISPATCHER_QUEUE_SEMAPHORE');
		INSERT INTO #IgnorableWaits VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT');
		INSERT INTO #IgnorableWaits VALUES ('FT_IFTSHC_MUTEX');
		INSERT INTO #IgnorableWaits VALUES ('HADR_CLUSAPI_CALL');
		INSERT INTO #IgnorableWaits VALUES ('HADR_FABRIC_CALLBACK');
		INSERT INTO #IgnorableWaits VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION');
		INSERT INTO #IgnorableWaits VALUES ('HADR_LOGCAPTURE_WAIT');
		INSERT INTO #IgnorableWaits VALUES ('HADR_NOTIFICATION_DEQUEUE');
		INSERT INTO #IgnorableWaits VALUES ('HADR_TIMER_TASK');
		INSERT INTO #IgnorableWaits VALUES ('HADR_WORK_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('LAZYWRITER_SLEEP');
		INSERT INTO #IgnorableWaits VALUES ('LOGMGR_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('ONDEMAND_TASK_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_DRAIN_WORKER');
		INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_LOG_CACHE');
		INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST');
		INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC');
		INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK');
		INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS');
		INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM');
		INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS');
		INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS');
		INSERT INTO #IgnorableWaits VALUES ('PVS_PREALLOCATE');
		INSERT INTO #IgnorableWaits VALUES ('PWAIT_EXTENSIBILITY_CLEANUP_TASK');
		INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP');
		INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP');
		INSERT INTO #IgnorableWaits VALUES ('QDS_SHUTDOWN_QUEUE');
		INSERT INTO #IgnorableWaits VALUES ('REDO_THREAD_PENDING_WORK');
		INSERT INTO #IgnorableWaits VALUES ('REQUEST_FOR_DEADLOCK_SEARCH');
		INSERT INTO #IgnorableWaits VALUES ('SLEEP_SYSTEMTASK');
		INSERT INTO #IgnorableWaits VALUES ('SLEEP_TASK');
		INSERT INTO #IgnorableWaits VALUES ('SOS_WORK_DISPATCHER');
		INSERT INTO #IgnorableWaits VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP');
		INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH');
		INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP');
		INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION');
		INSERT INTO #IgnorableWaits VALUES ('VDI_CLIENT_OTHER');
		INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG');
		INSERT INTO #IgnorableWaits VALUES ('WAITFOR');
		INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT');
		INSERT INTO #IgnorableWaits VALUES ('XE_LIVE_TARGET_TVF');
		INSERT INTO #IgnorableWaits VALUES ('XE_TIMER_EVENT');

		IF @Debug IN (1, 2) RAISERROR('Setting @MsSinceWaitsCleared', 0, 1) WITH NOWAIT;

        SELECT @MsSinceWaitsCleared = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP) * 60000.0
            FROM    sys.databases
            WHERE   name = 'tempdb';

		/* Have they cleared wait stats? Using a 10% fudge factor */
		IF @MsSinceWaitsCleared * .9 > (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE'))
			BEGIN
				
				IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 185) WITH NOWAIT;

				SET @MsSinceWaitsCleared = (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE'));
				IF @MsSinceWaitsCleared = 0 SET @MsSinceWaitsCleared = 1;
				INSERT  INTO #BlitzResults
						(   CheckID ,
							Priority ,
							FindingsGroup ,
							Finding ,
							URL ,
							Details
						)
					VALUES(		185,
								240,
								'Wait Stats',
								'Wait Stats Have Been Cleared',
								'https://www.brentozar.com/go/waits',
								'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' 
									+ CONVERT(NVARCHAR(100), 
										DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120));
			END;

		/* @CpuMsSinceWaitsCleared is used for waits stats calculations */
		
		IF @Debug IN (1, 2) RAISERROR('Setting @CpuMsSinceWaitsCleared', 0, 1) WITH NOWAIT;
		
		SELECT @CpuMsSinceWaitsCleared = @MsSinceWaitsCleared * scheduler_count
			FROM sys.dm_os_sys_info;

		/* If we're outputting CSV or Markdown, don't bother checking the plan cache because we cannot export plans. */
		IF @OutputType = 'CSV' OR @OutputType = 'MARKDOWN'
			SET @CheckProcedureCache = 0;

		/* If we're posting a question on Stack, include background info on the server */
		IF @OutputType = 'MARKDOWN'
			SET @CheckServerInfo = 1;

		/* Only run CheckUserDatabaseObjects if there are less than 50 databases. */
		IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1
			BEGIN
			SET @CheckUserDatabaseObjects = 0;
			PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.';
			PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.';
			INSERT  INTO #BlitzResults
			( CheckID ,
				Priority ,
				FindingsGroup ,
				Finding ,
				URL ,
				Details
			)
			SELECT 201 AS CheckID ,
					0 AS Priority ,
					'Informational' AS FindingsGroup ,
					'@CheckUserDatabaseObjects Disabled' AS Finding ,
					'https://www.BrentOzar.com/blitz/' AS URL ,
					'If you want to check 50+ databases, you have to also use @BringThePain = 1.' AS Details;
			END;

		/* Sanitize our inputs */
		SELECT
			@OutputServerName = QUOTENAME(@OutputServerName),
			@OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
			@OutputSchemaName = QUOTENAME(@OutputSchemaName),
			@OutputTableName = QUOTENAME(@OutputTableName);

		/* Get the major and minor build numbers */
		
		IF @Debug IN (1, 2) RAISERROR('Getting version information.', 0, 1) WITH NOWAIT;
		
		SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
		SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ),
			@ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2);
		
		/*
		Whew! we're finally done with the setup, and we can start doing checks.
		First, let's make sure we're actually supposed to do checks on this server.
		The user could have passed in a SkipChecks table that specified to skip ALL
		checks on this server, so let's check for that:
		*/
		IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName
													 FROM   #SkipChecks
													 WHERE  DatabaseName IS NULL
															AND CheckID IS NULL ) )
			 OR ( @SkipChecksTable IS NULL )
		   )
			BEGIN

				/*
				Extract DBCC DBINFO data from the server. This data is used for check 2 using
				the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field.
				NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS
				(which will have previously triggered inserting a checkID 223 record) and at
				least one of the relevant checks is not being skipped then we can extract the
				dbinfo information.
				*/
				IF NOT EXISTS
				(
					SELECT	1/0 
					FROM	#BlitzResults 
					WHERE	CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/'
				) AND NOT EXISTS
				(
					SELECT  1/0
					FROM    #SkipChecks
					WHERE   DatabaseName IS NULL AND CheckID IN (2, 68)
				)
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT;

						EXEC sp_MSforeachdb N'USE [?];
							SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
							INSERT #DBCCs
								(ParentObject,
								Object,
								Field,
								Value)
							EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS'');
							UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);';
					END

				/*
				Our very first check! We'll put more comments in this one just to
				explain exactly how it works. First, we check to see if we're
				supposed to skip CheckID 1 (that's the check we're working on.)
				*/
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 1 )
					BEGIN

						/*
						Below, we check master.sys.databases looking for databases
						that haven't had a backup in the last week. If we find any,
						we insert them into #BlitzResults, the temp table that
						tracks our server's problems. Note that if the check does
						NOT find any problems, we don't save that. We're only
						saving the problems, not the successful checks.
						*/

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT;

                        IF SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances need a special query */
                            BEGIN
						    INSERT  INTO #BlitzResults
								    ( CheckID ,
								      DatabaseName ,
								      Priority ,
								      FindingsGroup ,
								      Finding ,
								      URL ,
								      Details
								    )
								    SELECT  1 AS CheckID ,
										    d.[name] AS DatabaseName ,
										    1 AS Priority ,
										    'Backup' AS FindingsGroup ,
										    'Backups Not Performed Recently' AS Finding ,
										    'https://www.brentozar.com/go/nobak' AS URL ,
										    'Last backed up: '
										    + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
								    FROM    master.sys.databases d
										    LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
																      AND b.type = 'D'
																      AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server  */
								    WHERE   d.database_id <> 2  /* Bonus points if you know what that means */
										    AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
										    AND d.is_in_standby = 0 /* Not a log shipping target database */
										    AND d.source_database_id IS NULL /* Excludes database snapshots */
										    AND d.name NOT IN ( SELECT DISTINCT
																      DatabaseName
															    FROM  #SkipChecks
															    WHERE CheckID IS NULL OR CheckID = 1)
										    /*
										    The above NOT IN filters out the databases we're not supposed to check.
										    */
								    GROUP BY d.name
								    HAVING  MAX(b.backup_finish_date) <= DATEADD(dd,
																      -7, GETDATE())
                                            OR MAX(b.backup_finish_date) IS NULL;
                                END;

                        ELSE /* SERVERPROPERTY('EngineName') must be 8, Azure Managed Instances */
                            BEGIN
						    INSERT  INTO #BlitzResults
								    ( CheckID ,
								      DatabaseName ,
								      Priority ,
								      FindingsGroup ,
								      Finding ,
								      URL ,
								      Details
								    )
								    SELECT  1 AS CheckID ,
										    d.[name] AS DatabaseName ,
										    1 AS Priority ,
										    'Backup' AS FindingsGroup ,
										    'Backups Not Performed Recently' AS Finding ,
										    'https://www.brentozar.com/go/nobak' AS URL ,
										    'Last backed up: '
										    + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
								    FROM    master.sys.databases d
										    LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
																      AND b.type = 'D'
								    WHERE   d.database_id <> 2  /* Bonus points if you know what that means */
										    AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
										    AND d.is_in_standby = 0 /* Not a log shipping target database */
										    AND d.source_database_id IS NULL /* Excludes database snapshots */
										    AND d.name NOT IN ( SELECT DISTINCT
																      DatabaseName
															    FROM  #SkipChecks
															    WHERE CheckID IS NULL OR CheckID = 1)
										    /*
										    The above NOT IN filters out the databases we're not supposed to check.
										    */
								    GROUP BY d.name
								    HAVING  MAX(b.backup_finish_date) <= DATEADD(dd,
																      -7, GETDATE())
                                            OR MAX(b.backup_finish_date) IS NULL;
                                END;



						/*
						And there you have it. The rest of this stored procedure works the same
						way: it asks:
						- Should I skip this check?
						- If not, do I find problems?
						- Insert the results into #BlitzResults
						*/

					END;

				/*
				And that's the end of CheckID #1.

				CheckID #2 is a little simpler because it only involves one query, and it's
				more typical for queries that people contribute. But keep reading, because
				the next check gets more complex again.
				*/

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 2 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
										SELECT DISTINCT
										2 AS CheckID ,
										d.name AS DatabaseName ,
										1 AS Priority ,
										'Backup' AS FindingsGroup ,
										'Full Recovery Model w/o Log Backups' AS Finding ,
										'https://www.brentozar.com/go/biglogs' AS URL ,
										( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details
								FROM    master.sys.databases d
								LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime'
								WHERE   d.recovery_model IN ( 1, 2 )
										AND d.database_id NOT IN ( 2, 3 )
										AND d.source_database_id IS NULL
										AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
										AND d.is_in_standby = 0 /* Not a log shipping target database */
										AND d.source_database_id IS NULL /* Excludes database snapshots */
										AND d.name NOT IN ( SELECT DISTINCT
																  DatabaseName
															FROM  #SkipChecks
															WHERE CheckID IS NULL OR CheckID = 2)
										AND	(
												(
													/* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */
														[ll].[Value] Is Null
													AND NOT EXISTS ( SELECT *
																	 FROM   msdb.dbo.backupset b
																	 WHERE  d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
																				AND b.type = 'L'
																				AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE())
																	)
												)
												OR
												(
													Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE())
												)

											);
					END;

				/*
				CheckID #256 is searching for backups to NUL.
				*/

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 256 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
										SELECT DISTINCT
										256 AS CheckID ,
										d.name AS DatabaseName,
										1 AS Priority ,
										'Backup' AS FindingsGroup ,
										'Log Backups to NUL' AS Finding ,
										'https://www.brentozar.com/go/nul' AS URL ,
										N'The transaction log file has been backed up ' +  CAST((SELECT count(*)
														 FROM   msdb.dbo.backupset AS b INNER JOIN
																msdb.dbo.backupmediafamily AS bmf
																	ON	b.media_set_id = bmf.media_set_id
														 WHERE  b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS
																AND bmf.physical_device_name = 'NUL'
																AND b.type = 'L'
																AND b.backup_finish_date >= DATEADD(dd,
																  -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details
								FROM    master.sys.databases AS d
								WHERE   d.recovery_model IN ( 1, 2 )
										AND d.database_id NOT IN ( 2, 3 )
										AND d.source_database_id IS NULL
										AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
										AND d.is_in_standby = 0 /* Not a log shipping target database */
										AND d.source_database_id IS NULL /* Excludes database snapshots */
										--AND d.name NOT IN ( SELECT DISTINCT
										--						  DatabaseName
										--					FROM  #SkipChecks
										--					WHERE CheckID IS NULL OR CheckID = 2)
										AND EXISTS (	 SELECT *
														 FROM   msdb.dbo.backupset AS b INNER JOIN
																msdb.dbo.backupmediafamily AS bmf
																	ON	b.media_set_id = bmf.media_set_id
														 WHERE  d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
																AND bmf.physical_device_name = 'NUL'
																AND b.type = 'L'
																AND b.backup_finish_date >= DATEADD(dd,
																  -7, GETDATE()) );
					END;

				/*
				Next up, we've got CheckID 8. (These don't have to go in order.) This one
				won't work on SQL Server 2005 because it relies on a new DMV that didn't
				exist prior to SQL Server 2008. This means we have to check the SQL Server
				version first, then build a dynamic string with the query we want to run:
				*/

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 8 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
							BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 8) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults
							(CheckID, Priority,
							FindingsGroup,
							Finding, URL,
							Details)
					  SELECT 8 AS CheckID,
					  230 AS Priority,
					  ''Security'' AS FindingsGroup,
					  ''Server Audits Running'' AS Finding,
					  ''https://www.brentozar.com/go/audits'' AS URL,
					  (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status  OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;
					END;

				/*
				But what if you need to run a query in every individual database?
				Hop down to the @CheckUserDatabaseObjects section.

				And that's the basic idea! You can read through the rest of the
				checks if you like - some more exciting stuff happens closer to the
				end of the stored proc, where we start doing things like checking
				the plan cache, but those aren't as cleanly commented.

				To contribute your own checks or fix bugs, learn more here:
				https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/blob/main/CONTRIBUTING.md
				*/

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 93 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 93) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT
										93 AS CheckID ,
										1 AS Priority ,
										'Backup' AS FindingsGroup ,
										'Backing Up to Same Drive Where Databases Reside' AS Finding ,
										'https://www.brentozar.com/go/backup' AS URL ,
										CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive '
										+ UPPER(LEFT(bmf.physical_device_name, 3))
										+ ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details
								FROM    msdb.dbo.backupmediafamily AS bmf
										INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id
																  AND bs.backup_start_date >= ( DATEADD(dd,
																  -14, GETDATE()) )
										/* Filter out databases that were recently restored: */
										LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND rh.restore_date > DATEADD(dd, -14, GETDATE())
								WHERE   UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND
                                        bmf.physical_device_name NOT LIKE '\\%' AND -- GitHub Issue #2141
                                        @IsWindowsOperatingSystem = 1 AND -- GitHub Issue #1995
                                        UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN (
										SELECT DISTINCT
												UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3))
										FROM    sys.master_files AS mf
									    WHERE mf.database_id <> 2 )
										AND rh.destination_database_name IS NULL
								GROUP BY UPPER(LEFT(bmf.physical_device_name, 3));
					END;

					IF NOT EXISTS ( SELECT  1
									FROM    #SkipChecks
									WHERE   DatabaseName IS NULL AND CheckID = 119 )
						AND EXISTS ( SELECT *
									 FROM   sys.all_objects o
									 WHERE  o.name = 'dm_database_encryption_keys' )
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 119) WITH NOWAIT;

							SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details)
								SELECT 119 AS CheckID,
								1 AS Priority,
								''Backup'' AS FindingsGroup,
								''TDE Certificate Not Backed Up Recently'' AS Finding,
								db_name(dek.database_id) AS DatabaseName,
								''https://www.brentozar.com/go/tde'' AS URL,
								''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details
								FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint
								WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE())  OPTION (RECOMPILE);';
							
							IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
							IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
							
							EXECUTE(@StringToExecute);
						END;

                     IF NOT EXISTS ( SELECT  1
									FROM    #SkipChecks
									WHERE   DatabaseName IS NULL AND CheckID = 202 )
						AND EXISTS ( SELECT *
									 FROM   sys.all_columns c
									 WHERE  c.name = 'pvt_key_last_backup_date' )
						AND EXISTS ( SELECT *
									 FROM   msdb.INFORMATION_SCHEMA.COLUMNS c
									 WHERE  c.TABLE_NAME = 'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' )
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 202) WITH NOWAIT;

							SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
								SELECT DISTINCT 202 AS CheckID,
								1 AS Priority,
								''Backup'' AS FindingsGroup,
								''Encryption Certificate Not Backed Up Recently'' AS Finding,
								''https://www.brentozar.com/go/tde'' AS URL,
								''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details
								FROM sys.certificates c
                                INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint
                                WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);';
							
							IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
							IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
							
							EXECUTE(@StringToExecute);
						END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 3 )
					BEGIN
						IF DATEADD(dd, -60, GETDATE()) > (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date)

						BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 3) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT TOP 1
										3 AS CheckID ,
										'msdb' ,
										200 AS Priority ,
										'Backup' AS FindingsGroup ,
										'MSDB Backup History Not Purged' AS Finding ,
										'https://www.brentozar.com/go/history' AS URL ,
										( 'Database backup history retained back to '
										  + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
								FROM    msdb.dbo.backupset bs
                                LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name
                                WHERE rh.destination_database_name IS NULL
								ORDER BY bs.backup_start_date ASC;
						END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 186 )
					BEGIN
						IF DATEADD(dd, -2, GETDATE()) < (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date)

						BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 186) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  DatabaseName ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT TOP 1
											186 AS CheckID ,
											'msdb' ,
											200 AS Priority ,
											'Backup' AS FindingsGroup ,
											'MSDB Backup History Purged Too Frequently' AS Finding ,
											'https://www.brentozar.com/go/history' AS URL ,
											( 'Database backup history only retained back to '
											  + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
									FROM    msdb.dbo.backupset bs
									ORDER BY backup_start_date ASC;
						END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 178 )
					AND EXISTS (SELECT *
									FROM msdb.dbo.backupset bs
									WHERE bs.type = 'D'
									AND bs.backup_size >= 50000000000 /* At least 50GB */
									AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */
									AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */)
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 178) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT 178 AS CheckID ,
										200 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Snapshot Backups Occurring' AS Finding ,
										'https://www.brentozar.com/go/snaps' AS URL ,
										( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details
								FROM msdb.dbo.backupset bs
								WHERE bs.type = 'D'
								AND bs.backup_size >= 50000000000 /* At least 50GB */
								AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */
								AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 236 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 236) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT TOP 1 236 AS CheckID ,
										50 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Snapshotting Too Many Databases' AS Finding ,
										'https://www.brentozar.com/go/toomanysnaps' AS URL ,
										( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details
								FROM msdb.dbo.backupset bs
								WHERE bs.type = 'D'
								AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */
								GROUP BY bs.backup_finish_date
								HAVING SUM(1) >= 35
								ORDER BY SUM(1) DESC;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 4 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 4) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  4 AS CheckID ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Sysadmins' AS Finding ,
										'https://www.brentozar.com/go/sa' AS URL ,
										( 'Login [' + l.name
										  + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details
								FROM    master.sys.syslogins l
								WHERE   l.sysadmin = 1
										AND l.name <> SUSER_SNAME(0x01)
										AND l.denylogin = 0
										AND l.name NOT LIKE 'NT SERVICE\%'
										AND l.name <> 'l_certSignSmDetach'; /* Added in SQL 2016 */
					END;

                    IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   CheckID = 2301 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT;
						
                        /*
						#InvalidLogins is filled at the start during the permissions check IF we are not sysadmin
						filling it now if we are sysadmin
						*/
                        IF @sa = 1
							BEGIN
								INSERT INTO #InvalidLogins
			    				(
					    		[LoginSID]
					    		,[LoginName]
					    		)
					    		EXEC sp_validatelogins;
							END;
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  2301 AS CheckID ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Invalid login defined with Windows Authentication' AS Finding ,
										'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL ,
										( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment. Sometimes empty AD groups can show up here so check thoroughly.') AS Details
                                FROM #InvalidLogins
                                ;
                    END;
				    IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 5 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 5) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  5 AS CheckID ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Security Admins' AS Finding ,
										'https://www.brentozar.com/go/sa' AS URL ,
										( 'Login [' + l.name
										  + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details
								FROM    master.sys.syslogins l
								WHERE   l.securityadmin = 1
										AND l.name <> SUSER_SNAME(0x01)
										AND l.denylogin = 0;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 104 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 104) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( [CheckID] ,
								  [Priority] ,
								  [FindingsGroup] ,
								  [Finding] ,
								  [URL] ,
								  [Details]
								)
								SELECT  104 AS [CheckID] ,
										230 AS [Priority] ,
										'Security' AS [FindingsGroup] ,
										'Login Can Control Server' AS [Finding] ,
										'https://www.brentozar.com/go/sa' AS [URL] ,
										'Login [' + pri.[name]
										+ '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details]
								FROM    sys.server_principals AS pri
								WHERE   pri.[principal_id] IN (
										SELECT  p.[grantee_principal_id]
										FROM    sys.server_permissions AS p
										WHERE   p.[state] IN ( 'G', 'W' )
												AND p.[class] = 100
												AND p.[type] = 'CL' )
										AND pri.[name] NOT LIKE '##%##';
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 6 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT;

						
						IF @UsualOwnerOfJobs IS NULL
							SET @UsualOwnerOfJobs = SUSER_SNAME(0x01);

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  6 AS CheckID ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Jobs Owned By Users' AS Finding ,
										'https://www.brentozar.com/go/owners' AS URL ,
										( 'Job [' + j.name + '] is owned by ['
										  + SUSER_SNAME(j.owner_sid)
										  + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details
								FROM    msdb.dbo.sysjobs j
								WHERE   j.enabled = 1
										AND SUSER_SNAME(j.owner_sid) <> @UsualOwnerOfJobs;
					END;

				/* --TOURSTOP06-- */
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 7 )
					BEGIN
						/* --TOURSTOP02-- */
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 7) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  7 AS CheckID ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Stored Procedure Runs at Startup' AS Finding ,
										'https://www.brentozar.com/go/startup' AS URL ,
										( 'Stored procedure [master].['
										  + r.SPECIFIC_SCHEMA + '].['
										  + r.SPECIFIC_NAME
										  + '] runs automatically when SQL Server starts up.  Make sure you know exactly what this stored procedure is doing, because it could pose a security risk.' ) AS Details
								FROM    master.INFORMATION_SCHEMA.ROUTINES r
								WHERE   OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),
													   'ExecIsStartup') = 1;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 10 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults
							(CheckID,
							Priority,
							FindingsGroup,
							Finding,
							URL,
							Details)
					  SELECT 10 AS CheckID,
					  100 AS Priority,
					  ''Performance'' AS FindingsGroup,
					  ''Resource Governor Enabled'' AS Finding,
					  ''https://www.brentozar.com/go/rg'' AS URL,
					  (''Resource Governor is enabled.  Queries may be throttled.  Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 11 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults
							(CheckID,
							Priority,
							FindingsGroup,
							Finding,
							URL,
							Details)
					  SELECT 11 AS CheckID,
					  100 AS Priority,
					  ''Performance'' AS FindingsGroup,
					  ''Server Triggers Enabled'' AS Finding,
					  ''https://www.brentozar.com/go/logontriggers/'' AS URL,
					  (''Server Trigger ['' + [name] ++ ''] is enabled.  Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details
					  FROM sys.server_triggers
					  WHERE is_disabled = 0 AND is_ms_shipped = 0 AND name NOT LIKE ''rds^_%'' ESCAPE ''^'' OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 12 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 12) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  12 AS CheckID ,
										[name] AS DatabaseName ,
										10 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Auto-Close Enabled' AS Finding ,
										'https://www.brentozar.com/go/autoclose' AS URL ,
										( 'Database [' + [name]
										  + '] has auto-close enabled.  This setting can dramatically decrease performance.' ) AS Details
								FROM    sys.databases
								WHERE   is_auto_close_on = 1
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 12);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 13 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 13) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  13 AS CheckID ,
										[name] AS DatabaseName ,
										10 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Auto-Shrink Enabled' AS Finding ,
										'https://www.brentozar.com/go/autoshrink' AS URL ,
										( 'Database [' + [name]
										  + '] has auto-shrink enabled.  This setting can dramatically decrease performance.' ) AS Details
								FROM    sys.databases
								WHERE   is_auto_shrink_on = 1
										AND state <> 6 /* Offline */
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 13);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 14 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults
							(CheckID,
							DatabaseName,
							Priority,
							FindingsGroup,
							Finding,
							URL,
							Details)
					  SELECT 14 AS CheckID,
					  [name] as DatabaseName,
					  50 AS Priority,
					  ''Reliability'' AS FindingsGroup,
					  ''Page Verification Not Optimal'' AS Finding,
					  ''https://www.brentozar.com/go/torn'' AS URL,
					  (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification.  SQL Server may have a harder time recognizing and recovering from storage corruption.  Consider using CHECKSUM instead.'') COLLATE database_default AS Details
					  FROM sys.databases
					  WHERE page_verify_option < 2
					  AND name <> ''tempdb''
					  AND state NOT IN (1, 6) /* Restoring, Offline */
					  and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 15 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 15) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  15 AS CheckID ,
										[name] AS DatabaseName ,
										110 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Auto-Create Stats Disabled' AS Finding ,
										'https://www.brentozar.com/go/acs' AS URL ,
										( 'Database [' + [name]
										  + '] has auto-create-stats disabled.  SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details
								FROM    sys.databases
								WHERE   is_auto_create_stats_on = 0
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 15);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 16 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 16) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  16 AS CheckID ,
										[name] AS DatabaseName ,
										110 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Auto-Update Stats Disabled' AS Finding ,
										'https://www.brentozar.com/go/aus' AS URL ,
										( 'Database [' + [name]
										  + '] has auto-update-stats disabled.  SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details
								FROM    sys.databases
								WHERE   is_auto_update_stats_on = 0
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 16);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 17 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 17) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  17 AS CheckID ,
										[name] AS DatabaseName ,
										150 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Stats Updated Asynchronously' AS Finding ,
										'https://www.brentozar.com/go/asyncstats' AS URL ,
										( 'Database [' + [name]
										  + '] has auto-update-stats-async enabled.  When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details
								FROM    sys.databases
								WHERE   is_auto_update_stats_async_on = 1
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 17);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 20 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 20) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  20 AS CheckID ,
										[name] AS DatabaseName ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Date Correlation On' AS Finding ,
										'https://www.brentozar.com/go/corr' AS URL ,
										( 'Database [' + [name]
										  + '] has date correlation enabled.  This is not a default setting, and it has some performance overhead.  It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details
								FROM    sys.databases
								WHERE   is_date_correlation_on = 1
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 20);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 21 )
					BEGIN
						/* --TOURSTOP04-- */
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults
							(CheckID,
							DatabaseName,
							Priority,
							FindingsGroup,
							Finding,
							URL,
							Details)
					  SELECT 21 AS CheckID,
					  [name] as DatabaseName,
					  200 AS Priority,
					  ''Informational'' AS FindingsGroup,
					  ''Database Encrypted'' AS Finding,
					  ''https://www.brentozar.com/go/tde'' AS URL,
					  (''Database ['' + [name] + ''] has Transparent Data Encryption enabled.  Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details
					  FROM sys.databases
					  WHERE is_encrypted = 1
					  and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 21) OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;
					END;

				/*
				Believe it or not, SQL Server doesn't track the default values
				for sp_configure options! We'll make our own list here.
				*/

				IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT;

				INSERT INTO #ConfigurationDefaults
				VALUES
					( 'access check cache bucket count', 0, 1001 ),
					( 'access check cache quota', 0, 1002 ),
					( 'Ad Hoc Distributed Queries', 0, 1003 ),
					( 'affinity I/O mask', 0, 1004 ),
					( 'affinity mask', 0, 1005 ),
					( 'affinity64 mask', 0, 1066 ),
					( 'affinity64 I/O mask', 0, 1067 ),
					( 'Agent XPs', 0, 1071 ),
					( 'allow updates', 0, 1007 ),
					( 'awe enabled', 0, 1008 ),
					( 'backup checksum default', 0, 1070 ),
					( 'backup compression default', 0, 1073 ),
					( 'blocked process threshold', 0, 1009 ),
					( 'blocked process threshold (s)', 0, 1009 ),
					( 'c2 audit mode', 0, 1010 ),
					( 'clr enabled', 0, 1011 ),
					( 'common criteria compliance enabled', 0, 1074 ),
					( 'contained database authentication', 0, 1068 ),
					( 'cost threshold for parallelism', 5, 1012 ),
					( 'cross db ownership chaining', 0, 1013 ),
					( 'cursor threshold', -1, 1014 ),
					( 'Database Mail XPs', 0, 1072 ),
					( 'default full-text language', 1033, 1016 ),
					( 'default language', 0, 1017 ),
					( 'default trace enabled', 1, 1018 ),
					( 'disallow results from triggers', 0, 1019 ),
					( 'EKM provider enabled', 0, 1075 ),
					( 'filestream access level', 0, 1076 ),
					( 'fill factor (%)', 0, 1020 ),
					( 'ft crawl bandwidth (max)', 100, 1021 ),
					( 'ft crawl bandwidth (min)', 0, 1022 ),
					( 'ft notify bandwidth (max)', 100, 1023 ),
					( 'ft notify bandwidth (min)', 0, 1024 ),
					( 'index create memory (KB)', 0, 1025 ),
					( 'in-doubt xact resolution', 0, 1026 ),
					( 'lightweight pooling', 0, 1027 ),
					( 'locks', 0, 1028 ),
					( 'max degree of parallelism', 0, 1029 ),
					( 'max full-text crawl range', 4, 1030 ),
					( 'max server memory (MB)', 2147483647, 1031 ),
					( 'max text repl size (B)', 65536, 1032 ),
					( 'max worker threads', 0, 1033 ),
					( 'media retention', 0, 1034 ),
					( 'min memory per query (KB)', 1024, 1035 ),
					( 'nested triggers', 1, 1037 ),
					( 'network packet size (B)', 4096, 1038 ),
					( 'Ole Automation Procedures', 0, 1039 ),
					( 'open objects', 0, 1040 ),
					( 'optimize for ad hoc workloads', 0, 1041 ),
					( 'PH timeout (s)', 60, 1042 ),
					( 'precompute rank', 0, 1043 ),
					( 'priority boost', 0, 1044 ),
					( 'query governor cost limit', 0, 1045 ),
					( 'query wait (s)', -1, 1046 ),
					( 'recovery interval (min)', 0, 1047 ),
					( 'remote access', 1, 1048 ),
					( 'remote admin connections', 0, 1049 ),
					( 'remote login timeout (s)', CASE
						WHEN @@VERSION LIKE '%Microsoft SQL Server 2005%'
						  OR @@VERSION LIKE '%Microsoft SQL Server 2008%' THEN 20
						ELSE 10
					  END, 1069 ),
					( 'remote proc trans', 0, 1050 ),
					( 'remote query timeout (s)', 600, 1051 ),
					( 'Replication XPs', 0, 1052 ),
					( 'RPC parameter data validation', 0, 1053 ),
					( 'scan for startup procs', 0, 1054 ),
					( 'server trigger recursion', 1, 1055 ),
					( 'set working set size', 0, 1056 ),
					( 'show advanced options', 0, 1057 ),
					( 'SMO and DMO XPs', 1, 1058 ),
					( 'SQL Mail XPs', 0, 1059 ),
					( 'transform noise words', 0, 1060 ),
					( 'two digit year cutoff', 2049, 1061 ),
					( 'user connections', 0, 1062 ),
					( 'user options', 0, 1063 ),
					( 'Web Assistant Procedures', 0, 1064 ),
					( 'xp_cmdshell', 0, 1065 ),
					( 'automatic soft-NUMA disabled', 0, 269),
					( 'external scripts enabled', 0, 269),
					( 'clr strict security', 1, 269),
					( 'column encryption enclave type', 0, 269),
					( 'tempdb metadata memory-optimized', 0, 269),
					( 'ADR cleaner retry timeout (min)', 15, 269),
					( 'ADR Preallocation Factor', 4, 269),
					( 'version high part of SQL Server', 1114112, 269),
					( 'version low part of SQL Server', 52428803, 269),
					( 'Data processed daily limit in TB', 2147483647, 269),
					( 'Data processed weekly limit in TB', 2147483647, 269),
					( 'Data processed monthly limit in TB', 2147483647, 269),
					( 'ADR Cleaner Thread Count', 1, 269),
					( 'hardware offload enabled', 0, 269),
					( 'hardware offload config', 0, 269),
					( 'hardware offload mode', 0, 269),
					( 'backup compression algorithm', 0, 269),
					( 'ADR cleaner lock timeout (s)', 5, 269),
					( 'SLOG memory quota (%)', 75, 269),
					( 'max RPC request params (KB)', 0, 269),
					( 'max UCS send boxcars', 256, 269),
					( 'availability group commit time (ms)', 0, 269),
					( 'tiered memory enabled', 0, 269),
					( 'max server tiered memory (MB)', 2147483647, 269),
					( 'hadoop connectivity', 0, 269),
					( 'polybase network encryption', 1, 269),
					( 'remote data archive', 0, 269),
					( 'allow polybase export', 0, 269),
					( 'allow filesystem enumeration', 1, 269),
					( 'polybase enabled', 0, 269),
					( 'suppress recovery model errors', 0, 269),
					( 'openrowset auto_create_statistics', 1, 269),
					( 'external rest endpoint enabled', 0, 269),
					( 'external xtp dll gen util enabled', 0, 269),
					( 'external AI runtimes enabled', 0, 269),
					( 'allow server scoped db credentials', 0, 269);

				/* Either 0 or 16 is fine here */
				IF EXISTS (
				SELECT * FROM sys.configurations
				WHERE name = 'min server memory (MB)'
					AND value_in_use IN (0, 16)
				)
				BEGIN
					INSERT INTO #ConfigurationDefaults
					SELECT 'min server memory (MB)', CAST(value_in_use AS BIGINT), 1036
					FROM sys.configurations
					WHERE name = 'min server memory (MB)';
				END
				ELSE
				BEGIN
					INSERT INTO #ConfigurationDefaults
					VALUES ('min server memory (MB)', 0, 1036);
				END;


				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 22 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 22) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  cd.CheckID ,
										200 AS Priority ,
										'Non-Default Server Config' AS FindingsGroup ,
										cr.name AS Finding ,
										'https://www.brentozar.com/go/conf' AS URL ,
										( 'This sp_configure option has been changed.  Its default value is '
										  + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)),
													 '(unknown)')
										  + ' and it has been set to '
										  + CAST(cr.value_in_use AS VARCHAR(100))
										  + '.' ) AS Details
								FROM    sys.configurations cr
										INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name
										LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name
																  AND cdUsed.DefaultValue = cr.value_in_use
								WHERE   cdUsed.name IS NULL;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 190 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Setting @MinServerMemory and @MaxServerMemory', 0, 1) WITH NOWAIT;

						SELECT @MinServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'min server memory (MB)';
						SELECT @MaxServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'max server memory (MB)';
						
						IF (@MinServerMemory = @MaxServerMemory)
						BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 190) WITH NOWAIT;

						INSERT INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								VALUES
									(	190,
										200,
										'Performance',
										'Non-Dynamic Memory',
										'https://www.brentozar.com/go/memory',
										'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings'
									);
						END;
					END;
					
					IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 188 )
					BEGIN

						/* Let's set variables so that our query is still SARGable */
						
						IF @Debug IN (1, 2) RAISERROR('Setting @Processors.', 0, 1) WITH NOWAIT;
						
						SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info);
						
						IF @Debug IN (1, 2) RAISERROR('Setting @NUMANodes', 0, 1) WITH NOWAIT;
						
						SET @NUMANodes = (SELECT COUNT(1)
											FROM sys.dm_os_performance_counters pc
											WHERE pc.object_name LIKE '%Buffer Node%'
												AND counter_name = 'Page life expectancy');
						/* If Cost Threshold for Parallelism is default then flag as a potential issue */
						/* If MAXDOP is default and processors > 8 or NUMA nodes > 1 then flag as potential issue */
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 188) WITH NOWAIT;
						
						INSERT INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  188 AS CheckID ,
										200 AS Priority ,
										'Performance' AS FindingsGroup ,
										cr.name AS Finding ,
										'https://www.brentozar.com/go/cxpacket' AS URL ,
										( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.')
								FROM    sys.configurations cr
										INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name
											AND cr.value_in_use = cd.DefaultValue
								WHERE   cr.name = 'cost threshold for parallelism'
									OR (cr.name = 'max degree of parallelism' AND (@NUMANodes > 1 OR @Processors > 8));
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 24 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 24) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT DISTINCT
										24 AS CheckID ,
										DB_NAME(database_id) AS DatabaseName ,
										170 AS Priority ,
										'File Configuration' AS FindingsGroup ,
										'System Database on C Drive' AS Finding ,
										'https://www.brentozar.com/go/cdrive' AS URL ,
										( 'The ' + DB_NAME(database_id)
										  + ' database has a file on the C drive.  Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details
								FROM    sys.master_files
								WHERE   UPPER(LEFT(physical_name, 1)) = 'C'
										AND DB_NAME(database_id) IN ( 'master',
																  'model', 'msdb' );
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 25 )
                    AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 25) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT TOP 1
										25 AS CheckID ,
										'tempdb' ,
										20 AS Priority ,
										'File Configuration' AS FindingsGroup ,
										'TempDB on C Drive' AS Finding ,
										'https://www.brentozar.com/go/cdrive' AS URL ,
										CASE WHEN growth > 0
											 THEN ( 'The tempdb database has files on the C drive.  TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard.  C is also often much slower than other drives, so performance may be suffering.' )
											 ELSE ( 'The tempdb database has files on the C drive.  TempDB is not set to Autogrow, hopefully it is big enough.  C is also often much slower than other drives, so performance may be suffering.' )
										END AS Details
								FROM    sys.master_files
								WHERE   UPPER(LEFT(physical_name, 1)) = 'C'
										AND DB_NAME(database_id) = 'tempdb';
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 26 )
                    AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 26) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT DISTINCT
										26 AS CheckID ,
										DB_NAME(database_id) AS DatabaseName ,
										20 AS Priority ,
										'Reliability' AS FindingsGroup ,
										'User Databases on C Drive' AS Finding ,
										'https://www.brentozar.com/go/cdrive' AS URL ,
										( 'The ' + DB_NAME(database_id)
										  + ' database has a file on the C drive.  Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details
								FROM    sys.master_files
								WHERE   UPPER(LEFT(physical_name, 1)) = 'C'
										AND DB_NAME(database_id) NOT IN ( 'master',
																  'model', 'msdb',
																  'tempdb' )
										AND DB_NAME(database_id) NOT IN (
										SELECT DISTINCT
												DatabaseName
										FROM    #SkipChecks
										WHERE CheckID IS NULL OR CheckID = 26 );
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 27 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 27) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  27 AS CheckID ,
										'master' AS DatabaseName ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Tables in the Master Database' AS Finding ,
										'https://www.brentozar.com/go/mastuser' AS URL ,
										( 'The ' + name
										  + ' table in the master database was created by end users on '
										  + CAST(create_date AS VARCHAR(20))
										  + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details
								FROM    master.sys.tables
								WHERE   is_ms_shipped = 0
                                  AND   name NOT IN ('CommandLog','SqlServerVersions','$ndo$srvproperty')
								  AND	name NOT LIKE 'rds^_%' ESCAPE '^';
								  /* That last one is the Dynamics NAV licensing table: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2426 */
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 28 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 28) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  28 AS CheckID ,
								        'msdb' AS DatabaseName ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Tables in the MSDB Database' AS Finding ,
										'https://www.brentozar.com/go/msdbuser' AS URL ,
										( 'The ' + name
										  + ' table in the msdb database was created by end users on '
										  + CAST(create_date AS VARCHAR(20))
										  + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details
								FROM    msdb.sys.tables
								WHERE   is_ms_shipped = 0 AND name NOT LIKE '%DTA_%';
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 29 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 29) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  29 AS CheckID ,
								        'model' AS DatabaseName ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Tables in the Model Database' AS Finding ,
										'https://www.brentozar.com/go/model' AS URL ,
										( 'The ' + name
										  + ' table in the model database was created by end users on '
										  + CAST(create_date AS VARCHAR(20))
										  + '. Tables in the model database are automatically copied into all new databases.' ) AS Details
								FROM    model.sys.tables
								WHERE   is_ms_shipped = 0;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 30 )
					BEGIN
						IF ( SELECT COUNT(*)
							 FROM   msdb.dbo.sysalerts
							 WHERE  severity BETWEEN 19 AND 25
						   ) < 7

						   BEGIN

						   IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 30) WITH NOWAIT;

							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  30 AS CheckID ,
											200 AS Priority ,
											'Monitoring' AS FindingsGroup ,
											'Not All Alerts Configured' AS Finding ,
											'https://www.brentozar.com/go/alert' AS URL ,
											( 'Not all SQL Server Agent alerts have been configured.  This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details;
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 59 )
					BEGIN
						IF EXISTS ( SELECT  *
									FROM    msdb.dbo.sysalerts
									WHERE   enabled = 1
											AND COALESCE(has_notification, 0) = 0
											AND (job_id IS NULL OR job_id = 0x))

							BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 59) WITH NOWAIT;

							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  59 AS CheckID ,
											200 AS Priority ,
											'Monitoring' AS FindingsGroup ,
											'Alerts Configured without Follow Up' AS Finding ,
											'https://www.brentozar.com/go/alert' AS URL ,
											( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action.  This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details;
					
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 96 )
					BEGIN
						IF NOT EXISTS ( SELECT  *
										FROM    msdb.dbo.sysalerts
										WHERE   message_id IN ( 823, 824, 825 ) )
							
							BEGIN;
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 96) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  96 AS CheckID ,
											200 AS Priority ,
											'Monitoring' AS FindingsGroup ,
											'No Alerts for Corruption' AS Finding ,
											'https://www.brentozar.com/go/alert' AS URL ,
											( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825.  These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details;
					
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 61 )
					BEGIN
						IF NOT EXISTS ( SELECT  *
										FROM    msdb.dbo.sysalerts
										WHERE   severity BETWEEN 19 AND 25 )
							
							BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 61) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  61 AS CheckID ,
											200 AS Priority ,
											'Monitoring' AS FindingsGroup ,
											'No Alerts for Sev 19-25' AS Finding ,
											'https://www.brentozar.com/go/alert' AS URL ,
											( 'SQL Server Agent alerts do not exist for severity levels 19 through 25.  These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details;
					
							END;

					END;

		--check for disabled alerts
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 98 )
					BEGIN
						IF EXISTS ( SELECT  name
									FROM    msdb.dbo.sysalerts
									WHERE   enabled = 0 )
							
							BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 98) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  98 AS CheckID ,
											200 AS Priority ,
											'Monitoring' AS FindingsGroup ,
											'Alerts Disabled' AS Finding ,
											'https://www.brentozar.com/go/alert' AS URL ,
											( 'The following Alert is disabled, please review and enable if desired: '
											  + name ) AS Details
									FROM    msdb.dbo.sysalerts
									WHERE   enabled = 0;
			END;
		END;

		--check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send
		IF NOT EXISTS (
				SELECT 1
				FROM #SkipChecks
				WHERE DatabaseName IS NULL
					AND CheckID = 219
				)
		BEGIN;
			IF @Debug IN (1, 2)
			BEGIN;
				RAISERROR ('Running CheckId [%d].', 0, 1, 219) WITH NOWAIT;
			END;

			INSERT INTO #BlitzResults (
				CheckID
				,[Priority]
				,FindingsGroup
				,Finding
				,[URL]
				,Details
				)
			SELECT 219 AS CheckID
				,200 AS [Priority]
				,'Monitoring' AS FindingsGroup
				,'Alerts Without Event Descriptions' AS Finding
				,'https://www.brentozar.com/go/alert' AS [URL]
				,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name])
				+ '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details
			FROM msdb.dbo.sysalerts
			WHERE [enabled] = 1
			  AND include_event_description = 0 --bitmask: 1 = email, 2 = pager, 4 = net send
			;
		END;

		--check whether we have NO ENABLED operators!
		IF NOT EXISTS ( SELECT  1
						FROM    #SkipChecks
						WHERE   DatabaseName IS NULL AND CheckID = 31 )
		BEGIN;
						IF NOT EXISTS ( SELECT  *
										FROM    msdb.dbo.sysoperators
										WHERE   enabled = 1 )
							
							BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 31) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  31 AS CheckID ,
											200 AS Priority ,
											'Monitoring' AS FindingsGroup ,
											'No Operators Configured/Enabled' AS Finding ,
											'https://www.brentozar.com/go/op' AS URL ,
											( 'No SQL Server Agent operators (emails) have been configured.  This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details;
					
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 34 )
					BEGIN
						IF EXISTS ( SELECT  *
									FROM    sys.all_objects
									WHERE   name = 'dm_db_mirroring_auto_page_repair' )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT DISTINCT
		  34 AS CheckID ,
		  db.name ,
		  1 AS Priority ,
		  ''Corruption'' AS FindingsGroup ,
		  ''Database Corruption Detected'' AS Finding ,
		  ''https://www.brentozar.com/go/repair'' AS URL ,
		  ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details
		  FROM (SELECT rp2.database_id, rp2.modification_time
			FROM sys.dm_db_mirroring_auto_page_repair rp2
			WHERE rp2.[database_id] not in (
			SELECT db2.[database_id]
			FROM sys.databases as db2
			WHERE db2.[state] = 1
			) ) as rp
		  INNER JOIN master.sys.databases db ON rp.database_id = db.database_id
		  WHERE   rp.modification_time >= DATEADD(dd, -30, GETDATE())  OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 89 )
					BEGIN
						IF EXISTS ( SELECT  *
									FROM    sys.all_objects
									WHERE   name = 'dm_hadr_auto_page_repair' )
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT DISTINCT
		  89 AS CheckID ,
		  db.name ,
		  1 AS Priority ,
		  ''Corruption'' AS FindingsGroup ,
		  ''Database Corruption Detected'' AS Finding ,
		  ''https://www.brentozar.com/go/repair'' AS URL ,
		  ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details
		  FROM    sys.dm_hadr_auto_page_repair rp
		  INNER JOIN master.sys.databases db ON rp.database_id = db.database_id
		  WHERE   rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE) ;';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 90 )
					BEGIN
						IF EXISTS ( SELECT  *
									FROM    msdb.sys.all_objects
									WHERE   name = 'suspect_pages' )
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT DISTINCT
		  90 AS CheckID ,
		  db.name ,
		  1 AS Priority ,
		  ''Corruption'' AS FindingsGroup ,
		  ''Database Corruption Detected'' AS Finding ,
		  ''https://www.brentozar.com/go/repair'' AS URL ,
		  ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details
		  FROM    msdb.dbo.suspect_pages sp
		  INNER JOIN master.sys.databases db ON sp.database_id = db.database_id
		  WHERE   sp.last_update_date >= DATEADD(dd, -30, GETDATE())  OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 36 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 36) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT DISTINCT
										36 AS CheckID ,
										150 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Slow Storage Reads on Drive '
										+ UPPER(LEFT(mf.physical_name, 1)) AS Finding ,
										'https://www.brentozar.com/go/slow' AS URL ,
										'Reads are averaging longer than 200ms for at least one database on this drive.  For specific database file speeds, run the query from the information link.' AS Details
								FROM    sys.dm_io_virtual_file_stats(NULL, NULL)
										AS fs
										INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id
																  AND fs.[file_id] = mf.[file_id]
								WHERE   ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200
								AND num_of_reads > 100000;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 37 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 37) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT DISTINCT
										37 AS CheckID ,
										150 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Slow Storage Writes on Drive '
										+ UPPER(LEFT(mf.physical_name, 1)) AS Finding ,
										'https://www.brentozar.com/go/slow' AS URL ,
										'Writes are averaging longer than 100ms for at least one database on this drive.  For specific database file speeds, run the query from the information link.' AS Details
								FROM    sys.dm_io_virtual_file_stats(NULL, NULL)
										AS fs
										INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id
																  AND fs.[file_id] = mf.[file_id]
								WHERE   ( io_stall_write_ms / ( 1.0
																+ num_of_writes ) ) > 100
																AND num_of_writes > 100000;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 40 )
					BEGIN
						IF ( SELECT COUNT(*)
							 FROM   tempdb.sys.database_files
							 WHERE  type_desc = 'ROWS'
						   ) = 1
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT;

								INSERT  INTO #BlitzResults
										( CheckID ,
										  DatabaseName ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
								VALUES  ( 40 ,
										  'tempdb' ,
										  170 ,
										  'File Configuration' ,
										  'TempDB Only Has 1 Data File' ,
										  'https://www.brentozar.com/go/tempdb' ,
										  'TempDB is only configured with one data file.  More data files are usually required to alleviate SGAM contention.'
										);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 183 )

				BEGIN

						IF ( SELECT COUNT (distinct [size])
							FROM   tempdb.sys.database_files
							WHERE  type_desc = 'ROWS'
							HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1.
							) <> 1
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT;

								INSERT  INTO #BlitzResults
										( CheckID ,
										  DatabaseName ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
								VALUES  ( 183 ,
										  'tempdb' ,
										  170 ,
										  'File Configuration' ,
										  'TempDB Unevenly Sized Data Files' ,
										  'https://www.brentozar.com/go/tempdb' ,
										  'TempDB data files are not configured with the same size.  Unevenly sized tempdb data files will result in unevenly sized workloads.'
										);
							END;
				END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 44 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 44) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  44 AS CheckID ,
										150 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Queries Forcing Order Hints' AS Finding ,
										'https://www.brentozar.com/go/hints' AS URL ,
										CAST(occurrence AS VARCHAR(10))
										+ ' instances of order hinting have been recorded since restart.  This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good.  This can also explain why DBA tuning efforts aren''t working.' AS Details
								FROM    sys.dm_exec_query_optimizer_info
								WHERE   counter = 'order hint'
										AND occurrence > 1000;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 45 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 45) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  45 AS CheckID ,
										150 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Queries Forcing Join Hints' AS Finding ,
										'https://www.brentozar.com/go/hints' AS URL ,
										CAST(occurrence AS VARCHAR(10))
										+ ' instances of join hinting have been recorded since restart.  This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good.  This can also explain why DBA tuning efforts aren''t working.' AS Details
								FROM    sys.dm_exec_query_optimizer_info
								WHERE   counter = 'join hint'
										AND occurrence > 1000;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 49 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 49) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT DISTINCT
										49 AS CheckID ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Linked Server Configured' AS Finding ,
										'https://www.brentozar.com/go/link' AS URL ,
										+CASE WHEN l.remote_name = 'sa'
											  THEN COALESCE(s.data_source, s.name, s.provider)
												   + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.'
											  ELSE COALESCE(s.data_source, s.name, s.provider)
												   + ' is configured as a linked server. Check its security configuration to make sure it isn''t connecting with SA or some other bone-headed administrative login, because any user who queries it might get admin-level permissions.'
										 END AS Details
								FROM    sys.servers s
										INNER JOIN sys.linked_logins l ON s.server_id = l.server_id
								WHERE   s.is_linked = 1;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 50 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
		  SELECT  50 AS CheckID ,
		  100 AS Priority ,
		  ''Performance'' AS FindingsGroup ,
		  ''Max Memory Set Too High'' AS Finding ,
		  ''https://www.brentozar.com/go/max'' AS URL ,
		  ''SQL Server max memory is set to ''
			+ CAST(c.value_in_use AS VARCHAR(20))
			+ '' megabytes, but the server only has ''
			+ CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20))
			+ '' megabytes.  SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details
		  FROM    sys.dm_os_sys_memory m
		  INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)''
		  WHERE   CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 ) OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 51 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT

								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
		  SELECT  51 AS CheckID ,
		  1 AS Priority ,
		  ''Performance'' AS FindingsGroup ,
		  ''Memory Dangerously Low'' AS Finding ,
		  ''https://www.brentozar.com/go/max'' AS URL ,
		  ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20))
			+ '' megabytes are available.  As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details
		  FROM    sys.dm_os_sys_memory m
		  WHERE   CAST(m.available_physical_memory_kb AS BIGINT) < 262144 OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 159 )
					BEGIN
						IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
		  SELECT DISTINCT 159 AS CheckID ,
		  1 AS Priority ,
		  ''Performance'' AS FindingsGroup ,
		  ''Memory Dangerously Low in NUMA Nodes'' AS Finding ,
		  ''https://www.brentozar.com/go/max'' AS URL ,
		  ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details
		  FROM    sys.dm_os_nodes m
		  WHERE   node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);';

								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

								EXECUTE(@StringToExecute);
							END;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 53 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT;

                                         DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200)

                                         SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT)
                                         SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT)

                                         IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 
                                         BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured
                                                       SET @AOFCI = 1
                                         END

                                         SELECT @HAType =
                                         CASE
                                                       WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG'
                                                       WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI'
                                                       WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG'
                                                       ELSE 'STANDALONE'
                                         END

                                         IF (@HAType IN ('FCIAG','FCI','AG'))
                                         BEGIN

                                                   INSERT  INTO #BlitzResults
                                                               ( CheckID ,
                                                                      Priority ,
                                                                      FindingsGroup ,
                                                                      Finding ,
                                                                      URL ,
                                                                      Details
                                                               )

                                                   SELECT DISTINCT 53 AS CheckID ,
                                                   200 AS Priority ,
                                                   'Informational' AS FindingsGroup ,
                                                   'Cluster Node' AS Finding ,
                                                   'https://BrentOzar.com/go/node' AS URL ,
                                                   'This is a node in a cluster.' AS Details

                                                   IF @HAType = 'AG'
                                                   BEGIN
                                                                INSERT  INTO #BlitzResults
                                                                            ( CheckID ,
                                                                                   Priority ,
                                                                                  FindingsGroup ,
                                                                                   Finding ,
                                                                                   URL ,
                                                                                   Details
                                                                            )
                                                                SELECT DISTINCT 53 AS CheckID ,
                                                                200 AS Priority ,
                                                                'Informational' AS FindingsGroup ,
                                                                'Cluster Node Info' AS Finding ,
                                                                'https://BrentOzar.com/go/node' AS URL,
                                                                'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY'
                                                                ELSE 'SECONDARY'
                                                                END + '=' + UPPER(ar.replica_server_name) 
                                                                FROM sys.availability_groups AS ag
                                                                LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id
                                                                LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id
                                                                ORDER BY 1
                                                                FOR XML PATH ('')
                                                                ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details
                                                   END

                                                   IF @HAType = 'FCI'
                                                   BEGIN
                                                                INSERT  INTO #BlitzResults
                                                                            ( CheckID ,
                                                                                   Priority ,
                                                                                   FindingsGroup ,
                                                                                   Finding ,
                                                                                   URL ,
                                                                                   Details
                                                                            )
                                                                SELECT DISTINCT 53 AS CheckID ,
                                                                200 AS Priority ,
                                                                'Informational' AS FindingsGroup ,
                                                                'Cluster Node Info' AS Finding ,
                                                                'https://BrentOzar.com/go/node' AS URL,
                                                                'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner 
                                                                            WHEN 1 THEN 'PRIMARY'
                                                                           ELSE 'SECONDARY'
                                                                END + '=' + UPPER(NodeName) 
                                                                FROM sys.dm_os_cluster_nodes
                                                                ORDER BY 1
                                                                FOR XML PATH ('')
                                                                ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details
                                                   END

                                                   IF @HAType = 'FCIAG'
                                                   BEGIN
                                                                INSERT  INTO #BlitzResults
                                                                            ( CheckID ,
                                                                                   Priority ,
                                                                                   FindingsGroup ,
                                                                                   Finding ,
                                                                                   URL ,
                                                                                   Details
                                                                            )
                                                                SELECT DISTINCT 53 AS CheckID ,
                                                                200 AS Priority ,
                                                                'Informational' AS FindingsGroup ,
                                                                'Cluster Node Info' AS Finding ,
                                                                'https://BrentOzar.com/go/node' AS URL,
                                                                'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName
                                                                FROM (SELECT UPPER(ar.replica_server_name) AS ServerName
                                                                                     ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY'
                                                                                     ELSE 'SECONDARY'
                                                                                     END AS HighAvailabilityRoleDesc
                                                                                     FROM sys.availability_groups AS ag
                                                                                     LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id
                                                                                     LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id
                                                                                     WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name
                                                                                     UNION ALL
                                                                                     SELECT UPPER(NodeName) AS ServerName
                                                                                     ,CASE is_current_owner 
                                                                                     WHEN 1 THEN 'PRIMARY'
                                                                                     ELSE 'SECONDARY'
                                                                                     END AS HighAvailabilityRoleDesc
                                                                                     FROM sys.dm_os_cluster_nodes) AS Z
                                                                ORDER BY 1
                                                                FOR XML PATH ('')
                                                                ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details
                                                   END
                                         END

					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 55 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 55) WITH NOWAIT;

						IF @UsualDBOwner IS NULL
							SET @UsualDBOwner = SUSER_SNAME(0x01);

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT 55 AS CheckID ,
										[name] AS DatabaseName ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Database Owner <> ' + @UsualDBOwner AS Finding , 
										'https://www.brentozar.com/go/owndb' AS URL ,
										( 'Database name: ' + [name] + '   '
										  + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details
								FROM    sys.databases
								WHERE (((SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)) AND (name IN (N'master', N'model', N'msdb', N'tempdb')))
										OR ((SUSER_SNAME(owner_sid) <> @UsualDBOwner) AND (name NOT IN (N'master', N'model', N'msdb', N'tempdb')))
									  )
								AND name NOT IN ( SELECT DISTINCT DatabaseName
												  FROM    #SkipChecks
												  WHERE CheckID IS NULL OR CheckID = 55);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 213 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 213) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  213 AS CheckID ,
										[name] AS DatabaseName ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'Database Owner is Unknown' AS Finding ,
										'' AS URL ,
										( 'Database name: ' + [name] + '   '
										  + 'Owner name: ' + ISNULL(SUSER_SNAME(owner_sid),'~~ UNKNOWN ~~') ) AS Details
								FROM    sys.databases
								WHERE   SUSER_SNAME(owner_sid) is NULL
										AND name NOT IN ( SELECT DISTINCT DatabaseName
														  FROM    #SkipChecks 
														  WHERE CheckID IS NULL OR CheckID = 213);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 57 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 57) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  57 AS CheckID ,
										230 AS Priority ,
										'Security' AS FindingsGroup ,
										'SQL Agent Job Runs at Startup' AS Finding ,
										'https://www.brentozar.com/go/startup' AS URL ,
										( 'Job [' + j.name
										  + '] runs automatically when SQL Server Agent starts up.  Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details
								FROM    msdb.dbo.sysschedules sched
										JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id
										JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id
								WHERE   sched.freq_type = 64
								        AND sched.enabled = 1;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 97 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 97) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  97 AS CheckID ,
										100 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Unusual SQL Server Edition' AS Finding ,
										'https://www.brentozar.com/go/workgroup' AS URL ,
										( 'This server is using '
										  + CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
										  + ', which is capped at low amounts of CPU and memory.' ) AS Details
								WHERE   CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'
										AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%'
										AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%'
										AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%'
										AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%';
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 154 )
                    AND SERVERPROPERTY('EngineEdition') <> 8
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 154) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  154 AS CheckID ,
										10 AS Priority ,
										'Performance' AS FindingsGroup ,
										'32-bit SQL Server Installed' AS Finding ,
										'https://www.brentozar.com/go/32bit' AS URL ,
										( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details
								WHERE   CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%';
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 62 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 62) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  62 AS CheckID ,
										[name] AS DatabaseName ,
										200 AS Priority ,
										'Performance' AS FindingsGroup ,
										'Old Compatibility Level' AS Finding ,
										'https://www.brentozar.com/go/compatlevel' AS URL ,
										( 'Database ' + [name]
										  + ' is compatibility level '
										  + CAST(compatibility_level AS VARCHAR(20))
										  + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details
								FROM    sys.databases
								WHERE   name NOT IN ( SELECT DISTINCT
																DatabaseName
													  FROM      #SkipChecks
													  WHERE CheckID IS NULL OR CheckID = 62)
										AND compatibility_level <= 90;
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 94 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 94) WITH NOWAIT;

						;WITH las_job_run AS (
											SELECT  MAX(instance_id) AS instance_id, 
													job_id, COUNT_BIG(*) AS job_executions,
													SUM(CASE WHEN run_status = 0 THEN 1 ELSE 0 END) AS failed_executions
											FROM msdb.dbo.sysjobhistory
											WHERE step_id = 0
											GROUP BY job_id
											)
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  94 AS CheckID ,
										200 AS [Priority] ,
										'Monitoring' AS FindingsGroup ,
										'Agent Jobs Without Failure Emails' AS Finding ,
										'https://www.brentozar.com/go/alerts' AS URL ,
										'The job ' + [name]
										+ ' has not been set up to notify an operator if it fails.' 
										+ CASE 
											WHEN jh.run_date IS NULL OR jh.run_time IS NULL OR jh.run_status IS NULL THEN ''
											ELSE N' Executions: '+ CAST(ljr.job_executions AS VARCHAR(30)) 
												+ CASE ljr.failed_executions
													WHEN 0 THEN N''
													ELSE N' ('+CAST(ljr.failed_executions AS NVARCHAR(10)) + N' failed)'
													END
												+ N' - last execution started on '
												+ CAST(CONVERT(DATE,CAST(jh.run_date AS NVARCHAR(8)),113) AS NVARCHAR(10)) 
												+ N', at ' 
												+ STUFF(STUFF(RIGHT(N'000000' + CAST(jh.run_time AS varchar(6)),6),3,0,N':'),6,0,N':')
												+ N', with status "'
												+ CASE jh.run_status 
														WHEN 0 THEN N'Failed'
														WHEN 1 THEN N'Succeeded'
														WHEN 2 THEN N'Retry'
														WHEN 3 THEN N'Canceled'
														WHEN 4 THEN N'In Progress'
													END +N'".'
											END	AS Details
								FROM    msdb.[dbo].[sysjobs] j
										LEFT JOIN las_job_run ljr 
											ON ljr.job_id = j.job_id
										LEFT JOIN msdb.[dbo].[sysjobhistory] jh
											ON ljr.job_id = jh.job_id AND ljr.instance_id = jh.instance_id
								WHERE   j.enabled = 1
										AND j.notify_email_operator_id = 0
										AND j.notify_netsend_operator_id = 0
										AND j.notify_page_operator_id = 0
										AND j.category_id <> 100; /* Exclude SSRS category */
					END;

				IF EXISTS ( SELECT  1
							FROM    sys.configurations
							WHERE   name = 'remote admin connections'
									AND value_in_use = 0 )
					AND NOT EXISTS ( SELECT 1
									 FROM   #SkipChecks
									 WHERE  DatabaseName IS NULL AND CheckID = 100 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 100) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  100 AS CheckID ,
										170 AS Priority ,
										'Reliability' AS FindingGroup ,
										'Remote DAC Disabled' AS Finding ,
										'https://www.brentozar.com/go/dac' AS URL ,
										'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.';
					END;

				IF EXISTS ( SELECT  *
							FROM    sys.dm_os_schedulers
							WHERE   is_online = 0 )
					AND NOT EXISTS ( SELECT 1
									 FROM   #SkipChecks
									 WHERE  DatabaseName IS NULL AND CheckID = 101 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 101) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  101 AS CheckID ,
										50 AS Priority ,
										'Performance' AS FindingGroup ,
										'CPU Schedulers Offline' AS Finding ,
										'https://www.brentozar.com/go/schedulers' AS URL ,
										'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.';
					END;

					IF NOT EXISTS ( SELECT  1
									FROM    #SkipChecks
									WHERE   DatabaseName IS NULL AND CheckID = 110 )
								AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes')
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 110) WITH NOWAIT;

							SET @StringToExecute = 'IF EXISTS (SELECT  *
												FROM sys.dm_os_nodes n
												INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id
												WHERE n.node_state_desc = ''OFFLINE'')
												INSERT  INTO #BlitzResults
														( CheckID ,
														  Priority ,
														  FindingsGroup ,
														  Finding ,
														  URL ,
														  Details
														)
														SELECT  110 AS CheckID ,
																50 AS Priority ,
																''Performance'' AS FindingGroup ,
																''Memory Nodes Offline'' AS Finding ,
																''https://www.brentozar.com/go/schedulers'' AS URL ,
																''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)';
									
									IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
									IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
									
									EXECUTE(@StringToExecute);
						END;

				IF EXISTS ( SELECT  *
							FROM    sys.databases
							WHERE   state > 1 )
					AND NOT EXISTS ( SELECT 1
									 FROM   #SkipChecks
									 WHERE  DatabaseName IS NULL AND CheckID = 102 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 102) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  102 AS CheckID ,
										[name] ,
										20 AS Priority ,
										'Reliability' AS FindingGroup ,
										'Unusual Database State: ' + [state_desc] AS Finding ,
										'https://www.brentozar.com/go/repair' AS URL ,
										'This database may not be online.'
								FROM    sys.databases
								WHERE   state > 1;
					END;

				IF EXISTS ( SELECT  *
							FROM    master.sys.extended_procedures )
					AND NOT EXISTS ( SELECT 1
									 FROM   #SkipChecks
									 WHERE  DatabaseName IS NULL AND CheckID = 105 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 105) WITH NOWAIT;

						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  105 AS CheckID ,
										'master' ,
										200 AS Priority ,
										'Reliability' AS FindingGroup ,
										'Extended Stored Procedures in Master' AS Finding ,
										'https://www.brentozar.com/go/clr' AS URL ,
										'The [' + name
										+ '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.'
								FROM    master.sys.extended_procedures;
					END;

					IF NOT EXISTS ( SELECT 1
										 FROM   #SkipChecks
										 WHERE  DatabaseName IS NULL AND CheckID = 107 )
						BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 107) WITH NOWAIT;

							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  107 AS CheckID ,
											50 AS Priority ,
											'Performance' AS FindingGroup ,
											'Poison Wait Detected: ' + wait_type  AS Finding ,
											'https://www.brentozar.com/go/poison/#' + wait_type AS URL ,
											CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.'
									FROM sys.[dm_os_wait_stats]
									WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL')
									GROUP BY wait_type
								    HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
									AND SUM([wait_time_ms]) > 60000;
						END;

					IF NOT EXISTS ( SELECT 1
										 FROM   #SkipChecks
										 WHERE  DatabaseName IS NULL AND CheckID = 270 )
						AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history')
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 270) WITH NOWAIT;

							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  270 AS CheckID ,
											1 AS Priority ,
											'Performance' AS FindingGroup ,
											'Memory Dangerous Low Recently'  AS Finding ,
											'https://www.brentozar.com/go/memhist' AS URL ,
											CAST(SUM(1) AS NVARCHAR(10)) + N' instances of ' + CAST(severity_level_desc AS NVARCHAR(100))
											+ N' severity level memory issues reported in the last 4 hours in sys.dm_os_memory_health_history.'
									FROM sys.dm_os_memory_health_history
									WHERE severity_level > 1
									GROUP BY severity_level, severity_level_desc;
						END;





					IF NOT EXISTS ( SELECT 1
										 FROM   #SkipChecks
										 WHERE  DatabaseName IS NULL AND CheckID = 121 )
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 121) WITH NOWAIT;

							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  121 AS CheckID ,
											50 AS Priority ,
											'Performance' AS FindingGroup ,
											'Poison Wait Detected: Serializable Locking'  AS Finding ,
											'https://www.brentozar.com/go/serializable' AS URL ,
											CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.'
									FROM sys.[dm_os_wait_stats]
									WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X')
								    HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
									AND SUM([wait_time_ms]) > 60000;
						END;


						IF NOT EXISTS ( SELECT 1
										 FROM   #SkipChecks
										 WHERE  DatabaseName IS NULL AND CheckID = 111 )
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 111) WITH NOWAIT;

							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  DatabaseName ,
									  URL ,
									  Details
									)
									SELECT  111 AS CheckID ,
											50 AS Priority ,
											'Reliability' AS FindingGroup ,
											'Possibly Broken Log Shipping'  AS Finding ,
											d.[name] ,
											'https://www.brentozar.com/go/shipping' AS URL ,
											d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.'
											FROM [master].sys.databases d
											INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id
												AND dm.mirroring_role IS NULL
											WHERE ( d.[state] = 1
											OR (d.[state] = 0 AND d.[is_in_standby] = 1) )
											AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh
											INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id
											WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS
											AND rh.restore_date >= DATEADD(dd, -2, GETDATE()));

						END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 112 )
									AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases')
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT;

								SET @StringToExecute = 'INSERT INTO #BlitzResults
									(CheckID,
									Priority,
									FindingsGroup,
									Finding,
									DatabaseName,
									URL,
									Details)
							  SELECT 112 AS CheckID,
							  100 AS Priority,
							  ''Performance'' AS FindingsGroup,
							  ''Change Tracking Enabled'' AS Finding,
							  d.[name],
							  ''https://www.brentozar.com/go/tracking'' AS URL,
							  ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
										
										EXECUTE(@StringToExecute);
							END;

						IF NOT EXISTS ( SELECT 1
										 FROM   #SkipChecks
										 WHERE  DatabaseName IS NULL AND CheckID = 116 )
									AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size')
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 116) WITH NOWAIT

							SET @StringToExecute = 'INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  116 AS CheckID ,
											200 AS Priority ,
											''Informational'' AS FindingGroup ,
											''Backup Compression Default Off''  AS Finding ,
											''https://www.brentozar.com/go/backup'' AS URL ,
											''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.''
											FROM sys.configurations
											WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0
                                            AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
										
										EXECUTE(@StringToExecute);
						END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 117 )
									AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores')
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT;
								
								SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL)
								INSERT INTO #BlitzResults
									(CheckID,
									Priority,
									FindingsGroup,
									Finding,
									URL,
									Details)
							  SELECT 117 AS CheckID,
							  100 AS Priority,
							  ''Performance'' AS FindingsGroup,
							  ''Memory Pressure Affecting Queries'' AS Finding,
							  ''https://www.brentozar.com/go/grants'' AS URL,
							  CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.''
							  FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
										
										EXECUTE(@StringToExecute);
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 124 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT;
								
								INSERT INTO #BlitzResults
									(CheckID,
									Priority,
									FindingsGroup,
									Finding,
									URL,
									Details)
								SELECT 124, 
										150, 
										'Performance', 
										'Deadlocks Happening Daily', 
										'https://www.brentozar.com/go/deadlocks',
										CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details
								FROM sys.dm_os_performance_counters p
									INNER JOIN sys.databases d ON d.name = 'tempdb'
								WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec'
									AND RTRIM(p.instance_name) = '_Total'
									AND p.cntr_value > 0
									AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10;
							END;

						IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time)
						AND NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 125 )
						BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 125) WITH NOWAIT;
							
								DECLARE @user_perm_sql NVARCHAR(MAX) = N'';
								DECLARE @user_perm_gb_out DECIMAL(38,2);
								
								IF @ProductVersionMajor >= 11
								
								BEGIN
								
								SET @user_perm_sql += N'
									SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2.
											THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.))
											ELSE NULL 
										   END
									FROM sys.dm_os_memory_clerks
									WHERE type = ''USERSTORE_TOKENPERM''
									AND    name = ''TokenAndPermUserStore''
								';
								
								END
								
								IF @ProductVersionMajor < 11
								
								BEGIN
								SET @user_perm_sql += N'
									SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2.
											THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb)  / 1024.0 / 1024.))
											ELSE NULL 
										   END
									FROM sys.dm_os_memory_clerks
									WHERE type = ''USERSTORE_TOKENPERM''
									AND    name = ''TokenAndPermUserStore''
								';
								
								END

								EXEC sys.sp_executesql @user_perm_sql, 
								                       N'@user_perm_gb DECIMAL(38,2) OUTPUT', 
													   @user_perm_gb = @user_perm_gb_out OUTPUT

							INSERT INTO #BlitzResults
								(CheckID,
								Priority,
								FindingsGroup,
								Finding,
								URL,
								Details)
							SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/',
								'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) 
								+ CASE WHEN @user_perm_gb_out IS NULL 
								       THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.'
									   ELSE '. You also have ' + CONVERT(NVARCHAR(20), @user_perm_gb_out) + ' GB of USERSTORE_TOKENPERM, which could indicate unusual memory consumption.'
								  END
							FROM sys.dm_exec_query_stats WITH (NOLOCK)
							ORDER BY creation_time;	
						END;

						IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1))
						AND NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 126 )
						BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 126) WITH NOWAIT;
							
							INSERT INTO #BlitzResults
								(CheckID,
								Priority,
								FindingsGroup,
								Finding,
								URL,
								Details)
							VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/',
								'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.');
						END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 128 )
                            AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
							BEGIN

							IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR
							   (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR
							   (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR
							   (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR
							   (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR
							   (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR
							   (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR
							   (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/)
								BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT;
								
								INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
									VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported',
										'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + 
										CASE WHEN @ProductVersionMajor >= 12 THEN
										'.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.'
										ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END);
								END;

							END;
							
						/* Reliability - Dangerous Build of SQL Server (Corruption) */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 129 )
                            AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
							BEGIN
							IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR
							   (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR
							   (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342)
								BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT;
								
								INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
									VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds',
										'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.');
								END;

							END;

						/* Reliability - Dangerous Build of SQL Server (Security) */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 157 )
                            AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
							BEGIN
							IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR
							   (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR
							   (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR
							   (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR
							   (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR
							   (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR
							   (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR
							   (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370)
								BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT;
								
								INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
									VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044',
										'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.');
								END;

							END;
						
						/* Check if SQL 2016 Standard Edition but not SP1 */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 189 )
                            AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
							BEGIN
							IF (@ProductVersionMajor = 13 AND @ProductVersionMinor < 4001 AND @@VERSION LIKE '%Standard Edition%')
								BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT;
								
								INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
									VALUES(189, 100, 'Features', 'Missing Features', 'https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-server-2016-service-pack-1-sp1-released/',
										'SQL 2016 Standard Edition is being used but not Service Pack 1. Check the URL for a list of Enterprise Features that are included in Standard Edition as of SP1.');
								END;

							END;						
                        
                        /* Check if SQL 2017 but not CU3 */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 216 )
                            AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
							BEGIN
							IF (@ProductVersionMajor = 14 AND @ProductVersionMinor < 3015)
								BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT;
								
								INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
									VALUES(216, 100, 'Features', 'Missing Features', 'https://support.microsoft.com/en-us/help/4041814',
										'SQL 2017 is being used but not Cumulative Update 3. We''d recommend patching to take advantage of increased analytics when running BlitzCache.');
								END;

							END;		

                        /* Cumulative Update Available */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 217 )
                            AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /* Azure Managed Instances and Azure SQL DB*/
                            AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE')
                            AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */
							BEGIN
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT;
								
								INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
                                    SELECT TOP 1 217, 100, 'Reliability', 'Cumulative Update Available', COALESCE(v.Url, 'https://SQLServerUpdates.com/'),
										v.MinorVersionName + ' was released on ' + CAST(CONVERT(DATETIME, v.ReleaseDate, 112) AS VARCHAR(100))
                                    FROM dbo.SqlServerVersions v
                                    WHERE v.MajorVersionNumber = @ProductVersionMajor
                                      AND v.MinorVersionNumber > @ProductVersionMinor
                                    ORDER BY v.MinorVersionNumber DESC;
							END;		

                        /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 145 )
	                        AND EXISTS ( SELECT *
					                        FROM   sys.all_objects o
					                        WHERE  o.name = 'dm_db_xtp_table_memory_stats' )
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			                        SELECT 145 AS CheckID,
			                        10 AS Priority,
			                        ''Performance'' AS FindingsGroup,
			                        ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding,
			                        ''https://www.brentozar.com/go/hekaton'' AS URL,
			                        CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details
			                        FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP''
                                    WHERE c.name = ''max server memory (MB)''
                                    GROUP BY c.value_in_use
                                    HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0)
                                      OR SUM(mem.pages_kb / 1024.0) > 250000 OPTION (RECOMPILE)';
		
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
	                        END;

                        /* Performance - In-Memory OLTP (Hekaton) In Use */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 146 )
	                        AND EXISTS ( SELECT *
					                        FROM   sys.all_objects o
					                        WHERE  o.name = 'dm_db_xtp_table_memory_stats' )
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			                        SELECT 146 AS CheckID,
			                        200 AS Priority,
			                        ''Performance'' AS FindingsGroup,
			                        ''In-Memory OLTP (Hekaton) In Use'' AS Finding,
			                        ''https://www.brentozar.com/go/hekaton'' AS URL,
			                        CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details
			                        FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP''
                                    WHERE c.name = ''max server memory (MB)''
                                    GROUP BY c.value_in_use
                                    HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)';
		
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
	                        END;

                        /* In-Memory OLTP (Hekaton) - Transaction Errors */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 147 )
	                        AND EXISTS ( SELECT *
					                        FROM   sys.all_objects o
					                        WHERE  o.name = 'dm_xtp_transaction_stats' )
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			                        SELECT 147 AS CheckID,
			                        100 AS Priority,
			                        ''In-Memory OLTP (Hekaton)'' AS FindingsGroup,
			                        ''Transaction Errors'' AS Finding,
			                        ''https://www.brentozar.com/go/hekaton'' AS URL,
			                        ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details
			                        FROM sys.dm_xtp_transaction_stats
                                    WHERE validation_failures <> 0
                                            OR dependencies_failed <> 0
                                            OR write_conflicts <> 0
                                            OR unique_constraint_violations <> 0 OPTION (RECOMPILE);';
		
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
	                        END;

                        /* Reliability - Database Files on Network File Shares */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 148 )
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
				                        ( CheckID ,
					                        DatabaseName ,
					                        Priority ,
					                        FindingsGroup ,
					                        Finding ,
					                        URL ,
					                        Details
				                        )
				                        SELECT DISTINCT 148 AS CheckID ,
						                        d.[name] AS DatabaseName ,
						                        170 AS Priority ,
						                        'Reliability' AS FindingsGroup ,
						                        'Database Files on Network File Shares' AS Finding ,
						                        'https://www.brentozar.com/go/nas' AS URL ,
						                        ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details
				                        FROM    sys.databases d
                                          INNER JOIN sys.master_files mf ON d.database_id = mf.database_id
				                        WHERE mf.physical_name LIKE '\\%'
						                        AND d.name NOT IN ( SELECT DISTINCT
													                        DatabaseName
											                        FROM    #SkipChecks
																	WHERE CheckID IS NULL OR CheckID = 148);
	                        END;

                        /* Reliability - Database Files Stored in Azure */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 149 )
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
				                        ( CheckID ,
					                        DatabaseName ,
					                        Priority ,
					                        FindingsGroup ,
					                        Finding ,
					                        URL ,
					                        Details
				                        )
				                        SELECT DISTINCT 149 AS CheckID ,
						                        d.[name] AS DatabaseName ,
						                        170 AS Priority ,
						                        'Reliability' AS FindingsGroup ,
						                        'Database Files Stored in Azure' AS Finding ,
						                        'https://www.brentozar.com/go/azurefiles' AS URL ,
						                        ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details
				                        FROM    sys.databases d
                                          INNER JOIN sys.master_files mf ON d.database_id = mf.database_id
				                        WHERE mf.physical_name LIKE 'http://%'
						                        AND d.name NOT IN ( SELECT DISTINCT
													                        DatabaseName
											                        FROM    #SkipChecks
																	WHERE CheckID IS NULL OR CheckID = 149);
	                        END;

                        /* Reliability - Errors Logged Recently in the Default Trace */

						/* First, let's check that there aren't any issues with the trace files */
						BEGIN TRY

						IF @SkipTrace = 0
						BEGIN
						    INSERT INTO #fnTraceGettable
						    	(	TextData ,
						    		DatabaseName ,
						    		EventClass ,
						    		Severity ,
						    		StartTime ,
						    		EndTime ,
						    		Duration ,
						    		NTUserName ,
						    		NTDomainName ,
						    		HostName ,
						    		ApplicationName ,
						    		LoginName ,
						    		DBUserName
						    	)
						    	SELECT TOP 20000
						    		CONVERT(NVARCHAR(4000),t.TextData) ,
						    		t.DatabaseName ,
						    		t.EventClass ,
						    		t.Severity ,
						    		t.StartTime ,
						    		t.EndTime ,
						    		t.Duration ,
						    		t.NTUserName ,
						    		t.NTDomainName ,
						    		t.HostName ,
						    		t.ApplicationName ,
						    		t.LoginName ,
						    		t.DBUserName
						    	FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t
						    	WHERE
						    	(
						    		t.EventClass = 22
						    		AND t.Severity >= 17
						    		AND t.StartTime > DATEADD(dd, -30, GETDATE())
						    	)
						    	OR
						    	(
						    	    t.EventClass IN (92, 93)
                                    AND t.StartTime > DATEADD(dd, -30, GETDATE())
                                    AND t.Duration > 15000000
						    	)
						    	OR
						    	(
						    		t.EventClass IN (94, 95, 116)
						    	)
							END;

							SET @TraceFileIssue = 0

						END TRY
						BEGIN CATCH

							SET @TraceFileIssue = 1
						
						END CATCH
											
						IF @TraceFileIssue = 1
							BEGIN
						IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 199 )								
								
								INSERT  INTO #BlitzResults
								            ( CheckID ,
								                DatabaseName ,
								                Priority ,
								                FindingsGroup ,
								                Finding ,
								                URL ,
								                Details
								            )
											SELECT
												'199' AS CheckID ,
												'' AS DatabaseName ,
												50 AS Priority ,
												'Reliability' AS FindingsGroup ,
												'There Is An Error With The Default Trace' AS Finding ,
												'https://www.brentozar.com/go/defaulttrace' AS URL ,
												'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details
							END
						
						IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 150 )
                            AND @base_tracefilename IS NOT NULL
							AND @TraceFileIssue = 0
	                        BEGIN

		                        IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 150) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
				                        ( CheckID ,
					                        DatabaseName ,
					                        Priority ,
					                        FindingsGroup ,
					                        Finding ,
					                        URL ,
					                        Details
				                        )
				                        SELECT DISTINCT 150 AS CheckID ,
					                            t.DatabaseName,
						                        170 AS Priority ,
						                        'Reliability' AS FindingsGroup ,
						                        'Errors Logged Recently in the Default Trace' AS Finding ,
						                        'https://www.brentozar.com/go/defaulttrace' AS URL ,
						                         CAST(t.TextData AS NVARCHAR(4000)) AS Details
                                        FROM    #fnTraceGettable t
                                        WHERE t.EventClass = 22
                                          /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */
										  --AND t.Severity >= 17
                                          --AND t.StartTime > DATEADD(dd, -30, GETDATE());
	                        END;

                        /* Performance - File Growths Slow */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 151 )
                            AND @base_tracefilename IS NOT NULL
							AND @TraceFileIssue = 0
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
				                        ( CheckID ,
					                        DatabaseName ,
					                        Priority ,
					                        FindingsGroup ,
					                        Finding ,
					                        URL ,
					                        Details
				                        )
				                        SELECT DISTINCT 151 AS CheckID ,
					                            t.DatabaseName,
						                        50 AS Priority ,
						                        'Performance' AS FindingsGroup ,
						                        'File Growths Slow' AS Finding ,
						                        'https://www.brentozar.com/go/filegrowth' AS URL ,
						                        CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details
                                        FROM    #fnTraceGettable t
                                        WHERE t.EventClass IN (92, 93)
                                          /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */
										  --AND t.StartTime > DATEADD(dd, -30, GETDATE())
                                          --AND t.Duration > 15000000
                                        GROUP BY t.DatabaseName
                                        HAVING COUNT(*) > 1;
	                        END;

                        /* Performance - Many Plans for One Query */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 160 )
                            AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash')
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT;
								
								SET @StringToExecute = N'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			                        SELECT TOP 1 160 AS CheckID,
			                        100 AS Priority,
			                        ''Performance'' AS FindingsGroup,
			                        ''Many Plans for One Query'' AS Finding,
			                        ''https://www.brentozar.com/go/parameterization'' AS URL,
			                        CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details
			                        FROM sys.dm_exec_query_stats qs
                                    CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa
                                    WHERE pa.attribute = ''dbid''
                                    GROUP BY qs.query_hash, pa.value
                                    HAVING COUNT(DISTINCT plan_handle) > ';

								IF 50 > (SELECT COUNT(*) FROM sys.databases)
									SET @StringToExecute = @StringToExecute + N' 50 ';
								ELSE
									SELECT @StringToExecute = @StringToExecute + CAST(COUNT(*) * 2 AS NVARCHAR(50)) FROM sys.databases;

								SET @StringToExecute = @StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION (RECOMPILE);';
		
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
	                        END;

                        /* Performance - High Number of Cached Plans */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 161 )
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			                        SELECT TOP 1 161 AS CheckID,
			                        100 AS Priority,
			                        ''Performance'' AS FindingsGroup,
			                        ''High Number of Cached Plans'' AS Finding,
			                        ''https://www.brentozar.com/go/planlimits'' AS URL,
			                        ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details
			                        FROM sys.dm_os_memory_cache_hash_tables ht
			                        INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type
			                        where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' )
			                        AND cc.entries_count >= (3 * ht.buckets_count) OPTION (RECOMPILE)';
		
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
	                        END;

						/* Performance - Too Much Free Memory */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 165 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT;
								
								INSERT INTO #BlitzResults
									(CheckID,
									Priority,
									FindingsGroup,
									Finding,
									URL,
									Details)
								SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory',
									CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details
								FROM sys.dm_os_performance_counters cFree
								INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%'
									AND cTotal.counter_name = N'Total Server Memory (KB)                                                                                                        '
								WHERE cFree.object_name LIKE N'%Memory Manager%'
									AND cFree.counter_name = N'Free Memory (KB)                                                                                                                '
									AND CAST(cTotal.cntr_value AS BIGINT) > 20480000000
									AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT)
                                    AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%';

							END;

                        /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */
                        IF NOT EXISTS ( SELECT  1
				                        FROM    #SkipChecks
				                        WHERE   DatabaseName IS NULL AND CheckID = 155 )
				           AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6
	                        BEGIN
		
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
				                        ( CheckID ,
					                        Priority ,
					                        FindingsGroup ,
					                        Finding ,
					                        URL ,
					                        Details
				                        )
				                        SELECT 155 AS CheckID ,
						                        0 AS Priority ,
						                        'Outdated sp_Blitz' AS FindingsGroup ,
						                        'sp_Blitz is Over 6 Months Old' AS Finding ,
						                        'http://FirstResponderKit.org/' AS URL ,
						                        'Some things get better with age, like fine wine and your T-SQL. However, sp_Blitz is not one of those things - time to go download the current one.' AS Details;
	                        END;

						/* Populate a list of database defaults. I'm doing this kind of oddly -
						    it reads like a lot of work, but this way it compiles & runs on all
						    versions of SQL Server.
						*/
						
						IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT;
						
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_read_committed_snapshot_on', 
                            CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END,  /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
                            133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases');
						/* Not alerting for this since we actually want it and we have a separate check for it:
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases');
						*/
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases');
						--INSERT INTO #DatabaseDefaults
						--  SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL
						--  FROM sys.all_columns
						--  WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases');
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases')
                            AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
						INSERT INTO #DatabaseDefaults
						  SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL
						  FROM sys.all_columns
						  WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ;

						DECLARE DatabaseDefaultsLoop CURSOR FOR
						  SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details
						  FROM #DatabaseDefaults;

						OPEN DatabaseDefaultsLoop;
						FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails;
						WHILE @@FETCH_STATUS = 0
						BEGIN

							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, @CurrentCheckID) WITH NOWAIT;

							/* Target Recovery Time (142) can be either 0 or 60 due to a number of bugs */
						    IF @CurrentCheckID = 142
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
								   SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + '''
									FROM sys.databases d
									WHERE d.database_id > 4 AND DB_NAME(d.database_id) != ''rdsadmin'' AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);';
							ELSE
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
								   SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + '''
									FROM sys.databases d
									WHERE d.database_id > 4 AND DB_NAME(d.database_id) != ''rdsadmin'' AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);';
						
							IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
							IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
							
							EXEC (@StringToExecute);

						FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails;
						END;

						CLOSE DatabaseDefaultsLoop;
						DEALLOCATE DatabaseDefaultsLoop;

/* CheckID 272 - Performance - Optimized Locking Not Fully Set Up */
IF EXISTS (SELECT * FROM sys.all_columns WHERE name = 'is_optimized_locking_on' AND object_id = OBJECT_ID('sys.databases'))
			   AND NOT EXISTS ( SELECT  1
							    FROM    #SkipChecks
							    WHERE   DatabaseName IS NULL AND CheckID = 272 )
				BEGIN
					IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 272) WITH NOWAIT;
						
					SET @StringToExecute = N'
					INSERT    INTO [#BlitzResults]
							( [CheckID] ,
								[Priority] ,
								[FindingsGroup] ,
								[Finding] ,
								[DatabaseName] ,
								[URL] ,
								[Details] )

					SELECT
					272 AS [CheckID] ,
					100 AS [Priority] ,
					''Performance'' AS [FindingsGroup] ,
					''Optimized Locking Not Fully Set Up'' AS [Finding] ,
					name,
					''https://www.brentozar.com/go/optimizedlocking'' AS [URL] ,
					''RCSI should be enabled on this database to get the full benefits of optimized locking.''  AS [Details]
					FROM sys.databases
					WHERE is_optimized_locking_on = 1 AND is_read_committed_snapshot_on = 0;'

					EXEC(@StringToExecute);
				END; 

/* Check if target recovery interval <> 60 */
IF
    @ProductVersionMajor >= 10
    AND NOT EXISTS
        (
            SELECT
                1/0
            FROM  #SkipChecks AS sc
            WHERE sc.DatabaseName IS NULL
            AND   sc.CheckID = 257
        )
    BEGIN
        IF EXISTS
           (
               SELECT
                   1/0
               FROM sys.all_columns AS ac
               WHERE ac.name = 'target_recovery_time_in_seconds'
               AND   ac.object_id = OBJECT_ID('sys.databases')
           )
           BEGIN
               IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT;
        
               DECLARE
                   @tri nvarchar(max) = N'
                   SELECT
                       DatabaseName = 
                           d.name,
                       CheckId = 
                           257,
                       Priority = 
                           50,
                       FindingsGroup = 
                           N''Performance'',
                       Finding =
                           N''Recovery Interval Not Optimal'',
                       URL = 
                           N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'',
                       Details = 
                           N''The database '' +
                           QUOTENAME(d.name) +
                           N'' has a target recovery interval of '' +
                           RTRIM(d.target_recovery_time_in_seconds) + 
                           CASE
                               WHEN d.target_recovery_time_in_seconds = 0
                               THEN N'', which is a legacy default, and should be changed to 60.''
                               WHEN d.target_recovery_time_in_seconds <> 0 
                               THEN N'', which is probably a mistake, and should be changed to 60.''
                           END
                   FROM sys.databases AS d
                   WHERE d.database_id > 4
                   AND   d.is_read_only = 0
                   AND   d.is_in_standby = 0
                   AND   d.target_recovery_time_in_seconds <> 60;
                   ';

               INSERT INTO
			       #BlitzResults
               (
			       DatabaseName,
			       CheckID,
                   Priority,
                   FindingsGroup,
                   Finding,
                   URL,
                   Details
               )
               EXEC sys.sp_executesql
                   @tri;

            END;
        END;
							

/*This checks to see if Agent is Offline*/
IF @ProductVersionMajor >= 10
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 167 )
					BEGIN
					IF EXISTS ( SELECT  1
											FROM    sys.all_objects
											WHERE   name = 'dm_server_services' )
									BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 167) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							167 AS [CheckID] ,
							250 AS [Priority] ,
							'Server Info' AS [FindingsGroup] ,
							'Agent is Currently Offline' AS [Finding] ,
							'' AS [URL] ,
							( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.'
							   ) AS [Details]
						  FROM
							[sys].[dm_server_services]
						  WHERE [status_desc] <> 'Running'
						  AND [servicename] LIKE 'SQL Server Agent%'
						  AND CAST(SERVERPROPERTY('Edition') AS VARCHAR(1000)) NOT LIKE '%xpress%';

					END;
				END;
/* CheckID 258 - Security - SQL Server Service is running as LocalSystem or NT AUTHORITY\SYSTEM */
IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1)
			   AND NOT EXISTS ( SELECT  1
							    FROM    #SkipChecks
							    WHERE   DatabaseName IS NULL AND CheckID = 258 )
				BEGIN
				IF EXISTS ( SELECT  1
							FROM    sys.all_objects
							WHERE   [name] = 'dm_server_services' )
					BEGIN
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 258) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							258 AS [CheckID] ,
							1 AS [Priority] ,
							'Security' AS [FindingsGroup] ,
							'Dangerous Service Account' AS [Finding] ,
							'https://vladdba.com/SQLServerSvcAccount' AS [URL] ,
							'SQL Server''s service account is '+ [service_account] 
							+' - meaning that anyone who can use xp_cmdshell can do absolutely anything on the host.'  AS [Details]
						  FROM
							[sys].[dm_server_services]
						  WHERE ([service_account] = 'LocalSystem'
						    OR LOWER([service_account]) = 'nt authority\system')
						  AND [servicename] LIKE 'SQL Server%'
						  AND [servicename] NOT LIKE 'SQL Server Agent%';
					END;
				END;

/* CheckID 259 - Security - SQL Server Agent Service is running as LocalSystem or NT AUTHORITY\SYSTEM */
IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1)
			   AND NOT EXISTS ( SELECT  1
							    FROM    #SkipChecks
							    WHERE   DatabaseName IS NULL AND CheckID = 259 )
				BEGIN
				IF EXISTS ( SELECT  1
							FROM    sys.all_objects
							WHERE   [name] = 'dm_server_services' )
					BEGIN
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 259) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							259 AS [CheckID] ,
							1 AS [Priority] ,
							'Security' AS [FindingsGroup] ,
							'Dangerous Service Account' AS [Finding] ,
							'https://vladdba.com/SQLServerSvcAccount' AS [URL] ,
							'SQL Server Agent''s service account is '+ [service_account] 
							+' - meaning that anyone who can create and run jobs can do absolutely anything on the host.'  AS [Details]
						  FROM
							[sys].[dm_server_services]
						  WHERE ([service_account] = 'LocalSystem'
						    OR LOWER([service_account]) = 'nt authority\system')
						  AND [servicename] LIKE 'SQL Server Agent%';
					END;
				END;

/*This checks to see if the Full Text thingy is offline*/
IF @ProductVersionMajor >= 10
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 168 )
					BEGIN
					IF EXISTS ( SELECT  1
											FROM    sys.all_objects
											WHERE   name = 'dm_server_services' )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 168) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							168 AS [CheckID] ,
							250 AS [Priority] ,
							'Server Info' AS [FindingsGroup] ,
							'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] ,
							'' AS [URL] ,
							( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.'
							   ) AS [Details]
						  FROM
							[sys].[dm_server_services]
						  WHERE [status_desc] <> 'Running'
						  AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%';

					END;
					END;

/*This checks which service account SQL Server is running as.*/
IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1)
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 169 )

					BEGIN
					IF EXISTS ( SELECT  1
											FROM    sys.all_objects
											WHERE   name = 'dm_server_services' )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 169) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							169 AS [CheckID] ,
							250 AS [Priority] ,
							'Informational' AS [FindingsGroup] ,
							'SQL Server is running under an NT Service account' AS [Finding] ,
							'https://www.brentozar.com/go/setup' AS [URL] ,
							( 'I''m running as ' + [service_account] + '.'
							   ) AS [Details]
						  FROM
							[sys].[dm_server_services]
						  WHERE [service_account] LIKE 'NT Service%'
						  AND [servicename] LIKE 'SQL Server%'
						  AND [servicename] NOT LIKE 'SQL Server Agent%'
						  AND [servicename] NOT LIKE 'SQL Server Launchpad%';

					END;
					END;

/*This checks which service account SQL Agent is running as.*/
IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1)
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 170 )

					BEGIN
					IF EXISTS ( SELECT  1
											FROM    sys.all_objects
											WHERE   name = 'dm_server_services' )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 170) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							170 AS [CheckID] ,
							250 AS [Priority] ,
							'Informational' AS [FindingsGroup] ,
							'SQL Server Agent is running under an NT Service account' AS [Finding] ,
							'https://www.brentozar.com/go/setup' AS [URL] ,
							( 'I''m running as ' + [service_account] + '.'
							   ) AS [Details]
						  FROM
							[sys].[dm_server_services]
						  WHERE [service_account] LIKE 'NT Service%'
						  AND [servicename] LIKE 'SQL Server Agent%';

					END;
					END;
                    
/*This checks that First Responder Kit is consistent. 
It assumes that all the objects of the kit resides in the same database, the one in which this SP is stored
It also is ready to check for installation in another schema.
*/
IF(
    NOT EXISTS ( 
        SELECT  1
        FROM    #SkipChecks
        WHERE   DatabaseName IS NULL AND CheckID = 226 
    )
)
BEGIN

    IF @Debug IN (1, 2) RAISERROR('Running check with id %d',0,1,2000);

    SET @spBlitzFullName                    = QUOTENAME(DB_NAME()) + '.' +QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID));
    SET @BlitzIsOutdatedComparedToOthers    = 0;
    SET @tsql                               = NULL;
    SET @VersionCheckModeExistsTSQL         = NULL;
    SET @BlitzProcDbName                    = DB_NAME();
    SET @ExecRet                            = NULL;
    SET @InnerExecRet                       = NULL;
    SET @TmpCnt                             = NULL;

    SET @PreviousComponentName              = NULL;
    SET @PreviousComponentFullPath          = NULL;
    SET @CurrentStatementId                 = NULL;
    SET @CurrentComponentSchema             = NULL;
    SET @CurrentComponentName               = NULL;
    SET @CurrentComponentType               = NULL;
    SET @CurrentComponentVersionDate        = NULL;
    SET @CurrentComponentFullName           = NULL;
    SET @CurrentComponentMandatory          = NULL;
    SET @MaximumVersionDate                 = NULL;

    SET @StatementCheckName                 = NULL;
    SET @StatementOutputsCounter            = NULL;
    SET @OutputCounterExpectedValue         = NULL;
    SET @StatementOutputsExecRet            = NULL;
    SET @StatementOutputsDateTime           = NULL;

    SET @CurrentComponentMandatoryCheckOK   = NULL;
    SET @CurrentComponentVersionCheckModeOK = NULL;

    SET @canExitLoop                        = 0;
    SET @frkIsConsistent                    = 0;


    SET @tsql = 'USE ' + QUOTENAME(@BlitzProcDbName) + ';' + @crlf +
                'WITH FRKComponents (' + @crlf +
                '    ObjectName,' + @crlf +
                '    ObjectType,' + @crlf +
                '    MandatoryComponent' + @crlf +
                ')' + @crlf +
                'AS (' + @crlf +
                '    SELECT ''sp_AllNightLog'',''P'' ,0' + @crlf +
                '    UNION ALL' + @crlf +
                '    SELECT ''sp_AllNightLog_Setup'', ''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_Blitz'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_BlitzBackups'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_BlitzCache'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_BlitzFirst'',''P'',0' + @crlf +
                '    UNION ALL' + @crlf +
                '    SELECT ''sp_BlitzIndex'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_BlitzLock'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_BlitzQueryStore'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_BlitzWho'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_DatabaseRestore'',''P'',0' + @crlf +
                '    UNION ALL ' + @crlf +
                '    SELECT ''sp_ineachdb'',''P'',0' + @crlf +
                '    UNION ALL' + @crlf +
                '    SELECT ''SqlServerVersions'',''U'',0' + @crlf +
                ')' + @crlf +
                'INSERT INTO #FRKObjects (' + @crlf +
                '    DatabaseName,ObjectSchemaName,ObjectName, ObjectType,MandatoryComponent' + @crlf +
                ')' + @crlf +
                'SELECT DB_NAME(),SCHEMA_NAME(o.schema_id), c.ObjectName,c.ObjectType,c.MandatoryComponent' + @crlf +
                'FROM ' + @crlf +
                '    FRKComponents c' + @crlf +
                'LEFT JOIN ' + @crlf +
                '    sys.objects o' + @crlf +
                'ON c.ObjectName  = o.[name]' + @crlf +
                'AND c.ObjectType = o.[type]' + @crlf +
                --'WHERE o.schema_id IS NOT NULL' + @crlf +
                ';'
                ;

    EXEC @ExecRet = sp_executesql @tsql ;

    -- TODO: add check for statement success

    -- TODO: based on SP requirements and presence (SchemaName is not null) ==> update MandatoryComponent column

    -- Filling #StatementsToRun4FRKVersionCheck 
    INSERT INTO #StatementsToRun4FRKVersionCheck (
        CheckName,StatementText,SubjectName,SubjectFullPath, StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,StatementOutputsDateTime
    )
    SELECT 
        'Mandatory',
        'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;',
        ObjectName,
        QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName),
        1,
        0,
        0,
        0
    FROM #FRKObjects
    UNION ALL 
    SELECT 
        'VersionCheckMode',
        'SELECT @cnt = COUNT(*) FROM ' + 
        QUOTENAME(DatabaseName) + '.sys.all_parameters ' + 
        'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] = ''@VersionCheckMode'';',
        ObjectName,
        QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName),
        1,
        1,
        0,
        0
    FROM #FRKObjects
    WHERE ObjectType = 'P' 
    AND ObjectSchemaName IS NOT NULL
    UNION ALL
    SELECT 
        'VersionCheck',
        'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1 , @VersionDate = @ObjDate OUTPUT;',
        ObjectName,
        QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName),
        0,
        0,
        1,
        1
    FROM #FRKObjects
    WHERE ObjectType = 'P' 
    AND ObjectSchemaName IS NOT NULL
    ;
    IF(@Debug in (1,2))
    BEGIN
        SELECT * 
        FROM #StatementsToRun4FRKVersionCheck ORDER BY SubjectName,SubjectFullPath,StatementId  -- in case of schema change  ;
    END;
    
    
    -- loop on queries...
    WHILE(@canExitLoop = 0)
    BEGIN
        SET @CurrentStatementId         = NULL;

        SELECT TOP 1
            @StatementCheckName         = CheckName,
            @CurrentStatementId         = StatementId ,
            @CurrentComponentName       = SubjectName,
            @CurrentComponentFullName   = SubjectFullPath,
            @tsql                       = StatementText,
            @StatementOutputsCounter    = StatementOutputsCounter,
            @OutputCounterExpectedValue = OutputCounterExpectedValue ,
            @StatementOutputsExecRet    = StatementOutputsExecRet,
            @StatementOutputsDateTime   = StatementOutputsDateTime
        FROM #StatementsToRun4FRKVersionCheck
        ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema change */
        ;

        -- loop exit condition
        IF(@CurrentStatementId IS NULL)
        BEGIN
            BREAK;
        END;

        IF @Debug IN (1, 2) RAISERROR('    Statement: %s',0,1,@tsql);

        -- we start a new component
        IF(@PreviousComponentName IS NULL OR 
            (@PreviousComponentName IS NOT NULL AND @PreviousComponentName <> @CurrentComponentName) OR
            (@PreviousComponentName IS NOT NULL AND @PreviousComponentName = @CurrentComponentName AND @PreviousComponentFullPath <> @CurrentComponentFullName)
        )
        BEGIN
            -- reset variables
            SET @CurrentComponentMandatoryCheckOK   = 0;
            SET @CurrentComponentVersionCheckModeOK = 0;
            SET @PreviousComponentName              = @CurrentComponentName;
            SET @PreviousComponentFullPath          = @CurrentComponentFullName ;
        END;

        IF(@StatementCheckName NOT IN ('Mandatory','VersionCheckMode','VersionCheck'))
        BEGIN
            INSERT  INTO #BlitzResults( 
                CheckID ,
                Priority ,
                FindingsGroup ,
                Finding ,
                URL ,
                Details
            )
            SELECT 
                226 AS CheckID ,
                253 AS Priority ,
                'First Responder Kit' AS FindingsGroup ,
                'Version Check Failed (code generator changed)' AS Finding ,
                'http://FirstResponderKit.org' AS URL ,
                'Download an updated First Responder Kit. Your version check failed because a change has been made to the version check code generator.' + @crlf +
                'Error: No handler for check with name "' + ISNULL(@StatementCheckName,'') + '"' AS Details
            ;
            
            -- we will stop the test because it's possible to get the same message for other components
            SET @canExitLoop = 1;
            CONTINUE;
        END;

        IF(@StatementCheckName = 'Mandatory')
        BEGIN
            -- outputs counter
            EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT;

            IF(@ExecRet <> 0)
            BEGIN
            
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    226 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Version Check Failed (dynamic query failure)' AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. Your version check failed due to dynamic query failure.' + @crlf +
                    'Error: following query failed at execution (check if component [' + ISNULL(@CurrentComponentName,@CurrentComponentName) + '] is mandatory and missing)' + @crlf +
                    @tsql AS Details
                ;
                
                -- we will stop the test because it's possible to get the same message for other components
                SET @canExitLoop = 1;
                CONTINUE;
            END;

            IF(@TmpCnt <> @OutputCounterExpectedValue)
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    227 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Component Missing: ' + @CurrentComponentName AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated version of the First Responder Kit to install it.' AS Details
                ;
                
                -- as it's missing, no value for SubjectFullPath
                DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectName = @CurrentComponentName ;
                CONTINUE;
            END;

            SET @CurrentComponentMandatoryCheckOK = 1;
        END;
        
        IF(@StatementCheckName = 'VersionCheckMode')
        BEGIN
            IF(@CurrentComponentMandatoryCheckOK = 0)
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    226 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Version Check Failed (unexpectedly modified checks ordering)' AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. Version check failed because "Mandatory" check has not been completed before for current component' + @crlf +
                    'Error: version check mode happenned before "Mandatory" check for component called "' + @CurrentComponentFullName + '"'
                ;
                
                -- we will stop the test because it's possible to get the same message for other components
                SET @canExitLoop = 1;
                CONTINUE;
            END;

            -- outputs counter
            EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT;

            IF(@ExecRet <> 0)
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    226 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Version Check Failed (dynamic query failure)' AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. Version check failed because a change has been made to the code generator.' + @crlf +
                    'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf +
                    @tsql AS Details
                ;
                
                -- we will stop the test because it's possible to get the same message for other components
                SET @canExitLoop = 1;
                CONTINUE;
            END;

            IF(@TmpCnt <> @OutputCounterExpectedValue)
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    228 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Component Outdated: ' + @CurrentComponentFullName AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. Component ' + @CurrentComponentFullName + ' is not at the minimum version required to run this procedure' + @crlf +
                    'VersionCheckMode has been introduced in component version date after "20190320". This means its version is lower than or equal to that date.' AS Details;
                ;            
                            
                DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ;
                CONTINUE;
            END;

            SET @CurrentComponentVersionCheckModeOK = 1;
        END;    

        IF(@StatementCheckName = 'VersionCheck')
        BEGIN
            IF(@CurrentComponentMandatoryCheckOK = 0 OR @CurrentComponentVersionCheckModeOK = 0)
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    226 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Version Check Failed (unexpectedly modified checks ordering)' AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. Version check failed because "VersionCheckMode" check has not been completed before for component called "' + @CurrentComponentFullName + '"' + @crlf +
                    'Error: VersionCheck happenned before "VersionCheckMode" check for component called "' + @CurrentComponentFullName + '"'
                ;
                
                -- we will stop the test because it's possible to get the same message for other components
                SET @canExitLoop = 1;
                CONTINUE;
            END;

            EXEC @ExecRet = sp_executesql @tsql , N'@ExecRet INT OUTPUT, @ObjDate DATETIME OUTPUT', @ExecRet = @InnerExecRet OUTPUT, @ObjDate = @CurrentComponentVersionDate OUTPUT;

            IF(@ExecRet <> 0)
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    226 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Version Check Failed (dynamic query failure)' AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. The version check failed because a change has been made to the code generator.' + @crlf +
                    'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf +
                    @tsql AS Details
                ;
                
                -- we will stop the test because it's possible to get the same message for other components
                SET @canExitLoop = 1;
                CONTINUE;
            END;
            
            
            IF(@InnerExecRet <> 0)
            BEGIN                
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    226 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Version Check Failed (Failed dynamic SP call to ' + @CurrentComponentFullName + ')' AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download an updated First Responder Kit. Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf +
                    'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf +
                    'T-SQL Query: ' + @crlf + 
                    @tsql AS Details
                ;
                
                -- advance to next component
                DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ;
                CONTINUE;
            END;

            IF(@CurrentComponentVersionDate < @VersionDate)
            BEGIN
            
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    228 AS CheckID ,
                    253 AS Priority ,
                    'First Responder Kit' AS FindingsGroup ,
                    'Component Outdated: ' + @CurrentComponentFullName AS Finding ,
                    'http://FirstResponderKit.org' AS URL ,
                    'Download and install the latest First Responder Kit - you''re running some older code, and it doesn''t get better with age.' AS Details
                ;            
            
                RAISERROR('Component %s is outdated',10,1,@CurrentComponentFullName);
                -- advance to next component
                DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ;
                CONTINUE;
            END;

            ELSE IF(@CurrentComponentVersionDate > @VersionDate AND @BlitzIsOutdatedComparedToOthers = 0)
            BEGIN
                SET @BlitzIsOutdatedComparedToOthers = 1;
                RAISERROR('Procedure %s is outdated',10,1,@spBlitzFullName);
                IF(@MaximumVersionDate IS NULL OR @MaximumVersionDate < @CurrentComponentVersionDate)
                BEGIN
                    SET @MaximumVersionDate = @CurrentComponentVersionDate;
                END;
            END;
            /* Kept for debug purpose:
            ELSE 
            BEGIN
                INSERT  INTO #BlitzResults( 
                    CheckID ,
                    Priority ,
                    FindingsGroup ,
                    Finding ,
                    URL ,
                    Details
                )
                SELECT 
                    2000 AS CheckID ,
                    250 AS Priority ,
                    'Informational' AS FindingsGroup ,
                    'First Responder kit component ' + @CurrentComponentFullName + ' is at the expected version' AS Finding ,
                    'https://www.BrentOzar.com/blitz/' AS URL ,
                    'Version date is: ' + CONVERT(VARCHAR(32),@CurrentComponentVersionDate,121) AS Details
                ;
            END;
            */
        END;

        -- could be performed differently to minimize computation
        DELETE FROM #StatementsToRun4FRKVersionCheck WHERE StatementId = @CurrentStatementId ;
    END;
END;


/*This counts memory dumps and gives min and max date of in view*/
IF @ProductVersionMajor >= 10
               AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor < 4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 171 )
					BEGIN
					IF EXISTS ( SELECT  1
											FROM    sys.all_objects
											WHERE   name = 'dm_server_memory_dumps' )
						BEGIN
							IF EXISTS (SELECT * FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE()))

							BEGIN
							
							  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 171) WITH NOWAIT;
							
							  INSERT    INTO [#BlitzResults]
										( [CheckID] ,
										  [Priority] ,
										  [FindingsGroup] ,
										  [Finding] ,
										  [URL] ,
										  [Details] )

								SELECT
								171 AS [CheckID] ,
								20 AS [Priority] ,
								'Reliability' AS [FindingsGroup] ,
								'Memory Dumps Have Occurred' AS [Finding] ,
								'https://www.brentozar.com/go/dump' AS [URL] ,
								( 'That ain''t good. I''ve had ' +
									CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' +
									CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) +
									' and ' +
									CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) +
									'!'
								   ) AS [Details]
							  FROM
								[sys].[dm_server_memory_dumps]
							  WHERE [creation_time] >= DATEADD(year, -1, GETDATE());

							END;
						END;
					END;

/*Checks to see if you're on Developer or Evaluation*/
					IF	NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 173 )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 173) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							173 AS [CheckID] ,
							200 AS [Priority] ,
							'Licensing' AS [FindingsGroup] ,
							'Non-Production License' AS [Finding] ,
							'https://www.brentozar.com/go/licensing' AS [URL] ,
							( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' +
							CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) +
							' the good folks at Microsoft might get upset with you. Better start counting those cores.'
							   ) AS [Details]
							WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%'
							OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%';

					END;

/*Checks to see if Buffer Pool Extensions are in use*/
			IF @ProductVersionMajor >= 12
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 174 )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 174) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							174 AS [CheckID] ,
							200 AS [Priority] ,
							'Performance' AS [FindingsGroup] ,
							'Buffer Pool Extensions Enabled' AS [Finding] ,
							'https://www.brentozar.com/go/bpe' AS [URL] ,
							( 'You have Buffer Pool Extensions enabled, and one lives here: ' +
								[path] +
								'. It''s currently ' +
								CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0
																	 THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100))
																		  + ' GB'
																	 ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100))
																		  + ' MB'
								END +
								'. Did you know that BPEs only provide single threaded access 8KB (one page) at a time?'	
							   ) AS [Details]
							 FROM sys.dm_os_buffer_pool_extension_configuration
							 WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED';

					END;

/*Check for too many tempdb files*/
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 175 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 175) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
										SELECT DISTINCT
										175 AS CheckID ,
										'TempDB' AS DatabaseName ,
										170 AS Priority ,
										'File Configuration' AS FindingsGroup ,
										'TempDB Has >16 Data Files' AS Finding ,
										'https://www.brentozar.com/go/tempdb' AS URL ,
										'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details
								  FROM sys.[master_files] AS [mf]
								  WHERE [mf].[database_id] = 2 AND [mf].[type] = 0
								  HAVING COUNT_BIG(*) > 16;
					END;	

			IF NOT EXISTS ( SELECT  1
											FROM    #SkipChecks
											WHERE   DatabaseName IS NULL AND CheckID = 176 )
								BEGIN
			
			IF EXISTS ( SELECT  1
														FROM    sys.all_objects
														WHERE   name = 'dm_xe_sessions' )
								
								BEGIN
									
									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT;
									
									INSERT  INTO #BlitzResults
											( CheckID ,
											  DatabaseName ,
											  Priority ,
											  FindingsGroup ,
											  Finding ,
											  URL ,
											  Details
											)
													SELECT DISTINCT
													176 AS CheckID ,
													'' AS DatabaseName ,
													200 AS Priority ,
													'Monitoring' AS FindingsGroup ,
													'Extended Events Hyperextension' AS Finding ,
													'https://www.brentozar.com/go/xe' AS URL ,
													'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details
											    FROM sys.dm_xe_sessions
												WHERE [name] NOT IN
												( 'AlwaysOn_health', 
												  'system_health', 
												  'telemetry_xevents', 
												  'sp_server_diagnostics', 
												  'sp_server_diagnostics session', 
												  'hkenginexesession' )
												AND name NOT LIKE '%$A%'
											  HAVING COUNT_BIG(*) >= 2;
								END;	
								END;
			
			/*Harmful startup parameter*/
			IF NOT EXISTS ( SELECT  1
											FROM    #SkipChecks
											WHERE   DatabaseName IS NULL AND CheckID = 177 )
								BEGIN
								
								IF EXISTS ( SELECT  1
														FROM    sys.all_objects
														WHERE   name = 'dm_server_registry' )
			
								BEGIN
									
									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT;
									
									INSERT  INTO #BlitzResults
											( CheckID ,
											  DatabaseName ,
											  Priority ,
											  FindingsGroup ,
											  Finding ,
											  URL ,
											  Details
											)
													SELECT DISTINCT
													177 AS CheckID ,
													'' AS DatabaseName ,
													5 AS Priority ,
													'Monitoring' AS FindingsGroup ,
													'Disabled Internal Monitoring Features' AS Finding ,
													'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL ,
													'You have -x as a startup parameter. You should head to the URL and read more about what it does to your system.' AS Details
													FROM
													[sys].[dm_server_registry] AS [dsr]
													WHERE
													[dsr].[registry_key] LIKE N'%MSSQLServer\Parameters'
													AND [dsr].[value_data] = '-x';;
								END;		
								END;
			
			
			/* Reliability - Dangerous Third Party Modules - 179 */
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 179 )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 179) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							179 AS [CheckID] ,
							5 AS [Priority] ,
							'Reliability' AS [FindingsGroup] ,
							'Dangerous Third Party Modules' AS [Finding] ,
							'https://support.microsoft.com/en-us/kb/2033238' AS [URL] ,
							( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details]
							FROM sys.dm_os_loaded_modules
							WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */
							OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */
							OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */
							OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */
							OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */
							OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */
							OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */
							OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */
							OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */
							/* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */
					END;

			/*Find shrink database tasks*/

			IF NOT EXISTS ( SELECT  1
											FROM    #SkipChecks
											WHERE   DatabaseName IS NULL AND CheckID = 180 )
							AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 180) WITH NOWAIT;
					
						WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts])
						,[maintenance_plan_steps] AS (
							SELECT [name]
								, [id] -- ID required to link maintenace plan with jobs and jobhistory (sp_Blitz Issue #776)							
								, CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml]
							FROM [msdb].[dbo].[sysssispackages]
							WHERE [packagetype] = 6
						   )
							INSERT    INTO [#BlitzResults]
									( [CheckID] ,
										[Priority] ,
										[FindingsGroup] ,
										[Finding] ,
										[URL] ,
										[Details] )									
						SELECT
						180 AS [CheckID] ,
						-- sp_Blitz Issue #776
						-- Job has history and was executed in the last 30 days
						CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN
						    100
						ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled)
					        200
						END AS Priority,
						'Performance' AS [FindingsGroup] ,
						'Shrink Database Step In Maintenance Plan' AS [Finding] ,
						'https://www.brentozar.com/go/autoshrink' AS [URL] ,									
						'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.'
						+ CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details]
						FROM [maintenance_plan_steps] [mps]
							CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c])
                    	join msdb.dbo.sysmaintplan_subplans as sms
                    		on mps.id = sms.plan_id
                    	JOIN msdb.dbo.sysjobs j
                    		on sms.job_id = j.job_id
                    	LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step
                    		ON j.job_id = step.job_id
                    	LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc
                    		ON j.job_id = sjsc.job_id
                    	LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc
                    		ON sjsc.schedule_id = ssc.schedule_id
                    		AND sjsc.job_id = j.job_id
                    	LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh
                    		ON j.job_id = sjh.job_id
                    		AND step.step_id = sjh.step_id
                    		AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date
                    		AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time
						WHERE [c].[value]('(@dts:ObjectName)', 'VARCHAR(128)') = 'Shrink Database Task';

						END;

		/*Find repetitive maintenance tasks*/
		IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 181 )
						AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */
				BEGIN
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 181) WITH NOWAIT;

						WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts])
						,[maintenance_plan_steps] AS (
							SELECT [name]
								, CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml]
							FROM [msdb].[dbo].[sysssispackages]
							WHERE [packagetype] = 6
							), [maintenance_plan_table] AS (
						SELECT [mps].[name]
							,[c].[value]('(@dts:ObjectName)', 'NVARCHAR(128)') AS [step_name]
						FROM [maintenance_plan_steps] [mps]
							CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c])
						), [mp_steps_pretty] AS (SELECT DISTINCT [m1].[name] ,
								STUFF((SELECT N', ' + [m2].[step_name]  FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name]
								FOR XML PATH(N'')), 1, 2, N'') AS [maintenance_plan_steps]
						FROM [maintenance_plan_table] AS [m1])
						
							INSERT    INTO [#BlitzResults]
									( [CheckID] ,
										[Priority] ,
										[FindingsGroup] ,
										[Finding] ,
										[URL] ,
										[Details] )						
						
						SELECT
						181 AS [CheckID] ,
						100 AS [Priority] ,
						'Performance' AS [FindingsGroup] ,
						'Repetitive Steps In Maintenance Plans' AS [Finding] ,
						'https://ola.hallengren.com/' AS [URL] ,
						'The maintenance plan ' + [m].[name] + ' is doing repetitive work on indexes and statistics. Perhaps it''s time to try something more modern?' AS [Details]
						FROM [mp_steps_pretty] m
						WHERE m.[maintenance_plan_steps] LIKE '%Rebuild%Reorganize%'
						OR m.[maintenance_plan_steps] LIKE '%Rebuild%Update%';

						END;
			

			/* Reliability - No Failover Cluster Nodes Available - 184 */
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 184 )
				AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '10%'
				AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '9%'
					BEGIN
		                        IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			                        							SELECT TOP 1
							  184 AS CheckID ,
							  20 AS Priority ,
							  ''Reliability'' AS FindingsGroup ,
							  ''No Failover Cluster Nodes Available'' AS Finding ,
							  ''https://www.brentozar.com/go/node'' AS URL ,
							  ''There are no failover cluster nodes available if the active node fails'' AS Details
							FROM (
							  SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes]
							  FROM sys.dm_os_cluster_nodes
							) a
							WHERE [available_nodes] < 1 OPTION (RECOMPILE)';
		
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
					END;

		/* Reliability - TempDB File Error */
		IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 191 )
			AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files)
			/* User may have no permissions to see tempdb files in sys.master_files. In that case count returned will be 0 and we want to skip the check */
			AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> 0
				BEGIN
					
					IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT
					
					INSERT    INTO [#BlitzResults]
							( [CheckID] ,
								[Priority] ,
								[FindingsGroup] ,
								[Finding] ,
								[URL] ,
								[Details] )						
						
						SELECT
						191 AS [CheckID] ,
						50 AS [Priority] ,
						'Reliability' AS [FindingsGroup] ,
						'TempDB File Error' AS [Finding] ,
						'https://www.brentozar.com/go/tempdboops' AS [URL] ,
						'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details];
				END;

/*Perf - Odd number of cores in a socket*/
		IF NOT EXISTS ( SELECT  1
		                FROM    #SkipChecks
		                WHERE   DatabaseName IS NULL
		                        AND CheckID = 198 )
		   AND EXISTS ( SELECT  1
		                FROM    sys.dm_os_schedulers
		                WHERE   is_online = 1
		                        AND scheduler_id < 255
		                        AND parent_node_id < 64
		                GROUP BY parent_node_id,
		                        is_online
		                HAVING  ( COUNT(cpu_id) + 2 ) % 2 = 1 )
		   BEGIN
		
		         IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 198) WITH NOWAIT
				
				 INSERT INTO #BlitzResults
		                (
		                  CheckID,
		                  DatabaseName,
		                  Priority,
		                  FindingsGroup,
		                  Finding,
		                  URL,
		                  Details
				        )
		         SELECT 198 AS CheckID,
		                NULL AS DatabaseName,
		                10 AS Priority,
		                'Performance' AS FindingsGroup,
		                'CPU w/Odd Number of Cores' AS Finding,
		                'https://www.brentozar.com/go/oddity' AS URL,
		                'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id))
		                + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.'
		                       ELSE ' cores assigned to it. This is a really bad NUMA configuration.'
		                  END AS Details
		         FROM   sys.dm_os_schedulers
		         WHERE  is_online = 1
		                AND scheduler_id < 255
		                AND parent_node_id < 64
						AND EXISTS (
									SELECT 1
									FROM ( SELECT    memory_node_id, SUM(online_scheduler_count) AS schedulers
									       FROM      sys.dm_os_nodes
									       WHERE     memory_node_id < 64
									       GROUP  BY memory_node_id ) AS nodes
										   HAVING MIN(nodes.schedulers) <> MAX(nodes.schedulers)
									)
		         GROUP BY parent_node_id,
		                is_online
		         HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1;
		
		   END;

/*Begin: checking default trace for odd DBCC activity*/
		
		--Grab relevant event data
		IF @TraceFileIssue = 0
		BEGIN
		SELECT UPPER(
					REPLACE(
						SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), 0,
								ISNULL(
									NULLIF(
										CHARINDEX('(', CONVERT(NVARCHAR(MAX), t.TextData)),
										 0),
									  LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )) --This replaces everything up to an open paren, if one exists.
										, SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData),
											ISNULL(
												NULLIF(
													CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData))
													, 0),
												LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1),
													LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )
					   , '') --This replaces any optional WITH clause to a DBCC command, like tableresults.
					) AS [dbcc_event_trunc_upper],
			UPPER(
				REPLACE(
					CONVERT(NVARCHAR(MAX), t.TextData), SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData),
											ISNULL(
												NULLIF(
													CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData))
													, 0),
												LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1),
													LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ), '')) AS [dbcc_event_full_upper],
			MIN(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS	min_start_time,
			MAX(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS max_start_time,
			t.NTUserName AS [nt_user_name],
			t.NTDomainName AS [nt_domain_name],
			t.HostName AS [host_name],
		    t.ApplicationName AS [application_name],
			t.LoginName [login_name],
			t.DBUserName AS [db_user_name]
		 	INTO #dbcc_events_from_trace
		    FROM #fnTraceGettable AS t
			WHERE t.EventClass = 116
			OPTION(RECOMPILE)
			END;

			/*Overall count of DBCC events excluding silly stuff*/
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 203 )
							AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 203) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )
			SELECT 203 AS CheckID ,
			        50 AS Priority ,
			        'DBCC Events' AS FindingsGroup ,
			        'Overall Events' AS Finding ,
			        'https://www.BrentOzar.com/go/dbcc' AS URL ,
			        CAST(COUNT(*) AS NVARCHAR(100)) + ' DBCC events have taken place between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30),  MAX(d.max_start_time)) +
					'. This does not include CHECKDB and other usually benign DBCC events.'
					AS Details
			FROM    #dbcc_events_from_trace d
			/* This WHERE clause below looks horrible, but it's because users can run stuff like
			   DBCC     LOGINFO
			   with lots of spaces (or carriage returns, or comments) in between the DBCC and the
			   command they're trying to run. See Github issues 1062, 1074, 1075.
			*/
			WHERE d.dbcc_event_full_upper NOT LIKE '%DBCC%ADDINSTANCE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%AUTOPILOT%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKALLOC%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCATALOG%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCONSTRAINTS%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKDB%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKFILEGROUP%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKIDENT%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKPRIMARYFILE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKTABLE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CLEANTABLE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%DBINFO%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%ERRORLOG%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INCREMENTINSTANCE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INPUTBUFFER%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%LOGINFO%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%OPENTRAN%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SETINSTANCE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOWFILESTATS%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOW_STATISTICS%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%NETSTATS%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%LOGSPACE%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEON%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEOFF%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACESTATUS%'
			AND d.dbcc_event_full_upper NOT LIKE '%DBCC%USEROPTIONS%'
			AND d.application_name NOT LIKE 'Critical Care(R) Collector'
			AND d.application_name NOT LIKE '%Red Gate Software Ltd SQL Prompt%'
			AND d.application_name NOT LIKE '%Spotlight Diagnostic Server%'
			AND d.application_name NOT LIKE '%SQL Diagnostic Manager%'
			AND d.application_name NOT LIKE 'SQL Server Checkup%'
			AND d.application_name NOT LIKE '%Sentry%'
			AND d.application_name NOT LIKE '%LiteSpeed%'
            AND d.application_name NOT LIKE '%SQL Monitor - Monitoring%'
			

			HAVING COUNT(*) > 0;
			
				END;

			/*Check for someone running drop clean buffers*/
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 207 )
							AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 207) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )
					SELECT 207 AS CheckID ,
					        10 AS Priority ,
					        'Performance' AS FindingsGroup ,
					        'DBCC DROPCLEANBUFFERS Ran Recently' AS Finding ,
					        'https://www.BrentOzar.com/go/dbcc' AS URL ,
					        'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC DROPCLEANBUFFERS ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30),  MAX(d.max_start_time)) +
							'. If this is a production box, know that you''re clearing all data out of memory when this happens. What kind of monster would do that?'
							AS Details
					FROM    #dbcc_events_from_trace d
					WHERE d.dbcc_event_full_upper = N'DBCC DROPCLEANBUFFERS'
					GROUP BY COALESCE(d.nt_user_name, d.login_name)
					HAVING COUNT(*) > 0;

					END;

			/*Check for someone running free proc cache*/
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 208 )
							AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 208) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )
					SELECT 208 AS CheckID ,
					        10 AS Priority ,
					        'DBCC Events' AS FindingsGroup ,
					        'DBCC FREEPROCCACHE Ran Recently' AS Finding ,
					        'https://www.BrentOzar.com/go/dbcc' AS URL ,
					        'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC FREEPROCCACHE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30),  MAX(d.max_start_time)) +
							'. This has bad idea jeans written all over its butt, like most other bad idea jeans.'
							AS Details
					FROM    #dbcc_events_from_trace d
					WHERE d.dbcc_event_full_upper = N'DBCC FREEPROCCACHE'
					GROUP BY COALESCE(d.nt_user_name, d.login_name)
					HAVING COUNT(*) > 0;

					END;

			/*Check for someone clearing wait stats*/
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 205 )
							AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 205) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )
					SELECT 205 AS CheckID ,
					        50 AS Priority ,
					        'Performance' AS FindingsGroup ,
					        'Wait Stats Cleared Recently' AS Finding ,
					        'https://www.BrentOzar.com/go/dbcc' AS URL ,
					        'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR) ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30),  MAX(d.max_start_time)) +
							'. Why are you clearing wait stats? What are you hiding?'
							AS Details
					FROM    #dbcc_events_from_trace d
					WHERE d.dbcc_event_full_upper = N'DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR)'
					GROUP BY COALESCE(d.nt_user_name, d.login_name)
					HAVING COUNT(*) > 0;

					END;

			/*Check for someone writing to pages. Yeah, right?*/
			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 209 )
							AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 209) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )
						SELECT 209 AS CheckID ,
						        10 AS Priority ,
						        'Reliability' AS FindingsGroup ,
						        'DBCC WRITEPAGE Used Recently' AS Finding ,
						        'https://www.BrentOzar.com/go/dbcc' AS URL ,
						        'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC WRITEPAGE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30),  MAX(d.max_start_time)) +
								'. So, uh, are they trying to fix corruption, or cause corruption?'
								AS Details
						FROM    #dbcc_events_from_trace d
						WHERE d.dbcc_event_trunc_upper = N'DBCC WRITEPAGE'
						GROUP BY COALESCE(d.nt_user_name, d.login_name)
						HAVING COUNT(*) > 0;

						END;

			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 210 )
							AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 210) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

						SELECT 210 AS CheckID ,
						        10 AS Priority ,
						        'Performance' AS FindingsGroup ,
						        'DBCC SHRINK% Ran Recently' AS Finding ,
						        'https://www.BrentOzar.com/go/dbcc' AS URL ,
						        'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30),  MAX(d.max_start_time)) +
								'. So, uh, are they trying to cause bad performance on purpose?'
								AS Details
						FROM    #dbcc_events_from_trace d
						WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%'
						GROUP BY COALESCE(d.nt_user_name, d.login_name)
						HAVING COUNT(*) > 0;

						END;

/*End: checking default trace for odd DBCC activity*/
				
				/*Begin check for autoshrink events*/

			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 206 )
						AND @TraceFileIssue = 0
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 206) WITH NOWAIT
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

						SELECT	206 AS CheckID ,
								        10 AS Priority ,
								        'Performance' AS FindingsGroup ,
								        'Auto-Shrink Ran Recently' AS Finding ,
								        '' AS URL ,
										N'The database ' + QUOTENAME(t.DatabaseName) + N' has had '
											+ CONVERT(NVARCHAR(10), COUNT(*))
												+ N' auto shrink events between '
													+ CONVERT(NVARCHAR(30), MIN(t.StartTime)) + ' and ' + CONVERT(NVARCHAR(30), MAX(t.StartTime))
														+ ' that lasted on average '
															+ CONVERT(NVARCHAR(10), AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)))
																+ ' seconds.' AS Details
						FROM #fnTraceGettable AS t
						WHERE t.EventClass IN (94, 95)
						GROUP BY t.DatabaseName
						HAVING AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)) > 5;
				
				END;

			IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 215 )
						AND @TraceFileIssue = 0
                        AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'database_id' AND object_id = OBJECT_ID('sys.dm_exec_sessions'))
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 215) WITH NOWAIT
						
						  SET @StringToExecute = 'INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
                                      [DatabaseName] ,
									  [URL] ,
									  [Details] )

								SELECT	215 AS CheckID ,
										100 AS Priority ,
										''Performance'' AS FindingsGroup ,
										''Implicit Transactions'' AS Finding ,
										DB_NAME(s.database_id) AS DatabaseName,
										''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL ,
										N''The database '' +
										DB_NAME(s.database_id)
										+ '' has ''
										+ CONVERT(NVARCHAR(20), COUNT_BIG(*))
										+ '' open implicit transactions with an oldest begin time of ''
										+ CONVERT(NVARCHAR(30), MIN(tat.transaction_begin_time)) 
										+ '' Run sp_BlitzWho and check the is_implicit_transaction column to see the culprits.'' AS details
								FROM    sys.dm_tran_active_transactions AS tat
								LEFT JOIN sys.dm_tran_session_transactions AS tst
								ON tst.transaction_id = tat.transaction_id
								LEFT JOIN sys.dm_exec_sessions AS s
								ON s.session_id = tst.session_id
								WHERE tat.name = ''implicit_transaction''
								GROUP BY DB_NAME(s.database_id), transaction_type, transaction_state;';


							IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
							IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
							EXECUTE(@StringToExecute);


				
				END;

						
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 221 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 221) WITH NOWAIT;
						
							WITH reboot_airhorn
							    AS
							     (
							         SELECT create_date
							         FROM   sys.databases
							         WHERE  database_id = 2
							         UNION ALL
							         SELECT CAST(DATEADD(SECOND, ( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME)
							         FROM   sys.dm_os_sys_info
							     )
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)														
							SELECT 221 AS CheckID,
							       10 AS Priority,
							       'Reliability' AS FindingsGroup,
							       'Server restarted in last 24 hours' AS Finding,
							       '' AS URL,
							       'Surprise! Your server was last restarted on: ' + CONVERT(VARCHAR(30), MAX(reboot_airhorn.create_date)) AS details
							FROM   reboot_airhorn
							HAVING MAX(reboot_airhorn.create_date) >= DATEADD(HOUR, -24, GETDATE());						
						

					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 229 )
						AND CAST(SERVERPROPERTY('Edition') AS NVARCHAR(4000)) LIKE '%Evaluation%'
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 229) WITH NOWAIT;
						
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)														
							SELECT 229 AS CheckID,
							       1 AS Priority,
							       'Reliability' AS FindingsGroup,
							       'Evaluation Edition' AS Finding,
							       'https://www.BrentOzar.com/go/workgroup' AS URL,
							       'This server will stop working on: ' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS details
							FROM   sys.server_principals
							WHERE sid = 0x010100000000000512000000; 						
						
					END;





				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 233 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 233) WITH NOWAIT;
						

						IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb')
							BEGIN
							/* SQL 2012+ version */
							SET @StringToExecute = N'
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)														
							SELECT 233 AS CheckID,
							       50 AS Priority,
							       ''Performance'' AS FindingsGroup,
							       ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding,
							       ''https://www.BrentOzar.com/go/userstore'' AS URL,
							       N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) 
								   		+ N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.''
								   AS details
							FROM sys.dm_os_memory_clerks
							HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1					
							  AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */';
							EXEC sp_executesql @StringToExecute;
							END
						ELSE
							BEGIN
							/* Antiques Roadshow SQL 2008R2 - version */
							SET @StringToExecute = N'
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)														
							SELECT 233 AS CheckID,
							       50 AS Priority,
							       ''Performance'' AS FindingsGroup,
							       ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding,
							       ''https://www.BrentOzar.com/go/userstore'' AS URL,
							       N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) 
								   		+ N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.''
								   AS details
							FROM sys.dm_os_memory_clerks
							HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1
							  AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */';
							EXEC sp_executesql @StringToExecute;
							END

					END;





				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 234 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 234) WITH NOWAIT;
						
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  DatabaseName ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)														
							SELECT 234 AS CheckID,
							       100 AS Priority,
								   db_name(f.database_id) AS DatabaseName,
							       'Reliability' AS FindingsGroup,
							       'SQL Server Update May Fail' AS Finding,
							       'https://desertdba.com/failovers-cant-serve-two-masters/' AS URL,
							       'This database has a file with a logical name of ''master'', which can break SQL Server updates. Rename it in SSMS by right-clicking on the database, go into Properties, and rename the file. Takes effect instantly.' AS details
							FROM master.sys.master_files f
							WHERE (f.name = N'master')
							  AND f.database_id > 4
							  AND db_name(f.database_id) <> 'master'; /* Thanks Michaels3 for catching this */
					END;



				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 268 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 268) WITH NOWAIT;
						
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  DatabaseName ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)														
							SELECT 268 AS CheckID,
							       5 AS Priority,
								   DB_NAME(ps.database_id),
							       'Availability' AS FindingsGroup,
							       'AG Replica Falling Behind' AS Finding,
							       'https://www.BrentOzar.com/go/ag' AS URL,
							       ag.name + N' AG replica server ' + 
										ar.replica_server_name + N' is ' + 
										CASE WHEN DATEDIFF(SECOND, ISNULL (drs.last_commit_time, drs.Last_hardened_time), ps.last_commit_time) < 200 THEN (CAST(DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) AS NVARCHAR(10)) + N' seconds ')
										ELSE (CAST(DATEDIFF(MINUTE, ISNULL (drs.last_commit_time, drs.Last_hardened_time), ps.last_commit_time) AS NVARCHAR(10)) + N' minutes ') END
										+ N' behind the primary.'
										AS details
							FROM sys.dm_hadr_database_replica_states AS drs
							JOIN sys.availability_replicas AS ar ON drs.replica_id = ar.replica_id
							JOIN sys.availability_groups AS ag ON ar.group_id = ag.group_id
							JOIN sys.dm_hadr_database_replica_states AS ps 
								ON drs.group_id = ps.group_id 
								AND drs.database_id = ps.database_id
								AND ps.is_local = 1 /* Primary */
							WHERE drs.is_local = 0 /* Secondary */
							  AND DATEDIFF(SECOND,ISNULL (drs.last_commit_time, drs.Last_hardened_time), ps.last_commit_time) > 60
					END;



				IF NOT EXISTS ( SELECT  1
							FROM    #SkipChecks
							WHERE   DatabaseName IS NULL AND CheckID = 271 )
				AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'group_max_tempdb_data_percent'
				                    AND [object_id] = OBJECT_ID('sys.resource_governor_workload_groups'))
					AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'group_max_tempdb_data_mb'
					                AND [object_id] = OBJECT_ID('sys.resource_governor_workload_groups'))
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 271) WITH NOWAIT;
						
						SET @tsql = N'SELECT @ExecRet_Out = COUNT(1) FROM sys.resource_governor_workload_groups
						WHERE group_max_tempdb_data_percent <> 0
						  AND group_max_tempdb_data_mb IS NULL';
						EXEC @ExecRet = sp_executesql @tsql, N'@ExecRet_Out INT OUTPUT', @ExecRet_Out = @ExecRet OUTPUT;
						IF @ExecRet > 0
							BEGIN
							DECLARE @TempDBfiles TABLE (config VARCHAR(50), data_files INT)
							/* Valid configs */
							INSERT INTO @TempDBfiles
								SELECT 'Fixed predictable growth' AS config, SUM(1) AS data_files
									FROM master.sys.master_files
									WHERE database_id = DB_ID('tempdb')
									  AND type = 0 /* data */
									  AND max_size <> -1 /* only limited ones */
									  AND growth <> 0 /* growth is set */
									HAVING SUM(1) > 0
								UNION ALL
								SELECT 'Growth turned off' AS config, SUM(1) AS data_files
									FROM master.sys.master_files
									WHERE database_id = DB_ID('tempdb')
									  AND type = 0 /* data */
									  AND max_size = -1 /* unlimited */
									  AND growth = 0
									HAVING SUM(1) > 0;

							IF 1 <> (SELECT COUNT(*) FROM @TempDBfiles)
								OR (SELECT SUM(data_files) FROM @TempDBfiles) <> 
									(SELECT SUM(1)
										FROM master.sys.master_files
										WHERE database_id = DB_ID('tempdb')
										  AND type = 0 /* data */)
								BEGIN
									INSERT INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  DatabaseName ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)														
									SELECT 271 AS CheckID,
										   170 AS Priority,
										   'tempdb',
										   'File Configuration' AS FindingsGroup,
										   'TempDB Governor Config Problem' AS Finding,
										   'https://www.BrentOzar.com/go/tempdbrg' AS URL,
										   'Resource Governor is configured to cap TempDB usage by percent, but the TempDB file configuration will not allow that to take effect.' AS details
								END
							END
					END

				IF @CheckUserDatabaseObjects = 1
					BEGIN

					IF @Debug IN (1, 2) RAISERROR('Starting @CheckUserDatabaseObjects section.', 0, 1) WITH NOWAIT

                        /*
                        But what if you need to run a query in every individual database?
				        Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no,
				        we're not happy about that. sp_MSforeachdb is known to have a lot
				        of issues, like skipping databases sometimes. However, this is the
				        only built-in option that we have. If you're writing your own code
				        for database maintenance, consider Aaron Bertrand's alternative:
				        http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/
				        We don't include that as part of sp_Blitz, of course, because
				        copying and distributing copyrighted code from others without their
				        written permission isn't a good idea.
				        */
				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 99 )
					        BEGIN
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM  sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0)   INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')';
					        END;
				        /*
				        Note that by using sp_MSforeachdb, we're running the query in all
				        databases. We're not checking #SkipChecks here for each database to
				        see if we should run the check in this database. That means we may
				        still run a skipped check if it involves sp_MSforeachdb. We just
				        don't output those results in the last step.
                        */

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 163 )
                            AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
							BEGIN
								/* --TOURSTOP03-- */

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 163) WITH NOWAIT;

								EXEC dbo.sp_MSforeachdb 'USE [?];
                                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			                            INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
		                              SELECT TOP 1 163,
		                              N''?'',
		                              200,
		                              ''Performance'',
		                              ''Query Store Disabled'',
		                              ''https://www.brentozar.com/go/querystore'',
		                              (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'')
		                              FROM [?].sys.database_query_store_options WHERE desired_state = 0
									  AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''rdsadmin'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 262 )
                            AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
							AND  @ProductVersionMajor > 13 /* The relevant column only exists in 2017+ */
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 262) WITH NOWAIT;

								EXEC dbo.sp_MSforeachdb 'USE [?];
                                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			                            INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
		                              SELECT TOP 1 262,
		                              N''?'',
		                              200,
		                              ''Performance'',
		                              ''Query Store Wait Stats Disabled'',
		                              ''https://www.sqlskills.com/blogs/erin/query-store-settings/'',
		                              (''The new SQL Server 2017 Query Store feature for tracking wait stats has not been enabled on this database. It is very useful for tracking wait stats at a query level.'')
		                              FROM [?].sys.database_query_store_options
									  WHERE desired_state <> 0
									  AND wait_stats_capture_mode = 0
									  AND ''?'' != ''rdsadmin''
									  OPTION (RECOMPILE)';
							END;
                    
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 263 )
                            AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
							BEGIN
              
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 263) WITH NOWAIT;

								EXEC dbo.sp_MSforeachdb 'USE [?];
                                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			                            INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
		                              SELECT TOP 1 263,
		                              N''?'',
		                              200,
		                              ''Performance'',
		                              ''Query Store Effectively Disabled'',
		                              ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify'',
		                              (''Query Store is not in a state where it is writing, so it is effectively disabled. Check your Query Store settings.'')
		                              FROM [?].sys.database_query_store_options
									  WHERE desired_state <> 0
									  AND actual_state <> 2
									  AND ''?'' != ''rdsadmin''
									  OPTION (RECOMPILE)';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 264 )
                            AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 264) WITH NOWAIT;

								EXEC dbo.sp_MSforeachdb 'USE [?];
                                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			                            INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
		                              SELECT TOP 1 264,
		                              N''?'',
		                              200,
		                              ''Performance'',
		                              ''Undesired Query Store State'',
		                              ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify'',
		                              (''You have asked for Query Store to be in '' + desired_state_desc + '' mode, but it is in '' + actual_state_desc + '' mode.'')
		                              FROM [?].sys.database_query_store_options
									  WHERE desired_state <> 0
									  AND desired_state <> actual_state 
									  AND ''?'' != ''rdsadmin''
									  OPTION (RECOMPILE)';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 265 )
  			                AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
							BEGIN
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 265) WITH NOWAIT;

								EXEC dbo.sp_MSforeachdb 'USE [?];
                                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			                            INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
		                              SELECT TOP 1 265,
		                              N''?'',
		                              200,
		                              ''Performance'',
		                              ''Query Store Unusually Configured'',
		                              ''https://www.sqlskills.com/blogs/erin/query-store-best-practices/'',
		                              (''The '' + query_capture_mode_desc + '' query capture mode '' +
											CASE query_capture_mode_desc
												WHEN ''ALL'' THEN ''captures more data than you will probably use. If your workload is heavily ad-hoc, then it can also cause Query Store to capture so much that it turns itself off.''
												WHEN ''NONE'' THEN ''stops Query Store capturing data for new queries.''
												WHEN ''CUSTOM'' THEN ''suggests that somebody has gone out of their way to only capture exactly what they want.''
											ELSE ''is not documented.'' END)
		                              FROM [?].sys.database_query_store_options
									  WHERE desired_state <> 0 /* No point in checking this if Query Store is off. */
									  AND query_capture_mode_desc <> ''AUTO''
									  AND ''?'' != ''rdsadmin''
									  OPTION (RECOMPILE)';
							END;
						
						IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it
							AND NOT EXISTS ( SELECT  1
											 FROM    #SkipChecks
											 WHERE   DatabaseName IS NULL AND CheckID = 182 )
							AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%'
							AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%'
							BEGIN
			
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 182) WITH NOWAIT;
							
							SET @StringToExecute = 'INSERT INTO #BlitzResults
													(CheckID,
													DatabaseName,
													Priority,
													FindingsGroup,
													Finding,
													URL,
													Details)
													SELECT TOP 1
													182,
													''Server'',
													20,
													''Reliability'',
													''Query Store Cleanup Disabled'',
													''https://www.brentozar.com/go/cleanup'',
													(''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'')
													FROM    sys.databases AS d
													WHERE   d.is_query_store_on = 1
													AND		d.name != ''rdsadmin''
													OPTION (RECOMPILE);';
							
							IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
							IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
							
							EXECUTE(@StringToExecute);
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 235 )
                            AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 235) WITH NOWAIT;

								EXEC dbo.sp_MSforeachdb 'USE [?];
                                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			                            INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
		                              SELECT TOP 1 235,
		                              N''?'',
		                              150,
		                              ''Performance'',
		                              ''Inconsistent Query Store metadata'',
		                              '''',
		                              (''Query store state in master metadata and database specific metadata not in sync.'')
		                              FROM [?].sys.database_query_store_options dqso
										join master.sys.databases D on D.name = N''?''
									  WHERE ((dqso.actual_state = 0 AND D.is_query_store_on = 1) OR (dqso.actual_state <> 0 AND D.is_query_store_on = 0))
									  AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''rdsadmin'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)';
							END;

				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 41 )
					        BEGIN
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'use [?];
		                              SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                      INSERT INTO #BlitzResults
		                              (CheckID,
		                              DatabaseName,
		                              Priority,
		                              FindingsGroup,
		                              Finding,
		                              URL,
		                              Details)
		                              SELECT 41,
		                              N''?'',
		                              170,
		                              ''File Configuration'',
		                              ''Multiple Log Files on One Drive'',
		                              ''https://www.brentozar.com/go/manylogs'',
		                              (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'')
		                              FROM [?].sys.database_files WHERE type_desc = ''LOG''
			                            AND ''?'' NOT IN (''rdsadmin'',''tempdb'')
		                              GROUP BY LEFT(physical_name, 1)
		                              HAVING COUNT(*) > 1 
									     AND SUM(size) < 268435456 OPTION (RECOMPILE);';
					        END;

				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 42 )
					        BEGIN
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'use [?];
			                            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                        INSERT INTO #BlitzResults
			                            (CheckID,
			                            DatabaseName,
			                            Priority,
			                            FindingsGroup,
			                            Finding,
			                            URL,
			                            Details)
			                            SELECT DISTINCT 42,
			                            N''?'',
			                            170,
			                            ''File Configuration'',
			                            ''Uneven File Growth Settings in One Filegroup'',
			                            ''https://www.brentozar.com/go/grow'',
			                            (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts.  This can lead to uneven file activity inside the filegroup.'')
			                            FROM [?].sys.database_files
			                            WHERE type_desc = ''ROWS''
										AND ''?'' != ''rdsadmin''
			                            GROUP BY data_space_id
			                            HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);';
					        END;

				            IF NOT EXISTS ( SELECT  1
								            FROM    #SkipChecks
								            WHERE   DatabaseName IS NULL AND CheckID = 82 )
					            BEGIN

									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT;

						            EXEC sp_MSforeachdb 'use [?];
		                                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                        INSERT INTO #BlitzResults
		                                (CheckID,
		                                DatabaseName,
		                                Priority,
		                                FindingsGroup,
		                                Finding,
		                                URL, Details)
		                                SELECT  DISTINCT 82 AS CheckID,
		                                N''?'' as DatabaseName,
		                                170 AS Priority,
		                                ''File Configuration'' AS FindingsGroup,
		                                ''File growth set to percent'',
		                                ''https://www.brentozar.com/go/percentgrowth'' AS URL,
		                                ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(20), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.''
		                                FROM    [?].sys.database_files f
		                                WHERE   is_percent_growth = 1 and size > 128000
										AND		''?'' != ''rdsadmin''
										OPTION (RECOMPILE);';
					            END;

                            /* addition by Henrik Staun Poulsen, Stovi Software */
				            IF NOT EXISTS ( SELECT  1
								            FROM    #SkipChecks
								            WHERE   DatabaseName IS NULL AND CheckID = 158 )
					            BEGIN

									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT;

						            EXEC sp_MSforeachdb 'use [?];
		                                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                        INSERT INTO #BlitzResults
		                                (CheckID,
		                                DatabaseName,
		                                Priority,
		                                FindingsGroup,
		                                Finding,
		                                URL, Details)
		                                SELECT  DISTINCT 158 AS CheckID,
		                                N''?'' as DatabaseName,
		                                170 AS Priority,
		                                ''File Configuration'' AS FindingsGroup,
		                                ''File growth set to 1MB'',
		                                ''https://www.brentozar.com/go/percentgrowth'' AS URL,
										''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.''
										FROM    [?].sys.database_files f
                                        WHERE is_percent_growth = 0 and growth=128 and size > 128000
										AND	   ''?'' != ''rdsadmin''
										OPTION (RECOMPILE);';
					            END;

				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 33 )
					        BEGIN
						        IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
							        AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
									AND @SkipBlockingChecks = 0
							        BEGIN
								
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT;
										
										EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                            INSERT INTO #BlitzResults
					                                (CheckID,
					                                DatabaseName,
					                                Priority,
					                                FindingsGroup,
					                                Finding,
					                                URL,
					                                Details)
		                                  SELECT DISTINCT 33,
		                                  db_name(),
		                                  200,
		                                  ''Licensing'',
		                                  ''Enterprise Edition Features In Use'',
		                                  ''https://www.brentozar.com/go/ee'',
		                                  (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''.  If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'')
		                                  FROM [?].sys.dm_db_persisted_sku_features
										  WHERE ''?'' != ''rdsadmin''
										  OPTION (RECOMPILE);';
							        END;
					        END;

				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 19 )
					        BEGIN
						        /* Method 1: Check sys.databases parameters */
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
								        ( CheckID ,
								          DatabaseName ,
								          Priority ,
								          FindingsGroup ,
								          Finding ,
								          URL ,
								          Details
								        )

								        SELECT  19 AS CheckID ,
										        [name] AS DatabaseName ,
										        200 AS Priority ,
										        'Informational' AS FindingsGroup ,
										        'Replication In Use' AS Finding ,
										        'https://www.brentozar.com/go/repl' AS URL ,
										        ( 'Database [' + [name]
										          + '] is a replication publisher, subscriber, or distributor.' ) AS Details
								        FROM    sys.databases
								        WHERE   name NOT IN ( SELECT DISTINCT
																        DatabaseName
													          FROM      #SkipChecks
													          WHERE CheckID IS NULL OR CheckID = 19)
										        AND (is_published = 1
										        OR is_subscribed = 1
										        OR is_merge_published = 1
										        OR is_distributor = 1);

						        /* Method B: check subscribers for MSreplication_objects tables */
						        EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                    INSERT INTO #BlitzResults
										        (CheckID,
										        DatabaseName,
										        Priority,
										        FindingsGroup,
										        Finding,
										        URL,
										        Details)
							          SELECT DISTINCT 19,
							          db_name(),
							          200,
							          ''Informational'',
							          ''Replication In Use'',
							          ''https://www.brentozar.com/go/repl'',
							          (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'')
							          FROM [?].sys.tables
							          WHERE name = ''dbo.MSreplication_objects''
									  AND ''?'' NOT IN (''master'', ''rdsadmin'')
									  OPTION (RECOMPILE)';
					        END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 32 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
			SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
            INSERT INTO #BlitzResults
			(CheckID,
			DatabaseName,
			Priority,
			FindingsGroup,
			Finding,
			URL,
			Details)
			SELECT 32,
			N''?'',
			150,
			''Performance'',
			''Triggers on Tables'',
			''https://www.brentozar.com/go/trig'',
			(''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'')
			FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id
			INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
			WHERE t.is_ms_shipped = 0
			AND ''?'' NOT IN (''rdsadmin'', ''ReportServer'')
			HAVING SUM(1) > 0 OPTION (RECOMPILE)';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 164 )
                            AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide')
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
			SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			SET QUOTED_IDENTIFIER ON;
            INSERT INTO #BlitzResults
			(CheckID,
			DatabaseName,
			Priority,
			FindingsGroup,
			Finding,
			URL,
			Details)
		  SELECT DISTINCT 164,
		  N''?'',
		  100,
		  ''Reliability'',
		  ''Plan Guides Failing'',
		  ''https://www.brentozar.com/go/misguided'',
		  (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'')
		  FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id)
		  WHERE ''?'' != ''rdsadmin''
		  OPTION (RECOMPILE)';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 46 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
		  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT 46,
		  N''?'',
		  150,
		  ''Performance'',
		  ''Leftover Fake Indexes From Wizards'',
		  ''https://www.brentozar.com/go/hypo'',
		  (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor.  This index is not actually helping performance and should be removed.'')
		  from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
		  WHERE i.is_hypothetical = 1
		  AND ''?'' != ''rdsadmin''
		  OPTION (RECOMPILE);';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 47 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
		  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT 47,
		  N''?'',
		  100,
		  ''Performance'',
		  ''Indexes Disabled'',
		  ''https://www.brentozar.com/go/ixoff'',
		  (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled.  This index is not actually helping performance and should either be enabled or removed.'')
		  from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
		  WHERE i.is_disabled = 1 OPTION (RECOMPILE);';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 48 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
		  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT DISTINCT 48,
		  N''?'',
		  150,
		  ''Performance'',
		  ''Foreign Keys Not Trusted'',
		  ''https://www.brentozar.com/go/trust'',
		  (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again.  Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'')
		  from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
		  WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND ''?''
		  NOT IN (''master'', ''model'', ''msdb'', ''rdsadmin'', ''ReportServer'', ''ReportServerTempDB'')
		  OPTION (RECOMPILE);';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 56 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
		  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT 56,
		  N''?'',
		  150,
		  ''Performance'',
		  ''Check Constraint Not Trusted'',
		  ''https://www.brentozar.com/go/trust'',
		  (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again.  Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'')
		  from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id
		  INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
		  WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 OPTION (RECOMPILE);';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 95 )
							BEGIN
								IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
									AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
									BEGIN
										
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT;
										
										EXEC dbo.sp_MSforeachdb 'USE [?];
			SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
            INSERT INTO #BlitzResults
				  (CheckID,
				  DatabaseName,
				  Priority,
				  FindingsGroup,
				  Finding,
				  URL,
				  Details)
			SELECT TOP 1 95 AS CheckID,
			N''?'' as DatabaseName,
			110 AS Priority,
			''Performance'' AS FindingsGroup,
			''Plan Guides Enabled'' AS Finding,
			''https://www.brentozar.com/go/guides'' AS URL,
			(''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details
			FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);';
									END;
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 60 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT;
								
								EXEC sp_MSforeachdb 'USE [?];
		  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT 60 AS CheckID,
		  N''?'' as DatabaseName,
		  100 AS Priority,
		  ''Performance'' AS FindingsGroup,
		  ''Fill Factor Changed'',
		  ''https://www.brentozar.com/go/fillfactor'' AS URL,
		  ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.''
		  FROM    [?].sys.indexes
		  WHERE   fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0
		  GROUP BY fill_factor OPTION (RECOMPILE);';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 78 )
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT;
								
								EXECUTE master.sys.sp_MSforeachdb 'USE [?];
                                    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                    INSERT INTO #Recompile
                                    SELECT DISTINCT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA
                                    FROM sys.sql_modules AS SM
                                    LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id()
                                    LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P''
                                    LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name()
                                    WHERE SM.is_recompiled=1  OPTION (RECOMPILE); /* oh the rich irony of recompile here */
                                    ';
                                INSERT INTO #BlitzResults
													(Priority,
													FindingsGroup,
                                                    Finding,
                                                    DatabaseName,
                                                    URL,
                                                    Details,
                                                    CheckID)
                                SELECT [Priority] = '100',
                                    FindingsGroup = 'Performance',
                                    Finding = 'Stored Procedure WITH RECOMPILE',
                                    DatabaseName = DBName,
                                    URL = 'https://www.brentozar.com/go/recompile',
                                    Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.',
                                    CheckID = '78'
                                FROM #Recompile AS TR 
								WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector'
								  AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb');
                                DROP TABLE #Recompile;
                            END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 86 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + '']  has the role ['' + g.name + ''].  This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);';
							END;

							/*Check for non-aligned indexes in partioned databases*/

										IF NOT EXISTS ( SELECT  1
														FROM    #SkipChecks
														WHERE   DatabaseName IS NULL AND CheckID = 72 )
											BEGIN
												
												IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT;
												
												EXEC dbo.sp_MSforeachdb 'USE [?];
								SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                insert into #partdb(dbname, objectname, type_desc)
								SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc
								FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id
								JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id
								LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID()
								WHERE  o.type = ''u''
								 -- Clustered and Non-Clustered indexes
								AND i.type IN (1, 2)
								AND o.object_id in
								  (
									SELECT a.object_id from
									  (SELECT ob.object_id, ds.type_desc from sys.objects ob JOIN sys.indexes ind on ind.object_id = ob.object_id join sys.data_spaces ds on ds.data_space_id = ind.data_space_id
									  GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1
								  )  OPTION (RECOMPILE);';
												INSERT  INTO #BlitzResults
														( CheckID ,
														  DatabaseName ,
														  Priority ,
														  FindingsGroup ,
														  Finding ,
														  URL ,
														  Details
														)
														SELECT DISTINCT
																72 AS CheckID ,
																dbname AS DatabaseName ,
																100 AS Priority ,
																'Performance' AS FindingsGroup ,
																'The partitioned database ' + dbname
																+ ' may have non-aligned indexes' AS Finding ,
																'https://www.brentozar.com/go/aligned' AS URL ,
																'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details
														FROM    #partdb
														WHERE   dbname IS NOT NULL
																AND dbname NOT IN ( SELECT DISTINCT
																						  DatabaseName
																					FROM  #SkipChecks
																					WHERE CheckID IS NULL OR CheckID = 72);
												DROP TABLE #partdb;
											END;

					IF NOT EXISTS ( SELECT  1
									FROM    #SkipChecks
									WHERE   DatabaseName IS NULL AND CheckID = 113 )
									BEGIN
							
							  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 113) WITH NOWAIT;
							
							  EXEC dbo.sp_MSforeachdb 'USE [?];
							  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                              INSERT INTO #BlitzResults
									(CheckID,
									DatabaseName,
									Priority,
									FindingsGroup,
									Finding,
									URL,
									Details)
							  SELECT DISTINCT 113,
							  N''?'',
							  50,
							  ''Reliability'',
							  ''Full Text Indexes Not Updating'',
							  ''https://www.brentozar.com/go/fulltext'',
							  (''At least one full text index in this database has not been crawled in the last week.'')
							  from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE())  OPTION (RECOMPILE);';
												END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 115 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?];
		  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          INSERT INTO #BlitzResults
				(CheckID,
				DatabaseName,
				Priority,
				FindingsGroup,
				Finding,
				URL,
				Details)
		  SELECT 115,
		  N''?'',
		  110,
		  ''Performance'',
		  ''Parallelism Rocket Surgery'',
		  ''https://www.brentozar.com/go/makeparallel'',
		  (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'')
		  from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 122 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT;
								
								/* SQL Server 2012 and newer uses temporary stats for Availability Groups, and those show up as user-created */
								IF EXISTS (SELECT *
									  FROM sys.all_columns c
									  INNER JOIN sys.all_objects o ON c.object_id = o.object_id
									  WHERE c.name = 'is_temporary' AND o.name = 'stats')
										
										EXEC dbo.sp_MSforeachdb 'USE [?];
												SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                                INSERT INTO #BlitzResults
													(CheckID,
													DatabaseName,
													Priority,
													FindingsGroup,
													Finding,
													URL,
													Details)
												SELECT TOP 1 122,
												N''?'',
												200,
												''Performance'',
												''User-Created Statistics In Place'',
												''https://www.brentozar.com/go/userstats'',
												(''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'')
												from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0
                                                HAVING SUM(1) > 0  OPTION (RECOMPILE);';

									ELSE
										EXEC dbo.sp_MSforeachdb 'USE [?];
												SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                                INSERT INTO #BlitzResults
													(CheckID,
													DatabaseName,
													Priority,
													FindingsGroup,
													Finding,
													URL,
													Details)
												SELECT 122,
												N''?'',
												200,
												''Performance'',
												''User-Created Statistics In Place'',
												''https://www.brentozar.com/go/userstats'',
												(''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'')
												from [?].sys.stats WHERE user_created = 1
                                                HAVING SUM(1) > 0 OPTION (RECOMPILE);';

							END; /* IF NOT EXISTS ( SELECT  1 */

		        /*Check for high VLF count: this will omit any database snapshots*/

				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 69 )
					        BEGIN
						        IF @ProductVersionMajor >= 11

							        BEGIN
								
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH NOWAIT;
										
										EXEC sp_MSforeachdb N'USE [?];
		                                      SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                              INSERT INTO #LogInfo2012
		                                      EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS'';
		                                      IF    @@ROWCOUNT > 999
		                                      BEGIN
			                                    INSERT  INTO #BlitzResults
			                                    ( CheckID
			                                    ,DatabaseName
			                                    ,Priority
			                                    ,FindingsGroup
			                                    ,Finding
			                                    ,URL
			                                    ,Details)
			                                    SELECT      69
			                                    ,DB_NAME()
			                                    ,170
			                                    ,''File Configuration''
			                                    ,''High VLF Count''
			                                    ,''https://www.brentozar.com/go/vlf''
			                                    ,''The ['' + DB_NAME() + ''] database has '' +  CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.''
			                                    FROM #LogInfo2012
			                                    WHERE EXISTS (SELECT name FROM master.sys.databases
					                                    WHERE source_database_id is null)  OPTION (RECOMPILE);
		                                      END
		                                    TRUNCATE TABLE #LogInfo2012;';
								        DROP TABLE #LogInfo2012;
							        END;
						        ELSE
							        BEGIN
								
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH NOWAIT;
										
										EXEC sp_MSforeachdb N'USE [?];
		                                      SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                              INSERT INTO #LogInfo
		                                      EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS'';
		                                      IF    @@ROWCOUNT > 999
		                                      BEGIN
			                                    INSERT  INTO #BlitzResults
			                                    ( CheckID
			                                    ,DatabaseName
			                                    ,Priority
			                                    ,FindingsGroup
			                                    ,Finding
			                                    ,URL
			                                    ,Details)
			                                    SELECT      69
			                                    ,DB_NAME()
			                                    ,170
			                                    ,''File Configuration''
			                                    ,''High VLF Count''
			                                    ,''https://www.brentozar.com/go/vlf''
			                                    ,''The ['' + DB_NAME() + ''] database has '' +  CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.''
			                                    FROM #LogInfo
			                                    WHERE EXISTS (SELECT name FROM master.sys.databases
			                                    WHERE source_database_id is null) OPTION (RECOMPILE);
		                                      END
		                                      TRUNCATE TABLE #LogInfo;';
								        DROP TABLE #LogInfo;
							        END;
					        END;

				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 80 )
					        BEGIN
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?]; 
                                    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 
                                    INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) 
                                    SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', 
                                    (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' 
                                        + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) 
                                        + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') 
                                    FROM sys.database_files df
                                    WHERE 0 = (SELECT is_read_only FROM sys.databases WHERE name = ''?'')
                                      AND df.max_size <> 268435456 
                                      AND df.max_size <> -1 
                                      AND df.type <> 2 
                                      AND df.growth > 0
                                      AND df.name <> ''DWDiagnostics'' OPTION (RECOMPILE);';

								DELETE br
								FROM #BlitzResults br
								INNER JOIN #SkipChecks sc ON sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName;
					        END;
                            
	
						/* Check if columnstore indexes are in use - for Github issue #615 */
				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */
					        BEGIN
								TRUNCATE TABLE #TemporaryDatabaseResults;
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.indexes WHERE type IN (5,6)) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);';
								IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1;
					        END;

						/* Check if Query Store is in use - for Github issue #3527 */
				        IF NOT EXISTS ( SELECT  1
								        FROM    #SkipChecks
								        WHERE   DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */
							AND  @ProductVersionMajor > 12 /* The relevant column only exists in versions that support Query store */
					        BEGIN
								TRUNCATE TABLE #TemporaryDatabaseResults;
						
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT;
								
								EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1 AND database_id <> 3) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);';
								IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @QueryStoreInUse = 1;
					        END;

						/* Non-Default Database Scoped Config - Github issue #598 */
				        IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' )
					        BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT;
								
								INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID)
								VALUES
									(1, 'MAXDOP', '0', NULL, 194),
									(2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195),
									(3, 'PARAMETER_SNIFFING', '1', NULL, 196),
									(4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197),
									(6, 'IDENTITY_CACHE', '1', NULL, 237),
									(7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238),
									(8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239),
									(9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240),
									(10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241),
									(11, 'ELEVATE_ONLINE', 'OFF', NULL, 242),
									(12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243),
									(13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244),
									(14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245),
									(15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246),
									(16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247),
									(17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248),
									(18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249),
									(19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250),
									(20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251),
									(21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252),
									(22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253),
									(23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254),
									(24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255),
									(25, 'PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES', '1440', NULL, 267),
									(26, 'DW_COMPATIBILITY_LEVEL', '0', NULL, 267),
									(27, 'EXEC_QUERY_STATS_FOR_SCALAR_FUNCTIONS', '1', NULL, 267),
									(28, 'PARAMETER_SENSITIVE_PLAN_OPTIMIZATION', '1', NULL, 267),
									(29, 'ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY', '0', NULL, 267),
									(31, 'CE_FEEDBACK', '1', NULL, 267),
									(33, 'MEMORY_GRANT_FEEDBACK_PERSISTENCE', '1', NULL, 267),
									(34, 'MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT', '1', NULL, 267),
									(35, 'OPTIMIZED_PLAN_FORCING', '1', NULL, 267),
									(37, 'DOP_FEEDBACK', CASE WHEN @ProductVersionMajor >= 17 THEN '1' ELSE '0' END, NULL, 267),
									(38, 'LEDGER_DIGEST_STORAGE_ENDPOINT', 'OFF', NULL, 267),
									(39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267),
									(40, 'READABLE_SECONDARY_TEMPORARY_STATS_AUTO_CREATE', '1', NULL, 267),
									(41, 'READABLE_SECONDARY_TEMPORARY_STATS_AUTO_UPDATE', '1', NULL, 267),
									(42, 'OPTIMIZED_SP_EXECUTESQL', '0', NULL, 267),
									(43, 'OPTIMIZED_HALLOWEEN_PROTECTION', '1', NULL, 267),
									(44, 'FULLTEXT_INDEX_VERSION', '2', NULL, 267),
									(47, 'OPTIONAL_PARAMETER_OPTIMIZATION', '1', NULL, 267),
									(48, 'PREVIEW_FEATURES', '0', NULL, 267);

EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
									SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty''))
									FROM [?].sys.database_scoped_configurations dsc
									INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id
									LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL)
									LEFT OUTER JOIN #SkipChecks sk ON (sk.CheckID IS NULL OR def.CheckID = sk.CheckID) AND (sk.DatabaseName IS NULL OR sk.DatabaseName = DB_NAME())
									WHERE def.configuration_id IS NULL AND sk.CheckID IS NULL
									AND ''?'' != ''rdsadmin''
									ORDER BY 1
									OPTION (RECOMPILE);';
			END;

			/* Check 218 - Show me the dodgy SET Options */
			IF NOT EXISTS (
					SELECT 1
					FROM #SkipChecks
					WHERE DatabaseName IS NULL
						AND CheckID = 218
					)
			BEGIN
				IF @Debug IN (1,2)
				BEGIN
					RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT;
				END

				EXECUTE sp_MSforeachdb 'USE [?];
					SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                    INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
					SELECT 218 AS CheckID
						,''?'' AS DatabaseName
						,150 AS Priority
						,''Performance'' AS FindingsGroup
						,''Objects created with dangerous SET Options'' AS Finding
						,''https://www.brentozar.com/go/badset'' AS URL
						,''The '' + QUOTENAME(DB_NAME())
							+ '' database has '' + CONVERT(VARCHAR(20),COUNT(1))
							+ '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.''
							+ '' These objects can break when using filtered indexes, indexed views''
							+ '' and other advanced SQL features.'' AS Details
					FROM sys.sql_modules sm
					JOIN sys.objects o ON o.[object_id] = sm.[object_id]
						AND (
							sm.uses_ansi_nulls <> 1
							OR sm.uses_quoted_identifier <> 1
							)
						AND o.is_ms_shipped = 0
						AND ''?'' != ''rdsadmin''
					HAVING COUNT(1) > 0;';
			END; --of Check 218.

			/* Check 225 - Reliability - Resumable Index Operation Paused */
			IF NOT EXISTS (
					SELECT 1
					FROM #SkipChecks
					WHERE DatabaseName IS NULL
						AND CheckID = 225
					)
                AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations')
			BEGIN
				IF @Debug IN (1,2)
				BEGIN
					RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT;
				END

				EXECUTE sp_MSforeachdb 'USE [?];
					SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                    INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
					SELECT 225 AS CheckID
						,''?'' AS DatabaseName
						,200 AS Priority
						,''Reliability'' AS FindingsGroup
						,''Resumable Index Operation Paused'' AS Finding
						,''https://www.brentozar.com/go/resumable'' AS URL
						,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', ''
                            + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: ''
                            + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details
					FROM sys.index_resumable_operations iro
					JOIN sys.objects o ON iro.[object_id] = o.[object_id]
					WHERE iro.state <> 0
					AND ''?'' != ''rdsadmin''
					;';
			END; --of Check 225.

			--/* Check 220 - Statistics Without Histograms */
			--IF NOT EXISTS (
			--		SELECT 1
			--		FROM #SkipChecks
			--		WHERE DatabaseName IS NULL
			--			AND CheckID = 220
			--		)
   --             AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram')
			--BEGIN
			--	IF @Debug IN (1,2)
			--	BEGIN
			--		RAISERROR ('Running CheckId [%d].',0,1,220) WITH NOWAIT;
			--	END

			--	EXECUTE sp_MSforeachdb 'USE [?];
            --      SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			--		INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
			--		SELECT 220 AS CheckID
			--			,DB_NAME() AS DatabaseName
			--			,110 AS Priority
			--			,''Performance'' AS FindingsGroup
			--			,''Statistics Without Histograms'' AS Finding
			--			,''https://www.brentozar.com/go/brokenstats'' AS URL
			--			,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,''
			--				+ '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details
   --                   FROM sys.all_objects o 
   --                   INNER JOIN sys.stats s ON o.object_id = s.object_id AND s.has_filter = 0
   --                   OUTER APPLY sys.dm_db_stats_histogram(o.object_id, s.stats_id) h
   --                   WHERE o.is_ms_shipped = 0 AND o.type_desc = ''USER_TABLE''
   --                     AND h.object_id IS NULL
   --                     AND 0 < (SELECT SUM(row_count) FROM sys.dm_db_partition_stats ps WHERE ps.object_id = o.object_id)
   --                     AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''rdsadmin'', ''tempdb'')
   --                   HAVING COUNT(DISTINCT o.object_id) > 0;';
			--END; --of Check 220.

		/*Check for the last good DBCC CHECKDB date */
		IF NOT EXISTS ( SELECT  1
						FROM    #SkipChecks
						WHERE   DatabaseName IS NULL AND CheckID = 68 )
			BEGIN
				
				IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT;
				
				/* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/
				--EXEC sp_MSforeachdb N'USE [?];
				--SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
				--INSERT #DBCCs
				--	(ParentObject,
				--	Object,
				--	Field,
				--	Value)
				--EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS'');
				--UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);';

				WITH    DB2
							AS ( SELECT DISTINCT
										Field ,
										Value ,
										DbName
								FROM     #DBCCs
								INNER JOIN sys.databases d ON #DBCCs.DbName = d.name
								WHERE    Field = 'dbi_dbccLastKnownGood'
									AND d.create_date < DATEADD(dd, -14, GETDATE())
								)
					INSERT  INTO #BlitzResults
							( CheckID ,
								DatabaseName ,
								Priority ,
								FindingsGroup ,
								Finding ,
								URL ,
								Details
							)
							SELECT  68 AS CheckID ,
									DB2.DbName AS DatabaseName ,
									1 AS PRIORITY ,
									'Reliability' AS FindingsGroup ,
									'Last good DBCC CHECKDB over 2 weeks old' AS Finding ,
									'https://www.brentozar.com/go/checkdb' AS URL ,
									'Last successful CHECKDB: '
									+ CASE DB2.Value
										WHEN '1900-01-01 00:00:00.000'
										THEN ' never.'
										ELSE DB2.Value
										END AS Details
							FROM    DB2
							WHERE   DB2.DbName <> 'tempdb'
									AND DB2.DbName NOT IN ( SELECT DISTINCT
															DatabaseName
														FROM
															#SkipChecks
														WHERE CheckID IS NULL OR CheckID = 68)
									AND DB2.DbName NOT IN ( SELECT  name
															FROM    sys.databases
															WHERE   is_read_only = 1)
									AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD,
															-14,
															CURRENT_TIMESTAMP);
			END;

		END; /* IF @CheckUserDatabaseObjects = 1 */

		IF @CheckProcedureCache = 1
					
					BEGIN

					IF @Debug IN (1, 2) RAISERROR('Begin checking procedure cache', 0, 1) WITH NOWAIT;
					
					BEGIN

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 35 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  35 AS CheckID ,
												100 AS Priority ,
												'Performance' AS FindingsGroup ,
												'Single-Use Plans in Procedure Cache' AS Finding ,
												'https://www.brentozar.com/go/single' AS URL ,
												( CAST(COUNT(*) AS VARCHAR(10))
												  + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details
										FROM    sys.dm_exec_cached_plans AS cp
										WHERE   cp.usecounts = 1
												AND cp.objtype = 'Adhoc'
												AND EXISTS ( SELECT
																  1
															 FROM sys.configurations
															 WHERE
																  name = 'optimize for ad hoc workloads'
																  AND value_in_use = 0 )
										HAVING  COUNT(*) > 1;
							END;

		  /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */
						IF @@VERSION LIKE '%Microsoft SQL Server 2005%'
							BEGIN
								IF @CheckProcedureCacheFilter = 'CPU'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
			  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
			  FROM sys.dm_exec_query_stats qs
			  ORDER BY qs.total_worker_time DESC)
			  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
			  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
			  FROM queries qs
			  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
			  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

								IF @CheckProcedureCacheFilter = 'Reads'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
		  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
		  FROM sys.dm_exec_query_stats qs
		  ORDER BY qs.total_logical_reads DESC)
		  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
		  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
		  FROM queries qs
		  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
		  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

								IF @CheckProcedureCacheFilter = 'ExecCount'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
		  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
		  FROM sys.dm_exec_query_stats qs
		  ORDER BY qs.execution_count DESC)
		  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
		  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
		  FROM queries qs
		  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
		  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

								IF @CheckProcedureCacheFilter = 'Duration'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
			AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
			FROM sys.dm_exec_query_stats qs
			ORDER BY qs.total_elapsed_time DESC)
			INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
			SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time]
			FROM queries qs
			LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
			WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

							END;
						IF @ProductVersionMajor >= 10
							BEGIN
								IF @CheckProcedureCacheFilter = 'CPU'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM sys.dm_exec_query_stats qs
		  ORDER BY qs.total_worker_time DESC)
		  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM queries qs
		  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
		  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

								IF @CheckProcedureCacheFilter = 'Reads'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM sys.dm_exec_query_stats qs
		  ORDER BY qs.total_logical_reads DESC)
		  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM queries qs
		  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
		  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

								IF @CheckProcedureCacheFilter = 'ExecCount'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM sys.dm_exec_query_stats qs
		  ORDER BY qs.execution_count DESC)
		  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM queries qs
		  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
		  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

								IF @CheckProcedureCacheFilter = 'Duration'
									OR @CheckProcedureCacheFilter IS NULL
									BEGIN
										SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM sys.dm_exec_query_stats qs
		  ORDER BY qs.total_elapsed_time DESC)
		  INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash])
		  SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
		  FROM queries qs
		  LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset
		  WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
										EXECUTE(@StringToExecute);
									END;

		/* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */
								UPDATE  #dm_exec_query_stats
								SET     query_plan_filtered = qp.query_plan
								FROM    #dm_exec_query_stats qs
										CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle,
																  qs.statement_start_offset,
																  qs.statement_end_offset)
										AS qp;

							END;

		/* Populate the additional query_plan, text, and text_filtered fields */
						UPDATE  #dm_exec_query_stats
						SET     query_plan = qp.query_plan ,
								[text] = st.[text] ,
								text_filtered = 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)
						FROM    #dm_exec_query_stats qs
								CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
								CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)
								AS qp;

		/* Dump instances of our own script. We're not trying to tune ourselves. */
						DELETE  #dm_exec_query_stats
						WHERE   text LIKE '%sp_Blitz%'
								OR text LIKE '%#BlitzResults%';

		/* Look for implicit conversions */

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 63 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details ,
										  QueryPlan ,
										  QueryPlanFiltered
										)
										SELECT  63 AS CheckID ,
												120 AS Priority ,
												'Query Plans' AS FindingsGroup ,
												'Implicit Conversion' AS Finding ,
												'https://www.brentozar.com/go/implicit' AS URL ,
												( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details ,
												qs.query_plan ,
												qs.query_plan_filtered
										FROM    #dm_exec_query_stats qs
										WHERE   COALESCE(qs.query_plan_filtered,
														 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%'
												AND COALESCE(qs.query_plan_filtered,
															 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%';
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 64 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details ,
										  QueryPlan ,
										  QueryPlanFiltered
										)
										SELECT  64 AS CheckID ,
												120 AS Priority ,
												'Query Plans' AS FindingsGroup ,
												'Implicit Conversion Affecting Cardinality' AS Finding ,
												'https://www.brentozar.com/go/implicit' AS URL ,
												( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details ,
												qs.query_plan ,
												qs.query_plan_filtered
										FROM    #dm_exec_query_stats qs
										WHERE   COALESCE(qs.query_plan_filtered,
														 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<PlanAffectingConvert ConvertIssue="Cardinality Estimate" Expression="CONVERT_IMPLICIT%';
							END;

							/* @cms4j, 29.11.2013: Look for RID or Key Lookups */
							IF NOT EXISTS ( SELECT  1
											FROM    #SkipChecks
											WHERE   DatabaseName IS NULL AND CheckID = 118 )
								BEGIN
									
									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 118) WITH NOWAIT;
									
									INSERT  INTO #BlitzResults
											( CheckID ,
											  Priority ,
											  FindingsGroup ,
											  Finding ,
											  URL ,
											  Details ,
											  QueryPlan ,
											  QueryPlanFiltered
											)
											SELECT  118 AS CheckID ,
													120 AS Priority ,
													'Query Plans' AS FindingsGroup ,
													'RID or Key Lookups' AS Finding ,
													'https://www.brentozar.com/go/lookup' AS URL ,
													'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details ,
													qs.query_plan ,
													qs.query_plan_filtered
											FROM    #dm_exec_query_stats qs
											WHERE   COALESCE(qs.query_plan_filtered,
															 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%Lookup="1"%';
								END; /* @cms4j, 29.11.2013: Look for RID or Key Lookups */

						/* Look for missing indexes */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 65 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 65) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details ,
										  QueryPlan ,
										  QueryPlanFiltered
										)
										SELECT  65 AS CheckID ,
												120 AS Priority ,
												'Query Plans' AS FindingsGroup ,
												'Missing Index' AS Finding ,
												'https://www.brentozar.com/go/missingindex' AS URL ,
												( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details ,
												qs.query_plan ,
												qs.query_plan_filtered
										FROM    #dm_exec_query_stats qs
										WHERE   COALESCE(qs.query_plan_filtered,
														 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%MissingIndexGroup%';
							END;

						/* Look for cursors */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 66 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 66) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details ,
										  QueryPlan ,
										  QueryPlanFiltered
										)
										SELECT  66 AS CheckID ,
												120 AS Priority ,
												'Query Plans' AS FindingsGroup ,
												'Cursor' AS Finding ,
												'https://www.brentozar.com/go/cursor' AS URL ,
												( 'One of the top resource-intensive queries is using a cursor.' ) AS Details ,
												qs.query_plan ,
												qs.query_plan_filtered
										FROM    #dm_exec_query_stats qs
										WHERE   COALESCE(qs.query_plan_filtered,
														 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<StmtCursor%';
							END;

		/* Look for scalar user-defined functions */

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 67 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 67) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details ,
										  QueryPlan ,
										  QueryPlanFiltered
										)
										SELECT  67 AS CheckID ,
												120 AS Priority ,
												'Query Plans' AS FindingsGroup ,
												'Scalar UDFs' AS Finding ,
												'https://www.brentozar.com/go/functions' AS URL ,
												( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details ,
												qs.query_plan ,
												qs.query_plan_filtered
										FROM    #dm_exec_query_stats qs
										WHERE   COALESCE(qs.query_plan_filtered,
														 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<UserDefinedFunction%';
							END;

					END; /* IF @CheckProcedureCache = 1 */
				END;
									
		/*Check to see if the HA endpoint account is set at the same as the SQL Server Service Account*/
		IF @ProductVersionMajor >= 10
								AND NOT EXISTS ( SELECT 1
								FROM #SkipChecks
								WHERE DatabaseName IS NULL AND CheckID = 187 )

		IF SERVERPROPERTY('IsHadrEnabled') = 1
    		BEGIN

				IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 187) WITH NOWAIT;
				
				INSERT    INTO [#BlitzResults]
                               	( [CheckID] ,
                                [Priority] ,
                                [FindingsGroup] ,
                                [Finding] ,
                                [URL] ,
                                [Details] )
               	SELECT
                        187 AS [CheckID] ,
                        230 AS [Priority] ,
                        'Security' AS [FindingsGroup] ,
                        'Endpoints Owned by Users' AS [Finding] ,
                       	'https://www.brentozar.com/go/owners' AS [URL] ,
                        ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.'
                        ) AS [Details]
					FROM sys.database_mirroring_endpoints ep
					LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account
					WHERE s.service_account IS NULL AND ep.principal_id <> 1;
    		END;


	/*Verify that the servername is set */
			IF NOT EXISTS ( SELECT  1
							FROM    #SkipChecks
							WHERE   DatabaseName IS NULL AND CheckID = 70 )
				BEGIN
					IF @@SERVERNAME IS NULL
						BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  70 AS CheckID ,
											200 AS Priority ,
											'Informational' AS FindingsGroup ,
											'@@Servername Not Set' AS Finding ,
											'https://www.brentozar.com/go/servername' AS URL ,
											'@@Servername variable is null. You can fix it by executing: "sp_addserver ''<LocalServerName>'', local"' AS Details;
						END;

					IF  /* @@SERVERNAME IS set */
						(@@SERVERNAME IS NOT NULL
						AND
						/* not a named instance */
						CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0
						AND
						/* not clustered, when computername may be different than the servername */
						SERVERPROPERTY('IsClustered') = 0
						AND
						/* @@SERVERNAME is different than the computer name */
						@@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR(128)) )
						 BEGIN
							
							IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT;
							
							INSERT  INTO #BlitzResults
									( CheckID ,
									  Priority ,
									  FindingsGroup ,
									  Finding ,
									  URL ,
									  Details
									)
									SELECT  70 AS CheckID ,
											200 AS Priority ,
											'Configuration' AS FindingsGroup ,
											'@@Servername Not Correct' AS Finding ,
											'https://www.brentozar.com/go/servername' AS URL ,
											'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details;
						END;

				END;
		/*Check to see if a failsafe operator has been configured*/
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 73 )
					BEGIN

						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT;

						INSERT INTO #AlertInfo
						EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  73 AS CheckID ,
										200 AS Priority ,
										'Monitoring' AS FindingsGroup ,
										'No Failsafe Operator Configured' AS Finding ,
										'https://www.brentozar.com/go/failsafe' AS URL ,
										( 'No failsafe operator is configured on this server.  This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details
								FROM    #AlertInfo
								WHERE   FailSafeOperator IS NULL;
					END;

                   /*Identify globally enabled trace flags*/
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 74 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT;
						
						INSERT  INTO #TraceStatus
								EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'
									);
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  74 AS CheckID ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Trace Flag On' AS Finding ,
										CASE WHEN [T].[TraceFlag] = '834'  AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239'
											 WHEN [T].[TraceFlag] IN ('7745', '7752') THEN 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/'
											 ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL ,
										'Trace flag ' +
										CASE WHEN [T].[TraceFlag] = '652'  THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.'
											 WHEN [T].[TraceFlag] = '661'  THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.'
										     WHEN [T].[TraceFlag] = '834'  AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.'
											 WHEN [T].[TraceFlag] = '834'  AND @CheckUserDatabaseObjects = 0 THEN '834 is enabled globally, but @CheckUserDatabaseObjects was set to 0, so we skipped checking if any databases have columnstore indexes. That combination is not recommended by Microsoft.'
											 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.'
											 WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.'
											 WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.'
											 WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.'
											 WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.'
											 WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.'
											 WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.'
											 WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.'
											 WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.'
											 WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.'
											 WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.'
											 WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.'
											 WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.'
											 WHEN [T].[TraceFlag] = '7745' AND @CheckUserDatabaseObjects = 0 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data. @CheckUserDatabaseObjects was set to 0, so we skipped checking if any databases have Query Store enabled.'
											 WHEN [T].[TraceFlag] = '7745' AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.'
											 WHEN [T].[TraceFlag] = '7745' AND  @ProductVersionMajor > 12 THEN '7745 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?'
											 WHEN [T].[TraceFlag] = '7745' AND  @ProductVersionMajor <= 12 THEN '7745 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?'
											 WHEN [T].[TraceFlag] = '7752' AND  @ProductVersionMajor > 14 THEN '7752 enabled globally, which is for Query Store. However, it has no effect in your SQL Server version. Consider turning it off.'
											 WHEN [T].[TraceFlag] = '7752' AND @CheckUserDatabaseObjects = 0 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery. @CheckUserDatabaseObjects was set to 0, so we skipped checking if any databases have Query Store enabled.'
											 WHEN [T].[TraceFlag] = '7752' AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.'
											 WHEN [T].[TraceFlag] = '7752' AND  @ProductVersionMajor > 12 THEN '7752 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?'
											 WHEN [T].[TraceFlag] = '7752' AND  @ProductVersionMajor <= 12 THEN '7752 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?'
											 WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.'
											 WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.'
                                             WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.'
											 WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0.  This is usually a very bad idea.'
											 ELSE [T].[TraceFlag] + ' is enabled globally.' END
										AS Details
								FROM    #TraceStatus T;


						IF NOT EXISTS ( SELECT  1
										FROM    #TraceStatus T
										WHERE   [T].[TraceFlag] = '7745' )
							AND @QueryStoreInUse = 1

							BEGIN
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  74 AS CheckID ,
												200 AS Priority ,
												'Informational' AS FindingsGroup ,
												'Recommended Trace Flag Off' AS Finding ,
												'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL ,
												'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details;
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #TraceStatus T
										WHERE   [T].[TraceFlag] = '7752' )
							AND @ProductVersionMajor < 15
							AND @QueryStoreInUse = 1

							BEGIN
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  74 AS CheckID ,
												200 AS Priority ,
												'Informational' AS FindingsGroup ,
												'Recommended Trace Flag Off' AS Finding ,
												'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL ,
												'Trace Flag 7752 not enabled globally. It stops queries needing to wait on Query Store loading up after database recovery. It is so recommended that it is enabled by default as of SQL Server 2019.' AS Details						
										FROM    #TraceStatus T
							END;
					END;

            /* High CMEMTHREAD waits that could need trace flag 8048.
               This check has to be run AFTER the globally enabled trace flag check,
               since it uses the #TraceStatus table to know if flags are enabled.
            */
			IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1
									FROM   #SkipChecks
									WHERE  DatabaseName IS NULL AND CheckID = 162 )
				BEGIN
							
					IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 162) WITH NOWAIT;

					INSERT  INTO #BlitzResults
							( CheckID ,
								Priority ,
								FindingsGroup ,
								Finding ,
								URL ,
								Details
							)
							SELECT  162 AS CheckID ,
									50 AS Priority ,
									'Performance' AS FindingGroup ,
									'Poison Wait Detected: CMEMTHREAD and NUMA'  AS Finding ,
									'https://www.brentozar.com/go/poison' AS URL ,
                                    CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded'
                                    + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.'
                                        ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.'
                                    END
							FROM sys.dm_os_nodes n
							INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD'
                            LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048 AND ts.status = 1
							WHERE n.node_id = 0 AND n.online_scheduler_count >= 8
								AND EXISTS (SELECT * FROM sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC')
							GROUP BY w.wait_type, ts.status
							HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
							AND SUM([wait_time_ms]) > 60000;
				END;


		/*Check for transaction log file larger than data file */
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 75 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 75) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  75 AS CheckID ,
										DB_NAME(a.database_id) ,
										50 AS Priority ,
										'Reliability' AS FindingsGroup ,
										'Transaction Log Larger than Data File' AS Finding ,
										'https://www.brentozar.com/go/biglog' AS URL ,
										'The database [' + DB_NAME(a.database_id)
										+ '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details
								FROM    sys.master_files a
								WHERE   a.type = 1
										AND DB_NAME(a.database_id) NOT IN (
										SELECT DISTINCT
												DatabaseName
										FROM    #SkipChecks
										WHERE CheckID = 75 OR CheckID IS NULL)
										AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */
										AND a.size > ( SELECT   SUM(CAST(b.size AS BIGINT))
													   FROM     sys.master_files b
													   WHERE    a.database_id = b.database_id
																AND b.type = 0
													 )
										AND a.database_id IN (
										SELECT  database_id
										FROM    sys.databases
										WHERE   source_database_id IS NULL );
					END;

		/*Check for collation conflicts between user databases and tempdb */
				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 76 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 76) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  76 AS CheckID ,
										name AS DatabaseName ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Collation is ' + collation_name AS Finding ,
										'https://www.brentozar.com/go/collate' AS URL ,
										'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details
								FROM    sys.databases
							WHERE   name NOT IN ( 'master', 'model', 'msdb')
										AND name NOT LIKE 'ReportServer%'
										AND name NOT IN ( SELECT DISTINCT
																  DatabaseName
														  FROM    #SkipChecks
														  WHERE CheckID IS NULL OR CheckID = 76)
										AND collation_name <> ( SELECT
																  collation_name
																FROM
																  sys.databases
																WHERE
																  name = 'tempdb'
															  );
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 77 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 77) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  DatabaseName ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  77 AS CheckID ,
										dSnap.[name] AS DatabaseName ,
										170 AS Priority ,
										'Reliability' AS FindingsGroup ,
										'Database Snapshot Online' AS Finding ,
										'https://www.brentozar.com/go/snapshot' AS URL ,
										'Database [' + dSnap.[name]
										+ '] is a snapshot of ['
										+ dOriginal.[name]
										+ ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details
								FROM    sys.databases dSnap
										INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id
																  AND dSnap.name NOT IN (
																  SELECT DISTINCT DatabaseName
																  FROM #SkipChecks
																  WHERE CheckID = 77 OR CheckID IS NULL);
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 79 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 79) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  79 AS CheckID ,
										-- sp_Blitz Issue #776
										-- Job has history and was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled
                						CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN
                						    100
                						ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled)
            						        200
                						END AS Priority,
										'Performance' AS FindingsGroup ,
										'Shrink Database Job' AS Finding ,
										'https://www.brentozar.com/go/autoshrink' AS URL ,
										'In the [' + j.[name] + '] job, step ['
										+ step.[step_name]
										+ '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.'
										+ CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS Details
								FROM    msdb.dbo.sysjobs j
										INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id
										LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc
										    ON j.job_id = sjsc.job_id
										LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc
										    ON sjsc.schedule_id = ssc.schedule_id
										    AND sjsc.job_id = j.job_id
										LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh
										    ON j.job_id = sjh.job_id
										    AND step.step_id = sjh.step_id
										    AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date
										    AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time
								WHERE   step.command LIKE N'%SHRINKDATABASE%'
										OR step.command LIKE N'%SHRINKFILE%';
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 81 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 81) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT  81 AS CheckID ,
										200 AS Priority ,
										'Non-Active Server Config' AS FindingsGroup ,
										cr.name AS Finding ,
										'https://www.BrentOzar.com/blitz/sp_configure/' AS URL ,
										( 'This sp_configure option isn''t running under its set value.  Its set value is '
										  + CAST(cr.[value] AS VARCHAR(100))
										  + ' and its running value is '
										  + CAST(cr.value_in_use AS VARCHAR(100))
										  + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details
								FROM    sys.configurations cr
								WHERE   cr.value <> cr.value_in_use
                                 AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16));
					END;

				IF NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 123 )
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 123) WITH NOWAIT;
						
						INSERT  INTO #BlitzResults
								( CheckID ,
								  Priority ,
								  FindingsGroup ,
								  Finding ,
								  URL ,
								  Details
								)
								SELECT TOP 1 123 AS CheckID ,
										200 AS Priority ,
										'Informational' AS FindingsGroup ,
										'Agent Jobs Starting Simultaneously' AS Finding ,
										'https://www.brentozar.com/go/busyagent/' AS URL ,
										( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details
								FROM    msdb.dbo.sysjobactivity
								WHERE start_execution_date > DATEADD(dd, -14, GETDATE())
								GROUP BY start_execution_date HAVING COUNT(*) > 1;
					END;

				IF @CheckServerInfo = 1
					BEGIN

/*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/
IF @ProductVersionMajor >= 10
			   AND NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 172 )
					BEGIN
					-- sys.dm_os_host_info includes both Windows and Linux info
					IF EXISTS (SELECT 1
									FROM	sys.all_objects
									WHERE	name = 'dm_os_host_info' )
					BEGIN

						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )

							SELECT
							172 AS [CheckID] ,
							250 AS [Priority] ,
							'Server Info' AS [FindingsGroup] ,
							'Operating System Version' AS [Finding] ,
							( CASE WHEN @IsWindowsOperatingSystem = 1
								THEN 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions'
								ELSE 'https://en.wikipedia.org/wiki/List_of_Linux_distributions'
								END
							) AS [URL] ,
							( CASE
								WHEN [ohi].[host_platform] = 'Linux' THEN 'You''re running the ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ' distribution of ' + CAST([ohi].[host_platform] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
								WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([ohi].[host_release] AS VARCHAR(5))
								WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '5' THEN 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
								ELSE 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
								END
							   ) AS [Details]
							 FROM [sys].[dm_os_host_info] [ohi];
					END;
					ELSE
					BEGIN
					-- Otherwise, stick with Windows-only detection

						IF EXISTS ( SELECT  1
												FROM    sys.all_objects
												WHERE   name = 'dm_os_windows_info' )

							BEGIN
						
								  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT;
						
								  INSERT    INTO [#BlitzResults]
											( [CheckID] ,
											  [Priority] ,
											  [FindingsGroup] ,
											  [Finding] ,
											  [URL] ,
											  [Details] )

									SELECT
									172 AS [CheckID] ,
									250 AS [Priority] ,
									'Server Info' AS [FindingsGroup] ,
									'Windows Version' AS [Finding] ,
									'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] ,
									( CASE
										WHEN [owi].[windows_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
										WHEN [owi].[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
										WHEN [owi].[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running Windows Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
										WHEN [owi].[windows_release] >= '6.2' AND [owi].[windows_release] <= '6.3' THEN 'You''re running Windows Server 2012/2012R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
										WHEN [owi].[windows_release] = '10.0' THEN 'You''re running Windows Server 2016/2019 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
										ELSE 'You''re running Windows Server, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
										END
									   ) AS [Details]
									 FROM [sys].[dm_os_windows_info] [owi];

							END;
						END;
					END;

/*
This check hits the dm_os_process_memory system view
to see if locked_page_allocations_kb is > 0,
which could indicate that locked pages in memory is enabled.
*/
IF @ProductVersionMajor >= 10 AND  NOT EXISTS ( SELECT  1
								FROM    #SkipChecks
								WHERE   DatabaseName IS NULL AND CheckID = 166 )
					BEGIN
						
						  IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT;
						
						  INSERT    INTO [#BlitzResults]
									( [CheckID] ,
									  [Priority] ,
									  [FindingsGroup] ,
									  [Finding] ,
									  [URL] ,
									  [Details] )
							SELECT
							166 AS [CheckID] ,
							250 AS [Priority] ,
							'Server Info' AS [FindingsGroup] ,
							'Locked Pages In Memory Enabled' AS [Finding] ,
							'https://www.brentozar.com/go/lpim' AS [URL] ,
							( 'You currently have '
							  + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0
									 THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100))
										  + ' GB'
									 ELSE CAST([dopm].[locked_page_allocations_kb] / 1024 AS VARCHAR(100))
										  + ' MB'
								END + ' of pages locked in memory.' ) AS [Details]
						  FROM
							[sys].[dm_os_process_memory] AS [dopm]
						  WHERE
							[dopm].[locked_page_allocations_kb] > 0;
					END;

			/* Server Info - Locked Pages In Memory Enabled - Check 166 - SQL Server 2016 SP1 and newer */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 166 )
							AND EXISTS ( SELECT  *
											FROM    sys.all_objects o
													INNER JOIN sys.all_columns c ON o.object_id = c.object_id
											WHERE   o.name = 'dm_os_sys_info'
													AND c.name = 'sql_memory_model' )
							BEGIN
										
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT;
										
										SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			SELECT  166 AS CheckID ,
			250 AS Priority ,
			''Server Info'' AS FindingsGroup ,
			''Memory Model Unconventional'' AS Finding ,
			''https://www.brentozar.com/go/lpim'' AS URL ,
			''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100))
			FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';

										EXECUTE(@StringToExecute);
									END;

					/* Performance - Instant File Initialization Not Enabled  - Check 192 */
					/* Server Info - Instant File Initialization Enabled      - Check 193 */
					IF NOT EXISTS (	SELECT 1/0
    								FROM   #SkipChecks
    								WHERE  DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */
								  ) OR NOT EXISTS
								  ( SELECT 1/0
								  	FROM   #SkipChecks
    								WHERE  DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */
								  )
					BEGIN
					    IF @Debug IN (1, 2) RAISERROR('Running  CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT;

						DECLARE @IFISetting varchar(1) = N'N'
        						,@IFIReadDMVFailed bit = 0
        						,@IFIAllFailed bit = 0;

						/* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */
					    IF EXISTS
						(
						    SELECT 1/0
						    FROM sys.all_columns
						    WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]')
						    AND [name] = N'instant_file_initialization_enabled'
						)
						BEGIN
        					/* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */
							SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf +
        					N'FROM sys.dm_server_services' + @crlf +
        					N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf +
        					N'OPTION (RECOMPILE);';
            
        					IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
        					IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
	
       						EXEC dbo.sp_executesql
       						    @StringToExecute
       						    ,N'@IFISetting varchar(1) OUTPUT'
       						    ,@IFISetting = @IFISetting OUTPUT
							
							SET @IFIReadDMVFailed = 0;
    					END
    					ELSE 
						/* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */
    					BEGIN
       						SET @IFIReadDMVFailed = 1;
       						/* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */
       						IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
       						AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
       						AND db_id('rdsadmin') IS NOT NULL
       						AND EXISTS ( SELECT 1/0
       					    			 FROM   master.sys.all_objects
       					    			 WHERE  name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')
       								   )
       						BEGIN
           						/* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */
           						INSERT INTO #ErrorLog
           						EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled';
       						END
       						ELSE
       						BEGIN
           						/* Try to read the error log, this might fail due to permissions */
           						BEGIN TRY
               						INSERT INTO #ErrorLog
               						EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled';
           						END TRY
           						BEGIN CATCH
               						IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT;
               						SET @IFIAllFailed = 1;
           						END CATCH
       						END;
    					END;

						IF @IFIAllFailed = 0
    					BEGIN
        					IF @IFIReadDMVFailed = 1
        					/* We couldn't read the DMV so set the @IFISetting variable using the error log */
        					BEGIN
        					    IF EXISTS ( SELECT 1/0
        					        		FROM   #ErrorLog
        					        		WHERE  LEFT([Text], 45) = N'Database Instant File Initialization: enabled'
        					    		  )
        					    BEGIN
        					        SET @IFISetting = 'Y';
        					    END
        					    ELSE
        					    BEGIN
        					        SET @IFISetting = 'N';
        					    END;
        					END;

					        IF NOT EXISTS ( SELECT 1/0
            								FROM   #SkipChecks
					    					WHERE  DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */
        								  ) AND @IFISetting = 'N'
        					BEGIN
        					    INSERT INTO #BlitzResults
        					    (
        					        CheckID ,
        					        [Priority] ,
        					        FindingsGroup ,
        					        Finding ,
        					        URL ,
        					        Details
        					    )
        					    SELECT
        					        192 AS [CheckID] ,
							        50 AS [Priority] ,
							        'Performance' AS [FindingsGroup] ,
        					        'Instant File Initialization Not Enabled' AS [Finding] ,
							        'https://www.brentozar.com/go/instant' AS [URL] ,
        					        'Consider enabling IFI for faster restores and data file growths.' AS [Details]
        					END;

					        IF NOT EXISTS ( SELECT 1/0
					            			FROM   #SkipChecks
					            			WHERE  DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */
					        			  ) AND @IFISetting = 'Y'
					        BEGIN
					            INSERT INTO #BlitzResults
					            (
					                CheckID ,
					                [Priority] ,
					                FindingsGroup ,
					                Finding ,
					                URL ,
					                Details
					            )
					            SELECT
					                193 AS [CheckID] ,
							        250 AS [Priority] ,
							        'Server Info' AS [FindingsGroup] ,
					                'Instant File Initialization Enabled' AS [Finding] ,
							        'https://www.brentozar.com/go/instant' AS [URL] ,
					                'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details]
					        END;
					    END;
					END;

					/* End of checkId 192 */
					/* End of checkId 193 */

					IF NOT EXISTS ( SELECT  1
									FROM    #SkipChecks
									WHERE   DatabaseName IS NULL AND CheckID = 130 )
						BEGIN
									
									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT;
									
									INSERT  INTO #BlitzResults
											( CheckID ,
											  Priority ,
											  FindingsGroup ,
											  Finding ,
											  URL ,
											  Details
											)
											SELECT  130 AS CheckID ,
													250 AS Priority ,
													'Server Info' AS FindingsGroup ,
													'Server Name' AS Finding ,
													'https://www.brentozar.com/go/servername' AS URL ,
													@@SERVERNAME AS Details
												WHERE @@SERVERNAME IS NOT NULL;
								END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 83 )
							BEGIN
								IF EXISTS ( SELECT  *
											FROM    sys.all_objects
											WHERE   name = 'dm_server_services' )
									BEGIN
										
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT;
										
				-- DATETIMEOFFSET and DATETIME have different minimum values, so there's
				-- a small workaround here to force 1753-01-01 if the minimum is detected
										SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
				SELECT  83 AS CheckID ,
				250 AS Priority ,
				''Server Info'' AS FindingsGroup ,
				''Services'' AS Finding ,
				'''' AS URL ,
				N''Service: '' + servicename + ISNULL((N'' runs under service account '' + service_account),'''') + N''. Last startup time: '' + COALESCE(CAST(CASE WHEN YEAR(last_startup_time) <= 1753 THEN CAST(''17530101'' as datetime) ELSE CAST(last_startup_time AS DATETIME) END AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.''
				FROM sys.dm_server_services OPTION (RECOMPILE);';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
										
										EXECUTE(@StringToExecute);
									END;
							END;

			/* Check 84 - SQL Server 2012 */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 84 )
							BEGIN
								IF EXISTS ( SELECT  *
											FROM    sys.all_objects o
													INNER JOIN sys.all_columns c ON o.object_id = c.object_id
											WHERE   o.name = 'dm_os_sys_info'
													AND c.name = 'physical_memory_kb' )
									BEGIN
										
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT;
										
										SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			SELECT  84 AS CheckID ,
			250 AS Priority ,
			''Server Info'' AS FindingsGroup ,
			''Hardware'' AS Finding ,
			'''' AS URL ,
			''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_kb / 1024.0 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.''
			FROM sys.dm_os_sys_info OPTION (RECOMPILE);';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
										
										EXECUTE(@StringToExecute);
									END;

			/* Check 84 - SQL Server 2008 */
								IF EXISTS ( SELECT  *
											FROM    sys.all_objects o
													INNER JOIN sys.all_columns c ON o.object_id = c.object_id
											WHERE   o.name = 'dm_os_sys_info'
													AND c.name = 'physical_memory_in_bytes' )
									BEGIN
										
										IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT;
										
										SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			SELECT  84 AS CheckID ,
			250 AS Priority ,
			''Server Info'' AS FindingsGroup ,
			''Hardware'' AS Finding ,
			'''' AS URL ,
			''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_in_bytes / 1024.0 / 1024 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.''
			FROM sys.dm_os_sys_info OPTION (RECOMPILE);';
										
										IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
										IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
										
										EXECUTE(@StringToExecute);
									END;
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 85 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  85 AS CheckID ,
												250 AS Priority ,
												'Server Info' AS FindingsGroup ,
												'SQL Server Service' AS Finding ,
												'' AS URL ,
												N'Version: '
												+ CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100))
												+ N'. Patch Level: '
												+ CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100))
								  				+ CASE WHEN SERVERPROPERTY('ProductUpdateLevel') IS NULL
												       THEN N''
												       ELSE N'. Cumulative Update: '
													   + CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(100))
												END
												+ N'. Edition: '
												+ CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
												+ N'. Availability Groups Enabled: '
												+ CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'),
																0) AS VARCHAR(100))
												+ N'. Availability Groups Manager Status: '
												+ CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'),
																0) AS VARCHAR(100));
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 88 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  88 AS CheckID ,
												250 AS Priority ,
												'Server Info' AS FindingsGroup ,
												'SQL Server Last Restart' AS Finding ,
												'' AS URL ,
												CAST(create_date AS VARCHAR(100))
										FROM    sys.databases
										WHERE   database_id = 2;
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 91 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  91 AS CheckID ,
												250 AS Priority ,
												'Server Info' AS FindingsGroup ,
												'Server Last Restart' AS Finding ,
												'' AS URL ,
												CAST(DATEADD(SECOND, (ms_ticks/1000)*(-1), GETDATE()) AS nvarchar(25))
										FROM sys.dm_os_sys_info;
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 92 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT;
								
								INSERT  INTO #driveInfo
										( drive, available_MB )
										EXEC master..xp_fixeddrives;
								
								IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_volume_stats')
								BEGIN
									SET @StringToExecute = 'Update #driveInfo
									SET
									logical_volume_name = v.logical_volume_name,
									total_MB = v.total_MB,
									used_percent = v.used_percent
									FROM
									#driveInfo
									inner join (
												SELECT DISTINCT
													SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point
													,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name
													,total_bytes/1024/1024 AS total_MB
													,available_bytes/1024/1024 AS available_MB
													,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100))  AS used_percent
												FROM
													(SELECT TOP 1 WITH TIES 
														database_id
														,file_id
														,SUBSTRING(physical_name,1,1) AS Drive
													 FROM sys.master_files
													 ORDER BY ROW_NUMBER() OVER(PARTITION BY SUBSTRING(physical_name,1,1) ORDER BY database_id)
													) f
												CROSS APPLY
													sys.dm_os_volume_stats(f.database_id, f.file_id)
									) as v on #driveInfo.drive = v.volume_mount_point;';
									EXECUTE(@StringToExecute);
								END;

								SET @StringToExecute ='INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  92 AS CheckID ,
												250 AS Priority ,
												''Server Info'' AS FindingsGroup ,
												''Drive '' + i.drive + '' Space'' AS Finding ,
												'''' AS URL ,
												CASE WHEN i.total_MB IS NULL THEN
													CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30))
													+ '' GB free on '' + i.drive
													+ '' drive''
													ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30))
													+ '' GB free on '' + i.drive
													+ '' drive '' + i.logical_volume_name
													+ '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30))
													+ '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END
												 AS Details
										FROM    #driveInfo AS i;'

									IF (@ProductVersionMajor >= 11)
									BEGIN
										SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.available_MB/1024 AS NUMERIC(18,2))','FORMAT(i.available_MB/1024,''N2'')');
										SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.total_MB/1024 AS NUMERIC(18,2))','FORMAT(i.total_MB/1024,''N2'')');
									END;

									EXECUTE(@StringToExecute);

								DROP TABLE #driveInfo;
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 103 )
							AND EXISTS ( SELECT *
										 FROM   sys.all_objects o
												INNER JOIN sys.all_columns c ON o.object_id = c.object_id
										 WHERE  o.name = 'dm_os_sys_info'
												AND c.name = 'virtual_machine_type_desc' )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
									SELECT 103 AS CheckID,
									250 AS Priority,
									''Server Info'' AS FindingsGroup,
									''Virtual Server'' AS Finding,
									''https://www.brentozar.com/go/virtual'' AS URL,
									''Type: ('' + virtual_machine_type_desc + '')'' AS Details
									FROM sys.dm_os_sys_info
									WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 214 )
							AND EXISTS ( SELECT *
										 FROM   sys.all_objects o
												INNER JOIN sys.all_columns c ON o.object_id = c.object_id
										 WHERE  o.name = 'dm_os_sys_info'
												AND c.name = 'container_type_desc' )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
									SELECT 214 AS CheckID,
									250 AS Priority,
									''Server Info'' AS FindingsGroup,
									''Container'' AS Finding,
									''https://www.brentozar.com/go/virtual'' AS URL,
									''Type: ('' + container_type_desc + '')'' AS Details
									FROM sys.dm_os_sys_info
									WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 114 )
							AND EXISTS ( SELECT *
										 FROM   sys.all_objects o
										 WHERE  o.name = 'dm_os_memory_nodes' )
							AND EXISTS ( SELECT *
										 FROM   sys.all_objects o
										 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
										 WHERE  o.name = 'dm_os_nodes'
                                	 		AND c.name = 'processor_group' )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
										SELECT  114 AS CheckID ,
												250 AS Priority ,
												''Server Info'' AS FindingsGroup ,
												''Hardware - NUMA Config'' AS Finding ,
												'''' AS URL ,
												''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc
												+ '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Offline schedulers: '' + CAST(oac.offline_schedulers AS VARCHAR(100)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10))
												+ '' Memory node: '' + CAST(n.memory_node_id AS NVARCHAR(10)) + '' Memory VAS Reserved GB: '' + CAST(CAST((m.virtual_address_space_reserved_kb / 1024.0 / 1024) AS INT) AS NVARCHAR(100))
										FROM sys.dm_os_nodes n
										INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id
										OUTER APPLY (SELECT
										COUNT(*) AS [offline_schedulers]
										FROM sys.dm_os_schedulers dos
										WHERE n.node_id = dos.parent_node_id
										AND dos.status = ''VISIBLE OFFLINE''
										) oac
										WHERE n.node_state_desc NOT LIKE ''%DAC%''
										ORDER BY n.node_id OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;
								

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 211 )
								BEGIN																		
								
								/* Variables for check 211: */
								DECLARE
									@powerScheme varchar(36)
									,@cpu_speed_mhz int
									,@cpu_speed_ghz decimal(18,2)
									,@ExecResult int;

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT;
								IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT;

									/* Get power plan if set by group policy [Git Hub Issue #1620] */						
									EXEC xp_regread @rootkey	= N'HKEY_LOCAL_MACHINE',
													@key		= N'SOFTWARE\Policies\Microsoft\Power\PowerSettings',
													@value_name	= N'ActivePowerScheme',
													@value		= @powerScheme OUTPUT,
													@no_output	= N'no_output';

									IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/
									EXEC xp_regread @rootkey	= N'HKEY_LOCAL_MACHINE',
													@key		= N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes',
													@value_name	= N'ActivePowerScheme',
													@value		= @powerScheme OUTPUT;
								
									/* Get the cpu speed*/
									EXEC @ExecResult = xp_regread @rootkey		= N'HKEY_LOCAL_MACHINE',
					                							  @key			= N'HARDWARE\DESCRIPTION\System\CentralProcessor\0',
					                							  @value_name	= N'~MHz',
					                							  @value		= @cpu_speed_mhz OUTPUT;

									/* Convert the Megahertz to Gigahertz */
									IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1)

									SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2));

									INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)							
							SELECT  211 AS CheckId,
									250 AS Priority,
									'Server Info' AS FindingsGroup,
									'Power Plan' AS Finding,
									'https://www.brentozar.com/blitz/power-mode/' AS URL,
									'Your server has '
									+ ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ')
									+ 'GHz CPUs, and is in '
									+ CASE @powerScheme
							             WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a'
							             THEN 'power saving mode -- are you sure this is a production SQL Server?'
							             WHEN '381b4222-f694-41f0-9685-ff5bb260df2e'
							             THEN 'balanced power mode -- Uh... you want your CPUs to run at full speed, right?'
							             WHEN '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
							             THEN 'high performance power mode'
							             WHEN 'e9a42b02-d5df-448d-aa00-03f14749eb61'
							             THEN 'ultimate performance power mode'
										 ELSE 'an unknown power mode.'
							        END AS Details
								
								END;

						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 212 )
								BEGIN																		
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT;

						        INSERT INTO #Instances (Instance_Number, Instance_Name, Data_Field)
								EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE',
								                           @key = 'SOFTWARE\Microsoft\Microsoft SQL Server',
								                           @value_name = 'InstalledInstances'
								
                                IF (SELECT COUNT(*) FROM #Instances) > 1
                                BEGIN

                                    DECLARE @InstanceCount NVARCHAR(MAX)
                                    SELECT @InstanceCount = COUNT(*) FROM #Instances

									INSERT INTO #BlitzResults
										(
										  CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)							
							        SELECT
									    212 AS CheckId ,
									    250 AS Priority ,
									    'Server Info' AS FindingsGroup ,
									    'Instance Stacking' AS Finding ,
									    'https://www.brentozar.com/go/babygotstacked/' AS URL ,
									    'Your Server has ' + @InstanceCount + ' Instances of SQL Server installed. More than one is usually a bad idea. Read the URL for more info.'
							    END;
	                        END;
							
							IF NOT EXISTS ( SELECT  1
											FROM    #SkipChecks
											WHERE   DatabaseName IS NULL AND CheckID = 106 )
											AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1
                                AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC')
                                AND @TraceFileIssue = 0
							BEGIN

								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT;
								
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT
												 106 AS CheckID
												,250 AS Priority
												,'Server Info' AS FindingsGroup
												,'Default Trace Contents' AS Finding
												,'https://www.brentozar.com/go/trace' AS URL
												,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data'
												+' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30))
												+('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx)
												) as Details
										FROM    ::fn_trace_gettable( @base_tracefilename, default )
										WHERE EventClass BETWEEN 65500 and 65600;
							END; /* CheckID 106 */

							IF NOT EXISTS ( SELECT  1
											FROM    #SkipChecks
											WHERE   DatabaseName IS NULL AND CheckID = 152 )
							BEGIN
								IF EXISTS (SELECT * FROM sys.dm_os_wait_stats ws
											LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type
											WHERE wait_time_ms > .1 * @CpuMsSinceWaitsCleared AND waiting_tasks_count > 0
											AND i.wait_type IS NULL)
									BEGIN
									/* Check for waits that have had more than 10% of the server's wait time */
									
									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT;
									
									WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms)
									AS
									(SELECT ws.wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms
										FROM sys.dm_os_wait_stats ws
										LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type
											WHERE i.wait_type IS NULL
												AND wait_time_ms > .1 * @CpuMsSinceWaitsCleared
												AND waiting_tasks_count > 0)
									INSERT  INTO #BlitzResults
											( CheckID ,
											  Priority ,
											  FindingsGroup ,
											  Finding ,
											  URL ,
											  Details
											)
											SELECT TOP 9
													 152 AS CheckID
													,240 AS Priority
													,'Wait Stats' AS FindingsGroup
													, CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding
													,'https://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL
													, Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' hours of waits, ' +
													CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) / @MsSinceWaitsCleared  AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' minutes average wait time per hour, ' +
													/* CAST(CAST(
														100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type)
														/ (1. * SUM(os.wait_time_ms) OVER () )
														AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% of waits, ' + */
													CAST(CAST(
														100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type)
														/ (1. * SUM(os.wait_time_ms) OVER ())
														AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% signal wait, ' +
													CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' +
													CAST(CASE WHEN  SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0
													THEN
														CAST(
															SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type)
																/ (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type))
															AS NUMERIC(18,1))
													ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.'
											FROM    os
											ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC;
									END; /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */

								/* If no waits were found, add a note about that */
								IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162))
								BEGIN
									
									IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT;
									
									INSERT  INTO #BlitzResults
											( CheckID ,
											  Priority ,
											  FindingsGroup ,
											  Finding ,
											  URL ,
											  Details
											)
										VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.');
								END;
							END; /* CheckID 152 */

                        /* CheckID 222 - Server Info - Azure Managed Instance */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 222 )
							AND 4 = ( SELECT COUNT(*)
										 FROM   sys.all_objects o
										 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
										 WHERE  o.name = 'dm_os_job_object'
                                	 		AND c.name IN ('cpu_rate', 'memory_limit_mb', 'process_memory_limit_mb', 'workingset_limit_mb' ))
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT;
								
								SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
										SELECT  222 AS CheckID ,
												250 AS Priority ,
												''Server Info'' AS FindingsGroup ,
												''Azure Managed Instance'' AS Finding ,
												''https://www.BrentOzar.com/go/azurevm'' AS URL ,
												''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + 
												'', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + 
												'', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + 
												'', workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20))
										FROM sys.dm_os_job_object OPTION (RECOMPILE);';
								
								IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
								IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
								
								EXECUTE(@StringToExecute);
							END;

                        /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 224 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT;
                                
								IF (SELECT value_in_use FROM  sys.configurations WHERE [name] = 'xp_cmdshell') = 1
                                BEGIN
                                    
                                    IF OBJECT_ID('tempdb..#services') IS NOT NULL DROP TABLE #services;
                                    CREATE TABLE #services (cmdshell_output varchar(max));
                                    
                                    INSERT INTO #services
                                    EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */
                                    
                                    IF EXISTS (SELECT 1 
                                                FROM #services 
                                                WHERE cmdshell_output LIKE '%SQL Server Reporting Services%'
                                                    OR cmdshell_output LIKE '%SQL Server Integration Services%'
                                                    OR cmdshell_output LIKE '%SQL Server Analysis Services%')
                                    BEGIN
								    INSERT  INTO #BlitzResults
								    		( CheckID ,
								    		  Priority ,
								    		  FindingsGroup ,
								    		  Finding ,
								    		  URL ,
								    		  Details
								    		)
								    		SELECT
								    				 224 AS CheckID
								    				,200 AS Priority
								    				,'Performance' AS FindingsGroup
								    				,'SSAS/SSIS/SSRS Installed' AS Finding
								    				,'https://www.BrentOzar.com/go/services' AS URL
								    				,'Did you know you have other SQL Server services installed on this box other than the engine? It can be a real performance pain.' as Details
								    									    		
								    END;
								    
                                 END;
                            END;

                        /* CheckID 232 - Server Info - Data Size */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 232 )
							BEGIN
								
								IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT;
                                
								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 ((SERVERPROPERTY('Edition')) = 'SQL Azure' 
									 AND (OBJECT_ID('sys.master_files') IS NULL))
									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);


								INSERT  INTO #BlitzResults
								    	( CheckID ,
								    		Priority ,
								    		FindingsGroup ,
								    		Finding ,
											URL ,
								    		Details
								    	)
								    	SELECT  232 AS CheckID
								    			,250 AS Priority
								    			,'Server Info' AS FindingsGroup
								    			,'Data Size' AS Finding
												,'' AS URL
								    			,CAST(COUNT(DISTINCT database_id) AS NVARCHAR(100)) + N' databases, ' + CAST(CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS MONEY) AS VARCHAR(100)) + ' GB total file size' as Details
										FROM #MasterFiles
										WHERE database_id > 4;

                            END;

                        /* CheckID 260 - Security - SQL Server service account is member of Administrators */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 260 ) AND @ProductVersionMajor >= 10 
							BEGIN
                                
								IF (SELECT value_in_use FROM  sys.configurations WHERE [name] = 'xp_cmdshell') = 1
								AND EXISTS ( SELECT  1 FROM    sys.all_objects	WHERE   [name] = 'dm_server_services' )
                                BEGIN
								    IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 260) WITH NOWAIT;
                                    IF OBJECT_ID('tempdb..#localadmins') IS NOT NULL DROP TABLE #localadmins;
                                    CREATE TABLE #localadmins (cmdshell_output NVARCHAR(1000));
                                    
                                    INSERT INTO #localadmins
                                    EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */
                                    
                                    IF EXISTS (SELECT 1 
                                                FROM #localadmins 
                                                WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account])
												                                 FROM   [sys].[dm_server_services]
												                                 WHERE  [servicename] LIKE 'SQL Server%'
												                                   AND [servicename] NOT LIKE 'SQL Server%Agent%'
												                                   AND [servicename] NOT LIKE 'SQL Server Launchpad%'))
                                    BEGIN
								    INSERT  INTO #BlitzResults
								    		( CheckID ,
								    		  Priority ,
								    		  FindingsGroup ,
								    		  Finding ,
								    		  URL ,
								    		  Details
								    		)
								    		SELECT
								    				 260 AS CheckID
								    				,1 AS Priority
								    				,'Security' AS FindingsGroup
								    				,'Dangerous Service Account' AS Finding
								    				,'https://vladdba.com/SQLServerSvcAccount' AS URL
								    				,'SQL Server''s service account is a member of the local Administrators group - meaning that anyone who can use xp_cmdshell can do anything on the host.' as Details
								    									    		
								    END;
								    
                                 END;
                            END;

                        /* CheckID 261 - Security - SQL Server Agent service account is member of Administrators */
						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 261 ) AND @ProductVersionMajor >= 10 
							BEGIN
								
								IF (SELECT value_in_use FROM  sys.configurations WHERE [name] = 'xp_cmdshell') = 1
								AND EXISTS ( SELECT  1 FROM    sys.all_objects	WHERE   [name] = 'dm_server_services' )
                                BEGIN
                                    IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 261) WITH NOWAIT;
									/*If this table exists and CheckId 260 was not skipped, then we're piggybacking off of 260's results */
                                    IF OBJECT_ID('tempdb..#localadmins') IS NOT NULL 
									   AND NOT EXISTS ( SELECT  1
										                FROM    #SkipChecks
										                WHERE   DatabaseName IS NULL AND CheckID = 260 )
                                    BEGIN 
									    IF @Debug IN (1, 2) RAISERROR('CheckId [%d] - found #localadmins table from CheckID 260 - no need to call xp_cmdshell again', 0, 1, 261) WITH NOWAIT;
                                    
                                        IF EXISTS (SELECT 1 
                                                    FROM #localadmins 
                                                    WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account])
												                                     FROM   [sys].[dm_server_services]
												                                     WHERE  [servicename] LIKE 'SQL Server%Agent%'
												                                       AND [servicename] NOT LIKE 'SQL Server Launchpad%'))
                                        BEGIN
								        INSERT  INTO #BlitzResults
								    		    ( CheckID ,
								    		      Priority ,
								    		      FindingsGroup ,
								    		      Finding ,
								    		      URL ,
								    		      Details
								    		    )
								    		    SELECT
								    		    		 261 AS CheckID
								    		    		,1 AS Priority
								    		    		,'Security' AS FindingsGroup
									    				,'Dangerous Service Account' AS Finding
									    				,'https://vladdba.com/SQLServerSvcAccount' AS URL
								    		    		,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details
								    									    		
								        END;
								    END; /*piggyback*/
									ELSE /*can't piggyback*/
									BEGIN
									    /*had to use a different table name because SQL Server/SSMS complains when parsing that the table still exists when it gets to the create part*/
									    IF OBJECT_ID('tempdb..#localadminsag') IS NOT NULL DROP TABLE #localadminsag;
									    CREATE TABLE #localadminsag (cmdshell_output NVARCHAR(1000));
										/* language specific call of xp cmdshell */
										IF (SELECT os_language_version FROM sys.dm_os_windows_info) = 1031  /* os language code for German. Again, this is a very specific fix, see #3673  */
										BEGIN
											INSERT INTO #localadminsag
											EXEC /**/xp_cmdshell/**/ N'net localgroup Administratoren' /* german */
										END
										ELSE
										BEGIN
											INSERT INTO #localadminsag
											EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */
										END	
                                    
										IF EXISTS (SELECT 1 
                                                FROM #localadminsag 
                                                WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account])
												                                 FROM   [sys].[dm_server_services]
												                                 WHERE  [servicename] LIKE 'SQL Server%Agent%'
												                                   AND [servicename] NOT LIKE 'SQL Server Launchpad%'))
										BEGIN
								        INSERT  INTO #BlitzResults
								    		    ( CheckID ,
								    		      Priority ,
								    		      FindingsGroup ,
								    		      Finding ,
								    		      URL ,
								    		      Details
								    		    )
								    		    SELECT
								    		    		 261 AS CheckID
								    		    		,1 AS Priority
								    		    		,'Security' AS FindingsGroup
									    				,'Dangerous Service Account' AS Finding
									    				,'https://vladdba.com/SQLServerSvcAccount' AS URL
								    		    		,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details
								    									    		
								        END;

									END;/*can't piggyback*/
                                 END;
                            END; /* CheckID 261 */


						IF NOT EXISTS ( SELECT  1
										FROM    #SkipChecks
										WHERE   DatabaseName IS NULL AND CheckID = 266 )
							BEGIN
								INSERT  INTO #BlitzResults
										( CheckID ,
										  Priority ,
										  FindingsGroup ,
										  Finding ,
										  URL ,
										  Details
										)
										SELECT  266 AS CheckID ,
												250 AS Priority ,
												'Server Info' AS FindingsGroup ,
												'Hardware - Memory Counters' AS Finding ,
												'https://www.brentozar.com/go/target' AS URL ,
												N'Target Server Memory (GB): ' + CAST((CAST((pTarget.cntr_value / 1024.0 / 1024.0) AS DECIMAL(10,1))) AS NVARCHAR(100))
													+ N' Total Server Memory (GB): ' + CAST((CAST((pTotal.cntr_value / 1024.0 / 1024.0) AS DECIMAL(10,1))) AS NVARCHAR(100))
										FROM    sys.dm_os_performance_counters pTarget
										INNER JOIN sys.dm_os_performance_counters pTotal
											ON pTotal.object_name LIKE 'SQLServer:Memory Manager%'
											AND pTotal.counter_name LIKE 'Total Server Memory (KB)%'
										WHERE pTarget.object_name LIKE 'SQLServer:Memory Manager%'
										  AND pTarget.counter_name LIKE 'Target Server Memory (KB)%'
							END



					END; /* IF @CheckServerInfo = 1 */
			END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */

				/* Delete priorites they wanted to skip. */
				IF @IgnorePrioritiesAbove IS NOT NULL
					DELETE  #BlitzResults
					WHERE   [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1;

				IF @IgnorePrioritiesBelow IS NOT NULL
					DELETE  #BlitzResults
					WHERE   [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1;

				/* Delete checks they wanted to skip. */
				IF @SkipChecksTable IS NOT NULL
					BEGIN
						DELETE  FROM #BlitzResults
						WHERE   DatabaseName IN ( SELECT    DatabaseName
												  FROM      #SkipChecks
												  WHERE CheckID IS NULL
												  AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')));
						DELETE  FROM #BlitzResults
						WHERE   CheckID IN ( SELECT    CheckID
												  FROM      #SkipChecks
												  WHERE DatabaseName IS NULL
												  AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')));
						DELETE r FROM #BlitzResults r
							INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID
												  AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'));
					END;

				/* Add summary mode */
				IF @SummaryMode > 0
					BEGIN
					UPDATE #BlitzResults
					  SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')'
					  FROM #BlitzResults br
						INNER JOIN (SELECT FindingsGroup, Finding, Priority, COUNT(*) AS recs FROM #BlitzResults GROUP BY FindingsGroup, Finding, Priority) brTotals ON br.FindingsGroup = brTotals.FindingsGroup AND br.Finding = brTotals.Finding AND br.Priority = brTotals.Priority
						WHERE brTotals.recs > 1;

					DELETE br
					  FROM #BlitzResults br
					  WHERE EXISTS (SELECT * FROM #BlitzResults brLower WHERE br.FindingsGroup = brLower.FindingsGroup AND br.Finding = brLower.Finding AND br.Priority = brLower.Priority AND br.ID > brLower.ID);

					END;

			/* Add credits for the nice folks who put so much time into building and maintaining this for free: */
				
				INSERT  INTO #BlitzResults
						( CheckID ,
						  Priority ,
						  FindingsGroup ,
						  Finding ,
						  URL ,
						  Details
						)
				VALUES  ( -1 ,
						  255 ,
						  'Thanks!' ,
						  'From Your Community Volunteers' ,
						  'http://FirstResponderKit.org' ,
						  'We hope you found this tool useful.'
						);

				INSERT  INTO #BlitzResults
						( CheckID ,
						  Priority ,
						  FindingsGroup ,
						  Finding ,
						  URL ,
						  Details

						)
				VALUES  ( -1 ,
						  0 ,
						  'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)),
						  'SQL Server First Responder Kit' ,
						  'http://FirstResponderKit.org/' ,
						  'To get help or add your own contributions, join us at http://FirstResponderKit.org.'

						);

				INSERT  INTO #BlitzResults
						( CheckID ,
						  Priority ,
						  FindingsGroup ,
						  Finding ,
						  URL ,
						  Details

						)
				SELECT 156 ,
						  254 ,
						  'Rundate' ,
						  GETDATE() ,
						  'http://FirstResponderKit.org/' ,
						  'Captain''s log: stardate something and something...';
						
				IF @EmailRecipients IS NOT NULL
					BEGIN
					
					IF @Debug IN (1, 2) RAISERROR('Sending an email.', 0, 1) WITH NOWAIT;
					
					/* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */
					IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults;
					SELECT * INTO ##BlitzResults FROM #BlitzResults;
					SET @query_result_separator = char(9);
					SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] ,  [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;';
					SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME;
					SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org';
					IF @EmailProfile IS NULL
						EXEC msdb.dbo.sp_send_dbmail
							@recipients = @EmailRecipients,
							@subject = @EmailSubject,
							@body = @EmailBody,
							@query_attachment_filename = 'sp_Blitz-Results.csv',
							@attach_query_result_as_file = 1,
							@query_result_header = 1,
							@query_result_width = 32767,
							@append_query_error = 1,
							@query_result_no_padding = 1,
							@query_result_separator = @query_result_separator,
							@query = @StringToExecute;
					ELSE
						EXEC msdb.dbo.sp_send_dbmail
							@profile_name = @EmailProfile,
							@recipients = @EmailRecipients,
							@subject = @EmailSubject,
							@body = @EmailBody,
							@query_attachment_filename = 'sp_Blitz-Results.csv',
							@attach_query_result_as_file = 1,
							@query_result_header = 1,
							@query_result_width = 32767,
							@append_query_error = 1,
							@query_result_no_padding = 1,
							@query_result_separator = @query_result_separator,
							@query = @StringToExecute;
					IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults;
				END;

				/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */
				DECLARE @ValidOutputServer BIT;
				DECLARE @ValidOutputLocation BIT;
				DECLARE @LinkedServerDBCheck NVARCHAR(2000);
				DECLARE @ValidLinkedServerDB INT;
				DECLARE @tmpdbchk table (cnt int);
				IF @OutputServerName IS NOT NULL
					BEGIN
						
						IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT;
						
						IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName)
							BEGIN
								SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')';
								INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck;
								SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk);
								IF (@ValidLinkedServerDB > 0)
									BEGIN
										SET @ValidOutputServer = 1;
										SET @ValidOutputLocation = 1;
									END;
								ELSE
									RAISERROR('The specified database was not found on the output server', 16, 0);
							END;
						ELSE
							BEGIN
								RAISERROR('The specified output server was not found', 16, 0);
							END;
					END;
				ELSE
					BEGIN
						IF @OutputDatabaseName IS NOT NULL
							AND @OutputSchemaName IS NOT NULL
							AND @OutputTableName IS NOT NULL
							AND EXISTS ( SELECT *
								 FROM   sys.databases
								 WHERE  QUOTENAME([name]) = @OutputDatabaseName)
							BEGIN
								SET @ValidOutputLocation = 1;
							END;
						ELSE IF @OutputDatabaseName IS NOT NULL
							AND @OutputSchemaName IS NOT NULL
							AND @OutputTableName IS NOT NULL
							AND NOT EXISTS ( SELECT *
								 FROM   sys.databases
								 WHERE  QUOTENAME([name]) = @OutputDatabaseName)
							BEGIN
								RAISERROR('The specified output database was not found on this server', 16, 0);
							END;
						ELSE
							BEGIN
								SET @ValidOutputLocation = 0;
							END;
					END;

				/* @OutputTableName lets us export the results to a permanent table */
				IF @ValidOutputLocation = 1
					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,
								Priority TINYINT ,
								FindingsGroup VARCHAR(50) ,
								Finding VARCHAR(200) ,
								DatabaseName NVARCHAR(128),
								URL VARCHAR(200) ,
								Details NVARCHAR(4000) ,
								QueryPlan [XML] NULL ,
								QueryPlanFiltered [NVARCHAR](MAX) NULL,
								CheckID INT ,
								CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));';
						IF @ValidOutputServer = 1
							BEGIN
								SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+'''''');
								SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+'''''');
								SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)');
								EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
							END;
						ELSE
							BEGIN
								IF @OutputXMLasNVARCHAR = 1
								BEGIN
									SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)');
								END;
                                EXEC(@StringToExecute);
							END;
						IF @ValidOutputServer = 1
							BEGIN
								SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
								+ @OutputServerName + '.'
								+ @OutputDatabaseName
								+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
								+ @OutputSchemaName + ''') INSERT '
								+ @OutputServerName + '.'
								+ @OutputDatabaseName + '.'
								+ @OutputSchemaName + '.'
								+ @OutputTableName
								+ ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
								+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
								+ ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details';

								EXEC(@StringToExecute);
							END;
						ELSE
							BEGIN
								IF @OutputXMLasNVARCHAR = 1
								BEGIN
									SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
								+ @OutputDatabaseName
								+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
								+ @OutputSchemaName + ''') INSERT '
								+ @OutputDatabaseName + '.'
								+ @OutputSchemaName + '.'
								+ @OutputTableName
								+ ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
								+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
								+ ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details';
								END;
								ELSE
                                begin
								SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
								+ @OutputDatabaseName
								+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
								+ @OutputSchemaName + ''') INSERT '
								+ @OutputDatabaseName + '.'
								+ @OutputSchemaName + '.'
								+ @OutputTableName
								+ ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
								+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
								+ ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details';
								END;
								EXEC(@StringToExecute);
							
							END;
					END;
				ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
					BEGIN
						IF @ValidOutputServer = 1
							BEGIN
								RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0);
							END;
						ELSE
							BEGIN
								SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
									+ @OutputTableName
									+ ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';'
									+ 'CREATE TABLE '
									+ @OutputTableName
									+ ' (ID INT IDENTITY(1,1) NOT NULL,
										ServerName NVARCHAR(128),
										CheckDate DATETIMEOFFSET,
										Priority TINYINT ,
										FindingsGroup VARCHAR(50) ,
										Finding VARCHAR(200) ,
										DatabaseName NVARCHAR(128),
										URL VARCHAR(200) ,
										Details NVARCHAR(4000) ,
										QueryPlan [XML] NULL ,
										QueryPlanFiltered [NVARCHAR](MAX) NULL,
										CheckID INT ,
										CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
									+ ' INSERT '
									+ @OutputTableName
									+ ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
									+ CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
									+ ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details';
							
									EXEC(@StringToExecute);
							END;
					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;

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

				IF @OutputType = 'COUNT'
					BEGIN
						SELECT  COUNT(*) AS Warnings
						FROM    #BlitzResults;
					END;
				ELSE
					IF @OutputType IN ( 'CSV', 'RSV' )
						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    #BlitzResults
							ORDER BY Priority ,
									FindingsGroup ,
									Finding ,
									DatabaseName ,
									Details;
						END;
					ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE'
						BEGIN
							SELECT  [Priority] ,
									[FindingsGroup] ,
									[Finding] ,
									[DatabaseName] ,
									[URL] ,
									[Details] ,
									CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan,
									[QueryPlanFiltered] ,
									CheckID
							FROM    #BlitzResults
							ORDER BY Priority ,
									FindingsGroup ,
									Finding ,
									DatabaseName ,
									Details;
						END;
					ELSE IF @OutputType = 'MARKDOWN'
						BEGIN
							WITH Results AS (SELECT row_number() OVER (ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, *
												FROM #BlitzResults
												WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL
												AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */)
							SELECT 
							    Markdown = CONVERT(XML, STUFF((SELECT
																   CASE
																	   WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup  THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf
																	   ELSE N''
																   END
																   + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf
																	      WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N'    * ' + COALESCE(r.DatabaseName, N'') + @crlf
																	      WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE  N'    * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N'  - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END
																	      ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE  N'    * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N'  - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END
																   END + @crlf
															     FROM Results r
															     LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1
															     LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1
															   ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '')
                                           + '<style>p { margin: 0 0 0.5em }</style>');
						END;
                    ELSE IF @OutputType = 'XML'
                        BEGIN
							/* --TOURSTOP05-- */
							SELECT  [Priority] ,
									[FindingsGroup] ,
									[Finding] ,
									[DatabaseName] ,
									[URL] ,
									[Details] ,
									[QueryPlanFiltered] ,
									CheckID
							FROM    #BlitzResults
							ORDER BY Priority ,
									FindingsGroup ,
									Finding ,
									DatabaseName ,
									Details
                            FOR XML PATH('Result'), ROOT('sp_Blitz_Output');
						END;
					ELSE IF @OutputType <> 'NONE'
						BEGIN
							/* --TOURSTOP05-- */
							SELECT  [Priority] ,
									[FindingsGroup] ,
									[Finding] ,
									[DatabaseName] ,
									[URL] ,
									[Details] ,
									[QueryPlan] ,
									[QueryPlanFiltered] ,
									CheckID
							FROM    #BlitzResults
							ORDER BY Priority ,
									FindingsGroup ,
									Finding ,
									DatabaseName ,
									Details;
						END;

				DROP TABLE #BlitzResults;

				IF @OutputProcedureCache = 1
				AND @CheckProcedureCache = 1
					SELECT TOP 20
							total_worker_time / execution_count AS AvgCPU ,
							total_worker_time AS TotalCPU ,
							CAST(ROUND(100.00 * total_worker_time
									   / ( SELECT   SUM(total_worker_time)
										   FROM     sys.dm_exec_query_stats
										 ), 2) AS MONEY) AS PercentCPU ,
							total_elapsed_time / execution_count AS AvgDuration ,
							total_elapsed_time AS TotalDuration ,
							CAST(ROUND(100.00 * total_elapsed_time
									   / ( SELECT   SUM(total_elapsed_time)
										   FROM     sys.dm_exec_query_stats
										 ), 2) AS MONEY) AS PercentDuration ,
							total_logical_reads / execution_count AS AvgReads ,
							total_logical_reads AS TotalReads ,
							CAST(ROUND(100.00 * total_logical_reads
									   / ( SELECT   SUM(total_logical_reads)
										   FROM     sys.dm_exec_query_stats
										 ), 2) AS MONEY) AS PercentReads ,
							execution_count ,
							CAST(ROUND(100.00 * execution_count
									   / ( SELECT   SUM(execution_count)
										   FROM     sys.dm_exec_query_stats
										 ), 2) AS MONEY) AS PercentExecutions ,
							CASE WHEN DATEDIFF(mi, creation_time,
											   qs.last_execution_time) = 0 THEN 0
								 ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi,
																  creation_time,
																  qs.last_execution_time) ) AS MONEY)
							END AS executions_per_minute ,
							qs.creation_time AS plan_creation_time ,
							qs.last_execution_time ,
							text ,
							text_filtered ,
							query_plan ,
							query_plan_filtered ,
							sql_handle ,
							query_hash ,
							plan_handle ,
							query_plan_hash
					FROM    #dm_exec_query_stats qs
					ORDER BY CASE UPPER(@CheckProcedureCacheFilter)
							   WHEN 'CPU' THEN total_worker_time
							   WHEN 'READS' THEN total_logical_reads
							   WHEN 'EXECCOUNT' THEN execution_count
							   WHEN 'DURATION' THEN total_elapsed_time
							   ELSE total_worker_time
							 END DESC;

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

    /*
	   Cleanups - drop temporary tables that have been created by this SP.													 
    */
																 
	IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL)
	BEGIN
		EXEC sp_executesql N'DROP TABLE #InvalidLogins;';
	END;

	IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL
	BEGIN
		EXEC sp_executesql N'DROP TABLE #AlertInfo;';
	END;

	/*
	Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. 
	See Github issue #2302 for more info.
	*/
	IF @NeedToTurnNumericRoundabortBackOn = 1
		SET NUMERIC_ROUNDABORT ON;

    SET NOCOUNT OFF;
GO

/*
--Sample execution call with the most common parameters:
EXEC [dbo].[sp_Blitz] 
    @CheckUserDatabaseObjects = 1 ,
    @CheckProcedureCache = 0 ,
    @OutputType = 'TABLE' ,
    @OutputProcedureCache = 0 ,
    @CheckProcedureCacheFilter = NULL,
    @CheckServerInfo = 1
*/
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON

IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' 
END
GO

ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] (
@Help TINYINT = 0,
@StartDate DATETIMEOFFSET(7) = NULL,
@EndDate DATETIMEOFFSET(7) = NULL,
@OutputDatabaseName NVARCHAR(256) = 'DBAtools',
@OutputSchemaName NVARCHAR(256) = N'dbo',
@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', 
@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats',
@OutputTableNamePerfmonStats NVARCHAR(256)  = N'BlitzFirst_PerfmonStats',
@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats',
@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache',
@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho',
@Servername NVARCHAR(128) = @@SERVERNAME,
@Databasename NVARCHAR(128) = NULL,
@BlitzCacheSortorder NVARCHAR(20) = N'cpu',
@MaxBlitzFirstPriority INT = 249,
@ReadLatencyThreshold INT = 100,
@WriteLatencyThreshold INT = 100,
@WaitStatsTop TINYINT = 10,
@Version VARCHAR(30) = NULL OUTPUT,
@VersionDate DATETIME = NULL OUTPUT,
@VersionCheckMode BIT = 0,
@BringThePain BIT = 0,
@Maxdop INT = 1,
@Debug BIT = 0
)
AS 
SET NOCOUNT ON;
SET STATISTICS XML OFF;

SELECT @Version = '8.29', @VersionDate = '20260203';

IF(@VersionCheckMode = 1)
BEGIN
	RETURN;
END;

IF (@Help = 1) 
BEGIN 
	PRINT 'EXEC sp_BlitzAnalysis 
@StartDate = NULL,		/* Specify a datetime or NULL will get an hour ago */
@EndDate = NULL,		/* Specify a datetime or NULL will get an hour of data since @StartDate */
@OutputDatabaseName = N''DBA'',		/* Specify the database name where where we can find your logged blitz data */
@OutputSchemaName = N''dbo'',		/* Specify the schema */
@OutputTableNameBlitzFirst = N''BlitzFirst'',		/* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ 
@OutputTableNameFileStats = N''BlitzFirst_FileStats'',		/* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ 
@OutputTableNamePerfmonStats  = N''BlitzFirst_PerfmonStats'',		/* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ 
@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'',		/* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ 
@OutputTableNameBlitzCache = N''BlitzCache'',		/* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ 
@OutputTableNameBlitzWho = N''BlitzWho'',		/* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */
@Databasename = NULL,		/* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */
@MaxBlitzFirstPriority = 249,		/* Max priority to include in the results */
@BlitzCacheSortorder = ''cpu'',		/* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */
@WaitStatsTop = 3,		/* Controls the top for wait stats only */
@Maxdop = 1,		/* Control the degree of parallelism that the queries within this proc can use if they want to*/
@Debug = 0;		/* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */

/*
Additional parameters:
@ReadLatencyThreshold INT		/* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */
@WriteLatencyThreshold INT		/* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */
@BringThePain BIT		/* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */
*/';
	RETURN;
END

/* Declare all local variables required */
DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); 
DECLARE @FullOutputTableNameFileStats NVARCHAR(1000);
DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000);
DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000);
DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000);
DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000);
DECLARE @Sql NVARCHAR(MAX);
DECLARE @NewLine NVARCHAR(2) = CHAR(13);
DECLARE @IncludeMemoryGrants BIT;
DECLARE @IncludeSpills BIT;

/* Validate the database name */
IF (DB_ID(@OutputDatabaseName) IS NULL)
BEGIN
	RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName);
	RETURN;
END

/* Set fully qualified table names */
SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst);
SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas');
SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals');
SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas');
SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache);
SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas');

IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL 
BEGIN
	DROP TABLE #BlitzFirstCounts;
END

CREATE TABLE #BlitzFirstCounts (
	[Priority] TINYINT NOT NULL,
	[FindingsGroup] VARCHAR(50) NOT NULL,
	[Finding] VARCHAR(200) NOT NULL,
	[TotalOccurrences] INT NULL,
	[FirstOccurrence] DATETIMEOFFSET(7) NULL,
	[LastOccurrence] DATETIMEOFFSET(7) NULL
);

/* Validate variables and set defaults as required */
IF (@BlitzCacheSortorder IS NULL) 
BEGIN 
	SET @BlitzCacheSortorder = N'cpu';
END 

SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder);

IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills'))
BEGIN
	RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT;
	RETURN;
END

/* Set @Maxdop to 1 if NULL was passed in */
IF (@Maxdop IS NULL)
BEGIN
	SET @Maxdop = 1;
END

/* iF @Maxdop is set higher than the core count just set it to 0 */
IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info))
BEGIN
	SET @Maxdop = 0;
END

/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */
SELECT @IncludeMemoryGrants = 
	CASE 
		WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1
		ELSE 0
	END;

SELECT @IncludeSpills = 
	CASE
		WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 
		ELSE 0
	END;


IF (@StartDate IS NULL)
BEGIN 
	RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT;
	/* Set StartDate to be an hour ago */
	SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET());

	IF (@EndDate IS NULL)
	BEGIN 
		RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT;
		/* Get data right up to now */
		SET @EndDate = SYSDATETIMEOFFSET();
	END
END 

IF (@EndDate IS NULL)
BEGIN 
	/* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */
	IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET())
	BEGIN 
		RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT;
		SET @EndDate = DATEADD(HOUR,1,@StartDate);
	END
	ELSE 
	BEGIN 
		RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT;
		SET @EndDate = SYSDATETIMEOFFSET();
	END
END 

/* Default to dbo schema if NULL is passed in */
IF (@OutputSchemaName IS NULL) 
BEGIN 
	SET @OutputSchemaName = 'dbo';
END

/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */
IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0)
BEGIN
	RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT;
	RETURN;
END

/* Output report window information */
SELECT 
	@Servername AS [ServerToReportOn],
	CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude],
	@StartDate AS [StartDatetime],
	@EndDate AS [EndDatetime];;


/* BlitzFirst data */
SET @Sql = N'
INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence])
SELECT 
[Priority],
[FindingsGroup],
[Finding],
COUNT(*) AS [TotalOccurrences],
MIN(CheckDate) AS [FirstOccurrence],
MAX(CheckDate) AS [LastOccurrence]
FROM '+@FullOutputTableNameBlitzFirst+N'
WHERE [ServerName] = @Servername
AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority
AND CheckDate BETWEEN @StartDate AND @EndDate
AND [CheckID] > -1
GROUP BY [Priority],[FindingsGroup],[Finding];

IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) 
BEGIN 
	SELECT 
	[Priority],
	[FindingsGroup],
	[Finding],
	[TotalOccurrences],
	[FirstOccurrence],
	[LastOccurrence]
	FROM #BlitzFirstCounts
	ORDER BY [Priority] ASC,[TotalOccurrences] DESC;
END
ELSE 
BEGIN 
	SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period'';
END
SELECT 
 [ServerName]
,[CheckDate]
,[CheckID]
,[Priority]
,[Finding]
,[URL]
,[Details]
,[HowToStopIt]
,[QueryPlan]
,[QueryText] 
FROM '+@FullOutputTableNameBlitzFirst+N' Findings
WHERE [ServerName] = @Servername
AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority
AND [CheckDate] BETWEEN @StartDate AND @EndDate
AND [CheckID] > -1
ORDER BY CheckDate ASC,[Priority] ASC
OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');';


RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT;

IF (@Debug = 1)
BEGIN 
	PRINT @Sql;
END

IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) 
BEGIN
	IF (@OutputTableNameBlitzFirst IS NULL)
	BEGIN
		RAISERROR('BlitzFirst data skipped',10,0);
		SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst';
	END
	ELSE
	BEGIN
		RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst);
		SELECT N'No BlitzFirst data available as the table cannot be found';
	END

END
ELSE /* Table exists then run the query */
BEGIN 
	EXEC sp_executesql @Sql,
	N'@StartDate DATETIMEOFFSET(7),
	@EndDate DATETIMEOFFSET(7),
	@Servername NVARCHAR(128),
	@MaxBlitzFirstPriority INT',
	@StartDate=@StartDate, 
	@EndDate=@EndDate,
	@Servername=@Servername,
	@MaxBlitzFirstPriority = @MaxBlitzFirstPriority;
END

/* Blitz WaitStats data */
SET @Sql = N'SELECT 
[ServerName], 
[CheckDate], 
[wait_type], 
[WaitsRank],
[WaitCategory], 
[Ignorable], 
[ElapsedSeconds], 
[wait_time_ms_delta], 
[wait_time_minutes_delta], 
[wait_time_minutes_per_minute], 
[signal_wait_time_ms_delta], 
[waiting_tasks_count_delta],
ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait]
FROM 
(
	SELECT
	[ServerName], 
	[CheckDate], 
	[wait_type], 
	[WaitCategory], 
	[Ignorable], 
	[ElapsedSeconds], 
	[wait_time_ms_delta], 
	[wait_time_minutes_delta], 
	[wait_time_minutes_per_minute], 
	[signal_wait_time_ms_delta], 
	[waiting_tasks_count_delta],
	ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank]
	FROM '+@FullOutputTableNameWaitStats+N' AS [Waits]
	WHERE [ServerName] = @Servername
	AND [CheckDate] BETWEEN @StartDate AND @EndDate
) TopWaits
WHERE [WaitsRank] <= @WaitStatsTop
ORDER BY 
[CheckDate] ASC, 
[wait_time_ms_delta] DESC
OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'

RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT;

IF (@Debug = 1)
BEGIN 
	PRINT @Sql;
END

IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) 
BEGIN
	IF (@OutputTableNameWaitStats IS NULL)
	BEGIN
		RAISERROR('Wait stats data skipped',10,0);
		SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats';
	END
	ELSE
	BEGIN
		RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats);
		SELECT N'No wait stats data available as the table cannot be found';
	END
END
ELSE /* Table exists then run the query */
BEGIN
	EXEC sp_executesql @Sql,
	N'@StartDate DATETIMEOFFSET(7),
	@EndDate DATETIMEOFFSET(7),
	@Servername NVARCHAR(128),
	@WaitStatsTop TINYINT',
	@StartDate=@StartDate, 
	@EndDate=@EndDate,
	@Servername=@Servername, 
	@WaitStatsTop=@WaitStatsTop;
END

/* BlitzFileStats info */ 
SET @Sql = N'
SELECT 
[ServerName], 
[CheckDate],
CASE 
	WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes''
	WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes''
	ELSE ''No'' 
END AS [io_stall_ms_breached],
LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath],
SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], 
SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], 
MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], 
MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], 
@ReadLatencyThreshold AS [is_stall_read_ms_threshold],
SUM([num_of_reads]) AS [num_of_reads], 
SUM([megabytes_read]) AS [megabytes_read], 
MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], 
MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], 
@WriteLatencyThreshold AS [io_stall_write_ms_average],
SUM([num_of_writes]) AS [num_of_writes], 
SUM([megabytes_written]) AS [megabytes_written]
FROM '+@FullOutputTableNameFileStats+N'
WHERE [ServerName] = @Servername
AND [CheckDate] BETWEEN @StartDate AND @EndDate
'
+CASE
	WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename)
'
	ELSE N''
END
+N'GROUP BY 
[ServerName], 
[CheckDate],
LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1)
ORDER BY 
[CheckDate] ASC
OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'

RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT;

IF (@Debug = 1)
BEGIN 
	PRINT @Sql;
END

IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) 
BEGIN
	IF (@OutputTableNameFileStats IS NULL)
	BEGIN
		RAISERROR('File stats data skipped',10,0);
		SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats';
	END
	ELSE
	BEGIN
		RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats);
		SELECT N'No File stats data available as the table cannot be found';
	END
END
ELSE /* Table exists then run the query */
BEGIN 
	EXEC sp_executesql @Sql,
	N'@StartDate DATETIMEOFFSET(7),
	@EndDate DATETIMEOFFSET(7),
	@Servername NVARCHAR(128),
	@Databasename NVARCHAR(128),
	@ReadLatencyThreshold INT,
	@WriteLatencyThreshold INT',
	@StartDate=@StartDate, 
	@EndDate=@EndDate,
	@Servername=@Servername,
	@Databasename = @Databasename,
	@ReadLatencyThreshold = @ReadLatencyThreshold,
	@WriteLatencyThreshold = @WriteLatencyThreshold;
END

/* Blitz Perfmon stats*/
SET @Sql = N'
SELECT 
    [ServerName]
	,[CheckDate]
	,[counter_name]
    ,[object_name]
    ,[instance_name]
    ,[cntr_value]
FROM '+@FullOutputTableNamePerfmonStats+N'
WHERE [ServerName] = @Servername
AND CheckDate BETWEEN @StartDate AND @EndDate
ORDER BY 
	[CheckDate] ASC,
	[counter_name] ASC
OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'

RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT;

IF (@Debug = 1)
BEGIN 
	PRINT @Sql;
END

IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) 
BEGIN
	IF (@OutputTableNamePerfmonStats IS NULL)
	BEGIN
		RAISERROR('Perfmon stats data skipped',10,0);
		SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats';
	END
	ELSE
	BEGIN
		RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats);
		SELECT N'No Perfmon data available as the table cannot be found';
	END
END
ELSE /* Table exists then run the query */
BEGIN 
	EXEC sp_executesql @Sql,
	N'@StartDate DATETIMEOFFSET(7),
	@EndDate DATETIMEOFFSET(7),
	@Servername NVARCHAR(128)',
	@StartDate=@StartDate, 
	@EndDate=@EndDate,
	@Servername=@Servername;
END

/* Blitz cache data */
RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT;

/* Set intial CTE */
SET @Sql = N'WITH CheckDates AS (
SELECT DISTINCT CheckDate 
FROM '
+@FullOutputTableNameBlitzCache
+N'
WHERE [ServerName] = @Servername
AND [CheckDate] BETWEEN @StartDate AND @EndDate'
+@NewLine
+CASE
	WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine
	ELSE N''
END
+N')'
;

SET @Sql += @NewLine;

/* Append additional CTEs based on sortorder */
SET @Sql += (
SELECT CAST(N',' AS NVARCHAR(MAX))
+[SortOptions].[Aliasname]+N' AS (
SELECT
	[ServerName]
    ,'+[SortOptions].[Aliasname]+N'.[CheckDate]
	,[Sortorder]
	,[TimeFrameRank]
	,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank]
	,'+[SortOptions].[Aliasname]+N'.[QueryType]
	,'+[SortOptions].[Aliasname]+N'.[QueryText]
	,'+[SortOptions].[Aliasname]+N'.[DatabaseName]
	,'+[SortOptions].[Aliasname]+N'.[AverageCPU]
	,'+[SortOptions].[Aliasname]+N'.[TotalCPU]
	,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType]
	,'+[SortOptions].[Aliasname]+N'.[AverageDuration]
	,'+[SortOptions].[Aliasname]+N'.[TotalDuration]
	,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType]
	,'+[SortOptions].[Aliasname]+N'.[AverageReads]
	,'+[SortOptions].[Aliasname]+N'.[TotalReads]
	,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType]
	,'+[SortOptions].[Aliasname]+N'.[AverageWrites]
	,'+[SortOptions].[Aliasname]+N'.[TotalWrites]
	,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType]
	,'+[SortOptions].[Aliasname]+N'.[ExecutionCount]
	,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight]
	,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType]
	,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute]
	,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime]
	,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours]
	,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime]
	,'+[SortOptions].[Aliasname]+N'.[PlanHandle]
	,'+[SortOptions].[Aliasname]+N'.[SqlHandle]
	,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info]
	,'+[SortOptions].[Aliasname]+N'.[QueryHash]
	,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info]
	,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash]
	,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset]
	,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset]
	,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[QueryPlan]
	,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans]
	,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans]
	,'+[SortOptions].[Aliasname]+N'.[MinGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed]
	,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant]
	,'+[SortOptions].[Aliasname]+N'.[MinSpills]
	,'+[SortOptions].[Aliasname]+N'.[MaxSpills]
	,'+[SortOptions].[Aliasname]+N'.[TotalSpills]
	,'+[SortOptions].[Aliasname]+N'.[AvgSpills]
	,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost]
FROM CheckDates
CROSS APPLY (
	SELECT TOP (5) 	
	[ServerName]
    ,'+[SortOptions].[Aliasname]+N'.[CheckDate]
	,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder]
	,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank]
	,'+[SortOptions].[Aliasname]+N'.[QueryType]
	,'+[SortOptions].[Aliasname]+N'.[QueryText]
	,'+[SortOptions].[Aliasname]+N'.[DatabaseName]
	,'+[SortOptions].[Aliasname]+N'.[AverageCPU]
	,'+[SortOptions].[Aliasname]+N'.[TotalCPU]
	,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType]
	,'+[SortOptions].[Aliasname]+N'.[AverageDuration]
	,'+[SortOptions].[Aliasname]+N'.[TotalDuration]
	,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType]
	,'+[SortOptions].[Aliasname]+N'.[AverageReads]
	,'+[SortOptions].[Aliasname]+N'.[TotalReads]
	,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType]
	,'+[SortOptions].[Aliasname]+N'.[AverageWrites]
	,'+[SortOptions].[Aliasname]+N'.[TotalWrites]
	,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType]
	,'+[SortOptions].[Aliasname]+N'.[ExecutionCount]
	,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight]
	,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType]
	,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute]
	,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime]
	,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours]
	,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime]
	,'+[SortOptions].[Aliasname]+N'.[PlanHandle]
	,'+[SortOptions].[Aliasname]+N'.[SqlHandle]
	,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info]
	,'+[SortOptions].[Aliasname]+N'.[QueryHash]
	,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info]
	,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash]
	,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset]
	,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset]
	,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows]
	,'+[SortOptions].[Aliasname]+N'.[QueryPlan]
	,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans]
	,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans]
	,'+[SortOptions].[Aliasname]+N'.[MinGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB]
	,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed]
	,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant]
	,'+[SortOptions].[Aliasname]+N'.[MinSpills]
	,'+[SortOptions].[Aliasname]+N'.[MaxSpills]
	,'+[SortOptions].[Aliasname]+N'.[TotalSpills]
	,'+[SortOptions].[Aliasname]+N'.[AvgSpills]
	,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost]
	FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N'
	WHERE [ServerName] = @Servername
	AND [CheckDate] BETWEEN @StartDate AND @EndDate
	AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]'
	+@NewLine
	+CASE
		WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine
		ELSE N''
	END
	+CASE 
		WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0'
		WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0'
		WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0'
		WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0'
		WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0'
		WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0'
		WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0'
		ELSE N''
	END
	+N'
	ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N'
)'
FROM (VALUES
		(N'cpu',N'TopCPU',N'TotalCPU'),
		(N'reads',N'TopReads',N'TotalReads'),
		(N'writes',N'TopWrites',N'TotalWrites'),
		(N'duration',N'TopDuration',N'TotalDuration'),
		(N'executions',N'TopExecutions',N'ExecutionCount'),
		(N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'),
		(N'spills',N'TopSpills',N'MaxSpills')
	) SortOptions(Sortorder,Aliasname,Columnname)
WHERE
	CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */
		WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL
		WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL
		ELSE [SortOptions].[Sortorder]
	END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder])
FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)');

SET @Sql += @NewLine;

/* Build the select statements to return the data after CTE declarations */
SET @Sql += (
SELECT STUFF((
SELECT @NewLine
+N'UNION ALL'
+@NewLine
+N'SELECT *
FROM '+[SortOptions].[Aliasname]
FROM (VALUES
		(N'cpu',N'TopCPU',N'TotalCPU'),
		(N'reads',N'TopReads',N'TotalReads'),
		(N'writes',N'TopWrites',N'TotalWrites'),
		(N'duration',N'TopDuration',N'TotalDuration'),
		(N'executions',N'TopExecutions',N'ExecutionCount'),
		(N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'),
		(N'spills',N'TopSpills',N'MaxSpills')
	) SortOptions(Sortorder,Aliasname,Columnname)
WHERE
	CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */
		WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL
		WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL
		ELSE [SortOptions].[Sortorder]
	END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder])
FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'')
);

/* Append Order By */
SET @Sql += @NewLine
+N'ORDER BY 
	[Sortorder] ASC,
	[CheckDate] ASC,
	[TimeFrameRank] ASC';

/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */
SET @Sql += @NewLine
+N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');';

RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT;

IF (@Debug = 1)
BEGIN 
	PRINT SUBSTRING(@Sql, 0, 4000);
	PRINT SUBSTRING(@Sql, 4000, 8000);
	PRINT SUBSTRING(@Sql, 8000, 12000);
	PRINT SUBSTRING(@Sql, 12000, 16000);
	PRINT SUBSTRING(@Sql, 16000, 20000);
	PRINT SUBSTRING(@Sql, 20000, 24000);
	PRINT SUBSTRING(@Sql, 24000, 28000);
END

IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) 
BEGIN
	IF (@OutputTableNameBlitzCache IS NULL)
	BEGIN
		RAISERROR('BlitzCache data skipped',10,0);
		SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache';
	END
	ELSE
	BEGIN
		RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache);
		SELECT N'No BlitzCache data available as the table cannot be found';
	END
END
ELSE /* Table exists then run the query */
BEGIN
	EXEC sp_executesql @Sql,
	N'@Servername NVARCHAR(128),
	@Databasename NVARCHAR(128),
	@BlitzCacheSortorder NVARCHAR(20),
	@StartDate DATETIMEOFFSET(7),
	@EndDate DATETIMEOFFSET(7)',
	@Servername = @Servername,
	@Databasename = @Databasename,
	@BlitzCacheSortorder = @BlitzCacheSortorder,
	@StartDate = @StartDate,
	@EndDate = @EndDate;
END



/* BlitzWho data */
SET @Sql = N'
SELECT [ServerName]
      ,[CheckDate]
      ,[elapsed_time]
      ,[session_id]
      ,[database_name]
      ,[query_text_snippet]
      ,[query_plan]
      ,[live_query_plan]
      ,[query_cost]
      ,[status]
      ,[wait_info]
      ,[top_session_waits]
      ,[blocking_session_id]
      ,[open_transaction_count]
      ,[is_implicit_transaction]
      ,[nt_domain]
      ,[host_name]
      ,[login_name]
      ,[nt_user_name]
      ,[program_name]
      ,[fix_parameter_sniffing]
      ,[client_interface_name]
      ,[login_time]
      ,[start_time]
      ,[request_time]
      ,[request_cpu_time]
      ,[degree_of_parallelism]
      ,[request_logical_reads]
      ,[Logical_Reads_MB]
      ,[request_writes]
      ,[Logical_Writes_MB]
      ,[request_physical_reads]
      ,[Physical_reads_MB]
      ,[session_cpu]
      ,[session_logical_reads]
      ,[session_logical_reads_MB]
      ,[session_physical_reads]
      ,[session_physical_reads_MB]
      ,[session_writes]
      ,[session_writes_MB]
      ,[tempdb_allocations_mb]
      ,[memory_usage]
      ,[estimated_completion_time]
      ,[percent_complete]
      ,[deadlock_priority]
      ,[transaction_isolation_level]
      ,[last_dop]
      ,[min_dop]
      ,[max_dop]
      ,[last_grant_kb]
      ,[min_grant_kb]
      ,[max_grant_kb]
      ,[last_used_grant_kb]
      ,[min_used_grant_kb]
      ,[max_used_grant_kb]
      ,[last_ideal_grant_kb]
      ,[min_ideal_grant_kb]
      ,[max_ideal_grant_kb]
      ,[last_reserved_threads]
      ,[min_reserved_threads]
      ,[max_reserved_threads]
      ,[last_used_threads]
      ,[min_used_threads]
      ,[max_used_threads]
      ,[grant_time]
      ,[requested_memory_kb]
      ,[grant_memory_kb]
      ,[is_request_granted]
      ,[required_memory_kb]
      ,[query_memory_grant_used_memory_kb]
      ,[ideal_memory_kb]
      ,[is_small]
      ,[timeout_sec]
      ,[resource_semaphore_id]
      ,[wait_order]
      ,[wait_time_ms]
      ,[next_candidate_for_memory_grant]
      ,[target_memory_kb]
      ,[max_target_memory_kb]
      ,[total_memory_kb]
      ,[available_memory_kb]
      ,[granted_memory_kb]
      ,[query_resource_semaphore_used_memory_kb]
      ,[grantee_count]
      ,[waiter_count]
      ,[timeout_error_count]
      ,[forced_grant_count]
      ,[workload_group_name]
      ,[resource_pool_name]
      ,[context_info]
      ,[query_hash]
      ,[query_plan_hash]
      ,[sql_handle]
      ,[plan_handle]
      ,[statement_start_offset]
      ,[statement_end_offset]
  FROM '+@FullOutputTableNameBlitzWho+N'
  WHERE [ServerName] = @Servername
  AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME))
  '
  +CASE
	WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename
  '
  	ELSE N''
  END
+N'ORDER BY [CheckDate] ASC
  OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');';

RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT;

IF (@Debug = 1)
BEGIN 
	PRINT @Sql;
END

IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) 
BEGIN
	IF (@OutputTableNameBlitzWho IS NULL)
	BEGIN
		RAISERROR('BlitzWho data skipped',10,0);
		SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho';
	END
	ELSE
	BEGIN
		RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho);
		SELECT N'No BlitzWho data available as the table cannot be found';
	END
END
ELSE
BEGIN 
	EXEC sp_executesql @Sql,
	N'@StartDate DATETIMEOFFSET(7),
	@EndDate DATETIMEOFFSET(7),
	@Servername NVARCHAR(128),
	@Databasename NVARCHAR(128)',
	@StartDate=@StartDate, 
	@EndDate=@EndDate,
	@Servername=@Servername,
	@Databasename = @Databasename;
END


GO
IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL
  EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;');
GO
ALTER PROCEDURE [dbo].[sp_BlitzBackups]
    @Help TINYINT = 0 ,
	@HoursBack INT = 168,
	@MSDBName NVARCHAR(256) = 'msdb',
	@AGName NVARCHAR(256) = NULL,
	@RestoreSpeedFullMBps INT = NULL,
	@RestoreSpeedDiffMBps INT = NULL,
	@RestoreSpeedLogMBps INT = NULL,
	@Debug TINYINT = 0,
	@PushBackupHistoryToListener BIT = 0,
	@WriteBackupsToListenerName NVARCHAR(256) = NULL,
    @WriteBackupsToDatabaseName NVARCHAR(256) = NULL,
    @WriteBackupsLastHours INT = 168,
    @Version     VARCHAR(30) = NULL OUTPUT,
	@VersionDate DATETIME = NULL OUTPUT,
    @VersionCheckMode BIT = 0
WITH RECOMPILE
AS
	BEGIN
    SET NOCOUNT ON;
	SET STATISTICS XML OFF;
	SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
	
	SELECT @Version = '8.29', @VersionDate = '20260203';
	
	IF(@VersionCheckMode = 1)
	BEGIN
		RETURN;
	END;

	IF @Help = 1 PRINT '
	/*
	sp_BlitzBackups from http://FirstResponderKit.org
	
	This script checks your backups to see how much data you might lose when
	this server fails, and how long it might take to recover.

	To learn more, visit http://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.

	Unknown limitations of this version:
	 - None.  (If we knew them, they would be known. Duh.)

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


	Parameter explanations:

	@HoursBack INT = 168		How many hours of history to examine, back from now.
								You can check just the last 24 hours of backups, for example.
	@MSDBName NVARCHAR(255) 	You can restore MSDB from different servers and check them
								centrally. Also useful if you create a DBA utility database
								and merge data from several servers in an AG into one DB.
	@RestoreSpeedFullMBps INT	By default, we use the backup speed from MSDB to guesstimate
								how fast your restores will go. If you have done performance
								tuning and testing of your backups (or if they horribly go even
								slower in your DR environment, and you want to account for
								that), then you can pass in different numbers here.
	@RestoreSpeedDiffMBps INT	See above.
	@RestoreSpeedLogMBps INT	See above.

	For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/

    MIT License
	
	Copyright (c) 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.




	*/';
ELSE
BEGIN
DECLARE @StringToExecute NVARCHAR(MAX) = N'', 
		@InnerStringToExecute NVARCHAR(MAX) = N'',
		@ProductVersion NVARCHAR(128), 
		@ProductVersionMajor DECIMAL(10, 2),
        @ProductVersionMinor DECIMAL(10, 2), 
		@StartTime DATETIME2, @ResultText NVARCHAR(MAX), 
		@crlf NVARCHAR(2),
        @MoreInfoHeader NVARCHAR(100), 
		@MoreInfoFooter NVARCHAR(100);

IF @HoursBack > 0
    SET @HoursBack = @HoursBack * -1;

IF	@WriteBackupsLastHours > 0
    SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1;

SELECT  @crlf = NCHAR(13) + NCHAR(10), 
		@StartTime = DATEADD(hh, @HoursBack, GETDATE()),
        @MoreInfoHeader = N'<?ClickToSeeDetails -- ' + @crlf, @MoreInfoFooter = @crlf + N' -- ?>';

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

CREATE TABLE #Backups
(
    id INT IDENTITY(1, 1),
    database_name NVARCHAR(128),
    database_guid UNIQUEIDENTIFIER,
    RPOWorstCaseMinutes DECIMAL(18, 1),
    RTOWorstCaseMinutes DECIMAL(18, 1),
    RPOWorstCaseBackupSetID INT,
    RPOWorstCaseBackupSetFinishTime DATETIME,
    RPOWorstCaseBackupSetIDPrior INT,
    RPOWorstCaseBackupSetPriorFinishTime DATETIME,
    RPOWorstCaseMoreInfoQuery XML,
    RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2),
    RTOWorstCaseMoreInfoQuery XML,
    FullMBpsAvg DECIMAL(18, 2),
    FullMBpsMin DECIMAL(18, 2),
    FullMBpsMax DECIMAL(18, 2),
    FullSizeMBAvg DECIMAL(18, 2),
    FullSizeMBMin DECIMAL(18, 2),
    FullSizeMBMax DECIMAL(18, 2),
    FullCompressedSizeMBAvg DECIMAL(18, 2),
    FullCompressedSizeMBMin DECIMAL(18, 2),
    FullCompressedSizeMBMax DECIMAL(18, 2),
    DiffMBpsAvg DECIMAL(18, 2),
    DiffMBpsMin DECIMAL(18, 2),
    DiffMBpsMax DECIMAL(18, 2),
    DiffSizeMBAvg DECIMAL(18, 2),
    DiffSizeMBMin DECIMAL(18, 2),
    DiffSizeMBMax DECIMAL(18, 2),
    DiffCompressedSizeMBAvg DECIMAL(18, 2),
    DiffCompressedSizeMBMin DECIMAL(18, 2),
    DiffCompressedSizeMBMax DECIMAL(18, 2),
    LogMBpsAvg DECIMAL(18, 2),
    LogMBpsMin DECIMAL(18, 2),
    LogMBpsMax DECIMAL(18, 2),
    LogSizeMBAvg DECIMAL(18, 2),
    LogSizeMBMin DECIMAL(18, 2),
    LogSizeMBMax DECIMAL(18, 2),
    LogCompressedSizeMBAvg DECIMAL(18, 2),
    LogCompressedSizeMBMin DECIMAL(18, 2),
    LogCompressedSizeMBMax DECIMAL(18, 2)
);

CREATE TABLE #RTORecoveryPoints
(
    id INT IDENTITY(1, 1),
    database_name NVARCHAR(128),
    database_guid UNIQUEIDENTIFIER,
    rto_worst_case_size_mb AS
        ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)),
    rto_worst_case_time_seconds AS
        ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)),
    full_backup_set_id INT,
    full_last_lsn NUMERIC(25, 0),
    full_backup_set_uuid UNIQUEIDENTIFIER,
    full_time_seconds BIGINT,
    full_file_size_mb DECIMAL(18, 2),
    diff_backup_set_id INT,
    diff_last_lsn NUMERIC(25, 0),
    diff_time_seconds BIGINT,
    diff_file_size_mb DECIMAL(18, 2),
    log_backup_set_id INT,
    log_last_lsn NUMERIC(25, 0),
    log_time_seconds BIGINT,
    log_file_size_mb DECIMAL(18, 2),
    log_backups INT
);

CREATE TABLE #Recoverability
	(
		Id INT IDENTITY ,
		DatabaseName NVARCHAR(128),
		DatabaseGUID UNIQUEIDENTIFIER,
		LastBackupRecoveryModel NVARCHAR(60),
		FirstFullBackupSizeMB DECIMAL (18,2),
		FirstFullBackupDate DATETIME,
		LastFullBackupSizeMB DECIMAL (18,2),
		LastFullBackupDate DATETIME,
		AvgFullBackupThroughputMB DECIMAL (18,2),
		AvgFullBackupDurationSeconds INT,
		AvgDiffBackupThroughputMB DECIMAL (18,2),
		AvgDiffBackupDurationSeconds INT,
		AvgLogBackupThroughputMB DECIMAL (18,2),
		AvgLogBackupDurationSeconds INT,
		AvgFullSizeMB DECIMAL (18,2),
		AvgDiffSizeMB DECIMAL (18,2),
		AvgLogSizeMB DECIMAL (18,2),
		IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.))  THEN 1 ELSE 0 END,
		IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END
	);

CREATE TABLE #Trending
(
    DatabaseName NVARCHAR(128),
	DatabaseGUID UNIQUEIDENTIFIER,
    [0] DECIMAL(18, 2),
    [-1] DECIMAL(18, 2),
    [-2] DECIMAL(18, 2),
    [-3] DECIMAL(18, 2),
    [-4] DECIMAL(18, 2),
    [-5] DECIMAL(18, 2),
    [-6] DECIMAL(18, 2),
    [-7] DECIMAL(18, 2),
    [-8] DECIMAL(18, 2),
    [-9] DECIMAL(18, 2),
    [-10] DECIMAL(18, 2),
    [-11] DECIMAL(18, 2),
    [-12] DECIMAL(18, 2)
);


CREATE TABLE #Warnings
(
    Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
    CheckId INT,
    Priority INT,
    DatabaseName NVARCHAR(128),
    Finding VARCHAR(256),
    Warning VARCHAR(8000)
);

IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName)
	BEGIN
	RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT;
	RETURN;
	END

IF @PushBackupHistoryToListener = 1
GOTO PushBackupHistoryToListener


	RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf
		+ ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf
		+ ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf
		+ ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf
		+ ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf
		+ ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf
		+ ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf
		+ ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf
		+ ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf
		+ ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf;


	SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf
		+ N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf
		+ N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf;

	SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf
		+ N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf
		+ N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf
		+ N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf
		+ N'SELECT bF.database_name, bF.database_guid ' + @crlf
		+ N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf
		+ N' , bF.MBpsMin AS FullMBpsMin ' + @crlf
		+ N' , bF.MBpsMax AS FullMBpsMax ' + @crlf
		+ N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf
		+ N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf
		+ N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf
		+ N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf
		+ N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf
		+ N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf
		+ N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf
		+ N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf
		+ N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf
		+ N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf
		+ N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf
		+ N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf
		+ N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf
		+ N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf
		+ N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf
		+ N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf
		+ N' , bL.MBpsMin AS LogMBpsMin ' + @crlf
		+ N' , bL.MBpsMax AS LogMBpsMax ' + @crlf
		+ N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf
		+ N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf
		+ N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf
		+ N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf
		+ N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf
		+ N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf
		+ N' FROM Backups bF ' + @crlf
		+ N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf
		+ N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf
		+ N' WHERE bF.backup_type = ''D''; ' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;


	RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT;

	SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							 SELECT  bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior,
							         bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior,
							         DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds
							 INTO #backup_gaps
							 FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs
							 CROSS APPLY ( 
							 	SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date
							 	FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1
							 	WHERE bs.database_name = bs1.database_name
							 	        AND bs.database_guid = bs1.database_guid
							 	        AND bs.backup_finish_date > bs1.backup_finish_date
							 			AND bs.backup_set_id > bs1.backup_set_id
							 	ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC 
							 ) bsPrior
							 WHERE bs.backup_finish_date > @StartTime
							 
							 CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds);
							 
							 WITH max_gaps AS (
							 SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, 
							        g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds 
							 FROM #backup_gaps AS g
							 GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior,  g.backup_finish_date
												)
							UPDATE #Backups
								SET   RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0
							        , RPOWorstCaseBackupSetID = bg.backup_set_id
									, RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date
									, RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior
									, RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior
								FROM #Backups b
								INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid
								LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds
								WHERE bgBigger.backup_set_id IS NULL;
								';
	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;

	RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT;

    UPDATE #Backups
      SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf
            + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf
            + N' WHERE database_name = ''' + database_name + ''' ' + @crlf
            + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf
            + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf
            + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf
            + N' ORDER BY backup_finish_date;'
            + @MoreInfoFooter;


/* RTO */

RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT;


	SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							 INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn)
							 SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn
							 FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog
							 WHERE type = ''L''
							 AND bLastLog.backup_finish_date >= @StartTime
							 GROUP BY database_name, database_guid;
							';

	IF @Debug = 1
		PRINT @StringToExecute;

		EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;

/* Find the most recent full backups for those logs */

RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							 UPDATE #RTORecoveryPoints
							 SET log_backup_set_id = bLasted.backup_set_id
							     ,full_backup_set_id = bLasted.backup_set_id
							     ,full_last_lsn = bLasted.last_lsn
							     ,full_backup_set_uuid = bLasted.backup_set_uuid
							 FROM #RTORecoveryPoints rp
							 		CROSS APPLY (
							 				SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name
							 				FROM  ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog 
							 				INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull 
							 					ON bLog.database_guid = bLastFull.database_guid 
							 					AND bLog.database_name = bLastFull.database_name
							 					AND bLog.first_lsn > bLastFull.last_lsn
							 					AND bLastFull.type = ''D''
							 				WHERE rp.database_guid = bLog.database_guid 
							 					AND rp.database_name = bLog.database_name
							 			) bLasted
							 LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name
							     AND bLasted.last_lsn < bLaterFulls.last_lsn
							     AND bLaterFulls.first_lsn < bLasted.last_lsn
							     AND bLaterFulls.type = ''D''
							 WHERE bLaterFulls.backup_set_id IS NULL;
							 ';

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute;

/* Add any full backups in the StartDate range that weren't part of the above log backup chain */

RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							 INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid)
							 SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid
							 FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull
							 LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid
							 WHERE bFull.type = ''D''
							     AND bFull.backup_finish_date IS NOT NULL
							     AND rp.full_backup_set_uuid IS NULL
							     AND bFull.backup_finish_date >= @StartTime;
							';

	IF @Debug = 1
		PRINT @StringToExecute;

		EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;

/* Fill out the most recent log for that full, but before the next full */

RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							UPDATE rp
						    SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'')
							FROM #RTORecoveryPoints rp
						    INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name
						        AND rp.full_last_lsn < rpNextFull.full_last_lsn
						    LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name
						        AND rp.full_last_lsn < rpEarlierFull.full_last_lsn
						        AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn
						    WHERE rpEarlierFull.full_backup_set_id IS NULL;
							';

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute;

/* Fill out a diff in that range */

RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							UPDATE #RTORecoveryPoints
							SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff
							                        WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name
							                            AND bDiff.type = ''I''
							                            AND bDiff.last_lsn < rp.log_last_lsn
							                            AND rp.full_backup_set_uuid = bDiff.differential_base_guid
							                            ORDER BY bDiff.last_lsn DESC)
							FROM #RTORecoveryPoints rp
							WHERE diff_last_lsn IS NULL;
							';

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute;

/* Get time & size totals for full & diff */

RAISERROR('Get time & size totals for full & diff', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							UPDATE #RTORecoveryPoints
							    SET full_time_seconds = DATEDIFF(ss,bFull.backup_start_date, bFull.backup_finish_date)
							    , full_file_size_mb = bFull.backup_size / 1048576.0
							    , diff_backup_set_id = bDiff.backup_set_id
							    , diff_time_seconds = DATEDIFF(ss,bDiff.backup_start_date, bDiff.backup_finish_date)
							    , diff_file_size_mb = bDiff.backup_size / 1048576.0
							FROM #RTORecoveryPoints rp
							INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull ON rp.database_guid = bFull.database_guid AND rp.database_name = bFull.database_name AND rp.full_last_lsn = bFull.last_lsn
							LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff ON rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name AND rp.diff_last_lsn = bDiff.last_lsn AND bDiff.last_lsn IS NOT NULL;
							';

	IF @Debug = 1
		PRINT @StringToExecute;
	
	EXEC sys.sp_executesql @StringToExecute;


/* Get time & size totals for logs */

RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							WITH LogTotals AS (
								 SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date))
								    , log_file_size = SUM(bLog.backup_size)
								    , SUM(1) AS log_backups
								        FROM #RTORecoveryPoints rp
								            INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L''
								            AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn)
								            AND bLog.first_lsn <= rp.log_last_lsn
								        GROUP BY rp.id
								)
								UPDATE #RTORecoveryPoints
								    SET log_time_seconds = lt.log_time_seconds
								    , log_file_size_mb = lt.log_file_size / 1048576.0
								    , log_backups = lt.log_backups
								FROM #RTORecoveryPoints rp
								    INNER JOIN LogTotals lt ON rp.id = lt.id;
									';

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute;

RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT;

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							WITH WorstCases AS (
								SELECT rp.*
								  FROM #RTORecoveryPoints rp
								    LEFT OUTER JOIN #RTORecoveryPoints rpNewer 
										ON rp.database_guid = rpNewer.database_guid 
										AND rp.database_name = rpNewer.database_name 
										AND rp.full_last_lsn < rpNewer.full_last_lsn 
										AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s	WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC)
								  WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC)
								    /* OR  rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */
								  AND rpNewer.database_guid IS NULL
								)
								UPDATE #Backups
										SET RTOWorstCaseMinutes = 
                                                                    /* Fulls */
                                                                    (CASE WHEN @RestoreSpeedFullMBps IS NULL 
																	   THEN wc.full_time_seconds / 60.0
																	   ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb
																	   END)

                                                                    /* Diffs, which might not have been taken */
                                                                    + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL
                                                                        THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb
                                                                        ELSE COALESCE(wc.diff_time_seconds,0) / 60.0
                                                                        END)

                                                                    /* Logs, which might not have been taken */
                                                                    + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL
                                                                        THEN @RestoreSpeedLogMBps / wc.log_file_size_mb
                                                                        ELSE COALESCE(wc.log_time_seconds,0) / 60.0
                                                                        END)
								        , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb
								FROM #Backups b
								INNER JOIN WorstCases wc 
								ON b.database_guid = wc.database_guid 
									AND b.database_name = wc.database_name;
								';

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps;



/*Populating Recoverability*/


	/*Get distinct list of databases*/
	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							 SELECT DISTINCT b.database_name, database_guid
							 FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;'

	IF @Debug = 1
		PRINT @StringToExecute;

	INSERT #Recoverability ( DatabaseName, DatabaseGUID )
	EXEC sys.sp_executesql @StringToExecute;


	/*Find most recent recovery model, backup size, and backup date*/
	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += 	N'
								UPDATE r
								SET r.LastBackupRecoveryModel = ca.recovery_model,
									r.LastFullBackupSizeMB = ca.compressed_backup_size,
									r.LastFullBackupDate = ca.backup_finish_date
								FROM #Recoverability r
									CROSS APPLY (
										SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date
										FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
										WHERE r.DatabaseName = b.database_name
										AND r.DatabaseGUID = b.database_guid
										AND b.type = ''D''
										AND b.backup_finish_date > @StartTime
										ORDER BY b.backup_finish_date DESC
												) ca;'

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;

	/*Find first backup size and date*/
	SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							UPDATE r
							SET r.FirstFullBackupSizeMB = ca.compressed_backup_size,
								r.FirstFullBackupDate = ca.backup_finish_date
							FROM #Recoverability r
								CROSS APPLY (
									SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date
									FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
									WHERE r.DatabaseName = b.database_name 
									AND r.DatabaseGUID = b.database_guid
									AND b.type = ''D''
									AND b.backup_finish_date > @StartTime
									ORDER BY b.backup_finish_date ASC
											) ca;'

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;


	/*Find average backup throughputs for full, diff, and log*/
	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							UPDATE r
							SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed,
								r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed,
								r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed,
								r.AvgFullBackupDurationSeconds = AvgFullDuration,
								r.AvgDiffBackupDurationSeconds = AvgDiffDuration,
								r.AvgLogBackupDurationSeconds = AvgLogDuration
							FROM #Recoverability AS r
							OUTER APPLY (
								SELECT b.database_name, 
									   AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed,
									   AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration
								FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b
								WHERE r.DatabaseName = b.database_name
								AND r.DatabaseGUID = b.database_guid
								AND b.type = ''D'' 
								AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0
								AND b.backup_finish_date > @StartTime
								GROUP BY b.database_name
										) ca_full
							OUTER APPLY (
								SELECT b.database_name, 
									   AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed,
									   AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration
								FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b
								WHERE r.DatabaseName = b.database_name
								AND r.DatabaseGUID = b.database_guid
								AND b.type = ''I'' 
								AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0
								AND b.backup_finish_date > @StartTime
								GROUP BY b.database_name
										) ca_diff
							OUTER APPLY (
								SELECT b.database_name, 
									   AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed,
									   AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration
								FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b
								WHERE r.DatabaseName = b.database_name
								AND r.DatabaseGUID = b.database_guid
								AND b.type = ''L''
								AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0
								AND b.backup_finish_date > @StartTime
								GROUP BY b.database_name
										) ca_log;'

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;


	/*Find max and avg diff and log sizes*/
	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							UPDATE r
							 SET r.AvgFullSizeMB = fulls.avg_full_size,
							 	 r.AvgDiffSizeMB = diffs.avg_diff_size,
							 	 r.AvgLogSizeMB = logs.avg_log_size
							 FROM #Recoverability AS r
							 OUTER APPLY (
							 	SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size
							 	FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							 	WHERE r.DatabaseName = b.database_name
								AND r.DatabaseGUID = b.database_guid
							 	AND b.type = ''D''
								AND b.backup_finish_date > @StartTime
							 	GROUP BY b.database_name
							 			) AS fulls
							 OUTER APPLY (
							 	SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size
							 	FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							 	WHERE r.DatabaseName = b.database_name
								AND r.DatabaseGUID = b.database_guid
							 	AND b.type = ''I''
								AND b.backup_finish_date > @StartTime
							 	GROUP BY b.database_name
							 			) AS diffs
							 OUTER APPLY (
							 	SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size
							 	FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							 	WHERE r.DatabaseName = b.database_name
								AND r.DatabaseGUID = b.database_guid
							 	AND b.type = ''L''
								AND b.backup_finish_date > @StartTime
							 	GROUP BY b.database_name
							 			) AS logs;'

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;
	
/*Trending - only works if backupfile is populated, which means in msdb */
IF @MSDBName = N'msdb'
BEGIN
	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf;

	SET @StringToExecute += N'
							SELECT  p.DatabaseName,
									p.DatabaseGUID,
									p.[0],
									p.[-1],
									p.[-2],
									p.[-3],
									p.[-4],
									p.[-5],
									p.[-6],
									p.[-7],
									p.[-8],
									p.[-9],
									p.[-10],
									p.[-11],
									p.[-12]
								FROM ( SELECT b.database_name AS DatabaseName,
											  b.database_guid AS DatabaseGUID,
											  DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo ,
											  CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB
										FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b 
										INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf
										ON b.backup_set_id = bf.backup_set_id
										WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' )
										AND bf.file_type = ''D''
										AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime)
										AND b.backup_start_date <= SYSDATETIME()
										GROUP BY b.database_name,
												 b.database_guid,	
												 DATEDIFF(mm, @StartTime, b.backup_start_date)
									 ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p
								ORDER BY p.DatabaseName;
								'

		IF @Debug = 1
			PRINT @StringToExecute;

		INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] )
		EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime;

END

/*End Trending*/

/*End populating Recoverability*/

RAISERROR('Returning data', 0, 1) WITH NOWAIT;

	SELECT   b.*
		FROM     #Backups AS b
		ORDER BY b.database_name;

	SELECT   r.*,
             t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12]
		FROM #Recoverability AS r
		LEFT JOIN #Trending t
		ON r.DatabaseName = t.DatabaseName
		AND r.DatabaseGUID = t.DatabaseGUID	
		WHERE r.LastBackupRecoveryModel IS NOT NULL
		ORDER BY r.DatabaseName


RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT;

/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'
							WITH common_people AS (
									SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records
									FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
									GROUP BY b.user_name
									ORDER BY Records DESC
													)								
								SELECT 
								1 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Non-Agent backups taken'' AS [Finding], 
								''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE  b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' 
							AND NOT EXISTS (
											SELECT 1
											FROM common_people AS cp
											WHERE cp.user_name = b.user_name
											)
							GROUP BY b.database_name, b.user_name
							HAVING COUNT(*) > 1;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								2 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Compatibility level changing'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							GROUP BY b.database_name
							HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								3 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Password backups'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.is_password_protected = 1
							GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								4 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Snapshot backups'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.is_snapshot = 1
							GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								5 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Read only state backups'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.is_readonly = 1
							GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								6 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Single user mode backups'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.is_single_user = 1
							GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								7 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''No CHECKSUMS'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.has_backup_checksums = 0
							AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME())
							GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								8 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Damaged backups'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning]
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.is_damaged = 1
							GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*Checking for encrypted backups and the last backup of the encryption key.*/

	/*2014 ONLY*/

IF @ProductVersionMajor >= 12
	BEGIN

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
								9 AS CheckId,
								100 AS [Priority],
								b.database_name AS [Database Name],
								''Encrypted backups'' AS [Finding],
								''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is '
								+ CASE WHEN LOWER(@MSDBName) <> N'msdb'
									THEN + N'...well, that information is on another server, anyway.'' AS [Warning]'
									ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]'
									END + 
							N'
							FROM   ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b
							WHERE b.encryptor_type IS NOT NULL
							GROUP BY b.database_name, b.encryptor_type;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	END
	
	/*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/

	SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
		10 AS CheckId,
		100 AS [Priority],
		b.database_name AS [Database Name],
		''Bulk logged backups'' AS [Finding],
		''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning]
	FROM   ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b
	WHERE b.has_bulk_logged_data = 1
	GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;
	
	/*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/

	SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
		11 AS CheckId,
		100 AS [Priority],
		b.database_name AS [Database Name],
		''Recovery model switched'' AS [Finding],
		''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning]
	FROM   ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b
	WHERE b.recovery_model <> ''BULK-LOGGED''
	GROUP BY b.database_name
	HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;

	/*Looking for uncompressed backups.*/

	SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT 
		12 AS CheckId,
		100 AS [Priority],
		b.database_name AS [Database Name],
		''Uncompressed backups'' AS [Finding],
		''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning]
	FROM   ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b
	WHERE backup_size = compressed_backup_size AND type = ''D''
	AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME())
	GROUP BY b.database_name;' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		EXEC sys.sp_executesql @StringToExecute;

RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT;

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		SELECT
			13 AS CheckId,
			100 AS Priority,
			r.DatabaseName as [DatabaseName],
			'Big Diffs' AS [Finding],
			'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning]
			FROM #Recoverability AS r
			WHERE r.IsBigDiff = 1

		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )
		SELECT
			13 AS CheckId,
			100 AS Priority,
			r.DatabaseName as [DatabaseName],
			'Big Logs' AS [Finding],
			'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning]
			FROM #Recoverability AS r
			WHERE r.IsBigLog = 1



/*Insert thank you stuff last*/
		INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning )

		SELECT
		2147483647 AS [CheckId],
		2147483647 AS [Priority],
		'From Your Community Volunteers' AS [DatabaseName],
		'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding],
		'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning];

RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT;

SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning
FROM #Warnings AS w
ORDER BY w.Priority, w.CheckId;

DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints


RETURN;

PushBackupHistoryToListener:

RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT;

DECLARE @msg NVARCHAR(4000) = N'';
DECLARE @RemoteCheck TABLE (c INT NULL);


IF @WriteBackupsToDatabaseName IS NULL
	BEGIN
	RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT
	RETURN;
	END

IF LOWER(@WriteBackupsToDatabaseName) = N'msdb'
	BEGIN
	RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT
	RETURN;
	END

IF @WriteBackupsToListenerName IS NULL
BEGIN
	IF @AGName IS NULL
		BEGIN
			RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT;
			RETURN;
		END
	ELSE
		BEGIN	
			SELECT @WriteBackupsToListenerName =  dns_name
			FROM sys.availability_groups AS ag
			JOIN sys.availability_group_listeners AS agl
			ON ag.group_id = agl.group_id
			WHERE name = @AGName;
		END

END

IF @WriteBackupsToListenerName IS NOT NULL
BEGIN
	IF NOT EXISTS 
		(
			SELECT *
			FROM sys.servers s
			WHERE name = @WriteBackupsToListenerName
		)
			BEGIN
				SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.';
				RAISERROR(@msg, 16, 1) WITH NOWAIT;
				RETURN;
			END
END

	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT TOP 1 1 FROM ' 
							+ QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;'

	IF @Debug = 1
		PRINT @StringToExecute;

	INSERT @RemoteCheck (c)
	EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName;

	IF @@ROWCOUNT = 0
		BEGIN
		SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.'
		RAISERROR(@msg, 16, 1) WITH NOWAIT
		RETURN;
		END


	SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

	SET @StringToExecute += N'SELECT TOP 1 1 FROM ' 
							+ QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo'';
							' + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

	INSERT @RemoteCheck (c)
	EXEC sp_executesql @StringToExecute;

	IF @@ROWCOUNT = 0
		BEGIN

		SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.'
		RAISERROR(@msg, 0, 1) WITH NOWAIT
		RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT
		
		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

		SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset
											( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, 
											  last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, 
											  software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, 
											  software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), 
											  database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, 
											  code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), 
											  machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), 
											  has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, 
											  is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, 
											  family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), 
											  encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) 
											);
								 ' + @crlf;
		
		SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute +  ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';'
	
	IF @Debug = 1
		PRINT @InnerStringToExecute;		
		
		EXEC sp_executesql @InnerStringToExecute


		RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT

		/*Checking for and creating the PK/CX*/

		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;
		
		SET @StringToExecute += N'
		
		IF NOT EXISTS (
		SELECT t.name, i.name
		FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t
		JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i  
		ON t.object_id = i.object_id
		WHERE t.name = ?
		AND i.name LIKE ?
		)

		BEGIN
		ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC)
		END
		'

		SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute +  ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';'
	
	IF @Debug = 1
		PRINT @InnerStringToExecute;		
		
		EXEC sp_executesql @InnerStringToExecute



		/*Checking for and creating index on backup_set_uuid*/

		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;
		
		SET @StringToExecute += N'IF NOT EXISTS (
		SELECT t.name, i.name
		FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t
		JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i  
		ON t.object_id = i.object_id
		WHERE t.name = ?
		AND i.name = ?
		)

		BEGIN
		CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC)
		END
		'

		SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute +  ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';'
	
	IF @Debug = 1
		PRINT @InnerStringToExecute;		
		
		EXEC sp_executesql @InnerStringToExecute		
		
		
		
		/*Checking for and creating index on media_set_id*/

		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;
		
		SET @StringToExecute += 'IF NOT EXISTS (
		SELECT t.name, i.name
		FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t
		JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i  
		ON t.object_id = i.object_id
		WHERE t.name = ?
		AND i.name = ?
		)

		BEGIN
		CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC)
		END
		'

		SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute +  ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';'
	
	IF @Debug = 1
		PRINT @InnerStringToExecute;		
		
		EXEC sp_executesql @InnerStringToExecute
		
				
				
		/*Checking for and creating index on backup_finish_date*/

		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;
		
		SET @StringToExecute += N'IF NOT EXISTS (
		SELECT t.name, i.name
		FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t
		JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i  
		ON t.object_id = i.object_id
		WHERE t.name = ?
		AND i.name = ?
		)

		BEGIN
		CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC)
		END
		'

		SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute +  ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';'
	
	IF @Debug = 1
		PRINT @InnerStringToExecute;		
		
		EXEC sp_executesql @InnerStringToExecute


			
		/*Checking for and creating index on database_name*/

		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;
		
		SET @StringToExecute += N'IF NOT EXISTS (
		SELECT t.name, i.name
		FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t
		JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i  
		ON t.object_id = i.object_id
		WHERE t.name = ?
		AND i.name = ?
		)

		BEGIN				
		CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) 
		END

		'

		SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute +  ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';'
	
	IF @Debug = 1
		PRINT @InnerStringToExecute;		
		
		EXEC sp_executesql @InnerStringToExecute

		RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT
		END


		RAISERROR('Beginning inserts', 0, 1) WITH NOWAIT;
		RAISERROR(@crlf, 0, 1) WITH NOWAIT;

		/*
		Batching code comes from the lovely and talented Michael J. Swart
		http://michaeljswart.com/2014/09/take-care-when-scripting-batches/
		If you're ever in Canada, he says you can stay at his house, too.
		*/


		SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf;

		SET @StringToExecute += N'	
									DECLARE
										@StartDate DATETIME = DATEADD(HOUR, @i_WriteBackupsLastHours, SYSDATETIME()),
										@StartDateNext DATETIME,
										@RC INT = 1,
										@msg NVARCHAR(4000) = N'''';
									
									SELECT @StartDate = MIN(b.backup_start_date)
									FROM msdb.dbo.backupset b
									WHERE b.backup_start_date >= @StartDate
									AND  NOT EXISTS (
													SELECT 1 
													FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2
													WHERE b.backup_set_uuid = b2.backup_set_uuid
													AND b2.backup_start_date >= @StartDate
													)

									SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate);

								 IF
									( @StartDate IS NULL )
										BEGIN
											SET @msg = N''No data to move, exiting.''
											RAISERROR(@msg, 0, 1) WITH NOWAIT	

											RETURN;
										END

									RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT;

									WHILE EXISTS (
												SELECT 1
												FROM msdb.dbo.backupset b
												WHERE NOT EXISTS (
													SELECT 1 
													FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2
													WHERE b.backup_set_uuid = b2.backup_set_uuid
													AND b2.backup_start_date >= @StartDate
																)
												)
									BEGIN
										
									SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' +  + CONVERT(NVARCHAR(30), @StartDateNext) + ''.''
									RAISERROR(@msg, 0, 1) WITH NOWAIT																			
										
										'

		SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset
									' 
		SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, 
									compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, 
									is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 
																																				THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf
																																				ELSE + N'has_bulk_logged_data)' + @crlf
																																				END
		
		SET @StringToExecute +=N'
									SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, 
									compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, 
									is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 
																																				THEN + N'encryptor_type, has_bulk_logged_data' + @crlf
																																				ELSE + N'has_bulk_logged_data' + @crlf
																																				END
		SET @StringToExecute +=N'
								 FROM msdb.dbo.backupset b
								 WHERE 1=1
								 AND b.backup_start_date >= @StartDate
								 AND b.backup_start_date < @StartDateNext
								 AND NOT EXISTS (
										SELECT 1 
										FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2
										WHERE b.backup_set_uuid = b2.backup_set_uuid
										AND b2.backup_start_date >= @StartDate
													)'  + @crlf;


		SET @StringToExecute +=N'
								 SET @RC = @@ROWCOUNT;
								 								
								SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.''
								RAISERROR(@msg, 0, 1) WITH NOWAIT
								 
								 SET @StartDate = @StartDateNext;
								 SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate);

								 IF
									( @StartDate > SYSDATETIME() )
										BEGIN
											
											SET @msg = N''No more data to move, exiting.''
											RAISERROR(@msg, 0, 1) WITH NOWAIT	
											
											BREAK;

										END
								 END'  + @crlf;

	IF @Debug = 1
		PRINT @StringToExecute;

	EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours;

END;

END;

GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO

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 @msg VARCHAR(8000); 
	SELECT @msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933);
	PRINT @msg;
	RETURN;
END;

IF OBJECT_ID('dbo.sp_BlitzCache') IS NULL
  EXEC ('CREATE PROCEDURE dbo.sp_BlitzCache AS RETURN 0;');
GO

IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheProcs', 'U') IS NOT NULL
    EXEC ('DROP TABLE ##BlitzCacheProcs;');
GO

IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheResults', 'U') IS NOT NULL
    EXEC ('DROP TABLE ##BlitzCacheResults;');
GO

CREATE TABLE ##BlitzCacheResults (
    SPID INT,
    ID INT IDENTITY(1,1),
    CheckID INT,
    Priority TINYINT,
    FindingsGroup VARCHAR(50),
    Finding VARCHAR(500),
    URL VARCHAR(200),
    Details VARCHAR(4000) 
);

CREATE TABLE ##BlitzCacheProcs (
        SPID INT ,
        QueryType NVARCHAR(258),
        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,
		LastCompletionTime 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,
		PlanGenerationNum BIGINT,
        MinReturnedRows BIGINT,
        MaxReturnedRows BIGINT,
        AverageReturnedRows MONEY,
        TotalReturnedRows BIGINT,
        LastReturnedRows BIGINT,
		/*The Memory Grant columns are only supported 
		  in certain versions, giggle giggle.
		*/
		MinGrantKB BIGINT,
		MaxGrantKB BIGINT,
		MinUsedGrantKB BIGINT, 
		MaxUsedGrantKB BIGINT,
		PercentMemoryGrantUsed MONEY,
		AvgMaxMemoryGrant MONEY,
		MinSpills BIGINT,
		MaxSpills BIGINT,
		TotalSpills BIGINT,
		AvgSpills 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 DECIMAL(30),
        TotalExecutionCountForType BIGINT,
        TotalWritesForType DECIMAL(30),
        NumberOfPlans INT,
        NumberOfDistinctPlans INT,
        SerialDesiredMemory FLOAT,
        SerialRequiredMemory FLOAT,
        CachedPlanSize FLOAT,
        CompileTime FLOAT,
        CompileCPU FLOAT ,
        CompileMemory FLOAT ,
		MaxCompileMemory 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_fast_forward_cursor BIT,
		is_cursor_dynamic 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 INT,
        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,
		is_spatial BIT,
		index_dml BIT,
		table_dml BIT,
		long_running_low_cpu BIT,
		low_cost_high_cpu BIT,
		stale_stats BIT, 
		is_adaptive BIT,
		index_spool_cost FLOAT,
		index_spool_rows FLOAT,
		table_spool_cost FLOAT,
		table_spool_rows FLOAT,
		is_spool_expensive BIT,
		is_spool_more_rows BIT,
		is_table_spool_expensive BIT,
		is_table_spool_more_rows BIT,
		estimated_rows FLOAT,
		is_bad_estimate BIT, 
		is_paul_white_electric BIT,
		is_row_goal BIT,
		is_big_spills BIT,
		is_mstvf BIT,
		is_mm_join BIT,
        is_nonsargable BIT,
		select_with_writes BIT,
		implicit_conversion_info XML,
		cached_execution_parameters XML,
		missing_indexes XML,
        SetOptions VARCHAR(MAX),
        Warnings VARCHAR(MAX),
    	Pattern NVARCHAR(20),
        ai_prompt NVARCHAR(MAX),
        ai_advice NVARCHAR(MAX),
        ai_payload NVARCHAR(MAX),
        ai_raw_response NVARCHAR(MAX)
    );
GO 

ALTER PROCEDURE dbo.sp_BlitzCache
    @Help BIT = 0,
    @Top INT = NULL,
    @SortOrder VARCHAR(50) = 'CPU',
    @UseTriggersAnyway BIT = NULL,
    @ExportToExcel BIT = 0,
    @ExpertMode TINYINT = 0,
	@OutputType VARCHAR(20) = 'TABLE' ,
    @OutputServerName NVARCHAR(258) = NULL ,
    @OutputDatabaseName NVARCHAR(258) = NULL ,
    @OutputSchemaName NVARCHAR(258) = NULL ,
    @OutputTableName NVARCHAR(258) = NULL , -- do NOT use ##BlitzCacheResults or ##BlitzCacheProcs as they are used as work tables in this procedure
    @ConfigurationDatabaseName NVARCHAR(128) = NULL ,
    @ConfigurationSchemaName NVARCHAR(258) = NULL ,
    @ConfigurationTableName NVARCHAR(258) = NULL ,
    @DurationFilter DECIMAL(38,4) = NULL ,
    @HideSummary BIT = 0 ,
    @IgnoreSystemDBs BIT = 1 ,
    @IgnoreReadableReplicaDBs 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,
	@SlowlySearchPlansFor NVARCHAR(4000) = NULL,
    @Reanalyze BIT = 0 ,
    @SkipAnalysis BIT = 0 ,
    @BringThePain BIT = 0 ,
    @MinimumExecutionCount INT = 0,
	@Debug TINYINT = 0, /* 0 = no debugging info, 1 = normal debugging info, 2 = AI debugging info */
	@CheckDateOverride DATETIMEOFFSET = NULL,
	@MinutesBack INT = NULL,
    @AI TINYINT = 0, /* 1 = ask for advice, 2 = build prompt but don't actually call AI. Only works with a single query plan: automatically sets @ExpertMode = 1, @KeepCRLF = 1. */
    @AIModel VARCHAR(200) = NULL, /* Defaults to gpt-4.1-mini */
    @AIURL VARCHAR(200) = NULL, /* Defaults to https://api.openai.com/v1/chat/completions */
    @AICredential VARCHAR(200) = NULL, /* Defaults to 'https://api.openai.com/' or the root of your AIURL, trailing slash included */
    @AIConfig NVARCHAR(500) = NULL, /* Table where AI config data is stored - can be in the format db.schema.table, schema.table, or just table. */
	@Version     VARCHAR(30) = NULL OUTPUT,
	@VersionDate DATETIME = NULL OUTPUT,
	@VersionCheckMode BIT = 0,
	@KeepCRLF BIT = 0
WITH RECOMPILE
AS
BEGIN
SET NOCOUNT ON;
SET STATISTICS XML OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT @Version = '8.29', @VersionDate = '20260203';
SET @OutputType = UPPER(@OutputType);

IF(@VersionCheckMode = 1)
BEGIN
	RETURN;
END;

DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ;
	
IF @Help = 1 
	BEGIN
	PRINT '
	sp_BlitzCache from http://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 http://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:
	 - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes
	   with no spaces between the hash values.

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



	MIT License

	Copyright (c) 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.
	';

	
	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", "Unused Grant", "Spills", "Query Hash", "Duplicate". 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'@OutputType',
			N'NVARCHAR(258)',
			N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.'
			
	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(258)',
			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(258)',
			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, dbmaintenance, dbadmin, dbatools 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'@IgnoreSqlHandles',
			N'VARCHAR(MAX)',
			N'One or more sql_handles to ignore.'

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

	UNION ALL
	SELECT N'@StoredProcName',
			N'NVARCHAR(128)',
			N'Name of stored procedure you want to find plans for.'

	UNION ALL
	SELECT N'@SlowlySearchPlansFor',
			N'NVARCHAR(4000)',
			N'String to search for in plan text. % wildcards allowed.'

	UNION ALL
	SELECT N'@BringThePain',
			N'BIT',
			N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.'

	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'', ''functions'', 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.'
    
	UNION ALL
	SELECT N'@Debug',
			N'BIT',
			N'Setting this to 1 will print dynamic SQL and select data from all tables used. Setting to 2 will debug AI calls.'

	UNION ALL
	SELECT N'@MinutesBack',
			N'INT',
			N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'

	UNION ALL
	SELECT N'@AI',
			N'TINYINT',
			N'1 = ask for advice. Only works with a single query plan for now. Automatically sets @ExpertMode = 1, @KeepCRLF = 1.'

	UNION ALL
	SELECT N'@AIModel',
			N'VARCHAR(200)',
			N'Defaults to gpt-4.1-mini. Can accept other models, or if you have a dbo.AI_Services table, we will look up services there.'

	UNION ALL
	SELECT N'@AIURL',
			N'VARCHAR(200)',
			N'Defaults to https://api.openai.com/v1/chat/completions. Can accept other URLs, or if you have a dbo.AI_Services table, we will look up services there.'

	UNION ALL
	SELECT N'@AICredential',
			N'VARCHAR(200)',
			N'The database scoped credential that you configured. Defaults to https://api.openai.com/ or the root URL of your @AIURL, trailing slash included. Must be a subset of the @AIURL parameter.'

	UNION ALL
	SELECT N'@Version',
			N'VARCHAR(30)',
			N'OUTPUT parameter holding version number.'
	
	UNION ALL
	SELECT N'@VersionDate',
			N'DATETIME',
			N'OUTPUT parameter holding version date.'

	UNION ALL
	SELECT N'@VersionCheckMode',
			N'BIT',
			N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'

	UNION ALL
	SELECT N'@KeepCRLF',
			N'BIT',
			N'Retain CR/LF in query text to avoid issues caused by line comments.';


	/* 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(258)',
			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'MinSpills',
			N'BIGINT',
			N'The minimum amount this query has spilled to tempdb in 8k pages.'

	UNION ALL
	SELECT N'MaxSpills',
			N'BIGINT',
			N'The maximum amount this query has spilled to tempdb in 8k pages.'

	UNION ALL
	SELECT N'TotalSpills',
			N'BIGINT',
			N'The total amount this query has spilled to tempdb in 8k pages.'

	UNION ALL
	SELECT N'AvgSpills',
			N'BIGINT',
			N'The average amount this query has spilled to tempdb in 8k pages.'

	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.'

	UNION ALL
	SELECT N'AI Advice',
			N'NVARCHAR(MAX)',
			N'If called with @AI parameters, the results from your AI provider.' ;


           
	/* 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.'

	UNION ALL
	SELECT N'AIModel Default' AS [Configuration Parameter] ,
			N'1' ,
			N'URL' ,
			N'Default provider is https://api.openai.com/v1/chat/completions. Override goes in the value_varchar_1, override system prompt in value_varchar_2. We use the lowest value column first.'

	RETURN;
	END; /* IF @Help = 1  */



/*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;

IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL))
BEGIN
    RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1);
    RETURN;
END;

IF(@OutputType = 'NONE') 
BEGIN
    SET @HideSummary = 1;
END;

/* Lets get @SortOrder set to lower case here for comparisons later */
SET @SortOrder = LOWER(@SortOrder);

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

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


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

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

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

CREATE TABLE #ai_configuration
(Id INT PRIMARY KEY CLUSTERED,
 AI_Model NVARCHAR(100) INDEX AI_Model,
 AI_URL NVARCHAR(500),
 AI_Database_Scoped_Credential_Name NVARCHAR(500),
 AI_System_Prompt_Override NVARCHAR(4000),
 AI_Parameters NVARCHAR(4000),
 Payload_Template_Override NVARCHAR(4000),
 Timeout_Seconds TINYINT,
 Context INT,
 DefaultModel BIT DEFAULT 0);

DECLARE
    @AIConfigDatabaseName NVARCHAR(128) = CASE WHEN @AIConfig IS NULL THEN NULL ELSE PARSENAME(@AIConfig, 3) END,
    @AIConfigSchemaName NVARCHAR(258) = CASE WHEN @AIConfig IS NULL THEN NULL ELSE PARSENAME(@AIConfig, 2) END,
    @AIConfigTableName NVARCHAR(258) = CASE WHEN @AIConfig IS NULL THEN NULL ELSE PARSENAME(@AIConfig, 1) END,
    @AISystemPrompt NVARCHAR(4000),
    @AIParameters NVARCHAR(4000),
    @AIPayloadTemplate NVARCHAR(MAX),
    @AITimeoutSeconds TINYINT,
    @AIAdviceText NVARCHAR(MAX),
    @AIContext INT;


IF @AIConfig IS NOT NULL
BEGIN
   RAISERROR(N'Reading values from AI Configuration Table', 0, 1) WITH NOWAIT;
   SET @config_sql = N'INSERT INTO #ai_configuration (Id, AI_Model, AI_URL, AI_Database_Scoped_Credential_Name, AI_System_Prompt_Override, AI_Parameters, Payload_Template_Override, Timeout_Seconds, Context, DefaultModel)
        SELECT Id, AI_Model, AI_URL, AI_Database_Scoped_Credential_Name, AI_System_Prompt_Override, AI_Parameters, Payload_Template_Override, Timeout_Seconds, Context, DefaultModel FROM '
        + CASE WHEN @AIConfigDatabaseName IS NOT NULL THEN (QUOTENAME(@AIConfigDatabaseName) + N'.') ELSE N'' END
        + CASE WHEN @AIConfigSchemaName IS NOT NULL THEN (QUOTENAME(@AIConfigSchemaName) + N'.') ELSE N'' END
        + QUOTENAME(@AIConfigTableName) + N' WHERE (@AIModel IS NULL AND DefaultModel = 1) OR @AIModel IN (AI_Model, Nickname) ; ';
   EXEC sp_executesql @config_sql, N'@AIModel NVARCHAR(100)', @AIModel;
END;


IF @AI > 0
    BEGIN
    RAISERROR(N'Setting up AI configuration defaults', 0, 1) WITH NOWAIT;

    SELECT @ExpertMode = 1, @KeepCRLF = 1;

    IF @Debug = 2
        SELECT N'ai_configuration' AS TableLabel, * FROM #ai_configuration;

    IF @AI = 1 AND NOT EXISTS(SELECT * FROM sys.all_objects WHERE name = 'sp_invoke_external_rest_endpoint')
        BEGIN
        /* If someone was ambitious and they wanted to code a drop-in replacement for that stored proc,
        and use it in earlier versions of SQL Server, they could, and just use the same name and
        parameters, and we'd use it. I'm not coding support for different proc names. */
        SET @AI = 2
        RAISERROR(N'@AI was set to 1, but sp_invoke_external_rest_endpoint does not exist here, so we can''t call AI services. Setting @AI to 2 instead to just generate prompts.', 0, 1) WITH NOWAIT;
        END

    IF @AIModel IS NULL
        /* Check the config table */
        SELECT TOP 1 @AIModel = AI_Model, @AIURL = AI_URL,
            @AICredential = AI_Database_Scoped_Credential_Name,
            @AISystemPrompt = AI_System_Prompt_Override,
            @AIParameters = AI_Parameters,
            @AITimeoutSeconds = COALESCE(Timeout_Seconds, 230),
            @AIContext = Context,
            @AIPayloadTemplate = Payload_Template_Override
            FROM #ai_configuration
            WHERE DefaultModel = 1
            ORDER BY Id;
    ELSE
        SELECT TOP 1 @AIModel = AI_Model, 
            @AIURL = COALESCE(@AIURL, AI_URL),
            @AICredential = COALESCE(@AICredential, AI_Database_Scoped_Credential_Name),
            @AISystemPrompt = AI_System_Prompt_Override,
            @AIParameters = AI_Parameters,
            @AITimeoutSeconds = COALESCE(Timeout_Seconds, 230),
            @AIContext = Context,
            @AIPayloadTemplate = Payload_Template_Override
            FROM #ai_configuration
            ORDER BY Id;
        
    IF @AIModel IS NULL
        SET @AIModel = N'gpt-5-nano';
    
    IF @AIURL IS NULL OR @AIURL NOT LIKE N'http%'
        SET @AIURL = CASE 
            WHEN @AIModel LIKE 'gemini%' THEN N'https://generativelanguage.googleapis.com/v1beta/models/' + @AIModel + N':generateContent'
            ELSE N'https://api.openai.com/v1/chat/completions' /* Default to ChatGPT */
            END;

    /* Try to guess the credential based on the root of their URL: */
    IF @AICredential IS NULL
        SET @AICredential = LEFT(@AIURL, CHARINDEX('/', @AIURL, CHARINDEX('://', @AIURL) + 3));

    IF @AITimeoutSeconds IS NULL OR @AITimeoutSeconds < 1 OR @AITimeoutSeconds > 230
        SET @AITimeoutSeconds = 230;

    IF @AISystemPrompt IS NULL OR @AISystemPrompt = N''
        SET @AISystemPrompt = N'You are a very senior database developer working with Microsoft SQL Server and Azure SQL DB. You focus on real-world, actionable advice that will make a big difference, quickly. You value everyone''s time, and while you are friendly and courteous, you do not waste time with pleasantries or emoji because you work in a fast-paced corporate environment.

    You have a query that isn''t performing to end user expectations. You have been tasked with making serious improvements to it, quickly. You are not allowed to change server-level settings or make frivolous suggestions like updating statistics. Instead, you need to focus on query changes or index changes. 
    
    Do not offer followup options: the customer can only contact you once, so include all necessary information, tasks, and scripts in your initial reply. Render your output in Markdown, as it will be shown in plain text to the customer.';

    IF @AIModel LIKE 'gemini%' AND @AIPayloadTemplate IS NULL
        SET @AIPayloadTemplate = N'{
          "contents": [
            {
              "parts": [
                {"text": "@AISystemPrompt @CurrentAIPrompt"}
              ]
            }
          ]
        }';
    ELSE IF @AIPayloadTemplate IS NULL /* Default to ChatGPT format */
        SET @AIPayloadTemplate = N'{
                    "model": "@AIModel",
                    "messages": [
                        {
                            "role": "system",
                            "content": "@AISystemPrompt"
                        },
                        {
                            "role": "user",
                            "content": "@CurrentAIPrompt"
                        }
                    ]
                }';

    IF @Debug = 2 OR (@AI = 1 AND (@AIModel IS NULL OR @AIURL IS NULL OR @AISystemPrompt IS NULL OR @AICredential IS NULL OR @AIPayloadTemplate IS NULL))
        BEGIN
            SELECT @AIModel AS AIModel, @AIURL AS AIUrl, @AICredential AS AICredential,
                @AIContext AS AIContext, @AIParameters AS AIParameters, @AITimeoutSeconds AS AITimeoutSeconds,
                @AISystemPrompt AS AISystemPrompt, @AIPayloadTemplate AS AIPayloadTemplate;
        END;

    IF @AI = 1 AND (@AIModel IS NULL OR @AIURL IS NULL OR @AISystemPrompt IS NULL OR @AICredential IS NULL OR @AIPayloadTemplate IS NULL)
        BEGIN
            RAISERROR('@AI is set to 1, but not all of the necessary configuration is included.',12,1);
            RETURN;
        END;

    END


/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */
IF @SortOrder LIKE 'query hash%'
	BEGIN
	RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT;

    SELECT TOP(@Top) qs.query_hash, 
           MAX(qs.max_worker_time) AS max_worker_time,
           COUNT_BIG(*) AS records
    INTO #query_hash_grouped
    FROM sys.dm_exec_query_stats AS qs
    CROSS APPLY (   SELECT pa.value
                    FROM   sys.dm_exec_plan_attributes(qs.plan_handle) AS pa
                    WHERE  pa.attribute = 'dbid' ) AS ca
    GROUP BY qs.query_hash, ca.value
    HAVING COUNT_BIG(*) > 1
    ORDER BY max_worker_time DESC,
             records DESC;
    
    SELECT TOP (1)
	         @OnlyQueryHashes = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.query_hash, 1) 
    FROM #query_hash_grouped AS qhg 
    WHERE qhg.query_hash <> 0x00
    FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'')
	OPTION(RECOMPILE);

	/* When they ran it, @SortOrder probably looked like 'query hash, cpu', so strip the first sort order out: */
    SELECT @SortOrder = LTRIM(REPLACE(REPLACE(@SortOrder,'query hash', ''), ',', ''));
	
	/* If they just called it with @SortOrder = 'query hash', set it to 'cpu' for backwards compatibility: */
	IF @SortOrder = '' SET @SortOrder = 'cpu';

	END


/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */
IF @SortOrder LIKE 'duplicate%'
	BEGIN
	RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT;

	/* Find the query hashes that are the most duplicated */
	WITH MostCommonQueries AS (
		SELECT TOP(@Top) qs.query_hash, 
			   COUNT_BIG(*) AS plans
		FROM sys.dm_exec_query_stats AS qs
		GROUP BY qs.query_hash
		HAVING COUNT_BIG(*) > 100
		ORDER BY COUNT_BIG(*) DESC
	)
	SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans
	INTO #duplicate_query_filter
	FROM MostCommonQueries mcq
    CROSS APPLY (   SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time
                    FROM   sys.dm_exec_query_stats qs
                    WHERE  qs.query_hash = mcq.query_hash 
					ORDER BY qs.creation_time DESC) AS mcq_recent
	OPTION (RECOMPILE);

	SET @MinimumExecutionCount = 0;
	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;

IF @MinutesBack IS NOT NULL
    BEGIN
        RAISERROR(N'Checking @MinutesBack validity.', 0, 1) WITH NOWAIT;
        IF @MinutesBack > 0
            BEGIN
                RAISERROR(N'Setting @MinutesBack to a negative number', 0, 1) WITH NOWAIT;
				SET @MinutesBack *=-1;
            END;
        IF @MinutesBack = 0
            BEGIN
                RAISERROR(N'@MinutesBack can''t be 0, setting to -1', 0, 1) WITH NOWAIT;
				SET @MinutesBack = -1;
            END;
    END;



DECLARE @DurationFilter_i INT,
		@MinMemoryPerQuery INT,
        @msg NVARCHAR(4000),
		@NoobSaibot BIT = 0,
		@VersionShowsAirQuoteActualPlans BIT,
        @ObjectFullName NVARCHAR(2000),
        @user_perm_sql NVARCHAR(MAX) = N'',
        @user_perm_gb_out DECIMAL(10,2),
        @common_version DECIMAL(10,2),
        @buffer_pool_memory_gb DECIMAL(10,2),
        @user_perm_percent DECIMAL(10,2),
        @is_tokenstore_big BIT = 0,
        @sort NVARCHAR(MAX) = N'',
		@sort_filter NVARCHAR(MAX) = N'',
        @AIPayload NVARCHAR(MAX),
        @AIResponse NVARCHAR(MAX);


IF @SortOrder = 'sp_BlitzIndex'
BEGIN
	RAISERROR(N'Called for sp_BlitzIndex', 0, 1) WITH NOWAIT;
	SET @SortOrder = 'reads';
	SET @NoobSaibot = 1;

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 SERVERPROPERTY('EngineEdition') IN (5, 6) AND DB_NAME() <> @DatabaseName
BEGIN
   RAISERROR('You specified a database name other than the current database, but Azure SQL DB does not allow you to change databases. Execute sp_BlitzCache from the database you want to analyze.', 16, 1);
   RETURN;
END;
IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> N''
BEGIN
   RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1);
   RETURN;
END;
IF (SELECT DATABASEPROPERTYEX(ISNULL(@DatabaseName, 'master'), 'Collation')) IS NULL AND SERVERPROPERTY('EngineEdition') NOT IN (5, 6, 8)
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 = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', '');

SET @SortOrder = CASE 
                     WHEN @SortOrder IN ('executions per minute','execution per minute','executions / minute','execution / minute','xpm') THEN 'avg executions'
                     WHEN @SortOrder IN ('recent compilations','recent compilation','compile') THEN 'compiles'
                     WHEN @SortOrder IN ('read') THEN 'reads'
                     WHEN @SortOrder IN ('avg read') THEN 'avg reads'
                     WHEN @SortOrder IN ('write') THEN 'writes'
                     WHEN @SortOrder IN ('avg write') THEN 'avg writes'
                     WHEN @SortOrder IN ('memory grants') THEN 'memory grant'
                     WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant'
                     WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant'
                     WHEN @SortOrder IN ('spill') THEN 'spills'
                     WHEN @SortOrder IN ('avg spill') THEN 'avg spills'
                     WHEN @SortOrder IN ('execution') THEN 'executions'
                     WHEN @SortOrder IN ('duplicates') THEN 'duplicate'
                 ELSE @SortOrder END							  
							  
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', 'unused grant',
					   'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex',
					   'query hash', 'duplicate')
  BEGIN
  RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT;
  SET @SortOrder = 'cpu';
  END; 

SET @QueryFilter = LOWER(@QueryFilter);

IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro', 'fun')
  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; 

DECLARE @AllSortSql NVARCHAR(MAX) = N'';
DECLARE @VersionShowsMemoryGrants BIT;
IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')
    SET @VersionShowsMemoryGrants = 1;
ELSE
    SET @VersionShowsMemoryGrants = 0;

DECLARE @VersionShowsSpills BIT;
IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')
    SET @VersionShowsSpills = 1;
ELSE
    SET @VersionShowsSpills = 0;

IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan')
    SET @VersionShowsAirQuoteActualPlans = 1;
ELSE
    SET @VersionShowsAirQuoteActualPlans = 0;

IF @Reanalyze = 1 
  BEGIN
  IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL
    BEGIN
    RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT;
    SET @Reanalyze = 0;
	END
  ELSE
	BEGIN
	RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT;
    GOTO Results;
	END;
  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..#stored_proc_info') IS NOT NULL
    DROP TABLE #stored_proc_info;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 #plan_creation
(
    percent_24 DECIMAL(5, 2),
    percent_4 DECIMAL(5, 2),
    percent_1 DECIMAL(5, 2),
	total_plans INT,
    SPID INT
);

CREATE TABLE #est_rows
(
    QueryHash BINARY(8),
    estimated_rows FLOAT
);

CREATE TABLE #plan_cost
(
    QueryPlanCost FLOAT,
    SqlHandle VARBINARY(64),
	PlanHandle VARBINARY(64),
    QueryHash BINARY(8),
    QueryPlanHash BINARY(8)
);

CREATE TABLE #proc_costs
(
    PlanTotalQuery FLOAT,
    PlanHandle VARBINARY(64),
    SqlHandle VARBINARY(64)
);

CREATE TABLE #stats_agg
(
    SqlHandle VARBINARY(64),
	LastUpdate DATETIME2(7),
    ModificationCount BIGINT,
    SamplingPercent FLOAT,
    [Statistics] NVARCHAR(258),
    [Table] NVARCHAR(258),
    [Schema] NVARCHAR(258),
    [Database] NVARCHAR(258),
);

CREATE TABLE #trace_flags
(
    SqlHandle VARBINARY(64),
    QueryHash BINARY(8),
    global_trace_flags VARCHAR(1000),
    session_trace_flags VARCHAR(1000)
);

CREATE TABLE #stored_proc_info
(
    SPID INT,
	SqlHandle VARBINARY(64),
    QueryHash BINARY(8),
    variable_name NVARCHAR(258),
    variable_datatype NVARCHAR(258),
	converted_column_name NVARCHAR(258),
    compile_time_value NVARCHAR(258),
    proc_name NVARCHAR(1000),
    column_name NVARCHAR(4000),
    converted_to NVARCHAR(258),
	set_options NVARCHAR(1000)
);

CREATE TABLE #variable_info
(
    SPID INT,
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    proc_name NVARCHAR(1000),
    variable_name NVARCHAR(258),
    variable_datatype NVARCHAR(258),
    compile_time_value NVARCHAR(258)
);

CREATE TABLE #conversion_info
(
    SPID INT,
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    proc_name NVARCHAR(258),
    expression NVARCHAR(4000),
    at_charindex AS CHARINDEX('@', expression),
    bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression),
    comma_charindex AS CHARINDEX(',', expression) + 1,
    second_comma_charindex AS
        CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1,
    equal_charindex AS CHARINDEX('=', expression) + 1,
    paren_charindex AS CHARINDEX('(', expression) + 1,
    comma_paren_charindex AS
        CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1,
    convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression)
);


CREATE TABLE #missing_index_xml
(
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    impact FLOAT,
    index_xml XML
);


CREATE TABLE #missing_index_schema
(
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    impact FLOAT,
    database_name NVARCHAR(128),
    schema_name NVARCHAR(128),
    table_name NVARCHAR(128),
    index_xml XML
);


CREATE TABLE #missing_index_usage
(
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    impact FLOAT,
    database_name NVARCHAR(128),
    schema_name NVARCHAR(128),
    table_name NVARCHAR(128),
	usage NVARCHAR(128),
    index_xml XML
);


CREATE TABLE #missing_index_detail
(
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    impact FLOAT,
    database_name NVARCHAR(128),
    schema_name NVARCHAR(128),
    table_name NVARCHAR(128),
    usage NVARCHAR(128),
    column_name NVARCHAR(128)
);


CREATE TABLE #missing_index_pretty
(
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    impact FLOAT,
    database_name NVARCHAR(128),
    schema_name NVARCHAR(128),
    table_name NVARCHAR(128),
	equality NVARCHAR(MAX),
	inequality NVARCHAR(MAX),
	[include] NVARCHAR(MAX),
	executions NVARCHAR(128),
	query_cost NVARCHAR(128),
	creation_hours NVARCHAR(128),
	is_spool BIT,
	details AS N'/* '
	           + CHAR(10) 
			   + CASE is_spool 
			          WHEN 0 
					  THEN N'The Query Processor estimates that implementing the '
					  ELSE N'We estimate that implementing the '
				 END 
			   + N'following index could improve query cost (' + query_cost + N')'
			   + CHAR(10) 
			   + N'by '
			   + CONVERT(NVARCHAR(30), impact)
			   + N'% for ' + executions + N' executions of the query'
			   + N' over the last ' + 
					CASE WHEN creation_hours < 24
					     THEN creation_hours + N' hours.'
						 WHEN creation_hours = 24
						 THEN ' 1 day.'
						 WHEN creation_hours > 24
						 THEN (CONVERT(NVARCHAR(128), creation_hours / 24)) + N' days.'
					     ELSE N''
					END
			   + CHAR(10)
			   + N'*/'
			   + CHAR(10) + CHAR(13) 
			   + N'/* '
			   + CHAR(10)
			   + N'USE '
			   + database_name
			   + CHAR(10)
			   + N'GO'
			   + CHAR(10) + CHAR(13)
			   + N'CREATE NONCLUSTERED INDEX ix_'
			   + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''),   ', ', '_'), '')
			   + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '')
			   + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END 
			   + CHAR(10)
			   + N' ON '
			   + schema_name
			   + N'.'
			   + table_name
			   + N' (' + 
			   + CASE WHEN equality IS NOT NULL 
					  THEN equality
						+ CASE WHEN inequality IS NOT NULL
							   THEN N', ' + inequality
							   ELSE N''
						  END
					 ELSE inequality
				 END			   
			   + N')' 
			   + CHAR(10)
			   + CASE WHEN include IS NOT NULL
					  THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);'
					  ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);'
				 END
			   + CHAR(10)
			   + N'GO'
			   + CHAR(10)
			   + N'*/'
);


CREATE TABLE #index_spool_ugly
(
    QueryHash BINARY(8),
    SqlHandle VARBINARY(64),
    impact FLOAT,
    database_name NVARCHAR(128),
    schema_name NVARCHAR(128),
    table_name NVARCHAR(128),
	equality NVARCHAR(MAX),
	inequality NVARCHAR(MAX),
	[include] NVARCHAR(MAX),
	executions NVARCHAR(128),
	query_cost NVARCHAR(128),
	creation_hours NVARCHAR(128)
);


CREATE TABLE #ReadableDBs 
(
database_id INT
);


CREATE TABLE #plan_usage
(
    duplicate_plan_hashes BIGINT NULL,
    percent_duplicate DECIMAL(9, 2) NULL,
    single_use_plan_count BIGINT NULL,
    percent_single DECIMAL(9, 2) NULL,
    total_plans BIGINT NULL,
	spid INT
);


IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states')
BEGIN
	RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT;

    EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);');
    EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;');		-- Exclude internal resource database as well
END

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],
	   SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 1 THEN 1 ELSE 0 END) AS [plans_1],
	   COUNT(deqs.creation_time) AS [total_plans]
FROM sys.dm_exec_query_stats AS deqs
)
INSERT INTO #plan_creation ( percent_24, percent_4, percent_1, total_plans, SPID )
SELECT CONVERT(DECIMAL(5,2), NULLIF(x.plans_24, 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24],
	   CONVERT(DECIMAL(5,2), NULLIF(x.plans_4 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4],
	   CONVERT(DECIMAL(5,2), NULLIF(x.plans_1 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_1],
	   x.total_plans,
	   @@SPID AS SPID
FROM x
OPTION (RECOMPILE);


RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT;
WITH total_plans AS 
(
    SELECT
	    COUNT_BIG(deqs.query_plan_hash) AS total_plans
    FROM sys.dm_exec_query_stats AS deqs
),
     many_plans AS 
(
    SELECT
	    SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes
    FROM
	(
        SELECT
		    COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes
        FROM sys.dm_exec_query_stats qs
        LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle
        CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa
        WHERE pa.attribute = N'dbid'
		AND   pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/
        AND   qs.query_plan_hash <> 0x0000000000000000
        GROUP BY
		    /* qs.query_plan_hash,  BGO 20210524 commenting this out to fix #2909 */
            qs.query_hash,
			ps.object_id,
            pa.value
        HAVING COUNT_BIG(qs.query_plan_hash) > 5
    ) AS x
),
     single_use_plans AS 
(
    SELECT
	    COUNT_BIG(*) AS single_use_plan_count
    FROM sys.dm_exec_query_stats AS s
    WHERE s.execution_count = 1
)
INSERT
    #plan_usage
(
    duplicate_plan_hashes,
	percent_duplicate,
	single_use_plan_count,
	percent_single,
	total_plans,
	spid
)
SELECT
    m.duplicate_plan_hashes, 
    CONVERT
	(
	    decimal(5,2),
		m.duplicate_plan_hashes
		    / (1. * NULLIF(t.total_plans, 0))
    ) * 100. AS percent_duplicate,
    s.single_use_plan_count, 
    CONVERT
	(
	    decimal(5,2),
		s.single_use_plan_count
		    / (1. * NULLIF(t.total_plans, 0))
	) * 100. AS percent_single,
    t.total_plans,
	@@SPID
FROM many_plans AS m
CROSS JOIN single_use_plans AS s 
CROSS JOIN total_plans AS t;


/*
Erik Darling:
   Quoting this out to see if the above query fixes the issue
   2021-05-17, Issue #2909

UPDATE #plan_usage
	SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END,
	percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END;
*/

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)
			   OPTION (RECOMPILE) ;
               
               --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)
			   OPTION (RECOMPILE) ;

               --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)
			   OPTION (RECOMPILE) ;
               
               --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)
			   OPTION (RECOMPILE) ;

               --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;
	
    DECLARE @function_search_sql NVARCHAR(MAX) = N''
    
    INSERT #only_sql_handles
	        ( sql_handle )
	SELECT  ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'))
	FROM sys.dm_exec_procedure_stats AS deps
	WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName

    UNION ALL
    
    SELECT  ISNULL(dets.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'))
	FROM sys.dm_exec_trigger_stats AS dets
	WHERE OBJECT_NAME(dets.object_id, dets.database_id) = @StoredProcName
	OPTION (RECOMPILE);

    IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats')
        BEGIN
         SET @function_search_sql = @function_search_sql + N'
         SELECT  ISNULL(defs.sql_handle, CONVERT(VARBINARY(64),''0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000''))
	     FROM sys.dm_exec_function_stats AS defs
	     WHERE OBJECT_NAME(defs.object_id, defs.database_id) = @i_StoredProcName
         OPTION (RECOMPILE);
         '
        INSERT #only_sql_handles ( sql_handle )
        EXEC sys.sp_executesql @function_search_sql, N'@i_StoredProcName NVARCHAR(128)', @StoredProcName
       END
		
        IF (SELECT COUNT(*) FROM #only_sql_handles) = 0
			BEGIN
			RAISERROR(N'No information for that stored procedure was found.', 0, 1) WITH NOWAIT;
			RETURN;
			END;

END;



IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0)
    OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0))
   AND LEFT(@QueryFilter, 3) IN ('pro', 'fun')
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)
			   OPTION (RECOMPILE) ;
               
               --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)
			   OPTION (RECOMPILE) ;

               --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) 
			   OPTION (RECOMPILE) ;
               
               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) 
			   OPTION (RECOMPILE) ;
        END;
   END;
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 @VersionShowsMemoryGrants = 0
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;

IF (@SortOrder IN ('spills', 'avg spills') AND @VersionShowsSpills = 0)
BEGIN
   RAISERROR('Your version of SQL does not support sorting by spills. Please use another sort order.', 16, 1);
   RETURN;
END;

IF ((LEFT(@QueryFilter, 3) = 'fun') AND (@v < 13))
BEGIN
   RAISERROR('Your version of SQL does not support filtering by functions. Please use another filter.', 16, 1);
   RETURN;
END;

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

SET @insert_list += N'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType,
                    PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount,
                    ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime,
                    LastExecutionTime, LastCompletionTime, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows,
                    LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, 
					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, Pattern) ' ;

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 ;

IF @SortOrder = 'duplicate'	/* Issue #3345 */
    BEGIN
    SET @body += N'     INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ;
    END

IF @VersionShowsAirQuoteActualPlans = 1
    BEGIN
    SET @body += N'     CROSS APPLY sys.dm_exec_query_plan_stats(x.plan_handle) AS deqps ' + @nl ;
    END

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

	IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states')
    BEGIN
    RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT;
    SET @body += N'               AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ;
    END

IF @IgnoreSystemDBs = 1
    BEGIN
	RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT;
	SET @body += N'               AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') 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 <> N''
	BEGIN 
    RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT;
	SET @body += N'               AND CAST(xpa.value AS BIGINT) = DB_ID(N'
                 + 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; 

IF @MinutesBack IS NOT NULL
	BEGIN
	RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT;
	SET @body += N'       AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ;
	END;

IF @SlowlySearchPlansFor IS NOT NULL
    BEGIN
    RAISERROR(N'Setting string search for @SlowlySearchPlansFor, so remember, this is gonna be slow', 0, 1) WITH NOWAIT;
    SET @SlowlySearchPlansFor = REPLACE((REPLACE((REPLACE((REPLACE(@SlowlySearchPlansFor, N'[', N'_')), N']', N'_')), N'^', N'_')), N'''', N'''''');
    SET @body_where += N'       AND CAST(qp.query_plan AS NVARCHAR(MAX)) LIKE N''%' + @SlowlySearchPlansFor + N'%'' ' + @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'
								 WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb'
								 WHEN N'spills' THEN N'max_spills'
								 WHEN N'duplicate' THEN N'total_worker_time'	/* Issue #3345 */
                                 /* 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 spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / 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 DECIMAL(30))) AS t_TotalReads,
                         SUM(CAST(total_logical_writes AS DECIMAL(30))) 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 ;

IF @VersionShowsAirQuoteActualPlans = 1
    BEGIN
    SET @body += N'     CROSS APPLY sys.dm_exec_query_plan_stats(qs.plan_handle) AS deqps ' + @nl ;
    END

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

IF @NoobSaibot = 1
BEGIN
	SET @body_where += N'       AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ;
END

SET @plans_triggers_select_list += N'
SELECT TOP (@Top)
       @@SPID ,
       ''Procedure or Function: '' 
	   + QUOTENAME(COALESCE(OBJECT_SCHEMA_NAME(qs.object_id, qs.database_id),''''))
	   + ''.''
	   + QUOTENAME(COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''')) AS QueryType,
       COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), N''-- 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,
	   DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime,
       NULL AS StatementStartOffset,
       NULL AS StatementEndOffset,
	   NULL AS PlanGenerationNum, 
       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,';

    IF @VersionShowsSpills = 1
    BEGIN
        RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT;
		SET @plans_triggers_select_list += N'
           min_spills AS MinSpills,
           max_spills AS MaxSpills,
           total_spills AS TotalSpills,
		   CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ';
    END;
    ELSE
    BEGIN
        RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT;
		SET @plans_triggers_select_list += N'
           NULL AS MinSpills,
           NULL AS MaxSpills,
           NULL AS TotalSpills, 
		   NULL AS AvgSpills, ' ;
    END;		       
	     
	SET @plans_triggers_select_list +=  
	 N'st.text AS QueryText ,';

    IF @VersionShowsAirQuoteActualPlans = 1
        BEGIN
        SET @plans_triggers_select_list += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ;
        END;
    ELSE   
        BEGIN
        SET @plans_triggers_select_list += N' qp.query_plan AS QueryPlan, ' + @nl ;
        END;

	SET @plans_triggers_select_list +=  
    N'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="http://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,
       @SortOrder ';


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''-- 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 ,
           CASE WHEN t.t_TotalWorker = 0 THEN 0
                ELSE CAST(ROUND(100.00 * total_worker_time / 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 / 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,
           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,
		   DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime,
           qs.statement_start_offset AS StatementStartOffset,
           qs.statement_end_offset AS StatementEndOffset,
		   qs.plan_generation_num AS PlanGenerationNum, ';
    
    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 @VersionShowsMemoryGrants = 1
    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(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed,
		   CAST(ISNULL(NULLIF(( total_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;

	IF @VersionShowsSpills = 1
    BEGIN
        RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT;
		SET @sql += N'
           min_spills AS MinSpills,
           max_spills AS MaxSpills,
           total_spills AS TotalSpills,
		   CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills,';
    END;
    ELSE
    BEGIN
        RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT;
		SET @sql += N'
           NULL AS MinSpills,
           NULL AS MaxSpills,
           NULL AS TotalSpills, 
		   NULL AS AvgSpills, ' ;
    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 , ' + @nl ;


    IF @VersionShowsAirQuoteActualPlans = 1
        BEGIN
        SET @sql += N'           CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ;
        END
    ELSE
        BEGIN
        SET @sql += N'           query_plan AS QueryPlan, ' + @nl ;
        END

    SET @sql += N'
           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="http://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,
    	   @SortOrder ';
    
    SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ;

	SET @sort_filter += CASE @SortOrder  WHEN N'cpu' THEN N'AND total_worker_time > 0'
                                WHEN N'reads' THEN N'AND total_logical_reads > 0'
                                WHEN N'writes' THEN N'AND total_logical_writes > 0'
                                WHEN N'duration' THEN N'AND total_elapsed_time > 0'
                                WHEN N'executions' THEN N'AND execution_count > 0'
                                /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0'  BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */
								WHEN N'memory grant' THEN N'AND max_grant_kb > 0'
								WHEN N'unused grant' THEN N'AND max_grant_kb > 0'
								WHEN N'spills' THEN N'AND max_spills > 0'
                                /* And now the averages */
                                WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0'
                                WHEN N'avg reads' THEN N'AND (total_logical_reads / execution_count) > 0'
                                WHEN N'avg writes' THEN N'AND (total_logical_writes / execution_count) > 0'
                                WHEN N'avg duration' THEN N'AND (total_elapsed_time / execution_count) > 0'
								WHEN N'avg memory grant' THEN N'AND CASE WHEN max_grant_kb = 0 THEN 0 ELSE (max_grant_kb / execution_count) END > 0'
                                WHEN N'avg spills' THEN N'AND CASE WHEN total_spills = 0 THEN 0 ELSE (total_spills / execution_count) END > 0'
                                WHEN N'avg executions' THEN N'AND 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 > 0'
            ELSE N' /* No minimum threshold set */ '
               END;

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

	SET @sql += @sort_filter + @nl;
    
    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', 'unused grant', 'duplicate'))	/* Issue #3345 added 'duplicate' */
   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(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') 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 += @sort_filter + @nl;

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

IF (@v >= 13
   AND @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', 'unused grant', 'duplicate'))	/* Issue #3345 added 'duplicate' */
   AND (@SortOrder NOT IN ('spills', 'avg spills'))
   OR (LEFT(@QueryFilter, 3) = 'fun')
BEGIN
    SET @sql += @insert_list;
    SET @sql += REPLACE(REPLACE(@plans_triggers_select_list, '#query_type#', 'Function')
			, N'
           min_spills AS MinSpills,
           max_spills AS MaxSpills,
           total_spills AS TotalSpills,
		   CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ', 
		   N'
           NULL AS MinSpills,
           NULL AS MaxSpills,
           NULL AS TotalSpills, 
		   NULL AS AvgSpills, ') ;

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

    IF @IgnoreSystemDBs = 1
       SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') 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 += @sort_filter + @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', 'unused grant', 'duplicate'))	/* Issue #3345 added 'duplicate' */
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(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') 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 += @sort_filter + @nl;   

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



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'
								WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb'
								WHEN N'spills' THEN N'max_spills'
								WHEN N'duplicate' THEN N'total_worker_time'		/* Issue #3345 */
                                /* 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 spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / 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'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
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    ##BlitzCacheProcs
		 WHERE SPID = @@SPID) AS x
WHERE x.rn = 1
OPTION (RECOMPILE);

/* 
    This block was used to delete duplicate queries, but has been removed.
    For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2026
WITH d AS (
SELECT  SPID,
        ROW_NUMBER() OVER (PARTITION BY SqlHandle, QueryHash ORDER BY #sortable# DESC) AS rn
FROM    ##BlitzCacheProcs
WHERE SPID = @@SPID
)
DELETE d
WHERE d.rn > 1
AND SPID = @@SPID
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'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB'
								WHEN N'spills' THEN N'MaxSpills'
								WHEN N'duplicate' THEN N'TotalCPU'			/* Issue #3345 */
                                /* And now the averages */
                                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 spills' THEN N'AvgSpills'
                                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 @Debug = 1
    BEGIN
		PRINT N'Printing dynamic SQL stored in @sql: ';
        PRINT SUBSTRING(@sql, 0, 4000);
        PRINT SUBSTRING(@sql, 4000, 8000);
        PRINT SUBSTRING(@sql, 8000, 12000);
        PRINT SUBSTRING(@sql, 12000, 16000);
        PRINT SUBSTRING(@sql, 16000, 20000);
        PRINT SUBSTRING(@sql, 20000, 24000);
        PRINT SUBSTRING(@sql, 24000, 28000);
        PRINT SUBSTRING(@sql, 28000, 32000);
        PRINT SUBSTRING(@sql, 32000, 36000);
        PRINT SUBSTRING(@sql, 36000, 40000);
    END;

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


IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL
BEGIN
    CREATE TABLE ##BlitzCacheResults (
        SPID INT,
        ID INT IDENTITY(1,1),
        CheckID INT,
        Priority TINYINT,
        FindingsGroup VARCHAR(50),
        Finding VARCHAR(500),
        URL VARCHAR(200),
        Details VARCHAR(4000)
    );
END;
ELSE
BEGIN
  RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT;
  DELETE ##BlitzCacheResults
    WHERE SPID = @@SPID
	OPTION (RECOMPILE) ;
END


IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL
BEGIN
    CREATE TABLE ##BlitzCacheProcs (
        SPID INT ,
        QueryType NVARCHAR(258),
        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,
		LastCompletionTime 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,
		PlanGenerationNum BIGINT,
        MinReturnedRows BIGINT,
        MaxReturnedRows BIGINT,
        AverageReturnedRows MONEY,
        TotalReturnedRows BIGINT,
        LastReturnedRows BIGINT,
		MinGrantKB BIGINT,
		MaxGrantKB BIGINT,
		MinUsedGrantKB BIGINT, 
		MaxUsedGrantKB BIGINT,
		PercentMemoryGrantUsed MONEY,
		AvgMaxMemoryGrant MONEY,
		MinSpills BIGINT,
		MaxSpills BIGINT,
		TotalSpills BIGINT,
		AvgSpills 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 ,
		MaxCompileMemory 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_fast_forward_cursor BIT,
		is_cursor_dynamic 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 INT,
        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,
		is_spatial BIT,
		index_dml BIT,
		table_dml BIT,
		long_running_low_cpu BIT,
		low_cost_high_cpu BIT,
		stale_stats BIT, 
		is_adaptive BIT,
		index_spool_cost FLOAT,
		index_spool_rows FLOAT,
		table_spool_cost FLOAT,
		table_spool_rows FLOAT,
		is_spool_expensive BIT,
		is_spool_more_rows BIT,
		is_table_spool_expensive BIT,
		is_table_spool_more_rows BIT,
		estimated_rows FLOAT,
		is_bad_estimate BIT, 
		is_paul_white_electric BIT,
		is_row_goal BIT,
		is_big_spills BIT,
		is_mstvf BIT,
		is_mm_join BIT,
        is_nonsargable BIT,
		select_with_writes BIT,
		implicit_conversion_info XML,
		cached_execution_parameters XML,
		missing_indexes XML,
        SetOptions VARCHAR(MAX),
        Warnings VARCHAR(MAX),
		Pattern NVARCHAR(20),
        ai_prompt NVARCHAR(MAX),
        ai_advice NVARCHAR(MAX),
        ai_payload NVARCHAR(MAX),
        ai_raw_response NVARCHAR(MAX)
    );
END;
ELSE
BEGIN
  RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT;
  DELETE ##BlitzCacheProcs
    WHERE SPID = @@SPID
	OPTION (RECOMPILE) ;
END

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

    EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder;
END;

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


/* Update ##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,
		SUM(b.MinGrantKB) AS MinGrantKB,
		SUM(b.MaxGrantKB) AS MaxGrantKB,
		SUM(b.MinUsedGrantKB) AS MinUsedGrantKB,
		SUM(b.MaxUsedGrantKB) AS MaxUsedGrantKB,
        SUM(b.MinSpills) AS MinSpills,
        SUM(b.MaxSpills) AS MaxSpills,
        SUM(b.TotalSpills) AS TotalSpills
    FROM ##BlitzCacheProcs b
    WHERE b.SPID = @@SPID
	AND b.QueryHash IS NOT NULL
    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,
		b.MinGrantKB		  = b2.MinGrantKB,
		b.MaxGrantKB		  = b2.MaxGrantKB,
		b.MinUsedGrantKB	  = b2.MinUsedGrantKB,
		b.MaxUsedGrantKB      = b2.MaxUsedGrantKB,
        b.MinSpills           = b2.MinSpills,
        b.MaxSpills           = b2.MaxSpills,
        b.TotalSpills         = b2.TotalSpills
FROM ##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 ##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 = CASE WHEN @KeepCRLF = 1 
                         THEN REPLACE(QueryText, @tab, '  ')
                         ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, '  ')
					END
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    ##BlitzCacheProcs
        WHERE   PlanHandle IS NOT NULL
		AND SPID = @@SPID
        GROUP BY PlanHandle,
                TotalCPU,
                TotalDuration,
                TotalReads,
                TotalWrites,
                ExecutionCount,
                PlanCreationTime,
                LastExecutionTime
    ) AS x
) AS y
WHERE ##BlitzCacheProcs.PlanHandle = y.PlanHandle
      AND ##BlitzCacheProcs.PlanHandle IS NOT NULL
	  AND ##BlitzCacheProcs.SPID = @@SPID
OPTION (RECOMPILE) ;


RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT;
UPDATE ##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 = CASE WHEN @KeepCRLF = 1 
                         THEN REPLACE(QueryText, @tab, '  ')
                         ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, '  ')
					END
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    ##BlitzCacheProcs
		WHERE SPID = @@SPID
        GROUP BY DatabaseName,
                SqlHandle,
                QueryHash,
                TotalCPU,
                TotalDuration,
                TotalReads,
                TotalWrites,
                ExecutionCount,
                PlanCreationTime,
                LastExecutionTime
    ) AS x
) AS y
WHERE   ##BlitzCacheProcs.SqlHandle = y.SqlHandle
        AND ##BlitzCacheProcs.QueryHash = y.QueryHash
        AND ##BlitzCacheProcs.DatabaseName = y.DatabaseName
        AND ##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('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
SELECT  QueryHash ,
        SqlHandle ,
		PlanHandle,
        q.n.query('.') AS statement,
        0 AS is_cursor
INTO    #statements
FROM    ##BlitzCacheProcs p
        CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n) 
WHERE p.SPID = @@SPID
OPTION (RECOMPILE) ;

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

WITH XMLNAMESPACES('http://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('http://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  ##BlitzCacheProcs
SET     NumberOfDistinctPlans = distinct_plan_count,
        NumberOfPlans = number_of_plans ,
        plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END
FROM
    (
    SELECT    
        DatabaseName = 
            DB_NAME(CONVERT(int, pa.value)),
        QueryHash = 
            qs.query_hash,
        number_of_plans =
           COUNT_BIG(qs.query_plan_hash),
        distinct_plan_count = 
            COUNT_BIG(DISTINCT qs.query_plan_hash)
    FROM sys.dm_exec_query_stats AS qs
    CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa
    WHERE pa.attribute = 'dbid'
    GROUP BY 
        DB_NAME(CONVERT(int, pa.value)), 
        qs.query_hash
) AS x
WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash
AND   ##BlitzCacheProcs.DatabaseName = x.DatabaseName
OPTION (RECOMPILE) ;

-- query level checks
RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE  ##BlitzCacheProcs
SET     missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') ,
		unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END ,
        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'),
		MaxCompileMemory = query_plan.value('sum(//p:QueryPlan/p:OptimizerHardwareDependentProperties/@MaxCompileMemory)', 'float')
FROM    #query_plan qp
WHERE   qp.QueryHash = ##BlitzCacheProcs.QueryHash
AND     qp.SqlHandle = ##BlitzCacheProcs.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);

-- statement level checks
RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET     compile_timeout = 1 
FROM    #statements s
JOIN ##BlitzCacheProcs b
ON  s.QueryHash = b.QueryHash
AND SPID = @@SPID
WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1
OPTION (RECOMPILE);

RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET     compile_memory_limit_exceeded = 1 
FROM    #statements s
JOIN ##BlitzCacheProcs b
ON  s.QueryHash = b.QueryHash
AND SPID = @@SPID
WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1
OPTION (RECOMPILE);

IF @ExpertMode > 0
BEGIN
RAISERROR(N'Performing unparameterized query checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
unparameterized_query AS (
	SELECT s.QueryHash,
		   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 AS s
			)
UPDATE b
SET b.unparameterized_query = u.unparameterized_query
FROM ##BlitzCacheProcs b
JOIN unparameterized_query u
ON  u.QueryHash = b.QueryHash
AND SPID = @@SPID
WHERE u.unparameterized_query = 1
OPTION (RECOMPILE);
END;


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
index_dml AS (
	SELECT	s.QueryHash,	
			index_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1
							 WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1
						END
	FROM    #statements s
			)
	UPDATE b
		SET b.index_dml = i.index_dml
	FROM ##BlitzCacheProcs AS b
	JOIN index_dml i
	ON i.QueryHash = b.QueryHash
	WHERE i.index_dml = 1
	AND b.SPID = @@SPID
	OPTION (RECOMPILE);
END;


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
table_dml AS (
	SELECT s.QueryHash,			
		   table_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1
							WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1
							END
		 FROM #statements AS s
		 )
	UPDATE b
		SET b.table_dml = t.table_dml
	FROM ##BlitzCacheProcs AS b
	JOIN table_dml t
	ON t.QueryHash = b.QueryHash
	WHERE t.table_dml = 1
	AND b.SPID = @@SPID
	OPTION (RECOMPILE);
END; 


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
INSERT INTO #est_rows
SELECT DISTINCT 
		CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash,
		c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows
FROM   #statements AS s
CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n)
WHERE  c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1;

	UPDATE b
		SET b.estimated_rows = er.estimated_rows
	FROM ##BlitzCacheProcs AS b
	JOIN #est_rows er
	ON er.QueryHash = b.QueryHash
	WHERE b.SPID = @@SPID
	AND b.QueryType = 'Statement'
	OPTION (RECOMPILE);
END;

RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
UPDATE b
SET b.is_trivial = 1
FROM ##BlitzCacheProcs AS b
JOIN (
SELECT  s.SqlHandle
FROM    #statements AS s
JOIN    (   SELECT  r.SqlHandle
            FROM    #relop AS r
            WHERE   r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r
    ON r.SqlHandle = s.SqlHandle
WHERE   s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1
) AS s
ON b.SqlHandle = s.SqlHandle
OPTION (RECOMPILE);


--Gather costs
RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
INSERT INTO #plan_cost ( QueryPlanCost, SqlHandle, PlanHandle, QueryHash, QueryPlanHash )
SELECT  DISTINCT
		statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') QueryPlanCost,
		s.SqlHandle,
		s.PlanHandle,
		CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash,
		CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryPlanHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryPlanHash
FROM #statements s
CROSS APPLY s.statement.nodes('/p:StmtSimple') AS q(n)
WHERE statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0
OPTION (RECOMPILE);

RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT;
WITH pc AS (
	SELECT SUM(DISTINCT pc.QueryPlanCost) AS QueryPlanCostSum, pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle
	FROM #plan_cost AS pc
	GROUP BY pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle
)
	UPDATE b
		SET b.QueryPlanCost = ISNULL(pc.QueryPlanCostSum, 0)
		FROM pc
		JOIN ##BlitzCacheProcs b
		ON b.SqlHandle = pc.SqlHandle
		AND b.QueryHash = pc.QueryHash
		WHERE b.QueryType NOT LIKE '%Procedure%'
	OPTION (RECOMPILE);

IF EXISTS (
SELECT 1
FROM ##BlitzCacheProcs AS b
WHERE b.QueryType LIKE 'Procedure%'
)

BEGIN

RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT;
;WITH XMLNAMESPACES('http://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
	SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery,
    qc.PlanHandle,
    qc.SqlHandle
  FROM QueryCost qc
)
INSERT INTO #proc_costs
SELECT qcu.PlanTotalQuery, PlanHandle, SqlHandle
FROM QueryCostUpdate AS qcu
OPTION (RECOMPILE);


UPDATE b
    SET b.QueryPlanCost = ca.PlanTotalQuery
FROM ##BlitzCacheProcs AS b
CROSS APPLY (
		SELECT TOP 1 PlanTotalQuery 
		FROM #proc_costs qcu 
		WHERE qcu.PlanHandle = b.PlanHandle 
		ORDER BY PlanTotalQuery DESC
) ca
WHERE b.QueryType LIKE 'Procedure%'
AND b.SPID = @@SPID
OPTION (RECOMPILE);

END;

UPDATE b
SET b.QueryPlanCost = 0.0
FROM ##BlitzCacheProcs b
WHERE b.QueryPlanCost IS NULL
AND b.SPID = @@SPID
OPTION (RECOMPILE);

RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE  ##BlitzCacheProcs
SET plan_warnings = 1
FROM    #query_plan qp
WHERE   qp.SqlHandle = ##BlitzCacheProcs.SqlHandle
AND SPID = @@SPID
AND query_plan.exist('/p:QueryPlan/p:Warnings') = 1
OPTION (RECOMPILE);

RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE  ##BlitzCacheProcs
SET implicit_conversions = 1
FROM    #query_plan qp
WHERE   qp.SqlHandle = ##BlitzCacheProcs.SqlHandle
AND SPID = @@SPID
AND query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1
OPTION (RECOMPILE);

-- operator level checks
IF @ExpertMode > 0
BEGIN
RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://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   ##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);
END; 


RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://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   ##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);

IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://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 ##BlitzCacheProcs AS p
JOIN x ON x.SqlHandle = p.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);
END; 


RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://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 = 1
FROM ##BlitzCacheProcs AS p
JOIN x ON x.SqlHandle = p.SqlHandle
AND SPID = @@SPID
WHERE x.first_char = '@'
OPTION (RECOMPILE);

IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
, x AS (
SELECT qs.SqlHandle,
	   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 ##BlitzCacheProcs AS p
JOIN x ON x.SqlHandle = p.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);
END; 


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


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

RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##BlitzCacheProcs
SET sort_cost = y.max_sort_cost 
FROM (
	SELECT x.SqlHandle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost
	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
	GROUP BY x.SqlHandle
	) AS y
WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);

IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1)
BEGIN

RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT;

END

IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1)
BEGIN

RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT;

RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_optimistic_cursor =  1
FROM ##BlitzCacheProcs b
JOIN #statements AS qs
ON b.SqlHandle = qs.SqlHandle
CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn)
WHERE SPID = @@SPID
AND n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1
AND qs.is_cursor = 1
OPTION (RECOMPILE);


RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_forward_only_cursor = 1
FROM ##BlitzCacheProcs b
JOIN #statements AS qs
ON b.SqlHandle = qs.SqlHandle
CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn)
WHERE SPID = @@SPID
AND n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1
AND qs.is_cursor = 1
OPTION (RECOMPILE);

RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_fast_forward_cursor = 1
FROM ##BlitzCacheProcs b
JOIN #statements AS qs
ON b.SqlHandle = qs.SqlHandle
CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn)
WHERE SPID = @@SPID
AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1
AND qs.is_cursor = 1
OPTION (RECOMPILE);


RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_cursor_dynamic =  1
FROM ##BlitzCacheProcs b
JOIN #statements AS qs
ON b.SqlHandle = qs.SqlHandle
CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn)
WHERE SPID = @@SPID
AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1
AND qs.is_cursor = 1
OPTION (RECOMPILE);

END

IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT;
;WITH XMLNAMESPACES('http://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 ##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);
END; 


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##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 ##BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);
END; 


RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##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 ##BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);

IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),	
IndexOps AS 
(
	SELECT 
	r.QueryHash,
	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.QueryHash,
		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.QueryHash) 
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 ##BlitzCacheProcs AS b
JOIN iops ON  iops.QueryHash = b.QueryHash
WHERE SPID = @@SPID
OPTION (RECOMPILE);
END; 


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##BlitzCacheProcs
SET is_spatial = x.is_spatial
FROM (
SELECT qs.SqlHandle,
	   1 AS is_spatial
FROM   #relop qs
CROSS APPLY relop.nodes('/p:RelOp//p:Object') n(fn)
WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1
) AS x
WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle
AND SPID = @@SPID
OPTION (RECOMPILE);
END; 


RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES (
    'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
, selects
AS ( SELECT s.QueryHash
     FROM   #statements AS s
     WHERE  s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 )
, spools
AS ( SELECT DISTINCT r.QueryHash,
	   c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows,
       c.n.value('@EstimateIO', 'FLOAT') AS estimated_io,
       c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu,
       c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds
FROM   #relop AS r
JOIN   selects AS s
ON s.QueryHash = r.QueryHash
CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n)
WHERE  r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1
)
UPDATE b
		SET b.index_spool_rows = sp.estimated_rows,
			b.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END)
FROM ##BlitzCacheProcs b
JOIN spools sp
ON sp.QueryHash = b.QueryHash
OPTION (RECOMPILE);

RAISERROR('Checking for wonky Table Spools', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES (
    'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
, selects
AS ( SELECT s.QueryHash
     FROM   #statements AS s
     WHERE  s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 )
, spools
AS ( SELECT DISTINCT r.QueryHash,
	   c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows,
       c.n.value('@EstimateIO', 'FLOAT') AS estimated_io,
       c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu,
       c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds
FROM   #relop AS r
JOIN   selects AS s
ON s.QueryHash = r.QueryHash
CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n)
WHERE  r.relop.exist('/p:RelOp[@PhysicalOp="Table Spool" and @LogicalOp="Lazy Spool"]') = 1
)
UPDATE b
		SET b.table_spool_rows = (sp.estimated_rows * sp.estimated_rebinds),
			b.table_spool_cost = ((sp.estimated_io * sp.estimated_cpu * sp.estimated_rows) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END)
FROM ##BlitzCacheProcs b
JOIN spools sp
ON sp.QueryHash = b.QueryHash
OPTION (RECOMPILE);


RAISERROR('Checking for selects that cause non-spill and index spool writes', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES (
    'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
, selects
AS ( SELECT CONVERT(BINARY(8), 
                RIGHT('0000000000000000' 
				    + SUBSTRING(s.statement.value('(/p:StmtSimple/@QueryHash)[1]', 'VARCHAR(18)'), 
					    3, 18), 16), 2) AS QueryHash
     FROM   #statements AS s
	 JOIN ##BlitzCacheProcs b
	 ON s.QueryHash = b.QueryHash
	 WHERE b.index_spool_rows IS NULL
	 AND   b.index_spool_cost IS NULL
	 AND   b.table_spool_cost IS NULL
	 AND   b.table_spool_rows IS NULL
	 AND   b.is_big_spills IS NULL
	 AND   b.AverageWrites > 1024.
     AND  s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 
)
UPDATE b
   SET b.select_with_writes = 1
FROM ##BlitzCacheProcs b
JOIN selects AS s
ON s.QueryHash = b.QueryHash
AND b.AverageWrites > 1024.;

/* 2012+ only */
IF @v >= 11
BEGIN

	RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT;
	WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
	UPDATE  ##BlitzCacheProcs
	SET is_forced_serial = 1
	FROM    #query_plan qp
	WHERE   qp.SqlHandle = ##BlitzCacheProcs.SqlHandle
	AND SPID = @@SPID
	AND query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1
	AND (##BlitzCacheProcs.is_parallel = 0 OR ##BlitzCacheProcs.is_parallel IS NULL)
	OPTION (RECOMPILE);
	
	IF @ExpertMode > 0
	BEGIN
	RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT;
	WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
	UPDATE ##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 ##BlitzCacheProcs.SqlHandle = x.SqlHandle
	AND SPID = @@SPID
	OPTION (RECOMPILE);
	END; 

END;

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

    WITH XMLNAMESPACES('http://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    ##BlitzCacheProcs p
            JOIN #statements s ON p.QueryHash = s.QueryHash 
	WHERE SPID = @@SPID
	OPTION (RECOMPILE);
END ;

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

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

/* 2017+ only */
IF @v >= 14 OR	(@v = 13 AND @build >= 5026)
BEGIN

IF @ExpertMode > 0
BEGIN
RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
INSERT INTO #stats_agg
SELECT qp.SqlHandle,
	   x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate,
	   x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount,
	   x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent,
	   x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], 
	   x.c.value('@Table', 'NVARCHAR(258)') AS [Table], 
	   x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], 
	   x.c.value('@Database', 'NVARCHAR(258)') AS [Database]
FROM #query_plan AS qp
CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c)
OPTION (RECOMPILE);


RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT;
WITH  stale_stats AS (
	SELECT sa.SqlHandle
	FROM #stats_agg AS sa
	GROUP BY sa.SqlHandle
	HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME())
	AND AVG(sa.ModificationCount) >= 100000
)
UPDATE b
SET stale_stats = 1
FROM ##BlitzCacheProcs b
JOIN stale_stats os
ON b.SqlHandle = os.SqlHandle
AND b.SPID = @@SPID
OPTION (RECOMPILE);
END;

IF @v >= 14 AND @ExpertMode > 0
BEGIN
RAISERROR('Checking for adaptive joins', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
aj AS (
	SELECT 
			SqlHandle
	FROM #relop AS r
	CROSS APPLY r.relop.nodes('//p:RelOp') x(c)
	WHERE x.c.exist('@IsAdaptive[.=1]') = 1
)
UPDATE b
SET b.is_adaptive = 1
FROM ##BlitzCacheProcs b
JOIN aj
ON b.SqlHandle = aj.SqlHandle
AND b.SPID = @@SPID
OPTION (RECOMPILE);
END; 

IF ((@v >= 14 
       OR (@v = 13 AND @build >= 5026) 
       OR (@v = 12 AND @build >= 6024))
   AND @ExpertMode > 0)

BEGIN;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
row_goals AS(
SELECT qs.QueryHash
FROM   #relop qs
WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0
)
UPDATE b
SET b.is_row_goal = 1
FROM ##BlitzCacheProcs b
JOIN row_goals
ON b.QueryHash = row_goals.QueryHash
AND b.SPID = @@SPID
OPTION (RECOMPILE);
END ;

END;


/* END Testing using XML nodes to speed up processing */


/* 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    ##BlitzCacheProcs p
        JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle
WHERE   QueryType = 'Statement'
AND SPID = @@SPID
OPTION (RECOMPILE);

/* Update to grab stored procedure name for individual statements when PSPO is detected */
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    ##BlitzCacheProcs p
		OUTER APPLY (
				SELECT	REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText
				) a
		OUTER APPLY (
				SELECT	CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart
				) b
		OUTER APPLY (
				SELECT	SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring
				WHERE	b.OptionStart > 0
				) c
		OUTER APPLY (
				SELECT  PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength
				) d
		OUTER APPLY (
				SELECT	TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId
				) e
		JOIN    sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id
WHERE   p.QueryType = 'Statement'
AND		p.SPID = @@SPID
AND		s.object_id IS NOT NULL
OPTION (RECOMPILE);

RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT;
DECLARE @function_update_sql NVARCHAR(MAX) = N''
IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats')
    BEGIN
     SET @function_update_sql = @function_update_sql + N'
     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    ##BlitzCacheProcs p
             JOIN sys.dm_exec_function_stats s ON p.SqlHandle = s.sql_handle
     WHERE   QueryType = ''Statement''
     AND SPID = @@SPID
     OPTION (RECOMPILE);
     '
    EXEC sys.sp_executesql @function_update_sql
   END


/* Trace Flag Checks 2012 SP3, 2014 SP2 and 2016 SP1 only)*/
IF @v >= 11
BEGIN

RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT;
;WITH XMLNAMESPACES('http://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)
)
INSERT INTO #trace_flags
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
FROM tf_pretty AS tf1
OPTION (RECOMPILE);

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

END;


RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_mstvf = 1
FROM #relop AS r
JOIN ##BlitzCacheProcs AS b
ON b.SqlHandle = r.SqlHandle
WHERE  r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1
OPTION (RECOMPILE);


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE b
SET b.is_mm_join = 1
FROM #relop AS r
JOIN ##BlitzCacheProcs AS b
ON b.SqlHandle = r.SqlHandle
WHERE  r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1
OPTION (RECOMPILE);
END ;


IF @ExpertMode > 0
BEGIN
RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p),
is_paul_white_electric AS (
SELECT 1 AS [is_paul_white_electric], 
r.SqlHandle
FROM #relop AS r
CROSS APPLY r.relop.nodes('//p:RelOp') c(n)
WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1
)
UPDATE b
SET    b.is_paul_white_electric = ipwe.is_paul_white_electric
FROM   ##BlitzCacheProcs AS b
JOIN is_paul_white_electric ipwe 
ON ipwe.SqlHandle = b.SqlHandle 
WHERE b.SPID = @@SPID
OPTION (RECOMPILE);
END ;


RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
, nsarg
    AS (   SELECT       r.QueryHash, 1 AS fn, 0 AS jo, 0 AS lk
           FROM         #relop AS r
           CROSS APPLY  r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x)
           WHERE        (   ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1
                            OR     ca.x.exist('//p:ScalarOperator/p:IF') = 1 )
           UNION ALL
           SELECT       r.QueryHash, 0 AS fn, 1 AS jo, 0 AS lk
           FROM         #relop AS r
           CROSS APPLY  r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x)
           WHERE        r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1
                        AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1
           UNION ALL
           SELECT       r.QueryHash, 0 AS fn, 0 AS jo, 1 AS lk
           FROM         #relop AS r
           CROSS APPLY  r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x)
           CROSS APPLY  ca.x.nodes('//p:Const') AS co(x)
           WHERE        ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1
                        AND (   (   co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N'
                                    AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' )
                                OR (   co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N'
                                       AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))),
  d_nsarg
    AS (   SELECT   DISTINCT
                    nsarg.QueryHash
           FROM     nsarg
           WHERE    nsarg.fn = 1
                    OR nsarg.jo = 1
                    OR nsarg.lk = 1 )
UPDATE  b
SET     b.is_nonsargable = 1
FROM    d_nsarg AS d
JOIN    ##BlitzCacheProcs AS b
    ON b.QueryHash = d.QueryHash
WHERE   b.SPID = @@SPID
OPTION ( RECOMPILE );

/*Begin implicit conversion and parameter info */

RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT;

RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
INSERT #variable_info ( SPID, QueryHash, SqlHandle, proc_name, variable_name, variable_datatype, compile_time_value )
SELECT      DISTINCT @@SPID,
            qp.QueryHash,
            qp.SqlHandle,
            b.QueryType AS proc_name,
            q.n.value('@Column', 'NVARCHAR(258)') AS variable_name,
            q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype,
            q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value
FROM        #query_plan AS qp
JOIN        ##BlitzCacheProcs AS b
ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash)
OR 	(b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle)
CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n)
WHERE  b.SPID = @@SPID
OPTION (RECOMPILE);


RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
INSERT #conversion_info ( SPID, QueryHash, SqlHandle, proc_name, expression )
SELECT      DISTINCT @@SPID,
            qp.QueryHash,
            qp.SqlHandle,
            b.QueryType AS proc_name,
            qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression
FROM        #query_plan AS qp
JOIN        ##BlitzCacheProcs AS b
ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash)
OR 	(b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle)
CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c)
WHERE       qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1
            AND qp.QueryHash IS NOT NULL
            AND b.implicit_conversions = 1
AND b.SPID = @@SPID
OPTION (RECOMPILE);


RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT;
INSERT #stored_proc_info ( SPID, SqlHandle, QueryHash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value )
SELECT @@SPID AS SPID,
       ci.SqlHandle,
       ci.QueryHash,
       REPLACE(REPLACE(REPLACE(ci.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name,
       CASE WHEN ci.at_charindex > 0
                 AND ci.bracket_charindex > 0 
			THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex)
            ELSE N'**no_variable**'
       END AS variable_name,
	   N'**no_variable**' AS variable_datatype,
       CASE WHEN ci.at_charindex = 0
                 AND ci.comma_charindex > 0
                 AND ci.second_comma_charindex > 0 
			THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex)
            ELSE N'**no_column**'
       END AS converted_column_name,
       CASE WHEN ci.at_charindex = 0
                 AND ci.equal_charindex > 0 
				 AND ci.convert_implicit_charindex = 0
			THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000)
			WHEN ci.at_charindex = 0
                 AND (ci.equal_charindex -1) > 0 
				 AND ci.convert_implicit_charindex > 0
			THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1)
            WHEN ci.at_charindex > 0
                 AND ci.comma_charindex > 0
                 AND ci.second_comma_charindex > 0 
			THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex)
            ELSE N'**no_column **'
       END AS column_name,
       CASE WHEN ci.paren_charindex > 0
                 AND ci.comma_paren_charindex > 0 
			THEN SUBSTRING(ci.expression, ci.paren_charindex, ci.comma_paren_charindex)
       END AS converted_to,
       LEFT(CASE WHEN ci.at_charindex = 0
                 AND ci.convert_implicit_charindex = 0
                 AND ci.proc_name = 'Statement' 
			THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000)
            ELSE '**idk_man**'
       END, 258) AS compile_time_value
FROM   #conversion_info AS ci
OPTION (RECOMPILE);


RAISERROR(N'Updating variables for inserted procs', 0, 1) WITH NOWAIT;
UPDATE sp
SET sp.variable_datatype = vi.variable_datatype,
	sp.compile_time_value = vi.compile_time_value
FROM   #stored_proc_info AS sp
JOIN #variable_info AS vi
ON (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash)
OR 	(sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle)
AND sp.variable_name = vi.variable_name
OPTION (RECOMPILE);


RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT;
INSERT #stored_proc_info 
		( SPID, SqlHandle, QueryHash, variable_name, variable_datatype, compile_time_value, proc_name )
SELECT vi.SPID, vi.SqlHandle, vi.QueryHash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, REPLACE(REPLACE(REPLACE(vi.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name
FROM #variable_info AS vi
WHERE NOT EXISTS
(
	SELECT * 
	FROM   #stored_proc_info AS sp
	WHERE (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash)
	OR 	(sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle)
)
OPTION (RECOMPILE);


RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT;
UPDATE s
SET    s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' 
                                  THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1)
								  ELSE s.variable_datatype
                             END,
       s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' 
                             THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1)
                             ELSE s.converted_to
                        END,
	   s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' 
                                   THEN SUBSTRING(s.compile_time_value, 
												  CHARINDEX('(', s.compile_time_value) + 1,
												  CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value)
												  )
									WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') 
									AND s.variable_datatype NOT LIKE '%binary%' 
									AND s.compile_time_value NOT LIKE 'N''%'''
									AND s.compile_time_value NOT LIKE '''%''' 
									AND s.compile_time_value <> s.column_name
                                    AND s.compile_time_value <> '**idk_man**'
                                    THEN QUOTENAME(compile_time_value, '''')
									ELSE s.compile_time_value 
							  END
FROM   #stored_proc_info AS s
OPTION (RECOMPILE);


RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT;
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE s
SET set_options = set_options.ansi_set_options
FROM #stored_proc_info AS s
JOIN (
		SELECT  x.SqlHandle,
				N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) +
				N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) +
				N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) +
				N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) +
				N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) +
				N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) +
				N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options]
		FROM (
			SELECT
				s.SqlHandle,
				so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS],
				so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING],
				so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS],
				so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT],
				so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL],
				so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT],
				so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER]
			FROM #statements AS s
			CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o)
		   ) AS x
) AS set_options ON set_options.SqlHandle = s.SqlHandle
OPTION(RECOMPILE);


RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT;
WITH precheck AS (
SELECT spi.SPID,
	   spi.SqlHandle,
	   spi.proc_name,
			(SELECT  
			  CASE WHEN spi.proc_name <> 'Statement' 
				   THEN N'The stored procedure ' + spi.proc_name 
				   ELSE N'This ad hoc statement' 
			  END
			+ N' had the following implicit conversions: '
			+ CHAR(10)
			+ STUFF((
				SELECT DISTINCT 
						@nl
						+ CASE WHEN spi2.variable_name <> N'**no_variable**'
							   THEN N'The variable '
							   WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%')
							   THEN N'The compiled value '
							   WHEN spi2.column_name LIKE '%Expr%'
							   THEN 'The expression '
							   ELSE N'The column '
						  END 
						+ CASE WHEN spi2.variable_name <> N'**no_variable**'
							   THEN spi2.variable_name
							   WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%')
							   THEN spi2.compile_time_value
							   ELSE spi2.column_name
						  END 
						+ N' has a data type of '
						+ CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to
							   ELSE spi2.variable_datatype 
						  END
						+ N' which caused implicit conversion on the column '
						+ CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%'
							   THEN spi2.converted_column_name
							   WHEN spi2.column_name = N'**no_column**'
							   THEN spi2.converted_column_name
							   WHEN spi2.converted_column_name = N'**no_column**'
							   THEN spi2.column_name
							   WHEN spi2.column_name <> spi2.converted_column_name
							   THEN spi2.converted_column_name
							   ELSE spi2.column_name
						  END
						+ CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%')
							   THEN N''
							   WHEN spi2.column_name LIKE '%Expr%'
							   THEN N''
							   WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**')
							   AND spi2.compile_time_value <> spi2.column_name
							   THEN ' with the value ' + RTRIM(spi2.compile_time_value)
							ELSE N''
						 END 
						+ '.'
				FROM #stored_proc_info AS spi2
				WHERE spi.SqlHandle = spi2.SqlHandle
				FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'')
			  AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE )
			AS implicit_conversion_info
FROM #stored_proc_info AS spi
GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name
)
UPDATE b
SET b.implicit_conversion_info = pk.implicit_conversion_info
FROM ##BlitzCacheProcs AS b
JOIN precheck pk
ON pk.SqlHandle = b.SqlHandle
AND pk.SPID = b.SPID
OPTION (RECOMPILE);


RAISERROR(N'Updating cached parameter XML for stored procs', 0, 1) WITH NOWAIT;
WITH precheck AS (
SELECT spi.SPID,
	   spi.SqlHandle,
	   spi.proc_name,
	   (SELECT 
			set_options
			+ @nl
			+ @nl
			+ N'EXEC ' 
			+ spi.proc_name 
			+ N' '
			+ STUFF((
				SELECT DISTINCT N', ' 
						+ CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**'
								THEN spi2.variable_name + N' = '
								ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' 
						  END
						+ CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**'
							   THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans '
							   WHEN spi2.compile_time_value = N'NULL' 
							   THEN spi2.compile_time_value 
							   ELSE RTRIM(spi2.compile_time_value)
						  END
				FROM #stored_proc_info AS spi2
				WHERE spi.SqlHandle = spi2.SqlHandle
				AND spi2.proc_name <> N'Statement'
				FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'')
			AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE )
			AS cached_execution_parameters
FROM #stored_proc_info AS spi
GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options
) 
UPDATE b
SET b.cached_execution_parameters = pk.cached_execution_parameters
FROM ##BlitzCacheProcs AS b
JOIN precheck pk
ON pk.SqlHandle = b.SqlHandle
AND pk.SPID = b.SPID
WHERE b.QueryType <> N'Statement'
OPTION (RECOMPILE);


RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT;
WITH precheck AS (
SELECT spi.SPID,
	   spi.SqlHandle,
	   spi.proc_name,
	   (SELECT 
			set_options
			+ @nl
			+ @nl
			+ N' See QueryText column for full query text'
			+ @nl
			+ @nl
			+ STUFF((
				SELECT DISTINCT N', ' 
						+ CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**'
								THEN spi2.variable_name + N' = '
								ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' 
						  END
						+ CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**'
							   THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans '
							   WHEN spi2.compile_time_value = N'NULL' 
							   THEN spi2.compile_time_value 
							   ELSE RTRIM(spi2.compile_time_value)
						  END
				FROM #stored_proc_info AS spi2
				WHERE spi.SqlHandle = spi2.SqlHandle
				AND spi2.proc_name = N'Statement'
				AND spi2.variable_name NOT LIKE N'%msparam%'
				FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'')
			AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE )
			AS cached_execution_parameters
FROM #stored_proc_info AS spi
GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options
) 
UPDATE b
SET b.cached_execution_parameters = pk.cached_execution_parameters
FROM ##BlitzCacheProcs AS b
JOIN precheck pk
ON pk.SqlHandle = b.SqlHandle
AND pk.SPID = b.SPID
WHERE b.QueryType = N'Statement'
OPTION (RECOMPILE);

RAISERROR(N'Filling in implicit conversion and cached plan parameter info', 0, 1) WITH NOWAIT;
UPDATE b
SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL 
									  OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N''
									  THEN '<?NoNeedToClickMe -- N/A --?>' 
							     ELSE b.implicit_conversion_info END,
	b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL 
										 OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N''
										 THEN '<?NoNeedToClickMe -- N/A --?>' 
									ELSE b.cached_execution_parameters END
FROM ##BlitzCacheProcs AS b
WHERE b.SPID = @@SPID
OPTION (RECOMPILE);

/*End implicit conversion and parameter info*/

/*Begin Missing Index*/
IF EXISTS ( SELECT 1/0 
            FROM ##BlitzCacheProcs AS bbcp 
            WHERE bbcp.missing_index_count > 0
		    OR bbcp.index_spool_cost > 0
		    OR bbcp.index_spool_rows > 0
		    AND bbcp.SPID = @@SPID )
		   
		BEGIN		
		RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT;
		WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
		INSERT 	#missing_index_xml
		SELECT qp.QueryHash,
		       qp.SqlHandle,
		       c.mg.value('@Impact', 'FLOAT') AS Impact,
			   c.mg.query('.') AS cmg
		FROM   #query_plan AS qp
		CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg)
		WHERE  qp.QueryHash IS NOT NULL
		OPTION(RECOMPILE);
		
		RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT;	
		WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
		INSERT #missing_index_schema
		SELECT mix.QueryHash, mix.SqlHandle, mix.impact,
		       c.mi.value('@Database', 'NVARCHAR(128)'),
		       c.mi.value('@Schema', 'NVARCHAR(128)'),
		       c.mi.value('@Table', 'NVARCHAR(128)'),
			   c.mi.query('.')
		FROM #missing_index_xml AS mix
		CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi)
		OPTION(RECOMPILE);
		
		RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT;
		WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
		INSERT #missing_index_usage
		SELECT ms.QueryHash, ms.SqlHandle, ms.impact, ms.database_name, ms.schema_name, ms.table_name,
				c.cg.value('@Usage', 'NVARCHAR(128)'),
				c.cg.query('.')
		FROM #missing_index_schema ms
		CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg)
		OPTION(RECOMPILE);
		
		RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT;
		WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p )
		INSERT #missing_index_detail
		SELECT miu.QueryHash,
		       miu.SqlHandle,
		       miu.impact,
		       miu.database_name,
		       miu.schema_name,
		       miu.table_name,
		       miu.usage,
		       c.c.value('@Name', 'NVARCHAR(128)')
		FROM #missing_index_usage AS miu
		CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c)
		OPTION (RECOMPILE);
		
		RAISERROR(N'Inserting to missing indexes to #missing_index_pretty', 0, 1) WITH NOWAIT;
		INSERT #missing_index_pretty 
		       ( QueryHash,   SqlHandle,   impact,   database_name,   schema_name,   table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool )
		SELECT DISTINCT m.QueryHash, m.SqlHandle, m.impact, m.database_name, m.schema_name, m.table_name
		, STUFF((   SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name
		                 FROM   #missing_index_detail AS m2
		                 WHERE  m2.usage = 'EQUALITY'
						 AND m.QueryHash = m2.QueryHash
						 AND m.SqlHandle = m2.SqlHandle
						 AND m.impact = m2.impact
						 AND m.database_name = m2.database_name
						 AND m.schema_name = m2.schema_name
						 AND m.table_name = m2.table_name
		                 FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality
		, STUFF((   SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name
		                 FROM   #missing_index_detail AS m2
		                 WHERE  m2.usage = 'INEQUALITY'
						 AND m.QueryHash = m2.QueryHash
						 AND m.SqlHandle = m2.SqlHandle
						 AND m.impact = m2.impact
						 AND m.database_name = m2.database_name
						 AND m.schema_name = m2.schema_name
						 AND m.table_name = m2.table_name
		                 FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality
		, STUFF((   SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name
		                 FROM   #missing_index_detail AS m2
		                 WHERE  m2.usage = 'INCLUDE'
						 AND m.QueryHash = m2.QueryHash
						 AND m.SqlHandle = m2.SqlHandle
						 AND m.impact = m2.impact
						 AND m.database_name = m2.database_name
						 AND m.schema_name = m2.schema_name
						 AND m.table_name = m2.table_name
		                 FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include],
		bbcp.ExecutionCount,
		bbcp.QueryPlanCost,
		bbcp.PlanCreationTimeHours,
		0 as is_spool
		FROM #missing_index_detail AS m
		JOIN ##BlitzCacheProcs AS bbcp
		ON m.SqlHandle = bbcp.SqlHandle
		AND m.QueryHash = bbcp.QueryHash
		OPTION (RECOMPILE);
		
		RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT;
		WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
		INSERT #index_spool_ugly 
		        (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours)
		SELECT p.QueryHash, 
		       p.SqlHandle,                                                                               
		       (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) 
					/ ( 1 * NULLIF(p.QueryPlanCost, 0)) * 100 AS impact, 
			   o.n.value('@Database', 'NVARCHAR(128)') AS output_database, 
			   o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, 
			   o.n.value('@Table', 'NVARCHAR(128)') AS output_table, 
		       k.n.value('@Column', 'NVARCHAR(128)') AS range_column,
			   e.n.value('@Column', 'NVARCHAR(128)') AS expression_column,
			   o.n.value('@Column', 'NVARCHAR(128)') AS output_column, 
			   p.ExecutionCount, 
			   p.QueryPlanCost, 
			   p.PlanCreationTimeHours
		FROM #relop AS r
		JOIN ##BlitzCacheProcs p
		ON p.QueryHash = r.QueryHash
		CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n)
		CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n)
		OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n)
		OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n)
		WHERE  r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1

		RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT;
		INSERT #missing_index_pretty
			(QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool)
		SELECT DISTINCT 
		       isu.QueryHash,
		       isu.SqlHandle,
		       isu.impact,
		       isu.database_name,
		       isu.schema_name,
		       isu.table_name
			   , STUFF((   SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name
			   		                 FROM   #index_spool_ugly AS isu2
			   		                 WHERE  isu2.equality IS NOT NULL
			   						 AND isu.QueryHash = isu2.QueryHash
			   						 AND isu.SqlHandle = isu2.SqlHandle
			   						 AND isu.impact = isu2.impact
			   						 AND isu.database_name = isu2.database_name
			   						 AND isu.schema_name = isu2.schema_name
			   						 AND isu.table_name = isu2.table_name
			   		                 FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality
			   , STUFF((   SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name
			   		                 FROM   #index_spool_ugly AS isu2
			   		                 WHERE  isu2.inequality IS NOT NULL
			   						 AND isu.QueryHash = isu2.QueryHash
			   						 AND isu.SqlHandle = isu2.SqlHandle
			   						 AND isu.impact = isu2.impact
			   						 AND isu.database_name = isu2.database_name
			   						 AND isu.schema_name = isu2.schema_name
			   						 AND isu.table_name = isu2.table_name
			   		                 FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality
			   , STUFF((   SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name
			   		                 FROM   #index_spool_ugly AS isu2
			   		                 WHERE  isu2.include IS NOT NULL
			   						 AND isu.QueryHash = isu2.QueryHash
			   						 AND isu.SqlHandle = isu2.SqlHandle
			   						 AND isu.impact = isu2.impact
			   						 AND isu.database_name = isu2.database_name
			   						 AND isu.schema_name = isu2.schema_name
			   						 AND isu.table_name = isu2.table_name
			   		                 FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include,
		       isu.executions,
		       isu.query_cost,
		       isu.creation_hours,
			   1 AS is_spool
		FROM #index_spool_ugly AS isu


		RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT;
		WITH missing AS (
		SELECT DISTINCT
		       mip.QueryHash,
		       mip.SqlHandle, 
			   mip.executions,
			   N'<MissingIndexes><![CDATA['
			   + CHAR(10) + CHAR(13)
			   + STUFF((   SELECT CHAR(10) + CHAR(13) + ISNULL(mip2.details, '') AS details
		                   FROM   #missing_index_pretty AS mip2
						   WHERE mip.QueryHash = mip2.QueryHash
						   AND mip.SqlHandle = mip2.SqlHandle
						   AND mip.executions = mip2.executions
						   GROUP BY mip2.details
		                   ORDER BY MAX(mip2.impact) DESC
						   FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') 
			   + CHAR(10) + CHAR(13)
			   + N']]></MissingIndexes>' 
			   AS full_details
		FROM #missing_index_pretty AS mip
						)
		UPDATE bbcp
			SET bbcp.missing_indexes = m.full_details
		FROM ##BlitzCacheProcs AS bbcp
		JOIN missing AS m
		ON m.SqlHandle = bbcp.SqlHandle
		AND m.QueryHash = bbcp.QueryHash
		AND m.executions = bbcp.ExecutionCount
		AND SPID = @@SPID
		OPTION (RECOMPILE);

	END;

	RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT;
	UPDATE b
	SET b.missing_indexes = 
		CASE WHEN b.missing_indexes IS NULL 
			 THEN '<?NoNeedToClickMe -- N/A --?>' 
			 ELSE b.missing_indexes 
		END
	FROM ##BlitzCacheProcs AS b
	WHERE b.SPID = @@SPID
	OPTION (RECOMPILE);

/*End Missing Index*/


/* 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('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p)
UPDATE ##BlitzCacheProcs
SET    frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END ,
       parameter_sniffing = CASE WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold
                                      AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1
                                 WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold
                                      AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1
                                 WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold
                                      AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1
                                 WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold
                                      AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END ,
       near_parallel = CASE WHEN is_parallel <> 1 AND 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_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END,
	   long_running_low_cpu = CASE WHEN AverageDuration > AverageCPU * 4 AND AverageCPU < 500. THEN 1 END,
	   low_cost_high_cpu = CASE WHEN QueryPlanCost <= 10 AND AverageCPU > 5000. THEN 1 END,
	   is_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND index_spool_cost >= QueryPlanCost * .1 THEN 1 END,
	   is_spool_more_rows = CASE WHEN index_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END,
	   is_table_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND table_spool_cost >= QueryPlanCost / 4 THEN 1 END,
	   is_table_spool_more_rows = CASE WHEN table_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END,
	   is_bad_estimate = CASE WHEN AverageReturnedRows > 0 AND (estimated_rows * 1000 < AverageReturnedRows OR estimated_rows > AverageReturnedRows * 1000) THEN 1 END,
	   is_big_spills = CASE WHEN (AvgSpills / 128.) > 499. 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   ##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   ##BlitzCacheProcs p
       CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa
WHERE  pa.attribute LIKE '%cursor%' 
AND SPID = @@SPID
OPTION (RECOMPILE);

UPDATE p
SET    is_cursor = 1
FROM   ##BlitzCacheProcs p
WHERE QueryHash = 0x0000000000000000
OR QueryPlanHash = 0x0000000000000000
AND SPID = @@SPID
OPTION (RECOMPILE);



RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT;
/* Populate warnings */
UPDATE ##BlitzCacheProcs
SET    Warnings = 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 '; optimistic' ELSE '' END
							+ CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END
							+ CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END
                            + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' 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 > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') 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 (Heaps)' 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 ', >= 5 Indexes Modified' ELSE '' END +
				  CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + 
				  CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + 
				  CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END +
				  CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END +
				  CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + 
				  CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END +
				  CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END +
				  CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END +
				  CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END +
				  CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END +
				  CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END +
				  CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END +
				  CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END  +
				  CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + 
				  CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + 
                  CASE WHEN is_big_spills = 1 THEN ', >500mb Spills' ELSE '' END + 
				  CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + 
				  CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + 
                  CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + 
				  CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END +
				  CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END +
				  CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END +
				  CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END
				  , 3, 200000) 
WHERE SPID = @@SPID
OPTION (RECOMPILE);


RAISERROR('Populating Warnings column for stored procedures', 0, 1) WITH NOWAIT;
WITH statement_warnings AS 
	(
SELECT  DISTINCT
		SqlHandle,
		Warnings = 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 (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.missing_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END +
                  CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.unmatched_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END +                  
                  CASE WHEN is_cursor = 1 THEN ', Cursor' 
							+ CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END
							+ CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END
							+ CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END
                            + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' 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 > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') 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), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + 
				  CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' 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 ', >= 5 Indexes Modified' ELSE '' END +
				  CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + 
				  CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END +
				  CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END +
				  CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + 
				  CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + 
				  CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + 
				  CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END +
				  CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END +
				  CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END +
				  CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END +
				  CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END +
				  CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END +
				  CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END  +
				  CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + 
				  CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + 
                  CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + 
				  CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + 
				  CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + 
                  CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END +
				  CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END +
				  CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END +
				  CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END +
				  CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END
                  , 3, 200000) 
FROM ##BlitzCacheProcs b
WHERE SPID = @@SPID
AND QueryType LIKE 'Statement (parent%'
	)
UPDATE b
SET b.Warnings = s.Warnings
FROM ##BlitzCacheProcs AS b
JOIN statement_warnings s
ON b.SqlHandle = s.SqlHandle
WHERE QueryType LIKE 'Procedure or Function%'
AND SPID = @@SPID
OPTION (RECOMPILE);

RAISERROR('Checking for plans with >128 levels of nesting', 0, 1) WITH NOWAIT;	
WITH plan_handle AS (
SELECT b.PlanHandle
FROM ##BlitzCacheProcs b
   CROSS APPLY sys.dm_exec_text_query_plan(b.PlanHandle, 0, -1) tqp
   CROSS APPLY sys.dm_exec_query_plan(b.PlanHandle) qp
   WHERE tqp.encrypted = 0
   AND b.SPID = @@SPID
   AND (qp.query_plan IS NULL
			AND tqp.query_plan IS NOT NULL)
)
UPDATE b
SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' 
                        , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans')
FROM ##BlitzCacheProcs b
LEFT JOIN plan_handle ph ON
b.PlanHandle = ph.PlanHandle
WHERE b.QueryPlan IS NULL
AND b.SPID = @@SPID
OPTION (RECOMPILE);			  

RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT;	
UPDATE ##BlitzCacheProcs
SET Warnings = 'No warnings detected. ' + CASE @ExpertMode 
											WHEN 0 
											THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' 
											ELSE '' 
										  END
WHERE Warnings = '' OR	Warnings IS NULL
AND SPID = @@SPID
OPTION (RECOMPILE);



/* Artificial Intelligence: Like Sony Aibo, But For Your Database */
IF @AI >= 1
BEGIN
    RAISERROR('Building AI prompts for query plans', 0, 1) WITH NOWAIT;
    
    /* Update ai_prompt column with query metrics for rows that have query plans */
    UPDATE p
    SET ai_prompt = N'Here are the performance metrics we are seeing in production, as measured by the plan cache:

Database: ' + ISNULL(DatabaseName, N'Unknown') + N'
Query Type: ' + ISNULL(QueryType, N'Unknown') + N'
Execution Count: ' + ISNULL(CAST(ExecutionCount AS NVARCHAR(30)), N'N/A') + N'
Executions Per Minute: ' + ISNULL(CAST(ExecutionsPerMinute AS NVARCHAR(30)), N'N/A') + N'

CPU Metrics:
- Total CPU (ms): ' + ISNULL(CAST(TotalCPU AS NVARCHAR(30)), N'N/A') + N'
- Average CPU (ms): ' + ISNULL(CAST(AverageCPU AS NVARCHAR(30)), N'N/A') + N'
- Min Worker Time (ms): ' + ISNULL(CAST(min_worker_time AS NVARCHAR(30)), N'N/A') + N'
- Max Worker Time (ms): ' + ISNULL(CAST(max_worker_time AS NVARCHAR(30)), N'N/A') + N'

Duration Metrics:
- Total Duration (ms): ' + ISNULL(CAST(TotalDuration AS NVARCHAR(30)), N'N/A') + N'
- Average Duration (ms): ' + ISNULL(CAST(AverageDuration AS NVARCHAR(30)), N'N/A') + N'
- Min Elapsed Time (ms): ' + ISNULL(CAST(min_elapsed_time AS NVARCHAR(30)), N'N/A') + N'
- Max Elapsed Time (ms): ' + ISNULL(CAST(max_elapsed_time AS NVARCHAR(30)), N'N/A') + N'

I/O Metrics:
- Total Reads: ' + ISNULL(CAST(TotalReads AS NVARCHAR(30)), N'N/A') + N'
- Average Reads: ' + ISNULL(CAST(AverageReads AS NVARCHAR(30)), N'N/A') + N'
- Total Writes: ' + ISNULL(CAST(TotalWrites AS NVARCHAR(30)), N'N/A') + N'
- Average Writes: ' + ISNULL(CAST(AverageWrites AS NVARCHAR(30)), N'N/A') + N'

Row Statistics:
- Total Returned Rows: ' + ISNULL(CAST(TotalReturnedRows AS NVARCHAR(30)), N'N/A') + N'
- Average Returned Rows: ' + ISNULL(CAST(AverageReturnedRows AS NVARCHAR(30)), N'N/A') + N'
- Min Returned Rows: ' + ISNULL(CAST(MinReturnedRows AS NVARCHAR(30)), N'N/A') + N'
- Max Returned Rows: ' + ISNULL(CAST(MaxReturnedRows AS NVARCHAR(30)), N'N/A') + N'
- Estimated Rows: ' + ISNULL(CAST(estimated_rows AS NVARCHAR(30)), N'N/A') + N'

Memory Grant Info:
- Min Grant KB: ' + ISNULL(CAST(MinGrantKB AS NVARCHAR(30)), N'N/A') + N'
- Max Grant KB: ' + ISNULL(CAST(MaxGrantKB AS NVARCHAR(30)), N'N/A') + N'
- Min Used Grant KB: ' + ISNULL(CAST(MinUsedGrantKB AS NVARCHAR(30)), N'N/A') + N'
- Max Used Grant KB: ' + ISNULL(CAST(MaxUsedGrantKB AS NVARCHAR(30)), N'N/A') + N'
- Percent Memory Grant Used: ' + ISNULL(CAST(PercentMemoryGrantUsed AS NVARCHAR(30)), N'N/A') + N'%

Spill Info:
- Min Spills: ' + ISNULL(CAST(MinSpills AS NVARCHAR(30)), N'N/A') + N'
- Max Spills: ' + ISNULL(CAST(MaxSpills AS NVARCHAR(30)), N'N/A') + N'
- Total Spills: ' + ISNULL(CAST(TotalSpills AS NVARCHAR(30)), N'N/A') + N'
- Avg Spills: ' + ISNULL(CAST(AvgSpills AS NVARCHAR(30)), N'N/A') + N'

Plan Info:
- Query Plan Cost: ' + ISNULL(CAST(QueryPlanCost AS NVARCHAR(30)), N'N/A') + N'
- Plan Creation Time: ' + ISNULL(CONVERT(NVARCHAR(30), PlanCreationTime, 120), N'N/A') + N'
- Plan Age (hours): ' + ISNULL(CAST(PlanCreationTimeHours AS NVARCHAR(30)), N'N/A') + N'
- Last Execution Time: ' + ISNULL(CONVERT(NVARCHAR(30), LastExecutionTime, 120), N'N/A') + N'
- Number of Plans: ' + ISNULL(CAST(NumberOfPlans AS NVARCHAR(30)), N'N/A') + N'
- Number of Distinct Plans: ' + ISNULL(CAST(NumberOfDistinctPlans AS NVARCHAR(30)), N'N/A') + N'
- Is Parallel: ' + CASE WHEN is_parallel = 1 THEN N'Yes' ELSE N'No' END + N'
- Is Trivial Plan: ' + CASE WHEN is_trivial = 1 THEN N'Yes' ELSE N'No' END + N'

Here are the warnings that popular query analysis tool sp_BlitzCache detected and suggested that we focus on - although there may be more issues, too: ' + ISNULL(Warnings, N'None') + N'

Query Text (which is cut off for long queries):
' + ISNULL(LEFT(QueryText, 4000), N'N/A') + N'

' + CASE WHEN QueryType LIKE N'Statement (parent%' THEN N' The above query is part of a batch, stored procedure, or function, so other queries may show up in the query plan. However, those other queries are irrelevant here. Focus on this specific query above, because it is one of the most resource-intensive queries in the batch. The execution plan below includes other statements in the batch, but ignore those and focus only the query above and its specific plan in the batch below. ' ELSE N' ' END + N'

XML Execution Plan:
' + ISNULL(CAST(QueryPlan AS NVARCHAR(MAX)), N'N/A') + N'

Thank you.'
    FROM ##BlitzCacheProcs p
    WHERE p.SPID = @@SPID
    AND p.QueryPlan IS NOT NULL
    AND NOT (p.QueryType LIKE 'Procedure or Function:%'     /* This and the below exists query makes sure that we don't get advice for parent procs, only their statements, if the statements are in our result set. */
        AND EXISTS
        (
            SELECT 1
            FROM ##BlitzCacheProcs AS S
            WHERE
                S.SPID         = p.SPID
                AND S.DatabaseName = p.DatabaseName
                AND S.PlanHandle   = p.PlanHandle
                AND S.QueryType LIKE 'Statement (parent %'
                AND
                    /* Procedure name from "Procedure or Function: [dbo].[usp_X]" */
                    LTRIM(RTRIM(SUBSTRING(
                        p.QueryType,
                        CHARINDEX(':', p.QueryType) + 1,
                        8000
                    ))) =
                    /* Procedure name from "Statement (parent [dbo].[usp_X])" */
                    LTRIM(RTRIM(SUBSTRING(
                        S.QueryType,
                        LEN('Statement (parent ') + 1,
                        CHARINDEX(')', S.QueryType, LEN('Statement (parent ') + 1)
                          - (LEN('Statement (parent ') + 1)
                    )))
        )
    )
    OPTION (RECOMPILE);

    IF @Debug = 2
        SELECT 'After setting up ai_prompt, before calling AI' AS ai_stage, SqlHandle, QueryHash, PlanHandle, QueryPlan, ai_prompt, ai_advice, ai_raw_response
            FROM ##BlitzCacheProcs
            WHERE SPID = @@SPID;
        
    IF @AI = 1
    BEGIN
        RAISERROR('Calling AI endpoint for query plan analysis - starting loop', 0, 1) WITH NOWAIT;

        DECLARE @CurrentSqlHandle VARBINARY(64);
        DECLARE @CurrentQueryHash BINARY(8);
        DECLARE @CurrentPlanHandle VARBINARY(64);
        DECLARE @CurrentAIPrompt NVARCHAR(MAX);
        DECLARE @CurrentQueryClipped NVARCHAR(200);
        DECLARE @AIResponseJSON NVARCHAR(MAX);
        DECLARE @AIReturnValue INT;
        DECLARE @AIErrorMessage NVARCHAR(4000);
        
        DECLARE ai_cursor CURSOR LOCAL FAST_FORWARD FOR
        SELECT DISTINCT SqlHandle, QueryHash, PlanHandle, ai_prompt, COALESCE(QueryType, N'') + N' - ' + LEFT(QueryText, 100)
        FROM ##BlitzCacheProcs
        WHERE SPID = @@SPID
        AND QueryPlan IS NOT NULL
        AND ai_prompt IS NOT NULL;
        
        OPEN ai_cursor;
        
        FETCH NEXT FROM ai_cursor INTO @CurrentSqlHandle, @CurrentQueryHash, @CurrentPlanHandle, @CurrentAIPrompt, @CurrentQueryClipped;
        
        WHILE @@FETCH_STATUS = 0
        BEGIN
            BEGIN TRY
                SET @AIResponseJSON = NULL;
                SET @AIResponse = NULL;
                
                /* Build payload using the template. */
                SET @AIPayload = REPLACE(@AIPayloadTemplate, N'@AIModel', @AIModel);
                SET @AIPayload = REPLACE(@AIPayload, N'@AISystemPrompt',  REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@AISystemPrompt, '\', '\\'), '"', '\"'), CHAR(13), '\r'), CHAR(10), '\n'), CHAR(9), '\t'));
                SET @AIPayload = REPLACE(@AIPayload, N'@CurrentAIPrompt',  REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@CurrentAIPrompt, '\', '\\'), '"', '\"'), CHAR(13), '\r'), CHAR(10), '\n'), CHAR(9), '\t'));
                --SET @AIPayload = REPLACE(@AIPayload, N'@CurrentAIPrompt', @CurrentAIPrompt);
                
                IF @Debug = 2
                BEGIN
                    SELECT @CurrentQueryClipped AS CurrentQueryClipped, @AIPayload AS AIPayload, 
                        LEN(@AIPayload) AS AIPayload_Length, DATALENGTH(@AIPayload) AS AIPayload_DataLength;
                END;
                
                RAISERROR('Calling AI endpoint for query plan analysis on query: ', 0, 1) WITH NOWAIT;
                RAISERROR(@CurrentQueryClipped, 0, 1) WITH NOWAIT;

                EXEC @AIReturnValue = sp_invoke_external_rest_endpoint
                    @url = @AIURL,
                    @method = 'POST',
                    @payload = @AIPayload,
                    @headers = N'{"Content-Type":"application/json"}',
                    @credential = @AICredential,
                    @timeout = @AITimeoutSeconds,
                    @response = @AIResponseJSON OUTPUT;

                
                IF @Debug = 2
                BEGIN
                    PRINT N'API Response (first 4000 chars): ' + @nl + LEFT(ISNULL(@AIResponseJSON, N'NULL'), 4000);
                END;

                /* Parse the response to extract the AI's advice
                OpenAI format: {"choices":[{"message":{"content":"..."}}]} */
                IF @AIResponseJSON IS NOT NULL
                BEGIN
                    /* Try OpenAI ChatGPT chat completion by default: */
                    SET @AIAdviceText = (SELECT c.Content
                        FROM OPENJSON(@AIResponseJSON, '$.result.choices')
                        WITH (
                            Content nvarchar(max) '$.message.content'
                        ) AS c);

                    /* No data? How about Google Gemini: */
                    IF @AIAdviceText IS NULL
                        SET @AIAdviceText = (SELECT TOP 1 p.[text]
                            FROM OPENJSON(@AIResponseJSON, '$.result.candidates') AS cand
                            CROSS APPLY OPENJSON(cand.value, '$.content.parts')
                                   WITH ([text] nvarchar(max) '$.text') AS p);


                    /* If we still couldn't parse it, check for error codes */
                    IF @AIAdviceText IS NULL
                    BEGIN
                        DECLARE @ErrorMessage NVARCHAR(MAX);
                        SELECT @ErrorMessage = JSON_VALUE(@AIResponseJSON, '$.result.error.message');
                    
                        IF @ErrorMessage IS NULL
                            SELECT @ErrorMessage = JSON_VALUE(@AIResponseJSON, '$.error.message');
                    
                        IF @ErrorMessage IS NOT NULL
                            SET @AIAdviceText = N'API Error: ' + @ErrorMessage;
                        ELSE
                            SET @AIAdviceText = N'Unable to parse API response. Raw response stored for debugging.';
                    END;
                END
                ELSE
                BEGIN
                    SET @AIAdviceText = N'No response received from AI service.';
                END;

                /* Store the response in the the ai_advice column */
                UPDATE ##BlitzCacheProcs
                SET ai_advice = @AIAdviceText, ai_raw_response = @AIResponseJSON, ai_payload = @AIPayload
                WHERE SPID = @@SPID
                AND ((@CurrentSqlHandle IS NOT NULL AND SqlHandle = @CurrentSqlHandle)
                     OR (@CurrentSqlHandle IS NULL AND SqlHandle IS NULL))
                AND ((@CurrentQueryHash IS NOT NULL AND QueryHash = @CurrentQueryHash)
                     OR (@CurrentQueryHash IS NULL AND QueryHash IS NULL))
                AND ((@CurrentPlanHandle IS NOT NULL AND PlanHandle = @CurrentPlanHandle)
                     OR (@CurrentPlanHandle IS NULL AND PlanHandle IS NULL))
                OPTION (RECOMPILE);

            END TRY
            BEGIN CATCH
                SET @AIErrorMessage = N'Error calling AI service: ' + ERROR_MESSAGE();
            
                IF @Debug = 1
                BEGIN
                    PRINT @AIErrorMessage;
                END;

                -- Store the error message in ai_advice so the user knows what happened
                UPDATE ##BlitzCacheProcs
                SET ai_advice = @AIErrorMessage, ai_raw_response = @AIResponseJSON, ai_payload = @AIPayload
                WHERE SPID = @@SPID
                AND ((@CurrentSqlHandle IS NOT NULL AND SqlHandle = @CurrentSqlHandle)
                     OR (@CurrentSqlHandle IS NULL AND SqlHandle IS NULL))
                AND ((@CurrentQueryHash IS NOT NULL AND QueryHash = @CurrentQueryHash)
                     OR (@CurrentQueryHash IS NULL AND QueryHash IS NULL))
                AND ((@CurrentPlanHandle IS NOT NULL AND PlanHandle = @CurrentPlanHandle)
                     OR (@CurrentPlanHandle IS NULL AND PlanHandle IS NULL))
                OPTION (RECOMPILE);
            END CATCH;

            FETCH NEXT FROM ai_cursor INTO @CurrentSqlHandle, @CurrentQueryHash, @CurrentPlanHandle, @CurrentAIPrompt, @CurrentQueryClipped;
        END;

        CLOSE ai_cursor;
        DEALLOCATE ai_cursor;

        RAISERROR('AI analysis complete', 0, 1) WITH NOWAIT;
                        
        IF @Debug = 2
            SELECT 'After Calling AI' AS ai_stage, SqlHandle, QueryHash, PlanHandle, QueryPlan, ai_prompt, ai_advice, ai_raw_response
                FROM ##BlitzCacheProcs
                WHERE SPID = @@SPID;
        
    END;
    ELSE
    BEGIN
        /* @AI = 2: Just update ai_advice to indicate prompt-only mode */
        UPDATE ##BlitzCacheProcs
        SET ai_advice = N'AI prompt generated but not sent (running with @AI = 2). Review the ai_prompt column for the prompt that would be sent.'
        WHERE SPID = @@SPID
        AND QueryPlan IS NOT NULL
        OPTION (RECOMPILE);
    END;
END;







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

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

    SET @sql = N'
	SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    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,
			MinSpills, 
			MaxSpills, 
			TotalSpills, 
			AvgSpills,
            NumberOfPlans,
            NumberOfDistinctPlans,
            PlanCreationTime AS [Created At],
            LastExecutionTime AS [Last Execution],
            StatementStartOffset,
            StatementEndOffset,
			PlanGenerationNum,
			PlanHandle AS [Plan Handle],  
			SqlHandle AS [SQL Handle],  
            QueryHash,
            QueryPlanHash,
            COALESCE(SetOptions, '''') AS [SET Options]
    FROM    ##BlitzCacheProcs
    WHERE   1 = 1 
	AND SPID = @@SPID ' + @nl;

    IF @MinimumExecutionCount IS NOT NULL
      BEGIN
		SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ';
	  END;
	
   IF @MinutesBack IS NOT NULL
      BEGIN
		SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ';
	  END;

	SELECT @sql += N' ORDER BY ' + 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'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB'
							  WHEN N'spills' THEN N' MaxSpills'
							  WHEN N'duplicate' THEN N' plan_multiple_plans '	/* Issue #3345 */
                              WHEN N'avg cpu' THEN N' AverageCPU'
                              WHEN N'avg reads' THEN N' AverageReads'
                              WHEN N'avg writes' THEN N' AverageWrites'
                              WHEN N'avg duration' THEN N' AverageDuration'
                              WHEN N'avg executions' THEN N' ExecutionsPerMinute'
							  WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant'
							  WHEN N'avg spills' THEN N' AvgSpills'
                              END + N' DESC ';

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

	IF @sql IS NULL
		BEGIN
		RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT;
		END

	IF @Debug = 1
	BEGIN
		RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT;
	    PRINT SUBSTRING(@sql, 0, 4000);
	    PRINT SUBSTRING(@sql, 4000, 8000);
	    PRINT SUBSTRING(@sql, 8000, 12000);
	    PRINT SUBSTRING(@sql, 12000, 16000);
	    PRINT SUBSTRING(@sql, 16000, 20000);
	    PRINT SUBSTRING(@sql, 20000, 24000);
	    PRINT SUBSTRING(@sql, 24000, 28000);
	    PRINT SUBSTRING(@sql, 28000, 32000);
	    PRINT SUBSTRING(@sql, 32000, 36000);
	    PRINT SUBSTRING(@sql, 36000, 40000);
	END;

    EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @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],
	QueryPlan AS [Query Plan],
	missing_indexes AS [Missing Indexes],
	implicit_conversion_info AS [Implicit Conversion Info],
	cached_execution_parameters AS [Cached Execution Parameters],
    CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions],
    CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute],
    CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight],
    CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)],
    CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)],
    CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight],
    CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)],
    CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)],
    CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight],
    CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads],
    CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Avg Reads],
    CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight],
    CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes],
    CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Avg Writes],
    CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight],
    CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Average Rows],
	CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB],
	CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB],
	CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], 
	CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB],
	CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant],
	CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills],
	CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills],
	CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills],
	CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills],
    PlanCreationTime AS [Created At],
    LastExecutionTime AS [Last Execution],
	LastCompletionTime AS [Last Completion],
	PlanHandle AS [Plan Handle], 
	SqlHandle AS [SQL Handle], 
    COALESCE(SetOptions, '''') AS [SET Options],
	QueryHash AS [Query Hash],
	PlanGenerationNum,
	[Remove Plan Handle From Cache]';
END;
ELSE
BEGIN
    SET @columns = N' DatabaseName AS [Database],
		QueryPlanCost AS [Cost],
        QueryText AS [Query Text],
        QueryType AS [Query Type],
        Warnings AS [Warnings], 
		QueryPlan AS [Query Plan], 
		missing_indexes AS [Missing Indexes],
		implicit_conversion_info AS [Implicit Conversion Info],
		cached_execution_parameters AS [Cached Execution Parameters], '
        + CASE WHEN @AI = 2 THEN N'
        [AI Prompt] = (
            SELECT (@AISystemPrompt + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) + ai_prompt) AS [text()] FOR XML PATH(''ai_prompt''), TYPE),' ELSE N'' END
        + CASE WHEN @AI = 1 THEN N'
        [AI Advice] = CASE WHEN ai_advice IS NULL THEN NULL ELSE (
            SELECT ai_advice AS [text()] FOR XML PATH(''ai_advice''), TYPE) END, ' ELSE N'' END
        + @nl;

    IF @ExpertMode = 2 /* Opserver */
    BEGIN
        RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT;
		SET @columns += N'        
				  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 > 0 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 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 +
				  CASE WHEN is_spatial = 1 THEN '', 47'' ELSE '''' END +
				  CASE WHEN index_dml = 1 THEN '', 48'' ELSE '''' END +
				  CASE WHEN table_dml = 1 THEN '', 49'' ELSE '''' END + 
				  CASE WHEN long_running_low_cpu = 1 THEN '', 50'' ELSE '''' END +
				  CASE WHEN low_cost_high_cpu = 1 THEN '', 51'' ELSE '''' END + 
				  CASE WHEN stale_stats = 1 THEN '', 52'' ELSE '''' END +
				  CASE WHEN is_adaptive = 1 THEN '', 53'' ELSE '''' END	+
				  CASE WHEN is_spool_expensive = 1 THEN + '', 54'' ELSE '''' END +
				  CASE WHEN is_spool_more_rows = 1 THEN + '', 55'' ELSE '''' END  +
				  CASE WHEN is_table_spool_expensive = 1 THEN + '', 67'' ELSE '''' END +
				  CASE WHEN is_table_spool_more_rows = 1 THEN + '', 68'' ELSE '''' END  +
				  CASE WHEN is_bad_estimate = 1 THEN + '', 56'' ELSE '''' END  +
				  CASE WHEN is_paul_white_electric = 1 THEN '', 57'' ELSE '''' END + 
				  CASE WHEN is_row_goal = 1 THEN '', 58'' ELSE '''' END + 
                  CASE WHEN is_big_spills = 1 THEN '', 59'' ELSE '''' END +
				  CASE WHEN is_mstvf = 1 THEN '', 60'' ELSE '''' END + 
				  CASE WHEN is_mm_join = 1 THEN '', 61'' ELSE '''' END  + 
                  CASE WHEN is_nonsargable = 1 THEN '', 62'' ELSE '''' END + 
				  CASE WHEN CompileTime > 5000 THEN '', 63 '' ELSE '''' END +
				  CASE WHEN CompileCPU > 5000 THEN '', 64 '' ELSE '''' END +
				  CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN '', 65 '' ELSE '''' END +
				  CASE WHEN select_with_writes > 0 THEN '', 66'' ELSE '''' END
				  , 3, 200000) AS opserver_warning , ' + @nl ;
    END;
    
    SET @columns += N'        
        CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions],
        CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute],
        CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight],
        CONVERT(NVARCHAR(30), CAST((SerialDesiredMemory) AS BIGINT), 1) AS [Serial Desired Memory],
        CONVERT(NVARCHAR(30), CAST((SerialRequiredMemory) AS BIGINT), 1) AS [Serial Required Memory],
        CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)],
        CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)],
        CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight],
        CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)],
        CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)],
        CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight],
        CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads],
        CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Average Reads],
        CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight],
        CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes],
        CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Average Writes],
        CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight],
        CONVERT(NVARCHAR(30), CAST((PercentExecutionsByType) AS BIGINT), 1) AS [% Executions (Type)],
        CONVERT(NVARCHAR(30), CAST((PercentCPUByType) AS BIGINT), 1) AS [% CPU (Type)],
        CONVERT(NVARCHAR(30), CAST((PercentDurationByType) AS BIGINT), 1) AS [% Duration (Type)],
        CONVERT(NVARCHAR(30), CAST((PercentReadsByType) AS BIGINT), 1) AS [% Reads (Type)],
        CONVERT(NVARCHAR(30), CAST((PercentWritesByType) AS BIGINT), 1) AS [% Writes (Type)],
        CONVERT(NVARCHAR(30), CAST((TotalReturnedRows) AS BIGINT), 1) AS [Total Rows],
        CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Avg Rows],
        CONVERT(NVARCHAR(30), CAST((MinReturnedRows) AS BIGINT), 1) AS [Min Rows],
        CONVERT(NVARCHAR(30), CAST((MaxReturnedRows) AS BIGINT), 1) AS [Max Rows],
		CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB],
		CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB],
		CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], 
		CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB],
		CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant],
		CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills],
		CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills],
		CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills],
		CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills],
        CONVERT(NVARCHAR(30), CAST((NumberOfPlans) AS BIGINT), 1) AS [# Plans],
        CONVERT(NVARCHAR(30), CAST((NumberOfDistinctPlans) AS BIGINT), 1) AS [# Distinct Plans],
        PlanCreationTime AS [Created At],
        LastExecutionTime AS [Last Execution],
		LastCompletionTime AS [Last Completion],
        CONVERT(NVARCHAR(30), CAST((CachedPlanSize) AS BIGINT), 1) AS [Cached Plan Size (KB)],
        CONVERT(NVARCHAR(30), CAST((CompileTime) AS BIGINT), 1) AS [Compile Time (ms)],
        CONVERT(NVARCHAR(30), CAST((CompileCPU) AS BIGINT), 1) AS [Compile CPU (ms)],
        CONVERT(NVARCHAR(30), CAST((CompileMemory) AS BIGINT), 1) 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,
		PlanGenerationNum, '
        + CASE WHEN @AI <> 2 THEN N'
        [AI Prompt] = (
            SELECT (@AISystemPrompt + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) + ai_prompt) AS [text()] FOR XML PATH(''ai_prompt''), TYPE),' ELSE N'' END
        + CASE WHEN @AI = 1 THEN N'
        [AI Payload] = CASE WHEN ai_payload IS NULL THEN NULL ELSE (
            SELECT ai_payload AS [text()] FOR XML PATH(''ai_payload''), TYPE) END,
        [AI Raw Response] = CASE WHEN ai_raw_response IS NULL THEN NULL ELSE (
            SELECT ai_raw_response AS [text()] FOR XML PATH(''ai_raw_response''), TYPE) END, ' ELSE N'' END + N'
		[Remove Plan Handle From Cache],
		[Remove SQL Handle From Cache]';
END;

SET @sql = N'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT  TOP (@Top) ' + @columns + @nl + N'
FROM    ##BlitzCacheProcs
WHERE   SPID = @spid ' + @nl;

IF @MinimumExecutionCount IS NOT NULL
	BEGIN
		SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl;
	END;

IF @MinutesBack IS NOT NULL
    BEGIN
		SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ' + @nl;
    END;

SELECT @sql += N' ORDER BY ' + 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'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB '
                                                WHEN N'duplicate' THEN N' plan_multiple_plans '
												WHEN N'spills' THEN N' MaxSpills '
                                                WHEN N'avg cpu' THEN N' AverageCPU'
                                                WHEN N'avg reads' THEN N' AverageReads'
                                                WHEN N'avg writes' THEN N' AverageWrites'
                                                WHEN N'avg duration' THEN N' AverageDuration'
                                                WHEN N'avg executions' THEN N' ExecutionsPerMinute'
												WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant'
												WHEN N'avg spills' THEN N' AvgSpills'
                               END + N' DESC ';
SET @sql += N' OPTION (RECOMPILE) ; ';

IF @Debug = 1
    BEGIN
        PRINT SUBSTRING(@sql, 0, 4000);
        PRINT SUBSTRING(@sql, 4000, 8000);
        PRINT SUBSTRING(@sql, 8000, 12000);
        PRINT SUBSTRING(@sql, 12000, 16000);
        PRINT SUBSTRING(@sql, 16000, 20000);
        PRINT SUBSTRING(@sql, 20000, 24000);
        PRINT SUBSTRING(@sql, 24000, 28000);
        PRINT SUBSTRING(@sql, 28000, 32000);
        PRINT SUBSTRING(@sql, 32000, 36000);
        PRINT SUBSTRING(@sql, 36000, 40000);
    END;
IF(@OutputType <> 'NONE')
BEGIN 
    EXEC  sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT, @AISystemPrompt NVARCHAR(4000)', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack, @AISystemPrompt;
END;

/*

This section will check if:
 * >= 30% of plans were created in the last hour
 * Check on the memory_clerks DMV for space used by TokenAndPermUserStore
 * Compare that to the size of the buffer pool
 * If it's >10%, 
*/
IF EXISTS
(
    SELECT 1/0
    FROM #plan_creation AS pc
    WHERE pc.percent_1 >= 30
)
BEGIN

SELECT @common_version =
           CONVERT(DECIMAL(10,2), c.common_version)
FROM #checkversion AS c;

IF @common_version >= 11
	SET @user_perm_sql = N'
	SET @buffer_pool_memory_gb = 0;
	SELECT @buffer_pool_memory_gb = SUM(pages_kb)/ 1024. / 1024.
	FROM sys.dm_os_memory_clerks
	WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';'
ELSE
	SET @user_perm_sql = N'
	SET @buffer_pool_memory_gb = 0;
	SELECT @buffer_pool_memory_gb = SUM(single_pages_kb + multi_pages_kb)/ 1024. / 1024.
	FROM sys.dm_os_memory_clerks
	WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';'

EXEC sys.sp_executesql @user_perm_sql,
	N'@buffer_pool_memory_gb DECIMAL(10,2) OUTPUT',
	@buffer_pool_memory_gb = @buffer_pool_memory_gb OUTPUT;

IF @common_version >= 11
BEGIN
    SET @user_perm_sql = N'
    	SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024.0 / 1024.) >= 2.
    			                    THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024.0 / 1024.))
    			                    ELSE 0 
    		                   END
    	FROM sys.dm_os_memory_clerks
    	WHERE type = ''USERSTORE_TOKENPERM''
    	AND   name = ''TokenAndPermUserStore'';';
END;

IF @common_version < 11
BEGIN
    SET @user_perm_sql = N'
    	SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2.
    			                    THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb)  / 1024.0 / 1024.))
    			                    ELSE 0 
    		                   END
    	FROM sys.dm_os_memory_clerks
    	WHERE type = ''USERSTORE_TOKENPERM''
    	AND   name = ''TokenAndPermUserStore'';';
END;

EXEC sys.sp_executesql @user_perm_sql, 
                       N'@user_perm_gb DECIMAL(10,2) OUTPUT', 
					   @user_perm_gb = @user_perm_gb_out OUTPUT;

IF @buffer_pool_memory_gb > 0
	BEGIN
	IF (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100. >= 10
		BEGIN
			SET @is_tokenstore_big = 1;
			SET @user_perm_percent = (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100.;
		END
	END

END



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   ##BlitzCacheProcs
                   WHERE frequent_execution = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    1,
                    100,
                    'Execution Pattern',
                    'Frequent Execution',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  parameter_sniffing = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    2,
                    50,
                    'Parameterization',
                    'Parameter Sniffing',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  is_forced_plan = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    3,
                    50,
                    'Parameterization',
                    'Forced Plan',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  is_cursor = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    4,
                    200,
                    'Cursors',
                    'Cursor',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  is_cursor = 1
				   AND is_optimistic_cursor = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    4,
                    200,
                    'Cursors',
                    'Optimistic Cursors',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  is_cursor = 1
				   AND is_forward_only_cursor = 0
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    4,
                    200,
                    'Cursors',
                    'Non-forward Only Cursors',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  is_cursor = 1
				   AND is_cursor_dynamic = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    4,
                    200,
                    'Cursors',
                    'Dynamic Cursors',
                    'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/',
                    'Dynamic Cursors inhibit parallelism!.');

        IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs
                   WHERE  is_cursor = 1
				   AND is_fast_forward_cursor = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    4,
                    200,
                    'Cursors',
                    'Fast Forward Cursors',
                    'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/',
                    'Fast forward cursors inhibit parallelism!.');

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

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

        IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs p
                   WHERE  near_parallel = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    7,
                    200,
                    'Execution Plans',
                    'Nearly Parallel',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  plan_warnings = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    8,
                    50,
                    'Execution Plans',
                    'Plan Warnings',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  long_running = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    9,
                    50,
                    'Performance',
                    'Long Running Query',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.missing_index_count > 0
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    10,
                    50,
                    'Performance',
                    'Missing Indexes',
                    'https://www.brentozar.com/blitzcache/missing-index-request/',
                    'Queries found with missing indexes.');

        IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs p
                   WHERE  p.downlevel_estimator = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    13,
                    200,
                    'Cardinality',
                    'Downlevel CE',
                    'https://www.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 ##BlitzCacheProcs p
                   WHERE implicit_conversions = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    14,
                    50,
                    'Performance',
                    'Implicit Conversions',
                    'https://www.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   ##BlitzCacheProcs
                   WHERE  busy_loops = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                16,
                100,
                'Performance',
                'Busy Loops',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  tvf_join = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                17,
                50,
                'Performance',
                'Function Join',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  compile_timeout = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                18,
                50,
                'Execution Plans',
                'Compilation Timeout',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  compile_memory_limit_exceeded = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                19,
                50,
                'Execution Plans',
                'Compile Memory Limit Exceeded',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  warning_no_join_predicate = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                20,
                50,
                'Execution Plans',
                'No Join Predicate',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  plan_multiple_plans > 0
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                21,
                200,
                'Execution Plans',
                'Multiple Plans',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  unmatched_index_count > 0
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                22,
                100,
                'Performance',
                'Unmatched Indexes',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  unparameterized_query = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                23,
                100,
                'Parameterization',
                'Unparameterized Query',
                'https://www.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   ##BlitzCacheProcs
                   WHERE  is_trivial = 1
				   AND SPID = @@SPID)
        INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
        VALUES (@@SPID,
                24,
                100,
                'Execution Plans',
                'Trivial Plans',
                'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.is_forced_serial= 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    25,
                    10,
                    'Execution Plans',
                    'Forced Serialization',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.is_key_lookup_expensive= 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    26,
                    100,
                    'Execution Plans',
                    'Expensive Key Lookup',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.is_remote_query_expensive= 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    28,
                    100,
                    'Execution Plans',
                    'Expensive Remote Query',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.trace_flags_session IS NOT NULL
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    29,
                    200,
                    'Trace Flags',
                    'Session Level Trace Flags Enabled',
                    'https://www.brentozar.com/blitz/trace-flags-enabled-globally/',
                    'Someone is enabling session level Trace Flags in a query.') ;

        IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs p
                   WHERE  p.is_unused_grant IS NOT NULL
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    30,
                    100,
                    'Memory Grant',
                    'Unused Memory Grant',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.function_count > 0
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    31,
                    100,
                    'Compute Scalar That References A Function',
                    'Calls Functions',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.clr_function_count > 0
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    32,
                    100,
                    'Compute Scalar That References A CLR Function',
                    'Calls CLR Functions',
                    'https://www.brentozar.com/blitzcache/compute-scalar-functions/',
                    'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ;


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

        IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs p
                   WHERE  p.no_stats_warning = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    35,
                    100,
                    'Statistics',
                    'Columns With No Statistics',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.relop_warnings = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    36,
                    100,
                    'Warnings',
					'Operator Warnings',
                    'https://www.brentozar.com/blitzcache/query-plan-warnings/',
                    'Check the plan for more details.') ;

        IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs p
                   WHERE  p.is_table_scan = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    37,
                    100,
                    'Indexes',
                    'Table Scans (Heaps)',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.backwards_scan = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    38,
                    200,
                    'Indexes',
                    'Backwards Scans',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.forced_index = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    39,
                    100,
                    'Indexes',
                    'Forced Indexes',
                    'https://www.brentozar.com/blitzcache/optimizer-forcing/',
                    'This can cause inefficient plans, and will prevent missing index requests.') ;

		IF EXISTS (SELECT 1/0
                   FROM   ##BlitzCacheProcs p
                   WHERE  p.forced_seek = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    40,
                    100,
                    'Indexes',
					'Forced Seeks',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.forced_scan = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    40,
                    100,
                    'Indexes',
                    'Forced Scans',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.columnstore_row_mode = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    41,
                    100,
                    'Indexes',
                    'ColumnStore Row Mode',
                    'https://www.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   ##BlitzCacheProcs p
                   WHERE  p.is_computed_scalar = 1
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    42,
                    50,
                    'Functions',
                    'Computed Column UDF',
                    'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/',
                    'This can cause a whole mess of bad serializartion problems.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_sort_expensive = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     43,
                     100,
                     'Execution Plans',
                     'Expensive Sort',
                     'https://www.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   ##BlitzCacheProcs p
                    WHERE  p.is_computed_filter = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     44,
                     50,
                     'Functions',
                     'Filter UDF',
                     'https://www.brentozar.com/blitzcache/compute-scalar-functions/',
                     'Someone put a Scalar UDF in the WHERE clause!') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.index_ops >= 5
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     45,
                     100,
                     'Indexes',
                     '>= 5 Indexes Modified',
                     'https://www.brentozar.com/blitzcache/many-indexes-modified/',
                     'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_row_level = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     46,
                     200,
                     'Complexity',
                     'Row Level Security',
                     'https://www.brentozar.com/blitzcache/row-level-security/',
                     'You may see a lot of confusing junk in your query plan.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_spatial = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     47,
                     200,
                     'Complexity',
                     'Spatial Index',
                     'https://www.brentozar.com/blitzcache/spatial-indexes/',
                     'Purely informational.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.index_dml = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     48,
                     150,
                     'Complexity',
                     'Index DML',
                     'https://www.brentozar.com/blitzcache/index-dml/',
                     'This can cause recompiles and stuff.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.table_dml = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     49,
                     150,
                     'Complexity',
					 'Table DML',
                     'https://www.brentozar.com/blitzcache/table-dml/',
                     'This can cause recompiles and stuff.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.long_running_low_cpu = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     50,
                     150,
                     'Blocking',
                     'Long Running Low CPU',
                     'https://www.brentozar.com/blitzcache/long-running-low-cpu/',
                     'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.low_cost_high_cpu = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     51,
                     150,
                     'Complexity',
                     'Low Cost Query With High CPU',
                     'https://www.brentozar.com/blitzcache/low-cost-high-cpu/',
                     'This can be a sign of functions or Dynamic SQL that calls black-box code.') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.stale_stats = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     52,
                     150,
                     'Statistics',
                     'Statistics used have > 100k modifications in the last 7 days',
                     'https://www.brentozar.com/blitzcache/stale-statistics/',
                     'Ever heard of updating statistics?') ;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_adaptive = 1
  					AND SPID = @@SPID)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     53,
                     200,
                     'Complexity',
					 'Adaptive joins',
                     'https://www.brentozar.com/blitzcache/adaptive-joins/',
                     'This join will sometimes do seeks, and sometimes do scans.') ;	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_spool_expensive = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     54,
                     150,
                     'Indexes',
                     'Expensive Index Spool',
                     'https://www.brentozar.com/blitzcache/eager-index-spools/',
                     'Check operator predicates and output for index definition guidance') ;	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_spool_more_rows = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     55,
                     150,
                     'Indexes',
					 'Large Index Row Spool',
                     'https://www.brentozar.com/blitzcache/eager-index-spools/',
                     'Check operator predicates and output for index definition guidance') ;
					 
        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_bad_estimate = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     56,
                     100,
                     'Complexity',
                     'Row Estimate Mismatch',
                     'https://www.brentozar.com/blitzcache/bad-estimates/',
                     'Estimated rows are different from average rows by a factor of 10000. This may indicate a performance problem if mismatches occur regularly') ;	
					 					 						 				 
        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_paul_white_electric = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     57,
                     200,
                     'Is Paul White Electric?',
                     'This query has a Switch operator in it!',
                     'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html',
                     'You should email this query plan to Paul: SQLkiwi at gmail dot com') ;	

		IF @v >= 14 OR (@v = 13 AND @build >= 5026)
			BEGIN	

				INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
				SELECT 
				@@SPID,
				997,
				200,
				'Database Level Statistics',
				'The database ' + sa.[Database] + ' last had a stats update on '  + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.LastUpdate))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.ModificationCount)) + ' modifications on average.' AS [Finding],
				'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL,
				'Consider updating statistics more frequently,' AS [Details]
				FROM #stats_agg AS sa
				GROUP BY sa.[Database]
				HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME())
				AND AVG(sa.ModificationCount) >= 100000;

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_row_goal = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     58,
                     200,
                     'Complexity',
					 'Row Goals',
                     'https://www.brentozar.com/go/rowgoals/',
                     'This query had row goals introduced, which can be good or bad, and should be investigated for high read queries.') ;	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_big_spills = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     59,
                     100,
                     'TempDB',
					 '>500mb Spills',
                     'https://www.brentozar.com/blitzcache/tempdb-spills/',
                     'This query spills >500mb to tempdb on average. One way or another, this query didn''t get enough memory') ;	


			END; 

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_mstvf = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     60,
                     100,
                     'Functions',
					 'MSTVFs',
                     'https://www.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   ##BlitzCacheProcs p
                    WHERE  p.is_mm_join = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     61,
                     100,
                     'Complexity',
					 'Many to Many Merge',
                     'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/',
					 'These use secret worktables that could be doing lots of reads. Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.');	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_nonsargable = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     62,
                     50,
                     'Non-SARGable queries',
                     'non-SARGables',
                     'https://www.brentozar.com/blitzcache/non-sargable-predicates/',
					 'Looks for intrinsic functions and expressions as predicates, and leading wildcard LIKE searches.');	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  CompileTime > 5000
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     63,
                     100,
                     'Complexity',
					 'Long Compile Time',
                     'https://www.brentozar.com/blitzcache/high-compilers/',
					 'Queries are taking >5 seconds to compile. This can be normal for large plans, but be careful if they compile frequently');	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  CompileCPU > 5000
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     64,
                     50,
                     'Complexity',
                     'High Compile CPU',
                     'https://www.brentozar.com/blitzcache/high-compilers/',
					 'Queries taking >5 seconds of CPU to compile. If CPU is high and plans like this compile frequently, they may be related');

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  CompileMemory > 1024 
					AND    ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10.
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     65,
                     50,
                     'Complexity',
                     'High Compile Memory',
                     'https://www.brentozar.com/blitzcache/high-compilers/',
					 'Queries taking 10% of Max Compile Memory. If you see high RESOURCE_SEMAPHORE_QUERY_COMPILE waits, these may be related');

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.select_with_writes = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     66,
                     50,
					 'Complexity',
                     'Selects w/ Writes',
                     'https://dba.stackexchange.com/questions/191825/',
					 'This is thrown when reads cause writes that are not already flagged as big spills (2016+) or index spools.');

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_table_spool_expensive = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     67,
                     150,
                     'Expensive Table Spool',
                     'You have a table spool, this is usually a sign that queries are doing unnecessary work',
                     'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools',
                     'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join') ;	

        IF EXISTS (SELECT 1/0
                    FROM   ##BlitzCacheProcs p
                    WHERE  p.is_table_spool_more_rows = 1
  					)
             INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
             VALUES (@@SPID,
                     68,
                     150,
                     'Table Spools Many Rows',
                     'You have a table spool that spools more rows than the query returns',
                     'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools',
                     'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join');

        IF EXISTS (SELECT 1/0
                   FROM   #plan_creation p
                   WHERE (p.percent_24 > 0)
				   AND SPID = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            SELECT SPID,
                    999,
                    CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 1 ELSE 254 END AS Priority,
                    'Plan Cache Information',
                    CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 'Plan Cache Instability' ELSE 'Plan Cache Stability' END AS Finding,
                    'https://www.brentozar.com/archive/2018/07/tsql2sday-how-much-plan-cache-history-do-you-have/',
                    'You have ' + CONVERT(NVARCHAR(10), ISNULL(p.total_plans, 0)) 
								+ ' total plans in your cache, with ' 
								+ CONVERT(NVARCHAR(10), ISNULL(p.percent_24, 0)) 
								+ '% plans created in the past 24 hours, ' 
								+ CONVERT(NVARCHAR(10), ISNULL(p.percent_4, 0)) 
								+ '% created in the past 4 hours, and ' 
								+ CONVERT(NVARCHAR(10), ISNULL(p.percent_1, 0)) 
								+ '% created in the past 1 hour. '
								+ 'When 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   #plan_usage p
                   WHERE  p.percent_duplicate > 5
				   AND spid = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            SELECT spid,
                    999,
                    CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 1 ELSE 254 END AS Priority,
                    'Plan Cache Information',
                    CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 'Many Duplicate Plans' ELSE 'Duplicate Plans' END AS Finding,
					'https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/',
					'You have ' + CONVERT(NVARCHAR(10), p.total_plans)
					            + ' plans in your cache, and '
								+ CONVERT(NVARCHAR(10), p.percent_duplicate)
								+ '% are duplicates with more than 5 entries'
								+ ', meaning similar queries are generating the same plan repeatedly.'
								+ ' Forced Parameterization may fix the issue. To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; '
			FROM #plan_usage AS p ;

        IF EXISTS (SELECT 1/0
                   FROM   #plan_usage p
                   WHERE  p.percent_single > 5
				   AND spid = @@SPID)
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            SELECT spid,
                    999,
                    CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 1 ELSE 254 END AS Priority,
					'Plan Cache Information',
                    CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 'Many Single-Use Plans' ELSE 'Single-Use Plans' END AS Finding,
					'https://www.brentozar.com/blitz/single-use-plans-procedure-cache/',
					'You have ' + CONVERT(NVARCHAR(10), p.total_plans)
					            + ' plans in your cache, and '
								+ CONVERT(NVARCHAR(10), p.percent_single)
								+ '% are single use plans'
								+ ', meaning SQL Server thinks it''s seeing a lot of "new" queries and creating plans for them.'
								+ ' Forced Parameterization and/or Optimize For Ad Hoc Workloads may fix the issue.'
					            + 'To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; '
			FROM #plan_usage AS p ;

        IF @is_tokenstore_big = 1
		INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
		SELECT @@SPID,
		       69,
			   10,
			   N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB',
			   N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent)
			                                            + N'% of the buffer pool, and your plan cache seems to be unstable',
			   N'https://www.brentozar.com/go/userstore',
			   N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out'

		IF @v >= 11
		BEGIN	
        IF EXISTS (SELECT 1/0
                   FROM   #trace_flags AS tf 
                   WHERE  tf.global_trace_flags IS NOT NULL
				   )
            INSERT INTO ##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',
                    'https://www.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)) ;
		END; 

        IF NOT EXISTS (SELECT 1/0
					   FROM   ##BlitzCacheResults AS bcr
                       WHERE  bcr.Priority = 2147483646
				      )
            INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (@@SPID,
                    2147483646,
                    255,
                    'Need more help?' ,
                    'Paste your plan on the internet!',
                    'http://pastetheplan.com',
                    'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ;



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

IF @Debug = 1
    BEGIN
		
		SELECT '##BlitzCacheResults' AS table_name, *
		FROM   ##BlitzCacheResults
		OPTION ( RECOMPILE );
		
		SELECT '##BlitzCacheProcs' AS table_name, *
		FROM   ##BlitzCacheProcs
		OPTION ( RECOMPILE );
		
        IF @SkipAnalysis = 0
            BEGIN
		    SELECT '#statements' AS table_name, *
		    FROM #statements AS s
		    OPTION (RECOMPILE);

		    SELECT '#query_plan' AS table_name, *
		    FROM #query_plan AS qp
		    OPTION (RECOMPILE);
		
		    SELECT '#relop' AS table_name, *
		    FROM #relop AS r
		    OPTION (RECOMPILE);
            END

		SELECT '#only_query_hashes' AS table_name, *
		FROM   #only_query_hashes
		OPTION ( RECOMPILE );
		
		SELECT '#ignore_query_hashes' AS table_name, *
		FROM   #ignore_query_hashes
		OPTION ( RECOMPILE );
		
		SELECT '#only_sql_handles' AS table_name, *
		FROM   #only_sql_handles
		OPTION ( RECOMPILE );
		
		SELECT '#ignore_sql_handles' AS table_name, *
		FROM   #ignore_sql_handles
		OPTION ( RECOMPILE );
		
		SELECT '#p' AS table_name, *
		FROM   #p
		OPTION ( RECOMPILE );
		
		SELECT '#checkversion' AS table_name, *
		FROM   #checkversion
		OPTION ( RECOMPILE );
		
		SELECT '#configuration' AS table_name, *
		FROM   #configuration
		OPTION ( RECOMPILE );
		
		SELECT '#stored_proc_info' AS table_name, *
		FROM   #stored_proc_info
		OPTION ( RECOMPILE );

		SELECT '#conversion_info' AS table_name, *
		FROM #conversion_info AS ci
		OPTION ( RECOMPILE );
		
		SELECT '#variable_info' AS table_name, *
		FROM #variable_info AS vi
		OPTION ( RECOMPILE );

		SELECT '#missing_index_xml' AS table_name, *
		FROM #missing_index_xml AS mix
		OPTION ( RECOMPILE );

		SELECT '#missing_index_schema' AS table_name, *
		FROM #missing_index_schema AS mis
		OPTION ( RECOMPILE );

		SELECT '#missing_index_usage' AS table_name, *
		FROM #missing_index_usage AS miu
		OPTION ( RECOMPILE );

		SELECT '#missing_index_detail' AS table_name, *
		FROM #missing_index_detail AS mid
		OPTION ( RECOMPILE );

		SELECT '#missing_index_pretty' AS table_name, *
		FROM #missing_index_pretty AS mip
		OPTION ( RECOMPILE );

		SELECT '#plan_creation' AS table_name, *
		FROM   #plan_creation
		OPTION ( RECOMPILE );
		
		SELECT '#plan_cost' AS table_name, *
		FROM   #plan_cost
		OPTION ( RECOMPILE );
		
		SELECT '#proc_costs' AS table_name, *
		FROM   #proc_costs
		OPTION ( RECOMPILE );
		
		SELECT '#stats_agg' AS table_name, *
		FROM   #stats_agg
		OPTION ( RECOMPILE );
		
		SELECT '#trace_flags' AS table_name, *
		FROM   #trace_flags
		OPTION ( RECOMPILE );

		SELECT '#plan_usage' AS table_name, *
		FROM   #plan_usage
		OPTION ( RECOMPILE );

    END;

    IF @OutputTableName IS NOT NULL
	    --Allow for output to ##DB so don't check for DB or schema name here
	   GOTO OutputResultsToTable;
RETURN; --Avoid going into the AllSort GOTO

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


IF (
     @Top > 10
	 AND @SkipAnalysis = 0
     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 NVARCHAR(128),
           Cost FLOAT,
           QueryText NVARCHAR(MAX),
           QueryType NVARCHAR(258),
           Warnings VARCHAR(MAX),
		   QueryPlan XML,
		   missing_indexes XML,
		   implicit_conversion_info XML,
		   cached_execution_parameters XML,
           ExecutionCount NVARCHAR(30),
           ExecutionsPerMinute MONEY,
           ExecutionWeight MONEY,
           TotalCPU NVARCHAR(30),
           AverageCPU NVARCHAR(30),
           CPUWeight MONEY,
           TotalDuration NVARCHAR(30),
           AverageDuration NVARCHAR(30),
           DurationWeight MONEY,
           TotalReads NVARCHAR(30),
           AverageReads NVARCHAR(30),
           ReadWeight MONEY,
           TotalWrites NVARCHAR(30),
           AverageWrites NVARCHAR(30),
           WriteWeight MONEY,
           AverageReturnedRows MONEY,
           MinGrantKB NVARCHAR(30),
           MaxGrantKB NVARCHAR(30),
           MinUsedGrantKB NVARCHAR(30),
           MaxUsedGrantKB NVARCHAR(30),
           AvgMaxMemoryGrant MONEY,
		   MinSpills NVARCHAR(30),
		   MaxSpills NVARCHAR(30),
		   TotalSpills NVARCHAR(30),
		   AvgSpills MONEY,
           PlanCreationTime DATETIME,
           LastExecutionTime DATETIME,
		   LastCompletionTime DATETIME,
           PlanHandle VARBINARY(64),
           SqlHandle VARBINARY(64),
           SetOptions VARCHAR(MAX),
		   QueryHash BINARY(8),
		   PlanGenerationNum NVARCHAR(30),
		   RemovePlanHandleFromCache NVARCHAR(200),
           Pattern NVARCHAR(20)
         );
   END;


IF @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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE);
					 
					 '; 
					
					IF @VersionShowsMemoryGrants = 0
					BEGIN
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL,
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END; 

					END; 
					
					IF @VersionShowsMemoryGrants = 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
										  
										  EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 					  
										  UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);';
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL, 
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END; 

				    END;

					IF @VersionShowsSpills = 0
					BEGIN
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL,
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END; 

					END; 
					
					IF @VersionShowsSpills = 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) 					 
										  
										  EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 					  
										  UPDATE #bou_allsort SET Pattern = ''spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);';
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL, 
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END; 

				    END;

					IF(@OutputType <> 'NONE')
					BEGIN
						SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											  TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											  ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											  MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern 
											  FROM #bou_allsort 
											  ORDER BY Id 
											  OPTION(RECOMPILE);  ';
					END;
END; 			


IF @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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
					 
					 EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 
					 UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE);
					 
					 ';
					 
					IF @VersionShowsMemoryGrants = 0
					BEGIN
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL, 
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END;  

					END; 
					
					IF @VersionShowsMemoryGrants = 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
										  
										  EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 					  
										  UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);';
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL, 
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END; 

				    END;

					IF @VersionShowsSpills = 0
					BEGIN
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL, 
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END;  

					END; 
					
					IF @VersionShowsSpills = 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, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache )
										  
										  EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, 
                     @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE;
					 					  
										  UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);';
						IF @ExportToExcel = 1
						BEGIN
							SET @AllSortSql += N'  UPDATE #bou_allsort 
												   SET 
													QueryPlan = NULL,
													implicit_conversion_info = NULL, 
													cached_execution_parameters = NULL, 
													missing_indexes = NULL
												   OPTION (RECOMPILE);

												   UPDATE ##BlitzCacheProcs
												   SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000)
												   OPTION(RECOMPILE);';
						END; 

				    END;

					IF(@OutputType <> 'NONE')
					BEGIN
						SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, 
											  TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, 
											  ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, 
											  MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern
											  FROM #bou_allsort 
											  ORDER BY Id 
											  OPTION(RECOMPILE);  ';
					END;
END;

					IF @Debug = 1
						BEGIN
						    PRINT SUBSTRING(@AllSortSql, 0, 4000);
						    PRINT SUBSTRING(@AllSortSql, 4000, 8000);
						    PRINT SUBSTRING(@AllSortSql, 8000, 12000);
						    PRINT SUBSTRING(@AllSortSql, 12000, 16000);
						    PRINT SUBSTRING(@AllSortSql, 16000, 20000);
						    PRINT SUBSTRING(@AllSortSql, 20000, 24000);
						    PRINT SUBSTRING(@AllSortSql, 24000, 28000);
						    PRINT SUBSTRING(@AllSortSql, 28000, 32000);
						    PRINT SUBSTRING(@AllSortSql, 32000, 36000);
						    PRINT SUBSTRING(@AllSortSql, 36000, 40000);
						END;

					EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', 
                        @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack;

/* Avoid going into OutputResultsToTable
   ... otherwise the last result (e.g. spills) would be recorded twice into the output table.
*/
RETURN;

/*End of AllSort section*/


/*Begin code to write results to table */
OutputResultsToTable:
    
RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT;

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

/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */
DECLARE @ValidOutputServer BIT;
DECLARE @ValidOutputLocation BIT;
DECLARE @LinkedServerDBCheck NVARCHAR(2000);
DECLARE @ValidLinkedServerDB INT;
DECLARE @tmpdbchk table (cnt int);
IF @OutputServerName IS NOT NULL
	BEGIN
					
		IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT;
					
		IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName)
		    BEGIN
		        SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')';
		        INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck;
		        SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk);
		        IF (@ValidLinkedServerDB > 0)
                    BEGIN
                        SET @ValidOutputServer = 1;
                        SET @ValidOutputLocation = 1;
                    END;
		        ELSE
			        RAISERROR('The specified database was not found on the output server', 16, 0);
		    END;
        ELSE
            BEGIN
                RAISERROR('The specified output server was not found', 16, 0);
            END;
    END;
ELSE
	BEGIN
		IF @OutputDatabaseName IS NOT NULL
			AND @OutputSchemaName IS NOT NULL
			AND @OutputTableName IS NOT NULL
			AND EXISTS ( SELECT *
				FROM   sys.databases
				WHERE  QUOTENAME([name]) = @OutputDatabaseName)
			BEGIN
			SET @ValidOutputLocation = 1;
			END;
		ELSE IF @OutputDatabaseName IS NOT NULL
			AND @OutputSchemaName IS NOT NULL
			AND @OutputTableName IS NOT NULL
			AND NOT EXISTS ( SELECT *
					FROM   sys.databases
					WHERE  QUOTENAME([name]) = @OutputDatabaseName)
			BEGIN
				RAISERROR('The specified output database was not found on this server', 16, 0);
			END;
		ELSE
			BEGIN
				SET @ValidOutputLocation = 0;
			END;
	END;

    /* @OutputTableName lets us export the results to a permanent table */
    DECLARE @StringToExecute NVARCHAR(MAX) = N'' ;

    IF @ValidOutputLocation = 1
		BEGIN
			SET @StringToExecute = N'USE '
				+ @OutputDatabaseName
				+ N'; IF EXISTS(SELECT * FROM '
				+ @OutputDatabaseName
				+ N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
				+ @OutputSchemaName
				+ N''') AND NOT EXISTS (SELECT * FROM '
				+ @OutputDatabaseName
				+ N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
				+ @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = '''
				+ @OutputTableName + N''') CREATE TABLE '
				+ @OutputSchemaName + N'.'
				+ @OutputTableName
				+ CONVERT
                  (
                  nvarchar(MAX),
				 N'(ID bigint NOT NULL IDENTITY(1,1),
					ServerName NVARCHAR(258),
					CheckDate DATETIMEOFFSET,
					Version NVARCHAR(258),
					QueryType NVARCHAR(258),
					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,
					ExecutionsPerMinute money,
					PlanCreationTime datetime,' + N'
					PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]),
					LastExecutionTime datetime,
					LastCompletionTime 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,
					PlanGenerationNum bigint,
					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,
					MinSpills BIGINT,
					MaxSpills BIGINT,
					TotalSpills BIGINT,
					AvgSpills MONEY,
					QueryPlanCost FLOAT,
					Pattern NVARCHAR(20),
                    ai_prompt NVARCHAR(MAX),
                    ai_advice NVARCHAR(MAX),
                    ai_payload NVARCHAR(MAX),
                    ai_raw_response NVARCHAR(MAX),
					JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)),
					CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));'
				  );

			SET @StringToExecute += N'IF EXISTS(SELECT * FROM '
					+@OutputDatabaseName
					+N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
					+@OutputSchemaName
					+N''') AND EXISTS (SELECT * FROM '
					+@OutputDatabaseName+
					N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
					+@OutputSchemaName
					+N''' AND QUOTENAME(TABLE_NAME) = '''
					+@OutputTableName
					+N''') AND EXISTS (SELECT * FROM '
					+@OutputDatabaseName+
					N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N'''
					+@OutputTableName
					+N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'')
BEGIN 
	RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT;
	ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours];
	ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]);
END ';

            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+'''''');
					SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+'''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'xml','nvarchar(max)');
					SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '''');''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''');''''');
                    SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlySqlHandles = '''''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''''; ''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlyQueryHashes = '''''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''''; ''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''N/A''','''''N/A''''');
					
                    IF @Debug = 1
                    BEGIN
                        PRINT SUBSTRING(@StringToExecute, 0, 4000);
                        PRINT SUBSTRING(@StringToExecute, 4000, 8000);
                        PRINT SUBSTRING(@StringToExecute, 8000, 12000);
                        PRINT SUBSTRING(@StringToExecute, 12000, 16000);
                        PRINT SUBSTRING(@StringToExecute, 16000, 20000);
                        PRINT SUBSTRING(@StringToExecute, 20000, 24000);
                        PRINT SUBSTRING(@StringToExecute, 24000, 28000);
                        PRINT SUBSTRING(@StringToExecute, 28000, 32000);
                        PRINT SUBSTRING(@StringToExecute, 32000, 36000);
                        PRINT SUBSTRING(@StringToExecute, 36000, 40000);
                    END;
                    EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    IF @Debug = 1
                    BEGIN
                        PRINT SUBSTRING(@StringToExecute, 0, 4000);
                        PRINT SUBSTRING(@StringToExecute, 4000, 8000);
                        PRINT SUBSTRING(@StringToExecute, 8000, 12000);
                        PRINT SUBSTRING(@StringToExecute, 12000, 16000);
                        PRINT SUBSTRING(@StringToExecute, 16000, 20000);
                        PRINT SUBSTRING(@StringToExecute, 20000, 24000);
                        PRINT SUBSTRING(@StringToExecute, 24000, 28000);
                        PRINT SUBSTRING(@StringToExecute, 28000, 32000);
                        PRINT SUBSTRING(@StringToExecute, 32000, 36000);
                        PRINT SUBSTRING(@StringToExecute, 36000, 40000);
                    END;
					EXEC(@StringToExecute);
                END;
            
            /* If the table doesn't have the new LastCompletionTime column, add it. See Github #2377. */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''LastCompletionTime'')
                ALTER TABLE ' + @ObjectFullName + N' ADD LastCompletionTime DATETIME NULL;';
            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''LastCompletionTime''','''''LastCompletionTime''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
					EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END;

            /* If the table doesn't have the new PlanGenerationNum column, add it. See Github #2514. */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''PlanGenerationNum'')
                ALTER TABLE ' + @ObjectFullName + N' ADD PlanGenerationNum BIGINT NULL;';
			IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''PlanGenerationNum''','''''PlanGenerationNum''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
                    EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END;
            
			/* If the table doesn't have the new Pattern column, add it */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'')
                ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;';
			IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
                    EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END            
            
            /* If the table doesn't have the new ai_prompt column, add it. See Github #3669. */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''ai_prompt'')
                ALTER TABLE ' + @ObjectFullName + N' ADD ai_prompt NVARCHAR(MAX) NULL;';
            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''ai_prompt''','''''ai_prompt''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
					EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END;

            /* If the table doesn't have the new ai_advice column, add it. See Github #3669. */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''ai_advice'')
                ALTER TABLE ' + @ObjectFullName + N' ADD ai_advice NVARCHAR(MAX) NULL;';
            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''ai_advice''','''''ai_advice''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
					EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END;

            /* If the table doesn't have the new ai_payload column, add it. See Github #3669. */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''ai_payload'')
                ALTER TABLE ' + @ObjectFullName + N' ADD ai_payload NVARCHAR(MAX) NULL;';
            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''ai_payload''','''''ai_payload''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
					EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END;

            /* If the table doesn't have the new ai_raw_response column, add it. See Github #3669. */
            SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
            SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''ai_raw_response'')
                ALTER TABLE ' + @ObjectFullName + N' ADD ai_raw_response NVARCHAR(MAX) NULL;';
            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = REPLACE(@StringToExecute,'''ai_raw_response''','''''ai_raw_response''''');
					SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + '''''');
					EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
                END;
            ELSE
                BEGIN
                    EXEC(@StringToExecute);
                END;

            IF @CheckDateOverride IS NULL
                BEGIN
                    SET @CheckDateOverride = SYSDATETIMEOFFSET();
                END;
            
            IF @ValidOutputServer = 1
				BEGIN
					SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
					+ @OutputServerName + '.'
					+ @OutputDatabaseName
					+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
					+ @OutputSchemaName + ''') INSERT '
					+ @OutputServerName + '.'
					+ @OutputDatabaseName + '.'
					+ @OutputSchemaName + '.'
					+ @OutputTableName
					+ ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, '
                    + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, '
                    + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
                    + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern, ai_prompt, ai_advice, ai_payload, ai_raw_response ) '
                    + 'SELECT TOP (@Top) '
                    + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, '
                    + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', '
                    + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, '
                    + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, '
                    + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, '
                    + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern, ai_prompt, ai_advice, ai_payload, ai_raw_response '
                    + ' FROM ##BlitzCacheProcs '
                    + ' WHERE 1=1 ';

                IF @MinimumExecutionCount IS NOT NULL
                    BEGIN
                        SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount ';
                    END;
                IF @MinutesBack IS NOT NULL
                    BEGIN
                        SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ';
                    END;
                    SET @StringToExecute += N' AND SPID = @@SPID ';
                    SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN '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'spills' THEN N' MaxSpills'
                                                                    WHEN N'avg cpu' THEN N' AverageCPU'
                                                                    WHEN N'avg reads' THEN N' AverageReads'
                                                                    WHEN N'avg writes' THEN N' AverageWrites'
                                                                    WHEN N'avg duration' THEN N' AverageDuration'
                                                                    WHEN N'avg executions' THEN N' ExecutionsPerMinute'
                                                                    WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant'
                                                                    WHEN N'avg spills' THEN N' AvgSpills'
                                                                    WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB'
																	ELSE N' TotalCPU '
                                                                    END + N' DESC ';
                    SET @StringToExecute += N' OPTION (RECOMPILE) ; ';    
                    
                    IF @Debug = 1
                    BEGIN
                        PRINT SUBSTRING(@StringToExecute,     1, 4000);
                        PRINT SUBSTRING(@StringToExecute,  4001, 4000);
                        PRINT SUBSTRING(@StringToExecute,  8001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 12001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 16001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 20001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 24001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 28001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 32001, 4000);
                        PRINT SUBSTRING(@StringToExecute, 36001, 4000);
                    END;

                    EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount;
				END;
			ELSE
				BEGIN
					SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
					+ @OutputDatabaseName
					+ '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
					+ @OutputSchemaName + ''') INSERT '
					+ @OutputDatabaseName + '.'
					+ @OutputSchemaName + '.'
					+ @OutputTableName
					+ ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, '
                    + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, '
                    + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
                    + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern, ai_prompt, ai_advice, ai_payload, ai_raw_response ) '
                    + 'SELECT TOP (@Top) '
                    + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, '
                    + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', '
                    + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, '
                    + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, '
                    + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
                    + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern, ai_prompt, ai_advice, ai_payload, ai_raw_response '
                    + ' FROM ##BlitzCacheProcs '
                    + ' WHERE 1=1 ';

                IF @MinimumExecutionCount IS NOT NULL
                    BEGIN
                        SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount ';
                    END;
                IF @MinutesBack IS NOT NULL
                    BEGIN
                        SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ';
                    END;
                    SET @StringToExecute += N' AND SPID = @@SPID ';
                    SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN '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'spills' THEN N' MaxSpills'
                                                                    WHEN N'avg cpu' THEN N' AverageCPU'
                                                                    WHEN N'avg reads' THEN N' AverageReads'
                                                                    WHEN N'avg writes' THEN N' AverageWrites'
                                                                    WHEN N'avg duration' THEN N' AverageDuration'
                                                                    WHEN N'avg executions' THEN N' ExecutionsPerMinute'
                                                                    WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant'
                                                                    WHEN N'avg spills' THEN N' AvgSpills'
                                                                    WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB'
																	ELSE N' TotalCPU '
                                                                    END + N' DESC ';
                    SET @StringToExecute += N' OPTION (RECOMPILE) ; ';    
                    
                    IF @Debug = 1
                    BEGIN
                        PRINT SUBSTRING(@StringToExecute, 0, 4000);
                        PRINT SUBSTRING(@StringToExecute, 4000, 8000);
                        PRINT SUBSTRING(@StringToExecute, 8000, 12000);
                        PRINT SUBSTRING(@StringToExecute, 12000, 16000);
                        PRINT SUBSTRING(@StringToExecute, 16000, 20000);
                        PRINT SUBSTRING(@StringToExecute, 20000, 24000);
                        PRINT SUBSTRING(@StringToExecute, 24000, 28000);
                        PRINT SUBSTRING(@StringToExecute, 28000, 32000);
                        PRINT SUBSTRING(@StringToExecute, 32000, 36000);
                        PRINT SUBSTRING(@StringToExecute, 36000, 40000);
                    END;

                    EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount;
				END;
		END;
	ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
		BEGIN
			IF @ValidOutputServer = 1
				BEGIN
					RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0);
				END;
			ELSE IF @OutputTableName IN ('##BlitzCacheProcs','##BlitzCacheResults')
				BEGIN
					RAISERROR('OutputTableName is a reserved name for this procedure. We only use ##BlitzCacheProcs and ##BlitzCacheResults, please choose another table name.', 16, 0);
				END;
			ELSE
				BEGIN				
					SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
						+ @OutputTableName
						+ ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';'
						+ 'CREATE TABLE '
						+ @OutputTableName
						+ ' (ID bigint NOT NULL IDENTITY(1,1),
						ServerName NVARCHAR(258),
						CheckDate DATETIMEOFFSET,
						Version NVARCHAR(258),
						QueryType NVARCHAR(258),
						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,
						ExecutionsPerMinute money,
						PlanCreationTime datetime,' + N'
						PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()),
						LastExecutionTime datetime,
						LastCompletionTime 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,
						PlanGenerationNum bigint,
						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,
						MinSpills BIGINT,
						MaxSpills BIGINT,
						TotalSpills BIGINT,
						AvgSpills MONEY,
						QueryPlanCost FLOAT,
						Pattern NVARCHAR(20),
                        ai_prompt NVARCHAR(MAX),
                        ai_advice NVARCHAR(MAX),
                        ai_payload NVARCHAR(MAX),
                        ai_raw_response NVARCHAR(MAX),
                        JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)),
						CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));';
					SET @StringToExecute +=	N' INSERT '
						+ @OutputTableName
						+ ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, '
						+ ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, '
						+ ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
						+ ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern, ai_prompt, ai_advice, ai_payload, ai_raw_response ) '
						+ 'SELECT TOP (@Top) '
						+ QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, '
						+ QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', '
						+ ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, '
						+ ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, '
						+ ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, '
						+ ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern, ai_prompt, ai_advice, ai_payload, ai_raw_response '
						+ ' FROM ##BlitzCacheProcs '
						+ ' WHERE 1=1 ';
                        
                    IF @MinimumExecutionCount IS NOT NULL
                        BEGIN
                            SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount ';
                        END;

                    IF @MinutesBack IS NOT NULL
                        BEGIN
                            SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ';
                        END;

                        SET @StringToExecute += N' AND SPID = @@SPID ';
                        
                        SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN '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'spills' THEN N' MaxSpills'
                                                                        WHEN N'avg cpu' THEN N' AverageCPU'
                                                                        WHEN N'avg reads' THEN N' AverageReads'
                                                                        WHEN N'avg writes' THEN N' AverageWrites'
                                                                        WHEN N'avg duration' THEN N' AverageDuration'
                                                                        WHEN N'avg executions' THEN N' ExecutionsPerMinute'
                                                                        WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant'
																		WHEN N'avg spills' THEN N' AvgSpills'
																		WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB'
																		ELSE N' TotalCPU '
                                                                        END + N' DESC ';

                        SET @StringToExecute += N' OPTION (RECOMPILE) ; ';    
                        
                        IF @Debug = 1
                        BEGIN
                            PRINT SUBSTRING(@StringToExecute, 0, 4000);
                            PRINT SUBSTRING(@StringToExecute, 4000, 8000);
                            PRINT SUBSTRING(@StringToExecute, 8000, 12000);
                            PRINT SUBSTRING(@StringToExecute, 12000, 16000);
                            PRINT SUBSTRING(@StringToExecute, 16000, 20000);
                            PRINT SUBSTRING(@StringToExecute, 20000, 24000);
                            PRINT SUBSTRING(@StringToExecute, 24000, 28000);
                            PRINT SUBSTRING(@StringToExecute, 28000, 32000);
                            PRINT SUBSTRING(@StringToExecute, 32000, 36000);
                            PRINT SUBSTRING(@StringToExecute, 36000, 40000);
                            PRINT SUBSTRING(@StringToExecute, 34000, 40000);
                        END;
                    EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount;
				END;
		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; /* End of writing results to table */

END; /*Final End*/

GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO

IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL
  EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;');
GO

ALTER PROCEDURE dbo.sp_BlitzIndex
    @ObjectName NVARCHAR(386) = NULL, /* 'dbname.schema.table' -- if you are lazy and want to fill in @DatabaseName, @SchemaName and @TableName, and since it's the first parameter can simply do: sp_BlitzIndex 'sch.table' */
    @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/
    @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/
    @TableName NVARCHAR(261) = 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,
    @UsualStatisticsSamplingPercent FLOAT = 100, /* FLOAT to match sys.dm_db_stats_properties. More detail later. 100 by default because Brent suggests that if people are persisting statistics at all, they are probably doing 100 in lots of places and not filtering that out would produce noise. */
    @GetAllDatabases BIT = 0,
	@ShowColumnstoreOnly BIT = 0, /* Will show only the Row Group and Segment details for a table with a columnstore index. */
    @BringThePain BIT = 0,
    @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */
    @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */,
	@OutputType VARCHAR(20) = 'TABLE' ,
    @OutputServerName NVARCHAR(256) = NULL ,
    @OutputDatabaseName NVARCHAR(256) = NULL ,
    @OutputSchemaName NVARCHAR(256) = NULL ,
    @OutputTableName NVARCHAR(261) = NULL ,
	@IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */,
    @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/,
	@ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */,
	@SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */
	@SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */
    @Help TINYINT = 0,
	@Debug BIT = 0,
    @Version     VARCHAR(30) = NULL OUTPUT,
	@VersionDate DATETIME = NULL OUTPUT,
    @VersionCheckMode BIT = 0
WITH RECOMPILE
AS
SET NOCOUNT ON;
SET STATISTICS XML OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT @Version = '8.29', @VersionDate = '20260203';
SET @OutputType  = UPPER(@OutputType);

IF(@VersionCheckMode = 1)
BEGIN
	RETURN;
END;

IF @Help = 1 
BEGIN
PRINT '
/*
sp_BlitzIndex from http://FirstResponderKit.org
	
This script analyzes the design and performance of your indexes.

To learn more, visit http://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.
 - 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 problems.)

Unknown limitations of this version:
 - We knew them once, but we forgot.


MIT License

Copyright (c) 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.
';
RETURN;
END;    /* @Help = 1 */

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);
DECLARE @DaysUptimeInsertValue NVARCHAR(256);
DECLARE @DatabaseToIgnore NVARCHAR(MAX);
DECLARE @ColumnList NVARCHAR(MAX);
DECLARE @ColumnListWithApostrophes NVARCHAR(MAX);
DECLARE @PartitionCount INT;
DECLARE @OptimizeForSequentialKey BIT = 0;
DECLARE @ResumableIndexesDisappearAfter INT = 0;
DECLARE @StringToExecute NVARCHAR(MAX);
DECLARE @AzureSQLDB BIT = (SELECT CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END);

/* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */
SET @DatabaseName = COALESCE(@DatabaseName, PARSENAME(@ObjectName, 3)) /* 3 = Database name */
SET @SchemaName   = COALESCE(@SchemaName,   PARSENAME(@ObjectName, 2)) /* 2 = Schema name */
SET @TableName    = COALESCE(@TableName,    PARSENAME(@ObjectName, 1)) /* 1 = Table name */

/* Handle already quoted input if it wasn't fully qualified - only if @ObjectName is null*/
IF (@ObjectName IS NULL)
   BEGIN
        SELECT @DatabaseName = CASE WHEN @DatabaseName LIKE N'\[%\]' ESCAPE N'\' THEN PARSENAME(@DatabaseName,1) ELSE @DatabaseName 
                               END,
               @SchemaName   = ISNULL(
                                     CASE /*only apply parsename if the schema is actually quoted*/
                                      WHEN @SchemaName LIKE N'\[%\]' ESCAPE N'\' THEN  PARSENAME(@SchemaName,1) ELSE @SchemaName 
                                     END,
                                     CASE /*if we already have @TableName in the form of [some.schema].[some.table]*/
                                      WHEN @TableName LIKE N'\[%\].\[%\]' ESCAPE N'\' THEN PARSENAME(@TableName,2)
                                      /*I'm making an assumption here that people who use . in their naming conventions would have one in each object name*/
                                      WHEN LEN(@TableName)- LEN(REPLACE(@TableName,'.','')) = 1 THEN PARSENAME(@TableName,2) ELSE NULL 
                                     END),
               @TableName    = CASE 
                                 WHEN @TableName LIKE N'\[%\].\[%\]' ESCAPE N'\' OR @TableName LIKE N'\[%\]' ESCAPE N'\' THEN PARSENAME(@TableName,1)
                                 WHEN LEN(@TableName)- LEN(REPLACE(@TableName,'.','')) = 1 THEN PARSENAME(@TableName,1) ELSE @TableName 
                                END;
END;

/* If we're on Azure SQL DB let's cut people some slack */
IF (@TableName IS NOT NULL AND @AzureSQLDB = 1 AND @DatabaseName IS NULL)
   BEGIN
        SET @DatabaseName = DB_NAME();
   END;


IF (@SchemaName IS NULL AND @TableName IS NOT NULL)
   BEGIN
       /* If the target is in the current database 
  and there's just one table or view with this name, then we can grab the schema from sys.objects*/
       IF ((SELECT COUNT(1) FROM [sys].[objects] 
               WHERE [name] = @TableName AND [type] IN ('U','V'))=1 
              AND @TableName IS NOT NULL  AND @DatabaseName = DB_NAME())
          BEGIN
              SELECT @SchemaName = SCHEMA_NAME([schema_id]) 
               FROM [sys].[objects] 
               WHERE [name] = @TableName AND [type] IN ('U','V');
          END;
        /* If the target isn't in the current database, then use dynamic T-SQL*/  
        IF (@DatabaseName <> DB_NAME()) 
          BEGIN
               /*first make sure only one row is returned from sys.objects*/
               SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                    SELECT @RowcountOUT = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.[sys].[objects] 
                    WHERE [name] = @TableName_IN AND [type] IN (''U'',''V'') OPTION  (RECOMPILE);';
                SET @params = N'@TableName_IN NVARCHAR(128), @RowcountOUT BIGINT OUTPUT';
                EXEC sp_executesql @dsql, @params, @TableName_IN = @TableName, @RowcountOUT = @Rowcount OUTPUT;

                IF (@Rowcount = 1)
                  BEGIN
                      SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                      SELECT @SchemaName_OUT = s.[name] 
                       FROM ' + QUOTENAME(@DatabaseName) + N'.[sys].[objects] o
                       INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.[sys].[schemas] s
                          ON o.[schema_id] = s.[schema_id]
                       WHERE o.[name] = @TableName_IN AND o.[type] IN (''U'',''V'') OPTION  (RECOMPILE);';
                      SET @params = N'@TableName_IN NVARCHAR(128), @SchemaName_OUT NVARCHAR(128) OUTPUT';
                      EXEC sp_executesql @dsql, @params, @TableName_IN = @TableName, @SchemaName_OUT = @SchemaName OUTPUT;
                   END;
          END;  
 END;

/* Let's get @SortOrder set to lower case here for comparisons later */
SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_');
SET @SortDirection = LOWER(@SortDirection);

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);
SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), '');

SELECT
    @OptimizeForSequentialKey =
	    CASE WHEN EXISTS
		          (
                      SELECT
                          1/0
                      FROM sys.all_columns AS ac
                      WHERE ac.object_id = OBJECT_ID('sys.indexes')
                      AND   ac.name = N'optimize_for_sequential_key'
				  )
			THEN 1
			ELSE 0
		END;

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

																				
IF(@OutputType NOT IN ('TABLE','NONE'))
BEGIN
    RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1);
    RETURN;
END;

IF(@UsualStatisticsSamplingPercent <= 0 OR @UsualStatisticsSamplingPercent > 100)
BEGIN
    RAISERROR('Invalid value for parameter @UsualStatisticsSamplingPercent. Expected: 1 to 100',12,1);
    RETURN;
END;

/* Some prep-work for output object names before checking if they're ok or not */
IF (@OutputTableName IS NOT NULL)
BEGIN
     
    /*Deal with potentially quoted object names*/
    SET @OutputDatabaseName = PARSENAME(@OutputDatabaseName,1);
    SET @OutputSchemaName = ISNULL(PARSENAME(@OutputSchemaName,1),PARSENAME(@OutputTableName,2));
    SET @OutputTableName = PARSENAME(@OutputTableName,1);

    /* Running on Azure SQL DB or outputting to current database? */
    IF (@OutputDatabaseName IS NULL AND @AzureSQLDB = 1)
    BEGIN
        SET @OutputDatabaseName = DB_NAME();
    END;
    IF (@OutputSchemaName IS NULL AND @OutputDatabaseName = DB_NAME())
      BEGIN
          SET @OutputSchemaName = SCHEMA_NAME();
      END;
END; 

IF(@OutputType = 'TABLE' AND NOT (@OutputTableName IS NULL AND @OutputSchemaName IS NULL AND @OutputDatabaseName IS NULL AND @OutputServerName IS NULL))
BEGIN
	RAISERROR(N'One or more output parameters specified in combination with TABLE output, changing to NONE output mode', 0,1) WITH NOWAIT;
	SET @OutputType = 'NONE'
END;
                       
IF(@OutputType = 'NONE')
BEGIN

	IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL))
	BEGIN
        RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1);
        RETURN;
	END;

    IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)
    BEGIN
        RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1);
        RETURN;
    END;
	/* Output is supported for all modes, no reason to not bring pain and output
    IF(@BringThePain = 1)
    BEGIN
        RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1);
        RETURN;
    END;
	*/
	/* Eventually limit by mode																			   
    IF(@Mode not in (0,4)) 
	BEGIN
        RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode);
        RETURN;
	END;
	*/
END;

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..#UnindexedForeignKeys') IS NOT NULL 
    DROP TABLE #UnindexedForeignKeys;

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;

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

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

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

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

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

IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL 
    DROP TABLE #dm_db_partition_stats_etc
IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL 
    DROP TABLE #dm_db_index_operational_stats

        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 NVARCHAR(4000) NOT NULL,
              finding NVARCHAR(200) NOT NULL,
              [database_name] NVARCHAR(128) NULL,
              URL NVARCHAR(200) NOT NULL,
              details NVARCHAR(MAX) 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,
              sample_query_plan XML NULL
            );

        CREATE TABLE #IndexSanity
            (
              [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED,
              [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 ,
			  optimize_for_sequential_key BIT NULL,
			  is_indexed_view BIT NOT NULL ,
              is_unique BIT NOT NULL ,
              is_primary_key BIT NOT NULL ,
			  is_unique_constraint 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_json BIT NOT NULL,
              is_in_memory_oltp 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,
              filter_columns_not_in_index NVARCHAR(MAX),
            [db_schema_object_name] AS [schema_name] + N'.' + [object_name]  ,
            [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name]
                + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name
                ELSE N''
                END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' ,
            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 N'[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_json = 1 THEN N'[JSON] '
                    ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] '
                    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 AND is_unique_constraint = 0 THEN N'[UNIQUE] '
					ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] '
                    ELSE N'' END + CASE WHEN count_key_columns > 0 THEN 
                        N'[' + CAST(count_key_columns AS NVARCHAR(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 NVARCHAR(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
				CASE WHEN is_spatial = 1 THEN N'Not Tracked'
				WHEN is_disabled = 1 THEN N'Disabled'
				ELSE N'Reads: ' + 
					REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'')
					+ 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), N'.00', N'') + N' seek ' ELSE N'' END
							+ CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan '  ELSE N'' END
							+ CASE WHEN user_lookups > 0 THEN  REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END
							)
							+ N') '
						ELSE N' '
						END 
					+ N'Writes: ' + 
					REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'')
				END /* First "end" is about is_spatial */,
				[more_info] AS 
				CASE WHEN is_in_memory_oltp = 1 
					THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + 
					N', @tableName=' + QUOTENAME([object_name],N'''') + N';'
				ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + 
					N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';'
				END
		);
        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);


        CREATE TABLE #IndexPartitionSanity
            (
              [index_partition_sanity_id] INT IDENTITY,
              [index_sanity_id] INT NULL ,
              [database_id] INT NOT NULL ,
              [object_id] INT NOT NULL ,
			  [schema_name] NVARCHAR(128) 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 ,
              reserved_dictionary_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 NVARCHAR(60) NULL,
			  page_latch_wait_count BIGINT NULL,
			  page_latch_wait_in_ms BIGINT NULL,
			  page_io_latch_wait_count BIGINT NULL,
			  page_io_latch_wait_in_ms BIGINT NULL,
              lock_escalation_desc nvarchar(60) NULL
            );

        CREATE TABLE #IndexSanitySize
            (
              [index_sanity_size_id] INT IDENTITY NOT NULL ,
              [index_sanity_id] INT NULL ,
              [database_id] INT NOT NULL,
			  [schema_name] NVARCHAR(128) 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_reserved_dictionary_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 NVARCHAR(4000) NULL,
			  page_latch_wait_count BIGINT NULL,
			  page_latch_wait_in_ms BIGINT NULL,
			  page_io_latch_wait_count BIGINT NULL,
			  page_io_latch_wait_in_ms BIGINT NULL,
              lock_escalation_desc nvarchar(60) 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 ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END
                WHEN total_reserved_LOB_MB > 0 THEN
                    N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END
                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
                 + CASE WHEN total_reserved_dictionary_MB > 1024 THEN
                    N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries'
                WHEN total_reserved_dictionary_MB > 0 THEN
                    N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries'
                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; '
                    + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.'
                      ELSE N''
                      END
                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 +
                    CASE WHEN lock_escalation_desc = N'DISABLE' THEN
                        N'Lock escalation is disabled.'
                    ELSE N''
                    END
                END                  
                    ,'Error- NULL in computed column')
            );

        CREATE TABLE #IndexColumns
            (
              [database_id] INT NOT NULL,
			  [schema_name] NVARCHAR(128),
              [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 DECIMAL(38,0) NULL,
              increment_value DECIMAL(38,0) NULL ,
              last_value DECIMAL(38,0) NULL,
              is_not_for_replication BIT NULL
            );
        CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns
            (database_id, object_id, index_id);

        CREATE TABLE #MissingIndexes
            ([database_id] INT NOT NULL,
			[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(MAX),
            equality_columns_with_data_type NVARCHAR(MAX),
            inequality_columns NVARCHAR(MAX),
            inequality_columns_with_data_type NVARCHAR(MAX),
            included_columns NVARCHAR(MAX),
            included_columns_with_data_type NVARCHAR(MAX),
			is_low BIT,
                [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 COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL
						THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' '
                         ELSE N'' END +

                    CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL
						THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' '
                         ELSE N'' END +

                    CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL
						THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' '
                         ELSE N'' END,
                [create_tsql] AS N'CREATE INDEX [' 
                    + LEFT(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, 128) + 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=?, DATA_COMPRESSION=?' 
                    + 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';',
				[sample_query_plan] XML NULL
            );

        CREATE TABLE #ForeignKeys (
			[database_id] INT NOT NULL,
            [database_name] NVARCHAR(128) NOT NULL ,
			[schema_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 #UnindexedForeignKeys 
        (
        	[database_id] INT NOT NULL,
            [database_name] NVARCHAR(128) NOT NULL ,
        	[schema_name] NVARCHAR(128) NOT NULL ,
            foreign_key_name NVARCHAR(256),
            parent_object_name NVARCHAR(256),
			parent_object_id INT,
			referenced_object_name NVARCHAR(256),
			referenced_object_id INT
        );
        
        CREATE TABLE #IndexCreateTsql (
            index_sanity_id INT NOT NULL,
            create_tsql NVARCHAR(MAX) NOT NULL
        );

        CREATE TABLE #DatabaseList (
			DatabaseName NVARCHAR(256),
            secondary_role_allow_connections_desc NVARCHAR(50)

        );

		CREATE TABLE #PartitionCompressionInfo (
			[index_sanity_id] INT NULL,
			[partition_compression_detail] NVARCHAR(4000) NULL
        );

		CREATE TABLE #Statistics (
		  database_id INT NOT NULL,
		  database_name NVARCHAR(256) NOT NULL,
          object_id INT NOT NULL,
		  table_name NVARCHAR(128) NULL,
		  schema_name NVARCHAR(128) NULL,
		  index_name  NVARCHAR(128) NULL,
		  column_names  NVARCHAR(MAX) 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 BIGINT 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,
		  persisted_sample_percent FLOAT NULL,
          is_incremental BIT NULL
		); 

		CREATE TABLE #ComputedColumns
		(
		  index_sanity_id INT IDENTITY(1, 1) NOT NULL,
		  database_name NVARCHAR(128) NULL,
		  database_id INT NOT 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 NVARCHAR(10) ,
		 status BIT ,
		 Global BIT ,
		 Session BIT
		);

        CREATE TABLE #TemporalTables
        (
            index_sanity_id INT IDENTITY(1, 1) NOT NULL,
            database_name NVARCHAR(128) NOT NULL,
            database_id INT NOT NULL,
            schema_name NVARCHAR(128) NOT NULL,
            table_name NVARCHAR(128) NOT NULL,
            history_table_name NVARCHAR(128) NOT NULL,
            history_schema_name NVARCHAR(128) NOT NULL,
            start_column_name NVARCHAR(128) NOT NULL,
            end_column_name NVARCHAR(128) NOT NULL,
            period_name NVARCHAR(128) NOT NULL,
            history_table_object_id INT NULL
        );

		CREATE TABLE #CheckConstraints
		(
		  index_sanity_id INT IDENTITY(1, 1) NOT NULL,
		  database_name NVARCHAR(128) NULL,
		  database_id INT NOT NULL,
		  table_name NVARCHAR(128) NOT NULL,
		  schema_name NVARCHAR(128) NOT NULL,
		  constraint_name NVARCHAR(128) NULL,
		  is_disabled BIT NULL,
		  definition NVARCHAR(MAX) NULL,
		  uses_database_collation BIT NOT NULL,
		  is_not_trusted BIT NOT NULL,
		  is_function INT NOT NULL,
		  column_definition NVARCHAR(MAX) NULL
		);

		CREATE TABLE #FilteredIndexes
		(
		  index_sanity_id INT IDENTITY(1, 1) NOT NULL,
		  database_name NVARCHAR(128) NULL,
		  database_id INT NOT NULL,
		  schema_name NVARCHAR(128) NOT NULL,
		  table_name NVARCHAR(128) NOT NULL,
		  index_name NVARCHAR(128) NULL,
		  column_name NVARCHAR(128) NULL
		);

        CREATE TABLE #IndexResumableOperations
        (
		  database_name NVARCHAR(128) NULL,
		  database_id INT NOT NULL,
		  schema_name NVARCHAR(128) NOT NULL,
		  table_name NVARCHAR(128) NOT NULL,
 		  /*
          Every following non-computed column has
          the same definitions as in
          sys.index_resumable_operations.
          */
		  [object_id] INT NOT NULL,
		  index_id INT NOT NULL,
		  [name] NVARCHAR(128) NOT NULL,
          /*
          We have done nothing to make this query text pleasant
          to read. Until somebody has a better idea, we trust
          that copying Microsoft's approach is wise.
          */
		  sql_text NVARCHAR(MAX) NULL,
		  last_max_dop_used SMALLINT NOT NULL,
		  partition_number INT NULL,
		  state TINYINT NOT NULL,
		  state_desc NVARCHAR(60) NULL,
		  start_time DATETIME NOT NULL,
		  last_pause_time DATETIME NULL,
		  total_execution_time INT NOT NULL,
		  percent_complete FLOAT NOT NULL,
          page_count BIGINT NOT NULL,
          /*
          sys.indexes will not always have the name of the index
          because a resumable CREATE INDEX does not populate
          sys.indexes until it is done.
          So it is better to work out the full name here
          rather than pull it from another temp table.
          */
          [db_schema_table_index] AS
              [schema_name] + N'.' + [table_name] + N'.' + [name],
          /* For convenience. */
          reserved_MB_pretty_print AS
              CONVERT(NVARCHAR(100), CONVERT(MONEY, page_count * 8. / 1024.))
              + 'MB and '
              + state_desc,
          more_info AS
              N'New index: SELECT * FROM ' + QUOTENAME(database_name) +
              N'.sys.index_resumable_operations WHERE [object_id] = ' +
              CONVERT(NVARCHAR(100), [object_id]) +
              N'; Old index: ' +
              N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + 
			  N', @SchemaName=' + QUOTENAME([schema_name],N'''') +
              N', @TableName=' + QUOTENAME([table_name],N'''') + N';'
        );

        CREATE TABLE #Ignore_Databases 
        (
          DatabaseName NVARCHAR(128), 
          Reason NVARCHAR(100)
        );

/* Sanitize our inputs */
SELECT
	@OutputServerName = QUOTENAME(@OutputServerName),
	@OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
	@OutputSchemaName = QUOTENAME(@OutputSchemaName),
	@OutputTableName = QUOTENAME(@OutputTableName);
					
					
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 DB_NAME(database_id) NOT LIKE 'rdsadmin%'
		AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance')
        AND is_distributor = 0
		OPTION    ( RECOMPILE );

        /* Skip non-readable databases in an AG - see Github issue #1160 */
        IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc')
            BEGIN
            SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN (
                        SELECT DB_NAME(d.database_id)
                        FROM sys.dm_hadr_availability_replica_states rs
                        INNER JOIN sys.databases d ON rs.replica_id = d.replica_id
                        INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id
                        WHERE rs.role_desc = ''SECONDARY''
                        AND r.secondary_role_allow_connections_desc = ''NO'')
						OPTION    ( RECOMPILE );';
            EXEC sp_executesql @dsql;

            IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO')
                BEGIN
                INSERT    #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition,
                                                index_usage_summary, index_size_summary )
                VALUES  ( 1, 
				          0, 
		                  N'Skipped non-readable AG secondary databases.',
                          N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.',
				          N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.',
                          'http://FirstResponderKit.org', '', '', '', ''
                        );        
                END;
            END;

        IF @IgnoreDatabases IS NOT NULL
            AND LEN(@IgnoreDatabases) > 0
            BEGIN
                RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT;
                SET @DatabaseToIgnore = '';

                WHILE LEN(@IgnoreDatabases) > 0
                BEGIN
                    IF PATINDEX('%,%', @IgnoreDatabases) > 0
                    BEGIN  
                        SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ;
                        
                        INSERT INTO #Ignore_Databases (DatabaseName, Reason)
                        SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter'
                        OPTION (RECOMPILE) ;
                        
                        SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ;
                    END;
                    ELSE
                    BEGIN
                        SET @DatabaseToIgnore = @IgnoreDatabases ;
                        SET @IgnoreDatabases = NULL ;

                        INSERT INTO #Ignore_Databases (DatabaseName, Reason)
                        SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter'
                        OPTION (RECOMPILE) ;
                    END;
            END;
                
        END

    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 = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL AND ISNULL(D.secondary_role_allow_connections_desc, 'YES') != 'NO');
SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50));
RAISERROR (@msg,0,1) WITH NOWAIT;



/* 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 AND @TableName IS NULL
        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'http://FirstResponderKit.org',
                      N'From Your Community Volunteers',   
                      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 problems for the server and/or user.',
					  '', 
                      'http://FirstResponderKit.org', 
				      N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.',
					  '', 
					  '', 
					  ''
                    );        
            
			if(@OutputType <> 'NONE')
			BEGIN
				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;
				RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1);
			END;

		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;


RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT;
IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL
    BEGIN   
        DECLARE partition_cursor CURSOR FOR
        SELECT dl.DatabaseName
        FROM #DatabaseList dl
        LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName
        WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' 
        AND i.DatabaseName IS NULL

        OPEN partition_cursor
        FETCH NEXT FROM partition_cursor INTO @DatabaseName
        
        WHILE @@FETCH_STATUS = 0
        BEGIN
            /* Count the total number of partitions */
            SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                    SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION    ( RECOMPILE );';
            EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT;
            IF @Rowcount > 100
                BEGIN
                   RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT;
				INSERT INTO #Ignore_Databases (DatabaseName, Reason)
				SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze'
                END;
            FETCH NEXT FROM partition_cursor INTO @DatabaseName
        END;
        CLOSE partition_cursor
        DEALLOCATE partition_cursor

    END;					

INSERT    #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
                                index_usage_summary, index_size_summary )
SELECT  1, 0 , 
        'Database Skipped',
        i.DatabaseName,
        'http://FirstResponderKit.org',
        i.Reason, '', '', ''
FROM #Ignore_Databases i;


/* Last startup */
IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL
BEGIN
	SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2))
	FROM sys.dm_os_sys_info;
END
ELSE
BEGIN
	SELECT  @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2))
	FROM    sys.databases
	WHERE   database_id = 2;
END

IF @DaysUptime = 0 OR @DaysUptime IS NULL 
  SET @DaysUptime = .01;

SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime);


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

RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT;
DECLARE c1 CURSOR 
LOCAL FAST_FORWARD 
FOR 
SELECT dl.DatabaseName 
FROM #DatabaseList dl
LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName
WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' 
  AND i.DatabaseName IS NULL
ORDER BY dl.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';

----------------------------------------
--STEP 1: OBSERVE THE PATIENT
--This step puts index information into temp tables.
----------------------------------------
BEGIN TRY
    BEGIN
        DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121);
        RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT;

        --Validate SQL Server Version

        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 applies 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'/* sp_BlitzIndex */
				SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */
				SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ',
					s.name,    
                    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 DECIMAL(38,0)),
                    CAST(ic.increment_value AS DECIMAL(38,0)),
                    CAST(ic.last_value AS DECIMAL(38,0)),
                    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
				JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so  ON si.object_id = so.object_id
																		  AND so.is_ms_shipped = 0
				JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_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;
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql,     1, 4000);
                PRINT SUBSTRING(@dsql,  4001, 4000);
                PRINT SUBSTRING(@dsql,  8001, 4000);
                PRINT SUBSTRING(@dsql, 12001, 4000);
                PRINT SUBSTRING(@dsql, 16001, 4000);
                PRINT SUBSTRING(@dsql, 20001, 4000);
                PRINT SUBSTRING(@dsql, 24001, 4000);
                PRINT SUBSTRING(@dsql, 28001, 4000);
                PRINT SUBSTRING(@dsql, 32001, 4000);
                PRINT SUBSTRING(@dsql, 36001, 4000);
            END;
		BEGIN TRY
			INSERT    #IndexColumns ( database_id, [schema_name], [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;
		END TRY
		BEGIN CATCH
			RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 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 = 0, @ErrorState = ERROR_STATE();
			RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT;

			WHILE @@trancount > 0 
				ROLLBACK;

			RETURN;
		END CATCH;


        --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)) + ', 
					s.name,    
                    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
				JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so  ON si.object_id = so.object_id
																		  AND so.is_ms_shipped = 0
				JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_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;
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql, 0, 4000);
                PRINT SUBSTRING(@dsql, 4000, 8000);
                PRINT SUBSTRING(@dsql, 8000, 12000);
                PRINT SUBSTRING(@dsql, 12000, 16000);
                PRINT SUBSTRING(@dsql, 16000, 20000);
                PRINT SUBSTRING(@dsql, 20000, 24000);
                PRINT SUBSTRING(@dsql, 24000, 28000);
                PRINT SUBSTRING(@dsql, 28000, 32000);
                PRINT SUBSTRING(@dsql, 32000, 36000);
                PRINT SUBSTRING(@dsql, 36000, 40000);
            END;
        INSERT    #IndexColumns ( database_id, [schema_name], [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)) + N' AS database_id, 
                        so.object_id, 
                        si.index_id, 
                        si.type,
                        @i_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, 
						si.is_unique_constraint,
						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,
                        CASE when si.type = 9 then 1 else 0 end as is_json,
                        CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp,
                        si.is_disabled,
                        si.is_hypothetical, 
                        si.is_padded, 
                        si.fill_factor,'
                        + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'
                        CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition
                             ELSE N''''
                        END AS filter_definition' ELSE N''''' AS filter_definition' END 
						+ CASE
						      WHEN @OptimizeForSequentialKey = 1
						      THEN N', si.optimize_for_sequential_key'
							  ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key'
						  END
						+ N',
						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) + N'.sys.indexes AS si WITH (NOLOCK)
                        JOIN ' + QUOTENAME(@DatabaseName) + N'.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) + N'.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)) + N'
                WHERE    si.[type] IN ( 0, 1, 2, 3, 4, 5, 6, 9 ) 
                /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore, JSON */ ' +
                CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END +
                CASE WHEN ( @IncludeInactiveIndexes = 0
                            AND @Mode IN (0, 4)
                            AND @TableName IS NULL )
                     THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0'
                     ELSE N''
                END
        + N'OPTION    ( RECOMPILE );
        ';
        IF @dsql IS NULL 
            RAISERROR('@dsql is null',16,1);

        RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT;
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql, 0, 4000);
                PRINT SUBSTRING(@dsql, 4000, 8000);
                PRINT SUBSTRING(@dsql, 8000, 12000);
                PRINT SUBSTRING(@dsql, 12000, 16000);
                PRINT SUBSTRING(@dsql, 16000, 20000);
                PRINT SUBSTRING(@dsql, 20000, 24000);
                PRINT SUBSTRING(@dsql, 24000, 28000);
                PRINT SUBSTRING(@dsql, 28000, 32000);
                PRINT SUBSTRING(@dsql, 32000, 36000);
                PRINT SUBSTRING(@dsql, 36000, 40000);
            END;
        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_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_json, is_in_memory_oltp,
                                is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition,  [optimize_for_sequential_key], 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, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;


        RAISERROR (N'Checking partition count',0,1) WITH NOWAIT;
        IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL
            BEGIN
                /* Count the total number of partitions */
                SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                        SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION    ( RECOMPILE );';
                EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT;
                IF @Rowcount > 100
                    BEGIN
                        RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT;
                        SET @SkipPartitions = 1;
                        INSERT    #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
                                                        index_usage_summary, index_size_summary )
                        VALUES  ( 1, 0 , 
		                       'Some Checks Were Skipped',
                               '@SkipPartitions Forced to 1',
                               'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', ''
                                );
                    END;
            END;



		 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 partitions, the OUTER APPLY was crazy slow.

			-- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects 
			IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL
				DROP TABLE #dm_db_partition_stats_etc;

			create table #dm_db_partition_stats_etc
			(
				database_id smallint not null
				, object_id int not null
				, sname sysname NULL
				, index_id int
				, partition_number int
				, partition_id bigint
				, row_count bigint
				, reserved_MB NUMERIC(29,2)
				, reserved_LOB_MB NUMERIC(29,2)
				, reserved_row_overflow_MB NUMERIC(29,2)
				, lock_escalation_desc nvarchar(60)
				, data_compression_desc nvarchar(60)
			)

			-- get relevant info from sys.dm_db_index_operational_stats
			IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL
				DROP TABLE #dm_db_index_operational_stats;
			create table #dm_db_index_operational_stats
			(
				database_id smallint not null
				, object_id int not null
				, index_id int
				, partition_number int
				, hobt_id bigint
				, leaf_insert_count bigint
				, leaf_delete_count bigint
				, leaf_update_count bigint
				, range_scan_count bigint
				, singleton_lookup_count bigint
				, forwarded_fetch_count bigint
				, lob_fetch_in_pages bigint
				, lob_fetch_in_bytes bigint
				, row_overflow_fetch_in_pages bigint
				, row_overflow_fetch_in_bytes bigint
				, row_lock_count bigint
				, row_lock_wait_count bigint
				, row_lock_wait_in_ms bigint
				, page_lock_count bigint
				, page_lock_wait_count bigint
				, page_lock_wait_in_ms bigint
				, index_lock_promotion_attempt_count bigint
				, index_lock_promotion_count bigint
				, page_latch_wait_count bigint
				, page_latch_wait_in_ms bigint
				, page_io_latch_wait_count bigint
				, page_io_latch_wait_in_ms bigint
				)
  
            SET @dsql = N'
                        DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121)
                        RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT;
                        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                        INSERT INTO #dm_db_partition_stats_etc
                        (
                            database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc
                        )
                        SELECT  ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id,
                                ps.object_id, 
								s.name as sname,
                                ps.index_id, 
                                ps.partition_number, 
                                ps.partition_id,
                                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,
								le.lock_escalation_desc,
                            ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N'
';

            SET @dsql = @dsql + 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*/
					JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id
			            OUTER APPLY (SELECT st.lock_escalation_desc
			                         FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st
			                         WHERE st.object_id = ps.object_id
			                             AND ps.index_id < 2 ) le
                    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 + N'
            GROUP BY ps.object_id, 
								s.name,
                                ps.index_id, 
                                ps.partition_number, 
								ps.partition_id,
                                ps.row_count,
                                ps.reserved_page_count,
                                ps.lob_reserved_page_count,
                                ps.row_overflow_reserved_page_count,
								le.lock_escalation_desc,
                            ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N'
			ORDER BY ps.object_id,  ps.index_id, ps.partition_number
	        OPTION    ( RECOMPILE ' + 
			CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 '
				ELSE N' '
			END + N');

            SET @d = CONVERT(VARCHAR(19), GETDATE(), 121)
            RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT;
            
                       insert  into #dm_db_index_operational_stats
           (
                database_id
              , object_id
              , index_id
              , partition_number
              , hobt_id
              , 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
              , page_latch_wait_count
              , page_latch_wait_in_ms
              , page_io_latch_wait_count
              , page_io_latch_wait_in_ms
            )
            
            select os.database_id
                 , os.object_id
                 , os.index_id
                 , os.partition_number ' + 
				CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', os.hobt_id '
					ELSE N', NULL AS hobt_id '
				END + N'
                 , 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
                 , os.page_latch_wait_count
                 , os.page_latch_wait_in_ms
                 , os.page_io_latch_wait_count
                 , os.page_io_latch_wait_in_ms
                from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os 
	            OPTION    ( RECOMPILE ' + 
				CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 '
					ELSE N' '
				END + N');

                SET @d = CONVERT(VARCHAR(19), GETDATE(), 121)
                RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT;
            ';
        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 partitions 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)) + N' AS database_id,
                                ps.object_id, 
								s.name,
                                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,
								le.lock_escalation_desc,
                                ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N',
                                SUM(os.leaf_insert_count), 
                                SUM(os.leaf_delete_count), 
                                SUM(os.leaf_update_count), 
                                SUM(os.range_scan_count), 
                                SUM(os.singleton_lookup_count),  
                                SUM(os.forwarded_fetch_count),
                                SUM(os.lob_fetch_in_pages), 
                                SUM(os.lob_fetch_in_bytes), 
                                SUM(os.row_overflow_fetch_in_pages),
                                SUM(os.row_overflow_fetch_in_bytes), 
                                SUM(os.row_lock_count), 
                                SUM(os.row_lock_wait_count),
                                SUM(os.row_lock_wait_in_ms), 
                                SUM(os.page_lock_count), 
                                SUM(os.page_lock_wait_count), 
                                SUM(os.page_lock_wait_in_ms),
                                SUM(os.index_lock_promotion_attempt_count), 
                                SUM(os.index_lock_promotion_count),
								SUM(os.page_latch_wait_count),
								SUM(os.page_latch_wait_in_ms),
								SUM(os.page_io_latch_wait_count),								
								SUM(os.page_io_latch_wait_in_ms)';

		    /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */
			IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries')
				SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB ';
			ELSE
				SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB ';


            SET @dsql = @dsql + 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*/
						JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id
                        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
			            OUTER APPLY (SELECT st.lock_escalation_desc
			                         FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st
			                         WHERE st.object_id = ps.object_id
			                             AND ps.index_id < 2 ) le
                        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 + '
	            GROUP BY ps.object_id, 
								s.name,
                                ps.index_id, 
                                ps.partition_number,
								ps.partition_id,
                                ps.row_count,
                                ps.reserved_page_count,
                                ps.lob_reserved_page_count,
                                ps.row_overflow_reserved_page_count,
								le.lock_escalation_desc,
                            ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N'
				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;
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql, 0, 4000);
                PRINT SUBSTRING(@dsql, 4000, 8000);
                PRINT SUBSTRING(@dsql, 8000, 12000);
                PRINT SUBSTRING(@dsql, 12000, 16000);
                PRINT SUBSTRING(@dsql, 16000, 20000);
                PRINT SUBSTRING(@dsql, 20000, 24000);
                PRINT SUBSTRING(@dsql, 24000, 28000);
                PRINT SUBSTRING(@dsql, 28000, 32000);
                PRINT SUBSTRING(@dsql, 32000, 36000);
                PRINT SUBSTRING(@dsql, 36000, 40000);
            END;
        EXEC sp_executesql @dsql; 
        INSERT    #IndexPartitionSanity ( [database_id],
                                          [object_id], 
										  [schema_name],
                                          index_id, 
                                          partition_number, 
                                          row_count, 
                                          reserved_MB,
                                          reserved_LOB_MB, 
                                          reserved_row_overflow_MB,
										  lock_escalation_desc,										   
                                          data_compression_desc, 
                                          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,
								          page_latch_wait_count,
								          page_latch_wait_in_ms,
								          page_io_latch_wait_count,								
								          page_io_latch_wait_in_ms,
										  reserved_dictionary_MB)
                select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc,
                                SUM(os.leaf_insert_count), 
                                SUM(os.leaf_delete_count), 
                                SUM(os.leaf_update_count), 
                                SUM(os.range_scan_count), 
                                SUM(os.singleton_lookup_count),  
                                SUM(os.forwarded_fetch_count),
                                SUM(os.lob_fetch_in_pages), 
                                SUM(os.lob_fetch_in_bytes), 
                                SUM(os.row_overflow_fetch_in_pages),
                                SUM(os.row_overflow_fetch_in_bytes), 
                                SUM(os.row_lock_count), 
                                SUM(os.row_lock_wait_count),
                                SUM(os.row_lock_wait_in_ms), 
                                SUM(os.page_lock_count), 
                                SUM(os.page_lock_wait_count), 
                                SUM(os.page_lock_wait_in_ms),
                                SUM(os.index_lock_promotion_attempt_count), 
                                SUM(os.index_lock_promotion_count), 
								SUM(os.page_latch_wait_count),
								SUM(os.page_latch_wait_in_ms),
								SUM(os.page_io_latch_wait_count),								
								SUM(os.page_io_latch_wait_in_ms)
                                ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB 
                    from #dm_db_partition_stats_etc h
                    left JOIN #dm_db_index_operational_stats as os ON
                        h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number 
                    group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc                          
                
		END; --End Check For @SkipPartitions = 0


		IF @Mode NOT IN(1, 2)
		BEGIN
        RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT;
        SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'


		SET @dsql = @dsql + '
WITH 
    ColumnNamesWithDataTypes AS
(
    SELECT 
        id.index_handle,
        id.object_id,
        cn.IndexColumnType,
        STUFF
        (
            (
                SELECT 
                    '', '' + 
                    cn_inner.ColumnName + 
                    '' '' +
                    N'' {'' + 
                        CASE     
                            WHEN ty.name IN (''varchar'', ''char'') 
                            THEN ty.name + 
                                 ''('' + 
                                 CASE 
                                     WHEN co.max_length = -1 
                                     THEN ''max'' 
                                     ELSE CAST(co.max_length AS VARCHAR(25)) 
                                 END + 
                                 '')''
                            WHEN ty.name IN (''nvarchar'', ''nchar'') 
                            THEN ty.name + 
                                 ''('' + 
                                 CASE 
                                     WHEN co.max_length = -1 
                                     THEN ''max'' 
                                     ELSE CAST(co.max_length / 2 AS VARCHAR(25)) 
                                 END + 
                                 '')''
                            WHEN ty.name IN (''decimal'', ''numeric'') 
                            THEN ty.name + 
                                 ''('' + 
                                 CAST(co.precision AS VARCHAR(25)) + 
                                 '', '' + 
                                 CAST(co.scale AS VARCHAR(25)) + 
                                 '')''
                            WHEN ty.name IN (''datetime2'') 
                            THEN ty.name + 
                                 ''('' + 
                                 CAST(co.scale AS VARCHAR(25)) + 
                                 '')''
                            ELSE ty.name END + ''}''
                FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner
                CROSS APPLY
                (
                    SELECT    
                        LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, 
                        ''Equality'' AS IndexColumnType
                    FROM 
                    (
                        VALUES 
                            (CONVERT(XML, N''<x>'' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N''</x><x>'') + N''</x>''))
                    ) x (n)
                    CROSS APPLY n.nodes(''x'') node(v)
                UNION ALL
                    SELECT    
                        LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, 
                        ''Inequality'' AS IndexColumnType
                    FROM
                    (
                        VALUES 
                            (CONVERT(XML, N''<x>'' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N''</x><x>'') + N''</x>''))
                    ) x (n)
                    CROSS APPLY n.nodes(''x'') node(v)
                UNION ALL
                    SELECT    
                        LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, 
                        ''Included'' AS IndexColumnType
                    FROM    
                    (
                        VALUES (CONVERT(XML, N''<x>'' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N''</x><x>'') + N''</x>''))
                    ) x (n)
                    CROSS APPLY n.nodes(''x'') node(v)
                ) AS cn_inner        
                JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co 
                  ON   co.object_id = id_inner.object_id 
                  AND ''['' + co.name + '']'' = cn_inner.ColumnName
                JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty 
                  ON ty.user_type_id = co.user_type_id 
                WHERE id_inner.index_handle = id.index_handle
                AND   id_inner.object_id = id.object_id
				AND   id_inner.database_id = DB_ID(@i_DatabaseName)
                AND   cn_inner.IndexColumnType = cn.IndexColumnType
                FOR XML PATH('''')
                ),
                1,
                1,
                ''''
             ) AS ReplaceColumnNames
           FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id
           CROSS APPLY
           (
               SELECT    
                   LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, 
                   ''Equality'' AS IndexColumnType
               FROM
               (
                   VALUES (CONVERT(XML, N''<x>'' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N''</x><x>'') + N''</x>''))
               ) x (n)
               CROSS APPLY n.nodes(''x'') node(v)
               UNION ALL
               SELECT    
                   LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, 
                   ''Inequality'' AS IndexColumnType
               FROM
               (
                   VALUES (CONVERT(XML, N''<x>'' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N''</x><x>'') + N''</x>''))
               ) x (n)
               CROSS APPLY n.nodes(''x'') node(v)
               UNION ALL
               SELECT
                   LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, 
                   ''Included'' AS IndexColumnType
               FROM
               (
                   VALUES (CONVERT(XML, N''<x>'' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N''</x><x>'') + N''</x>''))
               ) x (n)
               CROSS APPLY n.nodes(''x'') node(v)
           )AS cn
		   WHERE id.database_id = DB_ID(@i_DatabaseName)
           GROUP BY    
               id.index_handle,
               id.object_id,
               cn.IndexColumnType
)
SELECT
    *
INTO #ColumnNamesWithDataTypes
FROM ColumnNamesWithDataTypes
OPTION(RECOMPILE);

SELECT  
    id.database_id, 
    id.object_id, 
    @i_DatabaseName, 
    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,
    (
        SELECT 
            ColumnNamesWithDataTypes.ReplaceColumnNames 
        FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes 
        WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle
        AND   ColumnNamesWithDataTypes.object_id = id.object_id
        AND   ColumnNamesWithDataTypes.IndexColumnType = ''Equality''
    ) AS equality_columns_with_data_type,
    (
        SELECT 
            ColumnNamesWithDataTypes.ReplaceColumnNames 
        FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes 
        WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle
        AND   ColumnNamesWithDataTypes.object_id = id.object_id
        AND   ColumnNamesWithDataTypes.IndexColumnType = ''Inequality''
    ) AS inequality_columns_with_data_type,
    (
        SELECT ColumnNamesWithDataTypes.ReplaceColumnNames 
        FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes 
        WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle
        AND ColumnNamesWithDataTypes.object_id = id.object_id
        AND ColumnNamesWithDataTypes.IndexColumnType = ''Included''
    ) AS included_columns_with_data_type,';

		/* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */
        IF NOT EXISTS
		(
		    SELECT
			    1/0
			FROM sys.all_objects AS o
			WHERE o.name = 'dm_db_missing_index_group_stats_query'
	    )
        SELECT
            @dsql += N'
    NULL AS sample_query_plan'
        ELSE
		BEGIN
            /* The DMV is only supposed to have 600 rows in it. If it's got more,
            they could see performance slowdowns - see Github #3085. */
            DECLARE @MissingIndexPlans BIGINT;
            SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;'
            EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT;

            IF @MissingIndexPlans > 1000
                BEGIN
                SELECT @dsql += N'
    NULL AS sample_query_plan /* Over 1000 plans found, skipping */';
                RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT;
                END
            ELSE
                SELECT
                    @dsql += N'
    sample_query_plan =
    (
        SELECT TOP (1)
            p.query_plan
        FROM sys.dm_db_missing_index_group_stats gs 
        CROSS APPLY
        (
            SELECT TOP (1)
                s.plan_handle
            FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q 
            INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s
              ON q.query_plan_hash = s.query_plan_hash
            WHERE gs.group_handle = q.group_handle 
            ORDER BY 
                (q.user_seeks + q.user_scans) DESC, 
                s.total_logical_reads DESC
        ) q2
        CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p
        WHERE ig.index_group_handle = gs.group_handle
    )'
		END
        
        

		SET @dsql = @dsql + N'
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id 
  ON ig.index_handle = id.index_handle
JOIN ' + QUOTENAME(@DatabaseName) + N'.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);
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql, 0, 4000);
                PRINT SUBSTRING(@dsql, 4000, 8000);
                PRINT SUBSTRING(@dsql, 8000, 12000);
                PRINT SUBSTRING(@dsql, 12000, 16000);
                PRINT SUBSTRING(@dsql, 16000, 20000);
                PRINT SUBSTRING(@dsql, 20000, 24000);
                PRINT SUBSTRING(@dsql, 24000, 28000);
                PRINT SUBSTRING(@dsql, 28000, 32000);
                PRINT SUBSTRING(@dsql, 32000, 36000);
                PRINT SUBSTRING(@dsql, 36000, 40000);
            END;
        INSERT    #MissingIndexes ( [database_id], [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, equality_columns_with_data_type, inequality_columns_with_data_type, 
                                    included_columns_with_data_type, sample_query_plan)
        EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;
		END;

        SET @dsql = N'
			SELECT DB_ID(@i_DatabaseName) AS [database_id], 
				@i_DatabaseName AS database_name,
				s.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
			JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_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(''.'', ''nvarchar(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(''.'', ''nvarchar(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;
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql, 0, 4000);
                PRINT SUBSTRING(@dsql, 4000, 8000);
                PRINT SUBSTRING(@dsql, 8000, 12000);
                PRINT SUBSTRING(@dsql, 12000, 16000);
                PRINT SUBSTRING(@dsql, 16000, 20000);
                PRINT SUBSTRING(@dsql, 20000, 24000);
                PRINT SUBSTRING(@dsql, 24000, 28000);
                PRINT SUBSTRING(@dsql, 28000, 32000);
                PRINT SUBSTRING(@dsql, 32000, 36000);
                PRINT SUBSTRING(@dsql, 36000, 40000);
            END;
        INSERT  #ForeignKeys ( [database_id], [database_name], [schema_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, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;

		IF @Mode NOT IN(1, 2)
		BEGIN
        SET @dsql = N'
                SELECT 
                    DB_ID(@i_DatabaseName) AS [database_id], 
			        @i_DatabaseName AS database_name,
                    foreign_key_schema = 
                        s.name,
                    foreign_key_name = 
                        fk.name,
                    foreign_key_table = 
                        OBJECT_NAME(fk.parent_object_id, DB_ID(@i_DatabaseName)),
					fk.parent_object_id,
					foreign_key_referenced_table = 
                        OBJECT_NAME(fk.referenced_object_id, DB_ID(@i_DatabaseName)),
					fk.referenced_object_id
                FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk
                JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s
                    ON s.schema_id = fk.schema_id
                WHERE fk.is_disabled = 0
                AND   EXISTS
                      (
                          SELECT  
                              1/0
                          FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc
                          WHERE fkc.constraint_object_id = fk.object_id
                          AND NOT EXISTS
                              (
                                  SELECT  
                                      1/0
                                  FROM  ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic
                                  WHERE ic.object_id = fkc.parent_object_id
                                  AND   ic.column_id = fkc.parent_column_id
                                  AND   ic.index_column_id = fkc.constraint_column_id
                              )
                      )
				OPTION (RECOMPILE);'
        IF @dsql IS NULL 
            RAISERROR('@dsql is null',16,1);

        RAISERROR (N'Inserting data into #UnindexedForeignKeys',0,1) WITH NOWAIT;
        IF @Debug = 1
            BEGIN
                PRINT SUBSTRING(@dsql, 0, 4000);
                PRINT SUBSTRING(@dsql, 4000, 8000);
                PRINT SUBSTRING(@dsql, 8000, 12000);
                PRINT SUBSTRING(@dsql, 12000, 16000);
                PRINT SUBSTRING(@dsql, 16000, 20000);
                PRINT SUBSTRING(@dsql, 20000, 24000);
                PRINT SUBSTRING(@dsql, 24000, 28000);
                PRINT SUBSTRING(@dsql, 28000, 32000);
                PRINT SUBSTRING(@dsql, 32000, 36000);
                PRINT SUBSTRING(@dsql, 36000, 40000);
            END;

        INSERT
		    #UnindexedForeignKeys
        (
            database_id,
            database_name,
            schema_name,
            foreign_key_name,
            parent_object_name,
			parent_object_id,
			referenced_object_name,
			referenced_object_id
        )
        EXEC sys.sp_executesql
            @dsql,
            N'@i_DatabaseName sysname',
            @DatabaseName;
		END;


		IF @Mode NOT IN(1, 2)
		BEGIN
		IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */
			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'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			INSERT #Statistics ( database_id, database_name, object_id, 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, persisted_sample_percent, is_incremental)
				SELECT DB_ID(@i_DatabaseName) AS [database_id], 
			        @i_DatabaseName AS database_name,
			        obj.object_id,
			        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 BIGINT)
			        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,
					'
					+ CASE WHEN EXISTS
					  (
						  /* We cannot trust checking version numbers, like we did above, because Azure disagrees. */
						  SELECT 1
						  FROM sys.all_columns AS all_cols
						  WHERE all_cols.[object_id] = OBJECT_ID(N'sys.dm_db_stats_properties', N'IF') AND all_cols.[name] = N'persisted_sample_percent'
					  )
					  THEN N'ddsp.persisted_sample_percent,'
					  ELSE N'NULL AS persisted_sample_percent,' END
					+ CASE WHEN EXISTS
					  (
						  SELECT 1
						  FROM sys.all_columns AS all_cols
						  WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental'
					  )
					  THEN N's.is_incremental'
					  ELSE N'NULL AS is_incremental' END
			+ N'
			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(''.'', ''nvarchar(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;
            IF @Debug = 1
                BEGIN
                    PRINT SUBSTRING(@dsql, 0, 4000);
                    PRINT SUBSTRING(@dsql, 4000, 8000);
                    PRINT SUBSTRING(@dsql, 8000, 12000);
                    PRINT SUBSTRING(@dsql, 12000, 16000);
                    PRINT SUBSTRING(@dsql, 16000, 20000);
                    PRINT SUBSTRING(@dsql, 20000, 24000);
                    PRINT SUBSTRING(@dsql, 24000, 28000);
                    PRINT SUBSTRING(@dsql, 28000, 32000);
                    PRINT SUBSTRING(@dsql, 32000, 36000);
                    PRINT SUBSTRING(@dsql, 36000, 40000);
                END;
			
			EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;
			END;
			ELSE 
			BEGIN
			RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT;
			SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
			INSERT #Statistics(database_id, database_name, object_id, 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, persisted_sample_percent, is_incremental)
							SELECT DB_ID(@i_DatabaseName) AS [database_id], 
							    @i_DatabaseName AS database_name,
								obj.object_id,
								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 BIGINT)
						        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
								/* Certainly NULL. This branch does not even join on the table that this column comes from. */
								+ N'NULL AS persisted_sample_percent,
                                '
                                + CASE WHEN EXISTS
                                  (
                                      SELECT 1
                                      FROM sys.all_columns AS all_cols
                                      WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental'
                                  )
                                  THEN N's.is_incremental'
                                  ELSE N'NULL AS is_incremental' END
						+ N'								
						FROM    ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
						INNER HASH JOIN    ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si
						ON      si.name = s.name AND s.object_id = si.id
						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(''.'', ''nvarchar(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;
            IF @Debug = 1
                BEGIN
                    PRINT SUBSTRING(@dsql, 0, 4000);
                    PRINT SUBSTRING(@dsql, 4000, 8000);
                    PRINT SUBSTRING(@dsql, 8000, 12000);
                    PRINT SUBSTRING(@dsql, 12000, 16000);
                    PRINT SUBSTRING(@dsql, 16000, 20000);
                    PRINT SUBSTRING(@dsql, 20000, 24000);
                    PRINT SUBSTRING(@dsql, 24000, 28000);
                    PRINT SUBSTRING(@dsql, 28000, 32000);
                    PRINT SUBSTRING(@dsql, 32000, 36000);
                    PRINT SUBSTRING(@dsql, 36000, 40000);
                END;
			
			EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;
			END;
			END;
			END;

		IF @Mode NOT IN(1, 2)
		BEGIN
			IF  (PARSENAME(@SQLServerProductVersion, 4) >= 10)
			BEGIN
			RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT;
			SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], 
							   @i_DatabaseName AS database_name,
   					   		   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 ''%|].|[%'' ESCAPE ''|'' 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_id, [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, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;
			END;
            END;

		IF @Mode NOT IN(1, 2)
		BEGIN
			RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT;
			INSERT #TraceStatus
			EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS');			

			IF  (PARSENAME(@SQLServerProductVersion, 4) >= 13)
			BEGIN
			RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT;
			SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name,
								   DB_ID(@i_DatabaseName) AS [database_id], 
								   s.name AS schema_name,
								   t.name AS table_name, 
								   oa.hsn as history_schema_name,
								   oa.htn AS history_table_name, 
								   c1.name AS start_column_name,
								   c2.name AS end_column_name,
								   p.name AS period_name,
								   t.history_table_id AS history_table_object_id
							FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p
							INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t
							ON  p.object_id = t.object_id
							INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1
							ON  t.object_id = c1.object_id
							    AND p.start_column_id = c1.column_id
							INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2
							ON  t.object_id = c2.object_id
							    AND p.end_column_id = c2.column_id
							INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s
							ON t.schema_id = s.schema_id
							CROSS APPLY ( SELECT s2.name as hsn, t2.name htn
							              FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2
										  INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2
										  ON t2.schema_id = s2.schema_id
							              WHERE t2.object_id = t.history_table_id
							              AND t2.temporal_type = 1 /*History table*/ ) AS oa
							WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/
							OPTION (RECOMPILE);
							';
			
			IF @dsql IS NULL 
			RAISERROR('@dsql is null',16,1);
			
			INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, 
									 history_table_name, start_column_name, end_column_name, period_name, history_table_object_id )
					
			EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;
        END;

             SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], 
             				   @i_DatabaseName AS database_name,
             		   		   t.name AS table_name,
             		           s.name AS schema_name,
             		           cc.name AS constraint_name,
             		           cc.is_disabled,
             		           cc.definition,
             		           cc.uses_database_collation,
             		           cc.is_not_trusted,
             		   		   CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function,
             		   		   ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + 
             		   		   '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition  + '';'' COLLATE DATABASE_DEFAULT AS [column_definition]
             		   FROM    ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc
             		   JOIN    ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t
             		   ON      t.object_id = cc.parent_object_id
             		   JOIN    ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s
             		   ON      s.schema_id = t.schema_id
             		   OPTION (RECOMPILE);';
             
             INSERT #CheckConstraints
                     ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, 
             		  uses_database_collation, is_not_trusted, is_function, column_definition )		
             EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;


		IF @Mode NOT IN(1, 2)
		BEGIN
            SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], 
             				   @i_DatabaseName AS database_name,
                               s.name AS missing_schema_name,
                               t.name AS missing_table_name,
                               i.name AS missing_index_name,
                               c.name AS missing_column_name
                        FROM   ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed
                        JOIN   ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t
                            ON t.object_id = sed.referenced_id
                        JOIN   ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s
                            ON t.schema_id = s.schema_id
                        JOIN   ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i
                            ON i.object_id = sed.referenced_id
                            AND i.index_id = sed.referencing_minor_id
                        JOIN   ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c
                            ON c.object_id = sed.referenced_id
                            AND c.column_id = sed.referenced_minor_id
                        WHERE  sed.referencing_class = 7
                        AND    sed.referenced_class = 1
                        AND    i.has_filter = 1
                        AND    NOT EXISTS (   SELECT 1/0
                                              FROM   ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic
                                              WHERE  ic.index_id = sed.referencing_minor_id
                                              AND    ic.column_id = sed.referenced_minor_id
                                              AND    ic.object_id = sed.referenced_id )
                        OPTION(RECOMPILE);'

				BEGIN TRY
					INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name )
					EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;
				END TRY
				BEGIN CATCH
					RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT;
				END CATCH
        END;

        IF @Mode NOT IN(1, 2, 3)
        /*
        The sys.index_resumable_operations view was a 2017 addition, so we need to check for it and go dynamic.
        */
        AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations')
        BEGIN
            SET @dsql=N'SELECT @i_DatabaseName AS database_name,
                               DB_ID(@i_DatabaseName) AS [database_id],
                               s.name AS schema_name,
                               t.name AS table_name,
                               iro.[object_id],
                               iro.index_id,
                               iro.name,
                               iro.sql_text,
                               iro.last_max_dop_used,
                               iro.partition_number,
                               iro.state,
                               iro.state_desc,
                               iro.start_time,
                               iro.last_pause_time,
                               iro.total_execution_time,
                               iro.percent_complete,
                               iro.page_count
                        FROM   ' + QUOTENAME(@DatabaseName) + N'.sys.index_resumable_operations AS iro
                        JOIN   ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t
                            ON t.object_id = iro.object_id
                        JOIN   ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s
                            ON t.schema_id = s.schema_id
                        OPTION(RECOMPILE);'

                BEGIN TRY
                    RAISERROR (N'Inserting data into #IndexResumableOperations',0,1) WITH NOWAIT;
                    INSERT #IndexResumableOperations
                    ( database_name, database_id, schema_name, table_name,
                      [object_id], index_id, name, sql_text, last_max_dop_used, partition_number, state, state_desc,
                      start_time, last_pause_time, total_execution_time, percent_complete, page_count )
                    EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName;

                    SET @dsql=N'SELECT @ResumableIndexesDisappearAfter = CAST(value AS INT) 
                        FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations
			            WHERE name = ''PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES''
			            AND value > 0;'
                    EXEC sp_executesql @dsql, N'@ResumableIndexesDisappearAfter INT OUT', @ResumableIndexesDisappearAfter out;

                    IF @ResumableIndexesDisappearAfter IS NULL
                        SET @ResumableIndexesDisappearAfter = 0;

            END TRY
                BEGIN CATCH
                    RAISERROR (N'Skipping #IndexResumableOperations population due to error, typically low permissions', 0,1) WITH NOWAIT;
                END CATCH
        END;


    END;
			
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: PREP THE TEMP TABLES
--EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY.
----------------------------------------

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 +
							CASE max_length WHEN -1 THEN N' (max)' ELSE
								CASE  
									WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' 
									WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' 
									ELSE N' ' + CAST(max_length AS NVARCHAR(50))
								END
							END
							+ N'}'
                                AS col_definition
                            FROM    #IndexColumns c
                            WHERE    c.database_id= si.database_id
									AND c.schema_name = si.schema_name
                                    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('.', 'nvarchar(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.schema_name = si.schema_name
                                    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('.', 'nvarchar(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''
							END
                            + N' {' + system_type_name +
							CASE max_length WHEN -1 THEN N' (max)' ELSE
								CASE  
									WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' 
									WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' 
									ELSE N' ' + CAST(max_length AS NVARCHAR(50))
								END
							END
							+ N'}'
                                AS col_definition
                    FROM    #IndexColumns c
                    WHERE    c.database_id= si.database_id
							AND c.schema_name = si.schema_name
                            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('.', 'nvarchar(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.schema_name = si.schema_name
                            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('.', 'nvarchar(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 +
								CASE max_length WHEN -1 THEN N' (max)' ELSE
									CASE
										WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')'
										WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')'
										ELSE N' ' + CAST(max_length AS NVARCHAR(50))
									END
								END
								+ N'}'
                        FROM    #IndexColumns c
                        WHERE    c.database_id= si.database_id
								AND c.schema_name = si.schema_name
                                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('.', 'nvarchar(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.schema_name = si.schema_name
                                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('.', 'nvarchar(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.schema_name = si.schema_name
                                    AND c.object_id = si.object_id
                                AND c.index_id = si.index_id 
                                ) AS D4 ( count_included_columns, count_key_columns );

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
								AND i.schema_name = ps.schema_name;


RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT;
INSERT    #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB,
                                total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_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, 
								page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms)
        SELECT  index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc,						
				COUNT(*), SUM(row_count), SUM(reserved_MB),
				SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */
                SUM(reserved_row_overflow_MB), 
                SUM(reserved_dictionary_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),4000),
				SUM(page_latch_wait_count), 
				SUM(page_latch_wait_in_ms), 
				SUM(page_io_latch_wait_count), 
				SUM(page_io_latch_wait_in_ms)
        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 = ipp2.database_id
				AND ipp.schema_name = ipp2.schema_name
            ORDER BY ipp2.partition_number
            FOR      XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) 
                data_compression_info(data_compression_rollup)
        GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc
        ORDER BY index_sanity_id 
OPTION    ( RECOMPILE );

RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT;
UPDATE #MissingIndexes 
SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 
					    OR unique_compiles = 1
				  THEN 1
				  ELSE 0 
			  END;

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 s.database_id=fk.database_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 NVARCHAR(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 nc.database_id = tb.database_id
	AND nc.schema_name = tb.schema_name
    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 (
    CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name])  + ' REBUILD;'
    ELSE 
        CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/
        ELSE 
            CASE WHEN is_primary_key=1 THEN
                N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + 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_unique_constraint = 1 AND is_primary_key = 0
				THEN
			   N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) +
                    N'.' + QUOTENAME([object_name]) + 
                    N' ADD CONSTRAINT [' +
                    index_name + 
                    N'] UNIQUE ' + 
                    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([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + 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([database_name]) + N'.' + 
                    QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + 
                        CASE WHEN is_NC_columnstore=1 THEN 
                            N' (' + ISNULL(include_column_names_no_types,'') +  N' )' 
                        ELSE /*Else not columnstore */ 
                            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-columnstore 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=?, DATA_COMPRESSION=?'
                + N')'
            ELSE N'' END
            + N';'
            END /*End non-spatial and non-xml CASE */ 
    END, '[Unknown Error]')
        AS create_tsql
FROM #IndexSanity;
	  
RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps;
WITH maps
    AS
     (
         SELECT ips.index_sanity_id,
                ips.partition_number,
                ips.data_compression_desc,
                ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc
                                                           ORDER BY ips.partition_number ) AS rn
         FROM   #IndexPartitionSanity AS ips
     )
SELECT *
INTO   #maps
FROM   maps;

IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps;
WITH grps
    AS
     (
         SELECT   MIN(maps.partition_number) AS MinKey,
                  MAX(maps.partition_number) AS MaxKey,
                  maps.index_sanity_id,
                  maps.data_compression_desc
         FROM     #maps AS maps
         GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc
     )
SELECT *
INTO   #grps
FROM   grps;

INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail )
SELECT DISTINCT
       grps.index_sanity_id,
       SUBSTRING(
           ( STUFF(
                 (   SELECT   N', ' + N' Partition'
                              + CASE
                                     WHEN grps2.MinKey < grps2.MaxKey
                                     THEN
                                     + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - '
                                     + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc
                                     ELSE
                                     N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' 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('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail
FROM   #grps AS 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;

RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT;
UPDATE    #IndexSanity
SET        filter_columns_not_in_index = D1.filter_columns_not_in_index
FROM    #IndexSanity si
        CROSS APPLY ( SELECT  RTRIM(STUFF( (SELECT  N', ' + c.column_name AS col_definition
                            FROM    #FilteredIndexes AS c
                            WHERE    c.database_id= si.database_id
									AND c.schema_name = si.schema_name
                                    AND c.table_name = si.object_name
                                    AND c.index_name = si.index_name   
                                    ORDER BY c.index_sanity_id
                    FOR      XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 
                                ( filter_columns_not_in_index );


IF @Debug = 1
BEGIN
    SELECT '#IndexSanity' AS table_name, * FROM  #IndexSanity;
    SELECT '#IndexPartitionSanity' AS table_name, * FROM  #IndexPartitionSanity;
    SELECT '#IndexSanitySize' AS table_name, * FROM  #IndexSanitySize;
    SELECT '#IndexColumns' AS table_name, * FROM  #IndexColumns;
    SELECT '#MissingIndexes' AS table_name, * FROM  #MissingIndexes;
    SELECT '#ForeignKeys' AS table_name, * FROM  #ForeignKeys;
	SELECT '#UnindexedForeignKeys' AS table_name, * FROM  #UnindexedForeignKeys;
    SELECT '#IndexCreateTsql' AS table_name, * FROM  #IndexCreateTsql;
    SELECT '#DatabaseList' AS table_name, * FROM  #DatabaseList;
    SELECT '#Statistics' AS table_name, * FROM  #Statistics;
    SELECT '#PartitionCompressionInfo' AS table_name, * FROM  #PartitionCompressionInfo;
    SELECT '#ComputedColumns' AS table_name, * FROM  #ComputedColumns;
    SELECT '#TraceStatus' AS table_name, * FROM  #TraceStatus;
    SELECT '#TemporalTables' AS table_name, * FROM  #TemporalTables;   
    SELECT '#CheckConstraints' AS table_name, * FROM  #CheckConstraints;   
    SELECT '#FilteredIndexes' AS table_name, * FROM  #FilteredIndexes;
    SELECT '#IndexResumableOperations' AS table_name, * FROM  #IndexResumableOperations;
END


----------------------------------------
--STEP 3: DIAGNOSE THE PATIENT
----------------------------------------


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.
 
   	IF (@ShowColumnstoreOnly = 0)
	BEGIN
	   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,
			sz.page_latch_wait_count,
			CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time,
			sz.page_io_latch_wait_count,
			CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time,
            ct.create_tsql,
            CASE 
                WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]'
                THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name])
                        + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';'
                WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]'
                THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name])
                        + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';'
                WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]'
                    THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + 
                        QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';'
                ELSE N''
            END AS drop_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'http://FirstResponderKit.org' ,
                N'From Your Community Volunteers',
                NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,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],
			page_latch_wait_count AS [Page Latch Wait Count],
			page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)],
			page_io_latch_wait_count AS [Page IO Latch Wait Count],								
			page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)],
            create_tsql AS [Create TSQL],
            drop_tsql AS [Drop 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;

	WITH create_date AS (
						SELECT i.database_id,
							   i.schema_name,
							   i.[object_id], 
							   ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days
						FROM #IndexSanity AS i
						GROUP BY i.database_id, i.schema_name, i.object_id
						)
        SELECT  N'Missing index.' AS Finding ,
                N'https://www.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 / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END)
                                        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],
				sample_query_plan AS [Sample Query Plan]
        FROM    #MissingIndexes mi
		LEFT JOIN create_date AS cd
		ON mi.[object_id] =  cd.object_id 
		AND mi.database_id = cd.database_id
		AND mi.schema_name = cd.schema_name
        WHERE   mi.[object_id] = @ObjectID
        AND (@ShowAllMissingIndexRequests=1
                /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/
            OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 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'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' 
                    WHEN system_type_name IN (N'nchar',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;

    /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */
    IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram')
    BEGIN
        SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N';
					SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], 
                        hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], 
                        hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows],
                        s.auto_created AS [Auto-Created], s.user_created AS [User-Created],
                        props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows],
						props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID]
                    FROM sys.stats AS s
                    INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1
                    INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id
                    CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props  
                    CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist
                    WHERE s.object_id = @ObjectID
                    ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;';
        EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID;
     END

    /* Check for resumable index operations. */
    IF (SELECT TOP (1) [object_id] FROM #IndexResumableOperations WHERE [object_id] = @ObjectID AND database_id = @DatabaseID) IS NOT NULL
    BEGIN
        SELECT
            N'Resumable Index Operation' AS finding,
            N'This may invalidate your analysis!' AS warning,
            iro.state_desc + N' on ' + iro.db_schema_table_index +
            CASE iro.state
                WHEN 0 THEN
                    N' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) +
                    N'. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + N'. ' +
                    CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete after ' +
                    CONVERT(NVARCHAR(30), iro.total_execution_time) +
                    N' minute(s). ' + 
                    CASE WHEN @ResumableIndexesDisappearAfter > 0
                        THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. '
                        ELSE N' Will not be automatically removed by the database server. '
                    END
                    + N'This blocks DDL and can pile up ghosts.'
                WHEN 1 THEN
                    N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' +
                    CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' +
                    /*
                    At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment.
                    Updating statistics is one of the things that it can do in this false paused state.
                    Updating stats can take a while, so we point it out as a likely delay.
                    It seems that any of the normal operations that happen at the very end of an index build can cause this.
                    */
                    CASE WHEN iro.percent_complete > 99.9
                         THEN N'. It is probably still running, perhaps updating statistics.'
                         ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time)
                              + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.'
                    END
                ELSE N' which is an undocumented resumable index state description.'
                END AS details,
            N'https://www.BrentOzar.com/go/resumable' AS URL,
            iro.more_info AS [More Info]
        FROM #IndexResumableOperations AS iro
        WHERE iro.database_id = @DatabaseID
        AND iro.[object_id] = @ObjectID            
        OPTION    ( RECOMPILE );
    END
    ELSE
    BEGIN
        SELECT N'No resumable index operations.' AS finding;
    END;

	END /* END @ShowColumnstoreOnly = 0 */

    /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */
    IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments'))
    BEGIN
        RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT;

		SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; 
			IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID)
				BEGIN
				SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N'''';
				WITH DistinctColumns AS (
				SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id
					FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p
					INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id
                    INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id
					INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id
					WHERE p.object_id = @ObjectID
					AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id)
					AND p.data_compression IN (3,4)
				)
				SELECT @ColumnList = @ColumnList + column_name + N'', '',
					@ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', ''
				FROM DistinctColumns
				ORDER BY column_id;
				SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4);
				END';

		IF @Debug = 1
			BEGIN
				PRINT SUBSTRING(@dsql, 0, 4000);
				PRINT SUBSTRING(@dsql, 4000, 8000);
				PRINT SUBSTRING(@dsql, 8000, 12000);
				PRINT SUBSTRING(@dsql, 12000, 16000);
				PRINT SUBSTRING(@dsql, 16000, 20000);
				PRINT SUBSTRING(@dsql, 20000, 24000);
				PRINT SUBSTRING(@dsql, 24000, 28000);
				PRINT SUBSTRING(@dsql, 28000, 32000);
				PRINT SUBSTRING(@dsql, 32000, 36000);
				PRINT SUBSTRING(@dsql, 36000, 40000);
			END;

        EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT;

		IF @PartitionCount < 2
			SET @ShowPartitionRanges = 0;

		IF @Debug = 1
			SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges;

		IF @ColumnList <> ''
		BEGIN
			/* Remove the trailing comma */
			SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1);
			SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1);

			SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; 
				SELECT partition_number, '
				+ CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END
				+ N' row_group_id, total_rows, deleted_rows, ' 
				+ @ColumnList
				+ CASE WHEN @ShowPartitionRanges = 1 THEN N' ,
                    state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization
				FROM (
					SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details,
						range_start_op,
						CASE
							WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000))
							ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start,
						range_end_op,
						CASE
							WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000))
							ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N',
                        state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization
					FROM (
						SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows,
                            phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization,
							details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' 
							+ CASE WHEN @ShowPartitionRanges = 1 THEN N',
							CASE
								WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126
								WHEN pp.system_type_id IN (59, 62) THEN 3
								WHEN pp.system_type_id IN (60, 122) THEN 2
								ELSE NULL END format_type,
							CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op,
							prvs.value range_start_value,
							CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op,
							prve.value range_end_value ' ELSE N' ' END + N'
						FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg 
						INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id
						INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number
						INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id 
                        LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' 
						LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id
						LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id
						LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id
						LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id
						LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1
						LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END 
						+ N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id
                        LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id
						WHERE rg.object_id = @ObjectID
						AND rg.state IN (1, 2, 3)
						AND c.name IN ( ' + @ColumnListWithApostrophes + N')' 
						+ CASE WHEN @ShowPartitionRanges = 1 THEN N'
					) AS y ' ELSE N' ' END + N'
				) AS x
				PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1
				ORDER BY partition_number, row_group_id;';
 
			IF @Debug = 1
				BEGIN
					PRINT SUBSTRING(@dsql, 0, 4000);
					PRINT SUBSTRING(@dsql, 4000, 8000);
					PRINT SUBSTRING(@dsql, 8000, 12000);
					PRINT SUBSTRING(@dsql, 12000, 16000);
					PRINT SUBSTRING(@dsql, 16000, 20000);
					PRINT SUBSTRING(@dsql, 20000, 24000);
					PRINT SUBSTRING(@dsql, 24000, 28000);
					PRINT SUBSTRING(@dsql, 28000, 32000);
					PRINT SUBSTRING(@dsql, 32000, 36000);
					PRINT SUBSTRING(@dsql, 36000, 40000);
				END;

			IF @dsql IS NULL 
				RAISERROR('@dsql is null',16,1);
			ELSE
				EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID;
		END
		ELSE /* No columns were found for this object */
		BEGIN
			SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization
			UNION ALL
			SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100));
		END
        RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT;
    END

    IF @ShowColumnstoreOnly = 1
        RETURN;

END; /* IF @TableName IS NOT NULL */








ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */
BEGIN

/* Validate and check table output params */


		/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */
		DECLARE @ValidOutputServer BIT;
		DECLARE @ValidOutputLocation BIT;
		DECLARE @LinkedServerDBCheck NVARCHAR(2000);
		DECLARE @ValidLinkedServerDB INT;
		DECLARE @tmpdbchk TABLE (cnt INT);
		
		IF @OutputServerName IS NOT NULL
			BEGIN
				IF (SUBSTRING(@OutputTableName, 2, 1) = '#')
					BEGIN
						RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0);
					END;
				ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName)
					BEGIN
						SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')';
						INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck;
						SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk);
						IF (@ValidLinkedServerDB > 0)
							BEGIN
								SET @ValidOutputServer = 1;
								SET @ValidOutputLocation = 1;
							END;
						ELSE
							RAISERROR('The specified database was not found on the output server', 16, 0);
					END;
				ELSE
					BEGIN
						RAISERROR('The specified output server was not found', 16, 0);
					END;
			END;
		ELSE
			BEGIN
				IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
					BEGIN
						SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@';
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
						EXEC(@StringToExecute);
						
						SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)));
						SET @OutputDatabaseName = '[tempdb]';
						SET @OutputSchemaName = '[dbo]';
						SET @ValidOutputLocation = 1;
					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;
				ELSE IF @OutputDatabaseName IS NOT NULL
					AND @OutputSchemaName IS NOT NULL
					AND @OutputTableName IS NOT NULL
					AND EXISTS ( SELECT *
						 FROM   sys.databases
						 WHERE  QUOTENAME([name]) = @OutputDatabaseName)
					BEGIN
						SET @ValidOutputLocation = 1;
						SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)));
					END;
				ELSE IF @OutputDatabaseName IS NOT NULL
					AND @OutputSchemaName IS NOT NULL
					AND @OutputTableName IS NOT NULL
					AND NOT EXISTS ( SELECT *
						 FROM   sys.databases
						 WHERE  QUOTENAME([name]) = @OutputDatabaseName)
					BEGIN
						RAISERROR('The specified output database was not found on this server', 16, 0);
					END;
				ELSE
					BEGIN
						SET @ValidOutputLocation = 0; 
					END;
			END;
																										
        IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE')
        BEGIN
            RAISERROR('Invalid output location and no output asked',12,1);
            RETURN;
        END;
																										
		/* @OutputTableName lets us export the results to a permanent table */
		DECLARE @RunID UNIQUEIDENTIFIER;
		SET @RunID = NEWID();

		DECLARE @TableExists BIT;
		DECLARE @SchemaExists BIT;

		DECLARE @TableExistsSql NVARCHAR(MAX);

		IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL)
			BEGIN
				SET @StringToExecute = 
					N'SET @SchemaExists = 0;
					SET @TableExists = 0;
					IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') 
						SET @SchemaExists = 1
					IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'')
					BEGIN
						SET @TableExists = 1
						IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@''
										AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'')
							EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT''
					END';
	
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName);
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName);
	
				EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT;


				SET @TableExistsSql = 
					N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') 
						AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'')
						SET @TableExists = 0
					ELSE
						SET @TableExists = 1';
				
				SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName);
				SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
				SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); 
				SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); 

			END




	IF @Mode IN (0, 4) /* DIAGNOSE */
	BEGIN;
	IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */
	BEGIN;
        RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT;

        ----------------------------------------
        --Multiple Index Personalities: Check_id 0-10
        ----------------------------------------
        RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT;
            WITH    duplicate_indexes
                      AS ( SELECT  [object_id], key_column_names, database_id, [schema_name]
                           FROM        #IndexSanity AS ip
                           WHERE  index_type IN (1,2) /* Clustered, NC only*/
                                AND is_hypothetical = 0
                                AND is_disabled = 0
								AND is_primary_key = 0
								AND EXISTS (
											SELECT 1/0
											FROM #IndexSanitySize ips 
											WHERE ip.index_sanity_id = ips.index_sanity_id 
								            AND ip.database_id = ips.database_id
											AND ip.schema_name = ips.schema_name
								            AND ips.total_reserved_MB >= CASE 
											                             WHEN (@GetAllDatabases = 1 OR @Mode = 0) 
																		 THEN @ThresholdMB 
																		 ELSE ips.total_reserved_MB 
																		 END
								            )
                           GROUP BY    [object_id], key_column_names, database_id, [schema_name]
                           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,
                                20 AS Priority,
                                'Redundant Indexes' AS findings_group,
                                'Duplicate keys' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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 ip.[schema_name] = di.[schema_name]
                                                         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
														  AND ip.schema_name = ips.schema_name
                        /* 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 ips.total_rows DESC, ip.[schema_name], ip.[object_name], 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 database_id, [object_id], first_key_column_name, key_column_names,
                                    COUNT([object_id]) OVER ( PARTITION BY database_id, [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,
                                30 AS Priority,
                                'Redundant Indexes' AS findings_group,
                                'Approximate Duplicate Keys' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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.database_id = ip.database_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                                          
                        ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names
            OPTION    ( RECOMPILE );

        ----------------------------------------
        --Resumable Indexing: Check_id 122-123
        ----------------------------------------
        /*
        This is more complicated than you would expect!
        As of SQL Server 2022, I am aware of 6 cases that we need to check:
           1) A resumable rowstore CREATE INDEX that is currently running
           2) A resumable rowstore CREATE INDEX that is currently paused
           3) A resumable rowstore REBUILD that is currently running
           4) A resumable rowstore REBUILD that is currently paused
           5) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently running
           6) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently paused
        In cases 1 and 2, sys.indexes has no data at all about the index in question.
        This makes #IndexSanity much harder to use, since it depends on sys.indexes.
        We must therefore get as much from #IndexResumableOperations as possible.
        */
        RAISERROR(N'check_id 122: Resumable Index Operation Paused', 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, create_tsql, more_info )
                SELECT  122 AS check_id, 
                        i.index_sanity_id,
                        10 AS Priority,
                        N'Resumable Indexing' AS findings_group,
                        N'Resumable Index Operation Paused' AS finding, 
                        iro.[database_name] AS [Database Name],
                        N'https://www.BrentOzar.com/go/resumable' AS URL,
                        iro.state_desc + N' on ' + iro.db_schema_table_index +
                            N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' +
                            CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' +
                            /*
                            At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment.
                            Updating statistics is one of the things that it can do in this false paused state.
                            Updating stats can take a while, so we point it out as a likely delay.
                            It seems that any of the normal operations that happen at the very end of an index build can cause this.
                            */
                            CASE WHEN iro.percent_complete > 99.9
                            THEN N'. It is probably still running, perhaps updating statistics.'
                            ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time)
                                 + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts. '
                            END +
                            CASE WHEN @ResumableIndexesDisappearAfter > 0
                                THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. '
                                ELSE N' Will not be automatically removed by the database server. '
                            END AS details,
                        N'Old index: ' + ISNULL(i.index_definition, N'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition,
                        i.secret_columns,
                        i.index_usage_summary,
                        N'New index: ' + iro.reserved_MB_pretty_print + N'; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary,
                        N'New index: ' + iro.sql_text AS create_tsql,
                        iro.more_info
                FROM    #IndexResumableOperations iro
                LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id
                                       AND i.[object_id] = iro.[object_id] 
                                       AND i.index_id = iro.index_id            
                LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
                WHERE iro.state = 1
                OPTION    ( RECOMPILE );

        RAISERROR(N'check_id 123: Resumable Index Operation Running', 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, create_tsql, more_info )
                SELECT  123 AS check_id, 
                        i.index_sanity_id,
                        10 AS Priority,
                        N'Resumable Indexing' AS findings_group,
                        N'Resumable Index Operation Running' AS finding, 
                        iro.[database_name] AS [Database Name],
                        N'https://www.BrentOzar.com/go/resumable' AS URL,
                        iro.state_desc + ' on ' + iro.db_schema_table_index +
                            ' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) +
                            '. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + '. ' +
                            CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete after ' +
                            CONVERT(NVARCHAR(30), iro.total_execution_time) +
                            ' minute(s). This blocks DDL and can pile up ghosts.' AS details,
                        'Old index: ' + ISNULL(i.index_definition, 'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition,
                        i.secret_columns,
                        i.index_usage_summary,
                        'New index: ' + iro.reserved_MB_pretty_print + '; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary,
                        'New index: ' + iro.sql_text AS create_tsql,
                        iro.more_info
                FROM    #IndexResumableOperations iro
                LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id
                                       AND i.[object_id] = iro.[object_id] 
                                       AND i.index_id = iro.index_id            
                LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
                WHERE iro.state = 0
                OPTION    ( RECOMPILE );

        ----------------------------------------
        --Aggressive Indexes: Check_id 10-19
        ----------------------------------------

        RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 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,
                        70 AS Priority,
                        N'Locking-Prone ' 
                            + CASE COALESCE((SELECT SUM(1) 
							                 FROM #IndexSanity iMe 
											 INNER JOIN #IndexSanity iOthers 
												ON iMe.database_id = iOthers.database_id 
												AND iMe.object_id = iOthers.object_id 
												AND iOthers.index_id > 1 
											 WHERE i.index_sanity_id = iMe.index_sanity_id
												AND iOthers.is_hypothetical = 0
												AND iOthers.is_disabled = 0
											), 0)
                                WHEN 0 THEN N'Under-Indexing'
                                WHEN 1 THEN N'Under-Indexing'
                                WHEN 2 THEN N'Under-Indexing'
                                WHEN 3 THEN N'Under-Indexing'
                                WHEN 4 THEN N'Indexes'
                                WHEN 5 THEN N'Indexes'
                                WHEN 6 THEN N'Indexes'
                                WHEN 7 THEN N'Indexes'
                                WHEN 8 THEN N'Indexes'
                                WHEN 9 THEN N'Indexes'
                                ELSE N'Over-Indexing'
                                END AS findings_group,
                        N'Total lock wait time > 5 minutes (row + page)' AS finding, 
                        [database_name] AS [Database Name],
                        N'https://www.brentozar.com/go/AggressiveIndexes' AS URL,
                        (i.db_schema_object_indexid + N': ' +
                            sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT +
							 CAST(COALESCE((SELECT SUM(1) 
							                FROM #IndexSanity iMe 
											INNER JOIN #IndexSanity iOthers 
												ON iMe.database_id = iOthers.database_id 
												AND iMe.object_id = iOthers.object_id 
												AND iOthers.index_id > 1 
											WHERE i.index_sanity_id = iMe.index_sanity_id
											AND iOthers.is_hypothetical = 0
											AND iOthers.is_disabled = 0
										   ), 0)
                                         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
				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
                ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8
                OPTION    ( RECOMPILE );



        ---------------------------------------- 
        --Index Hoarder: Check_id 20-29
        ----------------------------------------
            RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 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, 
                                10 AS Priority,
                                'Over-Indexing' AS findings_group,
                                'Many NC Indexes on a Single Table' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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 )
                        GROUP BY db_schema_object_name, [i].[database_name]
                        HAVING    COUNT(*) >= 10
                        ORDER BY i.db_schema_object_name DESC  
						OPTION    ( RECOMPILE );

                RAISERROR(N'check_id 22: NC indexes with 0 reads and >= 10,000 writes', 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,
                                10 AS Priority,
                                N'Over-Indexing' AS findings_group,
                                N'Unused NC Index with High Writes' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.brentozar.com/go/IndexHoarder' AS URL,
                                N'Reads: 0,'
								+ N' Writes: ' 
								+ REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'')
								+ N' 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    i.total_reads=0
						    AND i.user_updates >= 10000
                                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
								AND @Filter <> 1 /* 1 = "ignore unused */
                        ORDER BY i.db_schema_object_indexid
                        OPTION    ( RECOMPILE );


		RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 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  34 AS check_id, 
                                i.index_sanity_id,
                                80 AS Priority,
                                N'Abnormal Design Pattern' AS findings_group,
                                N'Filter Columns Not In Index Definition' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.brentozar.com/go/IndexFeatures' AS URL,
                                N'The index '
                                + QUOTENAME(i.index_name)
                                + N' on ['
                                + i.db_schema_object_name
                                + N'] has a filter on ['
                                + i.filter_definition
                                + N'] but is missing ['
                                + LTRIM(i.filter_columns_not_in_index)
                                + N'] from the index definition.'
                                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.filter_columns_not_in_index IS NOT NULL
                        ORDER BY i.db_schema_object_indexid
                        OPTION    ( RECOMPILE );

		RAISERROR(N'check_id 124: History Table With NonClustered 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  124 AS check_id, 
                                i.index_sanity_id,
                                80 AS Priority,
                                N'Abnormal Design Pattern' AS findings_group,
                                N'History Table With NonClustered Index' AS finding, 
                                i.[database_name] AS [Database Name],
                                N'https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/' AS URL,
                                N'The history table '
                                + QUOTENAME(hist.history_schema_name)
                                + '.'
                                + QUOTENAME(hist.history_table_name)
                                + ' has a non-clustered index. This can cause MERGEs on the main table to fail! See item 8 on the URL.'
                                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
                        JOIN #TemporalTables hist
                        ON i.[object_id] = hist.history_table_object_id
                        AND i.[database_id] = hist.[database_id]
                        WHERE hist.history_table_object_id IS NOT NULL
                        AND i.index_type = 2 /* NC only */
                        ORDER BY i.db_schema_object_indexid
                        OPTION    ( RECOMPILE );

         ----------------------------------------
        --Self Loathing Indexes : Check_id 40-49
        ----------------------------------------
        
            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'Indexes Worth Reviewing' AS findings_group,
                            N'Low Fill Factor on Nonclustered Index' AS finding, 
                            [database_name] AS [Database Name],
                            N'https://www.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    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'Indexes Worth Reviewing' AS findings_group,
                            N'Low Fill Factor on Clustered Index' AS finding, 
                            [database_name] AS [Database Name],
                            N'https://www.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 fill_factor BETWEEN 1 AND 80 OPTION    ( RECOMPILE );


            RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT;
            WITH    heaps_cte
                      AS ( SELECT   [object_id],
								    [database_id],
								    [schema_name],
                                    SUM(forwarded_fetch_count) AS forwarded_fetch_count,
                                    SUM(leaf_delete_count) AS leaf_delete_count
                           FROM        #IndexPartitionSanity
                           GROUP BY    [object_id],
								       [database_id],
								       [schema_name]
                           HAVING    SUM(forwarded_fetch_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'Indexes Worth Reviewing' AS findings_group,
                                N'Heaps with Forwarded Fetches' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.brentozar.com/go/SelfLoathing' AS URL,
                                CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477'
                                    WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid
                                    ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST(
                                    (h.forwarded_fetch_count /*/@DaysUptime */)
                                     AS BIGINT) AS MONEY), 1), '.00', '') 
                                    END + N' forwarded fetches per day 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] 
							 AND i.[database_id] = h.[database_id]
							 AND i.[schema_name] = h.[schema_name]
                        JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
                        WHERE    i.index_id = 0 
                        AND h.forwarded_fetch_count / @DaysUptime > 1000
                        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],
								    [database_id],
								    [schema_name], 
									SUM(forwarded_fetch_count) AS forwarded_fetch_count,
                                    SUM(leaf_delete_count) AS leaf_delete_count
                           FROM        #IndexPartitionSanity
                           GROUP BY  [object_id],
								     [database_id],
								     [schema_name]
                           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'Indexes Worth Reviewing' AS findings_group,
                                N'Large Active Heap' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.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] 
								AND i.[database_id] = h.[database_id]
								AND i.[schema_name] = h.[schema_name]
                        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.*/
                OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT;
            WITH    heaps_cte
                      AS ( SELECT   [object_id],
								    [database_id],
								    [schema_name], 
									SUM(forwarded_fetch_count) AS forwarded_fetch_count,
                                    SUM(leaf_delete_count) AS leaf_delete_count
                           FROM        #IndexPartitionSanity
                           GROUP BY  [object_id],
								     [database_id],
								     [schema_name]
                           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'Indexes Worth Reviewing' AS findings_group,
                                N'Medium Active heap' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.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] 
								AND i.[database_id] = h.[database_id]
								AND i.[schema_name] = h.[schema_name]
                        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.*/
                OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT;
            WITH    heaps_cte
                      AS ( SELECT   [object_id],
								    [database_id],
								    [schema_name], 
									SUM(forwarded_fetch_count) AS forwarded_fetch_count,
                                    SUM(leaf_delete_count) AS leaf_delete_count
                           FROM        #IndexPartitionSanity
                           GROUP BY  [object_id],
								     [database_id],
								     [schema_name]
                           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'Indexes Worth Reviewing' AS findings_group,
                                N'Small Active heap' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.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] 
								AND i.[database_id] = h.[database_id]
								AND i.[schema_name] = h.[schema_name]
                        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.*/
						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'Indexes Worth Reviewing' AS findings_group,
                                N'Heap with a Nonclustered Primary Key' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.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 EXISTS 
                            (
                              SELECT 1/0 
                              FROM #IndexSanity AS isa
                              WHERE i.database_id = isa.database_id
                              AND   i.object_id = isa.object_id
                              AND   isa.index_id = 0
                            )
						OPTION    ( RECOMPILE );


                RAISERROR(N'check_id 128: Heaps with PAGE compression.', 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  128 AS check_id, 
                                i.index_sanity_id,
                                100 AS Priority,
                                N'Indexes Worth Reviewing' AS findings_group,
                                N'Heap with PAGE compression' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://vladdba.com/PageCompressedHeaps' 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
                        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) /*it doesn't matter that much if it's not active*/
								AND sz.data_compression_desc LIKE '%PAGE%' /*using LIKE here because there are some variations for this value*/
                OPTION    ( RECOMPILE );

	            RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 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  48 AS check_id, 
                                i.index_sanity_id,
                                100 AS Priority,
                                N'Over-Indexing' AS findings_group,
                                N'NC index with High Writes:Reads' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.brentozar.com/go/IndexHoarder' AS URL,
                                N'Reads: '
								+ REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') 
								+ N' Writes: ' 
								+ REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'')
								+ N' on: '
								+ i.db_schema_object_indexid 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.total_reads > 0 /*Not totally unused*/
								AND i.user_updates >= 10000 /*Decent write activity*/
								AND i.total_reads < 10000
								AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/
                                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 );

        ----------------------------------------
        --Indexaphobia
        --Missing indexes with value >= 5 million: : Check_id 50-59
        ----------------------------------------
            RAISERROR(N'check_id 50: High Value Missing Index.', 0,1) WITH NOWAIT;
            WITH    index_size_cte
                      AS ( SELECT   i.database_id,
									i.schema_name,
									i.[object_id], 
                                    MAX(i.index_sanity_id) AS index_sanity_id,
									ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days,
                                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.schema_name, 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, sample_query_plan )
                        
                        SELECT check_id, t.index_sanity_id, t.Priority, 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, sample_query_plan
                        FROM
                        (
                            SELECT  ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum,
                                50 AS check_id, 
                                sz.index_sanity_id,
                                40 AS Priority,
                                N'Index Suggestion' AS findings_group,
                                N'High Value Missing Index' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.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,
								mi.is_low,
                                mi.sample_query_plan
                        FROM    #MissingIndexes mi
                                LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id 
										  AND mi.database_id = sz.database_id
										  AND mi.schema_name = sz.schema_name
                                        /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/
                        WHERE @ShowAllMissingIndexRequests=1
                        OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) 
						OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000
                        ) AS t
                        WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END
                        ORDER BY magic_benefit_number DESC
						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, 
                                80 AS Priority,
                                N'Abnormal Design Pattern' 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'https://www.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((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL')
                                    + N', seed of '
                                        + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),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'
                                            ELSE 'unknown'
                                        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.database_id = ic.database_id
							AND i.schema_name = ic.schema_name
                            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
                        OPTION (RECOMPILE);


		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,
                        80 AS Priority,
                        N'Abnormal Design Pattern' AS findings_group,
                        'Columnstore Indexes with Trace Flag 834' AS finding, 
                        [database_name] AS [Database Name],
                        N'https://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;

        ----------------------------------------
        --Statistics Info: Check_id 90-99, as well as 125
        ----------------------------------------

        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, 
				90 AS Priority,
				'Statistics Warnings' AS findings_group,
				'Statistics Not Updated Recently',
				s.database_name,
				'https://www.brentozar.com/go/stats' AS URL,
				'Statistics on this table were last updated ' + 
					CASE WHEN s.last_statistics_update IS 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() - 30) 
		AND s.percent_modifications >= 10. 
		AND s.rows >= 10000
		OPTION    ( RECOMPILE );

        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, 
				90 AS Priority,
				'Statistics Warnings' AS findings_group,
				'Low Sampling Rates',
				s.database_name,
				'https://www.brentozar.com/go/stats' 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 BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10)
		  OR (s.rows > 1000000 AND s.percent_sampled < 1)
		OPTION    ( RECOMPILE );

        RAISERROR(N'check_id 125: Persisted Sampling Rates (Unexpected)', 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  125 AS check_id,
				90 AS Priority,
				'Statistics Warnings' AS findings_group,
				'Persisted Sampling Rates (Unexpected)',
				s.database_name,
				'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL,
				'The persisted statistics sample rate is ' + CONVERT(NVARCHAR(100), s.persisted_sample_percent) + '%'
				+ CASE WHEN @UsualStatisticsSamplingPercent IS NOT NULL
				  	   THEN (N' rather than your expected @UsualStatisticsSamplingPercent value of ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + '%')
					   ELSE ''
					   END
				+ N'. This may indicate that somebody is doing statistics rocket surgery. If not, consider updating statistics more frequently.' 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
		/*
		We have to do float comparison here, so it is time to explain why @UsualStatisticsSamplingPercent is a float.
		The foremost reason is that it is a float because we are comparing it to the persisted_sample_percent column in sys.dm_db_stats_properties and that column is a float.
		You may correctly object that CREATE STATISTICS with a decimal as your WITH SAMPLE [...] PERCENT is a syntax error and conclude that integers are enough.
		However, `WITH SAMPLE [...] ROWS` is allowed with PERSIST_SAMPLE_PERCENT = ON and you can use that to persist a non-integer sample rate.
		So, yes, we really have to use floats.
		*/
		WHERE
		/* persisted_sample_percent is either zero or NULL when the statistic is not persisted.  */
		s.persisted_sample_percent > 0.0001
		AND
		(
			ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) > 0.1
		  	OR @UsualStatisticsSamplingPercent IS NULL
		)
		OPTION    ( RECOMPILE );

        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, 
				90 AS Priority,
				'Statistics Warnings' AS findings_group,
				'Statistics With NO RECOMPUTE',
				s.database_name,
				'https://www.brentozar.com/go/stats' 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
		OPTION    ( RECOMPILE );

	     RAISERROR(N'check_id 94: Check Constraints 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  94 AS check_id, 
				100 AS Priority,
				'Forced Serialization' AS findings_group,
				'Check Constraint with Scalar UDF' AS finding,
				cc.database_name,
				'https://www.brentozar.com/go/computedscalar' AS URL,
				'The check constraint ' + QUOTENAME(cc.constraint_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 #CheckConstraints AS cc
		WHERE cc.is_function = 1
		OPTION    ( RECOMPILE );

		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, 
				100 AS Priority,
				'Forced Serialization' AS findings_group,
				'Computed Column with Scalar UDF' AS finding,
				cc.database_name,
				'https://www.brentozar.com/go/serialudf' 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
		OPTION    ( RECOMPILE );
        

	END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */








    IF @Mode = 4 /* DIAGNOSE*/
    BEGIN;
        RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT;

            RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 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'Over-Indexing' AS findings_group,
                                N'More Than 5 Percent NC Indexes Are Unused' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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
                                /*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 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'Over-Indexing' AS findings_group,
                            N'Approximate: Wide Indexes (7 or More Columns)' AS finding, 
                            [database_name] AS [Database Name],
                            N'https://www.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
                    OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT;
                WITH count_columns AS (
                            SELECT database_id, [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 database_id, 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'Over-Indexing' AS findings_group,
                                N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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.database_id = i.database_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]
                                                   AND i.database_id = cc.database_id
                        WHERE    index_id = 1 /* clustered only */
                                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: High ratio of nullable columns.', 0,1) WITH NOWAIT;
                WITH count_columns AS (
                            SELECT [object_id],
								   [database_id],
								   [schema_name],
                                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],
								     [database_id],
								     [schema_name]
                            )
                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'Over-Indexing' AS findings_group,
                                N'High Ratio of Nulls' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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]
								AND cc.database_id = ip.database_id
								AND cc.[schema_name] = ip.[schema_name]
                        WHERE    i.index_id IN (1,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],
								   [database_id],
								   [schema_name],
                                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],
								     [database_id],
								     [schema_name]
                            )
                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'Over-Indexing' AS findings_group,
                                N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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]
								AND cc.database_id = i.database_id
								AND cc.[schema_name] = i.[schema_name]
                        WHERE    i.index_id IN (1,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: High Ratio of Strings.', 0,1) WITH NOWAIT;
                WITH count_columns AS (
                            SELECT [object_id],
								   [database_id],
								   [schema_name],
                                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],
								     [database_id],
								     [schema_name]
                            )
                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'Over-Indexing' AS findings_group,
                                N'High Ratio of Strings' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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]
								AND cc.database_id = i.database_id
								AND cc.[schema_name] = i.[schema_name]
                        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 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, 
                                150 AS Priority,
                                N'Over-Indexing' AS findings_group,
                                N'Non-Unique Clustered Index' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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.database_id = i.database_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 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 );

        RAISERROR(N'check_id 29: NC indexes with 0 reads and < 10,000 writes', 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  29 AS check_id, 
                        i.index_sanity_id,
                        150 AS Priority,
                        N'Over-Indexing' AS findings_group,
                        N'Unused NC index with Low Writes' AS finding, 
                        [database_name] AS [Database Name],
                        N'https://www.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.user_updates < 10000
                        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
                        /*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())
                ORDER BY i.db_schema_object_indexid
                OPTION    ( RECOMPILE );


        ----------------------------------------
        --Feature-Phobic Indexes: Check_id 30-39
        ---------------------------------------- 
            RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT;
            /* This does not work the way you'd expect with @GetAllDatabases = 1. For details:
               https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825
            */

			SELECT  database_name,
					SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes,
					100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes
			INTO #index_includes
            FROM    #IndexSanity
			WHERE is_hypothetical = 0
			AND is_disabled = 0
			AND NOT (@GetAllDatabases = 1 OR @Mode = 0)
			GROUP BY database_name;

                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  30 AS check_id, 
                                NULL AS index_sanity_id, 
                                250 AS Priority,
                                N'Omitted Index Features' AS findings_group,
								database_name AS [Database Name],
                                N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL,
                                N'No Indexes Use Includes' AS details,
                                database_name + N' (Entire database)' AS index_definition, 
                                N'' AS secret_columns, 
                                N'N/A' AS index_usage_summary, 
                                N'N/A' AS index_size_summary 
						FROM #index_includes
						WHERE number_indexes_with_includes = 0
						OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 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  31 AS check_id,
					        NULL AS index_sanity_id, 
					        250 AS Priority,
					        N'Omitted Index Features' AS findings_group,
					        N'Few Indexes Use Includes' AS findings,
					        database_name AS [Database Name],
					        N'https://www.brentozar.com/go/IndexFeatures' AS URL,
					        N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% 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
					FROM #index_includes
					WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3
					OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 32: filtered indexes and indexed views', 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  DISTINCT
							32 AS check_id, 
					        NULL AS index_sanity_id,
					        250 AS Priority,
					        N'Omitted Index Features' AS findings_group,
					        N'No Filtered Indexes or Indexed Views' AS finding, 
					        i.database_name AS [Database Name],
					        N'https://www.brentozar.com/go/IndexFeatures' AS URL,
					        N'These are NOT always needed-- but do you know when you would use them?' AS details,
					        i.database_name + N' (Entire database)' AS index_definition, 
					        N'' AS secret_columns,
					        N'N/A' AS index_usage_summary, 
					        N'N/A' AS index_size_summary 
					FROM #IndexSanity i
					WHERE i.database_name NOT IN (                
							SELECT   database_name
							FROM     #IndexSanity
							WHERE    filter_definition <> '' )
					AND i.database_name NOT IN (
					       SELECT  database_name
						   FROM    #IndexSanity
						   WHERE   is_indexed_view = 1 )
					OPTION    ( RECOMPILE );

        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'Omitted Index Features' AS findings_group,
					        N'Potential Filtered Index (Based on Column Name)' AS finding, 
					        [database_name] AS [Database Name],
					        N'https://www.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.database_id =i.database_id
						AND ic.schema_name = i.schema_name
						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%')
					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'Indexes Worth Reviewing' AS findings_group,
                            N'Hypothetical Index' AS finding,
                            [database_name] AS [Database Name],
                            N'https://www.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 
                    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'Indexes Worth Reviewing' AS findings_group,
                            N'Disabled Index' AS finding, 
                            [database_name] AS [Database Name],
                            N'https://www.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
                    OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT;
            WITH    heaps_cte
                      AS ( SELECT   [object_id],
								    [database_id],
								    [schema_name],
                                    SUM(leaf_delete_count) AS leaf_delete_count
                           FROM        #IndexPartitionSanity
                           GROUP BY    [object_id],
								       [database_id],
								       [schema_name]
                           HAVING    SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */
                                    AND 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  49 AS check_id, 
                                i.index_sanity_id,
                                200 AS Priority,
                                N'Indexes Worth Reviewing' AS findings_group,
                                N'Heaps with Deletes' AS finding, 
                                [database_name] AS [Database Name],
                                N'https://www.brentozar.com/go/SelfLoathing' AS URL,
                                CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' 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] 
							 AND i.[database_id] = h.[database_id]
							 AND i.[schema_name] = h.[schema_name]
                        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 );

         ----------------------------------------
        --Abnormal Psychology : Check_id 60-79
        ----------------------------------------
            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 Design Pattern' AS findings_group,
                            N'XML Index' AS finding, 
                            [database_name] AS [Database Name],
                            N'https://www.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 Design Pattern' 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'https://www.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 Design Pattern' AS findings_group,
                            N'Spatial Index' AS finding,
                            [database_name] AS [Database Name], 
                            N'https://www.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 Design Pattern' AS findings_group,
                            N'Compressed Index' AS finding,
                            [database_name] AS [Database Name], 
                            N'https://www.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 Design Pattern' AS findings_group,
                            N'Partitioned Index' AS finding,
                            [database_name] AS [Database Name], 
                            N'https://www.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 Design Pattern' AS findings_group,
                            N'Non-Aligned Index on a Partitioned Table' AS finding,
                            i.[database_name] AS [Database Name], 
                            N'https://www.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 i.database_id = iParent.database_id
						AND i.schema_name = iParent.schema_name
                        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 Design Pattern' AS findings_group,
                            N'Recently Created Tables/Indexes (1 week)' AS finding,
                            [database_name] AS [Database Name], 
                            N'https://www.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()) 
                    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 Design Pattern' AS findings_group,
                            N'Recently Modified Tables/Indexes (2 days)' AS finding,
                            [database_name] AS [Database Name], 
                            N'https://www.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 /*Exclude recently created tables.*/
                    i.create_date < DATEADD(dd,-7,GETDATE()) 
                    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],
								   database_id,
								   schema_name,
                                   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],
								     database_id,
								     schema_name
                            )
                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 Design Pattern' AS findings_group,
                                N'Column Collation Does Not Match Database Collation' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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]
								AND cc.database_id = i.database_id
								AND cc.schema_name = i.schema_name
                        WHERE    i.index_id IN (1,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],
								   database_id,
								   schema_name,
                                   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,
								     database_id,
								     schema_name
                            )
                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 Design Pattern' AS findings_group,
                                N'Replicated Columns' AS finding,
                                [database_name] AS [Database Name],
                                N'https://www.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]
								AND i.database_id = cc.database_id
								AND i.schema_name = cc.schema_name
                        WHERE    i.index_id IN (1,0)
                            AND replicated_column_count > 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 Design Pattern' AS findings_group,
                    N'Cascading Updates or Deletes' AS finding, 
                    [database_name] AS [Database Name],
                    N'https://www.brentozar.com/go/AbnormalPsychology' AS URL,
                    N'Foreign Key ' + QUOTENAME(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 AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name)
                        AS more_info
            FROM #ForeignKeys fk
            WHERE ([delete_referential_action_desc] <> N'NO_ACTION'
            OR [update_referential_action_desc] <> N'NO_ACTION')
			OPTION    ( RECOMPILE );

            RAISERROR(N'check_id 72: Unindexed foreign keys.', 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  72 AS check_id, 
                    NULL AS index_sanity_id,
                    150 AS Priority,
                    N'Abnormal Design Pattern' AS findings_group,
                    N'Unindexed Foreign Keys' AS finding, 
                    [database_name] AS [Database Name],
                    N'https://www.brentozar.com/go/AbnormalPsychology' AS URL,
                    N'Foreign Key ' + QUOTENAME(foreign_key_name) +
                    N' on ' + QUOTENAME(parent_object_name)  + N''
                        + N' referencing ' + QUOTENAME(referenced_object_name) + N''
                        + N' does not appear to have a supporting index.' AS details, 
                    N'N/A' 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 AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name)
                        AS more_info
            FROM #UnindexedForeignKeys AS fk
			OPTION    ( RECOMPILE );


            RAISERROR(N'check_id 73: In-Memory OLTP', 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  73 AS check_id, 
                            i.index_sanity_id,
                            150 AS Priority,
                            N'Abnormal Design Pattern' AS findings_group,
                            N'In-Memory OLTP' AS finding,
                            [database_name] AS [Database Name], 
                            N'https://www.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_in_memory_oltp = 1
					OPTION    ( RECOMPILE );

        RAISERROR(N'check_id 74: Identity column with unusual seed', 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  74 AS check_id, 
                            i.index_sanity_id, 
                            200 AS Priority,
                            N'Abnormal Design Pattern' AS findings_group,
                            N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding,
                            [database_name] AS [Database Name],
                            N'https://www.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((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL')
                                + N', seed of '
                                    + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),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'
                                        ELSE 'unknown'
                                    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.database_id = ic.database_id
						AND i.schema_name = ic.schema_name
                        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 );

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

        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'High Workloads' AS findings_group,
            N'Scan-a-lots (index-usage-stats)' AS finding,
            [database_name] AS [Database Name],
            N'https://www.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
        ORDER BY  i.user_scans * iss.total_reserved_MB DESC
		OPTION    ( RECOMPILE );

        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'High Workloads' AS findings_group,
            N'Top Recent Accesses (index-op-stats)' AS finding,
            [database_name] AS [Database Name],
            N'https://www.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)
        ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC
		OPTION    ( RECOMPILE );



        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,
				'Statistics Warnings' AS findings_group,
				'Statistics With Filters',
				s.database_name,
				'https://www.brentozar.com/go/stats' 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
		OPTION    ( RECOMPILE );


		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,
				'Repeated Calculations' AS findings_group,
				'Computed Columns Not Persisted' 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
		OPTION    ( RECOMPILE );

		RAISERROR(N'check_id 110: Temporal Tables.', 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  110 AS check_id, 
				200 AS Priority,
				'Abnormal Design Pattern' AS findings_group,
				'Temporal Tables',
				t.database_name,
				'' AS URL,
				'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' 
					+ QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.'
				 AS details,
				'' AS index_definition,
				'N/A' AS secret_columns,
				'N/A' AS index_usage_summary,
				'N/A' AS index_size_summary
		FROM #TemporalTables AS t
		ORDER BY t.database_name, t.schema_name, t.table_name
		OPTION    ( RECOMPILE );

		RAISERROR(N'check_id 121: Optimized For Sequential Keys.', 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  121 AS check_id, 
				200 AS Priority,
				'Specialized Indexes' AS findings_group,
				'Optimized For Sequential Keys',
				i.database_name,
				'' AS URL,
				'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details,
				'' AS index_definition,
				'N/A' AS secret_columns,
				'N/A' AS index_usage_summary,
				'N/A' AS index_size_summary
		FROM #IndexSanity AS i
		WHERE i.optimize_for_sequential_key = 1
		OPTION    ( RECOMPILE );


        /* See check_id 125. */
		RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 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	126 AS check_id,
				200 AS Priority,
				'Statistics Warnings' AS findings_group,
				'Persisted Sampling Rates (Expected)',
				s.database_name,
				'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL,
				CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details,
				s.database_name + N' (Entire database)' 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 ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1
		  AND @UsualStatisticsSamplingPercent IS NOT NULL
		GROUP BY s.database_name
		OPTION    ( RECOMPILE );

		RAISERROR(N'check_id 127: Partitioned Table Without Incremental 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, more_info )
		SELECT  127 AS check_id,
				200 AS Priority,
				'Statistics Warnings' AS findings_group,
				'Partitioned Table Without Incremental Statistics',
				partitioned_tables.database_name,
				'https://sqlperformance.com/2015/05/sql-statistics/improving-maintenance-incremental-statistics' AS URL,
				'The table ' + QUOTENAME(partitioned_tables.schema_name) + '.' + QUOTENAME(partitioned_tables.object_name) + ' is partitioned, but '
                + CONVERT(NVARCHAR(100), incremental_stats_counts.not_incremental_stats_count) + ' of its ' + CONVERT(NVARCHAR(100), incremental_stats_counts.stats_count) +
                ' statistics are not incremental. If this is a sliding/rolling window table, then consider making the statistics incremental. If not, then investigate why this table is partitioned.' AS details,
				partitioned_tables.object_name + N' (Entire table)' AS index_definition,
				'N/A' AS secret_columns,
				'N/A' AS index_usage_summary,
				'N/A' AS index_size_summary,
                partitioned_tables.more_info
		FROM
        (
            SELECT s.database_id,
                   s.object_id,
                   COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) AS not_incremental_stats_count,
                   COUNT(*) AS stats_count
            FROM #Statistics AS s
            GROUP BY s.database_id, s.object_id
            HAVING COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) > 0
        ) AS incremental_stats_counts
        JOIN
        (
            /* Just get the tables. We do not need the indexes. */
            SELECT DISTINCT i.database_name,
                            i.database_id,
                            i.object_id,
                            i.schema_name,
                            i.object_name,
                            /* This is a little bit dishonest, since it tells us nothing about if the statistics are incremental. */
                            i.more_info
            FROM #IndexSanity AS i
            WHERE i.partition_key_column_name IS NOT NULL
        ) AS partitioned_tables
        ON partitioned_tables.database_id = incremental_stats_counts.database_id AND partitioned_tables.object_id = incremental_stats_counts.object_id
		/* No need for a GROUP BY. What we are joining on has exactly one row in each sub-query. */
		OPTION    ( RECOMPILE );

	END /* IF @Mode = 4 */







	   	   
        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', 'http://FirstResponderKit.org/', 
                   'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.',
                    @DaysUptimeInsertValue,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'http://FirstResponderKit.org' ,
                    @DaysUptimeInsertValue,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'http://FirstResponderKit.org' ,
                    @DaysUptimeInsertValue, N'',N''
                    );
            INSERT    #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
                                            index_usage_summary, index_size_summary )
            VALUES  ( 1, 0 , 
		           N'No Major Problems Found',
                   N'Nice Work!',
                   N'http://FirstResponderKit.org', 
                   N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', 
                   N'The default Mode 0 only looks for very serious index issues.', 
                   @DaysUptimeInsertValue, N''
                    );

        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'http://FirstResponderKit.org' ,
                    @DaysUptimeInsertValue, N'',N''
                    );
            INSERT    #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition,
                                            index_usage_summary, index_size_summary )
            VALUES  ( 1, 0 , 
		           N'No Problems Found',
                   N'Nice job! Or more likely, you have a nearly empty database.',
                   N'http://FirstResponderKit.org', 'Time to go read some blog posts.', 
                   @DaysUptimeInsertValue, N'', N''
                    );

        END;

        IF (@Debug = 1)
          BEGIN
              SELECT '#BlitzIndexResults' AS table_name, * FROM  #BlitzIndexResults;
          END;

        RAISERROR(N'Returning results.', 0,1) WITH NOWAIT;
            
        /*Return results.*/
		IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL)
			BEGIN
				IF NOT @SchemaExists = 1
					BEGIN
						RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0);
						RETURN;
					END

				IF @TableExists = 0
					BEGIN
						SET @StringToExecute = 
							N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ 
								(
									[id] INT IDENTITY(1,1) NOT NULL, 
									[run_id] UNIQUEIDENTIFIER,
									[run_datetime] DATETIME, 
									[server_name] NVARCHAR(128), 
									[priority] INT,
									[finding] NVARCHAR(4000),
									[database_name] NVARCHAR(128),
									[details] NVARCHAR(MAX),
									[index_definition] NVARCHAR(MAX),
									[secret_columns] NVARCHAR(MAX),
									[index_usage_summary] NVARCHAR(MAX),
									[index_size_summary] NVARCHAR(MAX),
									[more_info] NVARCHAR(MAX),
									[url] NVARCHAR(MAX),
									[create_tsql] NVARCHAR(MAX),
									[sample_query_plan] XML,
									CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC)
								);';
		
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); 
								
						IF @ValidOutputServer = 1
							BEGIN
								SET @StringToExecute = REPLACE(@StringToExecute,'''','''''');
								EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
							END;   
						ELSE
							BEGIN
								EXEC(@StringToExecute);
							END;
					END; /* @TableExists = 0 */

				-- Re-check that table now exists (if not we failed creating it)	
				SET @TableExists = NULL;
				EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT;
						
				IF NOT @TableExists = 1
					BEGIN
						RAISERROR('Creation of the output table failed.', 16, 0);
						RETURN;
					END;

				SET @StringToExecute = 
					N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@
						(
							[run_id], 
							[run_datetime], 
							[server_name],
							[priority],
							[finding],
							[database_name],
							[details],
							[index_definition],
							[secret_columns],
							[index_usage_summary],
							[index_size_summary],
							[more_info],
							[url],
							[create_tsql],
							[sample_query_plan]
						)
					SELECT
						''@@@RunID@@@'',
						''@@@GETDATE@@@'',
						''@@@LocalServerName@@@'',
						-- Below should be a copy/paste of the real query
						-- Make sure all quotes are escaped
						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],
						br.sample_query_plan AS [Sample Query Plan]
					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);';
	
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName);
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID);
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE());
				SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)));
				EXEC(@StringToExecute);

			END
        ELSE
			BEGIN
				IF(@OutputType <> 'NONE')
				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],
						br.sample_query_plan AS [Sample Query Plan]
					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;

	END /* End @Mode=0 or 4 (diagnose)*/








	ELSE IF (@Mode=1) /*Summarize*/
    BEGIN
    --This mode is to give some overall stats on the database.
		IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL)
			BEGIN

				IF NOT @SchemaExists = 1
					BEGIN
						RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0);
						RETURN;
					END

				IF @TableExists = 0
					BEGIN
						SET @StringToExecute = 
							N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ 
								(
									[id] INT IDENTITY(1,1) NOT NULL, 
									[run_id] UNIQUEIDENTIFIER,
									[run_datetime] DATETIME, 
									[server_name] NVARCHAR(128), 
									[database_name] NVARCHAR(128),
									[object_count] INT,
									[reserved_gb] NUMERIC(29,1),
									[reserved_lob_gb] NUMERIC(29,1),
									[reserved_row_overflow_gb] NUMERIC(29,1),
									[clustered_table_count] INT,
									[clustered_table_gb] NUMERIC(29,1),
									[nc_index_count] INT,
									[nc_index_gb] NUMERIC(29,1),
									[table_nc_index_ratio] NUMERIC(29,1),
									[heap_count] INT,
									[heap_gb] NUMERIC(29,1),
									[partitioned_table_count] INT,
									[partitioned_nc_count] INT,
									[partitioned_gb] NUMERIC(29,1),
									[filtered_index_count] INT,
									[indexed_view_count] INT,
									[max_table_row_count] INT,
									[max_table_gb] NUMERIC(29,1),
									[max_nc_index_gb] NUMERIC(29,1),
									[table_count_over_1gb] INT,
									[table_count_over_10gb] INT,
									[table_count_over_100gb] INT,    
									[nc_index_count_over_1gb] INT,
									[nc_index_count_over_10gb] INT,
									[nc_index_count_over_100gb] INT,
									[min_create_date] DATETIME,
									[max_create_date] DATETIME,
									[max_modify_date] DATETIME,
									[display_order] INT,
									CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC)
								);';
		
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); 
								
						IF @ValidOutputServer = 1
							BEGIN
								SET @StringToExecute = REPLACE(@StringToExecute,'''','''''');
								EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
							END;   
						ELSE
							BEGIN
								EXEC(@StringToExecute);
							END;
					END; /* @TableExists = 0 */

					-- Re-check that table now exists (if not we failed creating it)	
					SET @TableExists = NULL;
					EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT;
						
					IF NOT @TableExists = 1
						BEGIN
							RAISERROR('Creation of the output table failed.', 16, 0);
							RETURN;
						END;

					SET @StringToExecute = 
						N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@
							(
								[run_id], 
								[run_datetime], 
								[server_name], 
								[database_name],
								[object_count],
								[reserved_gb],
								[reserved_lob_gb],
								[reserved_row_overflow_gb],
								[clustered_table_count],
								[clustered_table_gb],
								[nc_index_count],
								[nc_index_gb],
								[table_nc_index_ratio],
								[heap_count],
								[heap_gb],
								[partitioned_table_count],
								[partitioned_nc_count],
								[partitioned_gb],
								[filtered_index_count],
								[indexed_view_count],
								[max_table_row_count],
								[max_table_gb],
								[max_nc_index_gb],
								[table_count_over_1gb],
								[table_count_over_10gb],
								[table_count_over_100gb],    
								[nc_index_count_over_1gb],
								[nc_index_count_over_10gb],
								[nc_index_count_over_100gb],
								[min_create_date],
								[max_create_date],
								[max_modify_date],
								[display_order]
							)
						SELECT ''@@@RunID@@@'',
							''@@@GETDATE@@@'',
							''@@@LocalServerName@@@'',
							-- Below should be a copy/paste of the real query
							-- Make sure all quotes are escaped
							-- NOTE! information line is skipped from output and the query below
							-- NOTE! initial columns are not casted to nvarchar due to not outputing informational line
							DB_NAME(i.database_id) AS [Database Name],
								COUNT(*) AS [Number Objects],
								CAST(SUM(sz.total_reserved_MB)/
									1024. AS NUMERIC(29,1)) AS [All GB],
								CAST(SUM(sz.total_reserved_LOB_MB)/
									1024. AS NUMERIC(29,1)) AS [LOB GB],
								CAST(SUM(sz.total_reserved_row_overflow_MB)/
									1024. AS NUMERIC(29,1)) AS [Row Overflow GB],
								SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) 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)
							ORDER BY [Display Order] ASC
							OPTION (RECOMPILE);';
	
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName);
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID);
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE());
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)));
					EXEC(@StringToExecute);

			END; /* @ValidOutputLocation = 1 */
		ELSE
			BEGIN
				IF(@OutputType <> 'NONE')
				BEGIN

			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'http://FirstResponderKit.org' ,
					@DaysUptimeInsertValue,
					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;

    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;

		IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL)
			BEGIN
				IF @SchemaExists = 1
					BEGIN
						IF @TableExists = 0
							BEGIN
								SET @StringToExecute = 
									N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ 
										(
											[id] INT IDENTITY(1,1) NOT NULL, 
											[run_id] UNIQUEIDENTIFIER,
											[run_datetime] DATETIME, 
											[server_name] NVARCHAR(128), 
											[database_name] NVARCHAR(128), 
											[schema_name] NVARCHAR(128), 
											[table_name] NVARCHAR(128), 
											[index_name] NVARCHAR(128),
                                            [Drop_Tsql] NVARCHAR(MAX),
                                            [Create_Tsql] NVARCHAR(MAX), 
											[index_id] INT, 
											[db_schema_object_indexid] NVARCHAR(500), 
											[object_type] NVARCHAR(15), 
											[index_definition] NVARCHAR(MAX), 
											[key_column_names_with_sort_order] NVARCHAR(MAX), 
											[count_key_columns] INT, 
											[include_column_names] NVARCHAR(MAX), 
											[count_included_columns] INT, 
											[secret_columns] NVARCHAR(MAX), 
											[count_secret_columns] INT, 
											[partition_key_column_name] NVARCHAR(MAX), 
											[filter_definition] NVARCHAR(MAX), 
											[is_indexed_view] BIT, 
											[is_primary_key] BIT,
											[is_unique_constraint] BIT,
											[is_XML] BIT, 
											[is_spatial] BIT, 
											[is_NC_columnstore] BIT, 
											[is_CX_columnstore] BIT, 
											[is_in_memory_oltp] BIT, 
											[is_disabled] BIT, 
											[is_hypothetical] BIT, 
											[is_padded] BIT, 
											[fill_factor] INT, 
											[is_referenced_by_foreign_key] BIT,
											[last_user_seek] DATETIME, 
											[last_user_scan] DATETIME, 
											[last_user_lookup] DATETIME, 
											[last_user_update] DATETIME, 
											[total_reads] BIGINT, 
											[user_updates] BIGINT, 
											[reads_per_write] MONEY, 
											[index_usage_summary] NVARCHAR(200), 
											[total_singleton_lookup_count] BIGINT, 
											[total_range_scan_count] BIGINT, 
											[total_leaf_delete_count] BIGINT, 
											[total_leaf_update_count] BIGINT, 
											[index_op_stats] NVARCHAR(200), 
											[partition_count] INT, 
											[total_rows] BIGINT, 
											[total_reserved_MB] NUMERIC(29,2), 
											[total_reserved_LOB_MB] NUMERIC(29,2), 
											[total_reserved_row_overflow_MB] NUMERIC(29,2), 
											[index_size_summary] NVARCHAR(300), 
											[total_row_lock_count] BIGINT, 
											[total_row_lock_wait_count] BIGINT, 
											[total_row_lock_wait_in_ms] BIGINT, 
											[avg_row_lock_wait_in_ms] BIGINT, 
											[total_page_lock_count] BIGINT, 
											[total_page_lock_wait_count] BIGINT, 
											[total_page_lock_wait_in_ms] BIGINT, 
											[avg_page_lock_wait_in_ms] BIGINT, 
											[total_index_lock_promotion_attempt_count] BIGINT, 
											[total_index_lock_promotion_count] BIGINT, 
											[total_forwarded_fetch_count] BIGINT,
											[data_compression_desc] NVARCHAR(4000), 
						                    [page_latch_wait_count] BIGINT,
								            [page_latch_wait_in_ms] BIGINT,
								            [page_io_latch_wait_count] BIGINT,								
								            [page_io_latch_wait_in_ms] BIGINT,
											[create_date] DATETIME, 
											[modify_date] DATETIME, 
											[more_info] NVARCHAR(500),
											[display_order] INT,
											CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC)
										);';
		
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); 
								
								IF @ValidOutputServer = 1
									BEGIN
										SET @StringToExecute = REPLACE(@StringToExecute,'''','''''');
										EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
									END;   
								ELSE
									BEGIN
										EXEC(@StringToExecute);
									END;
							END; /* @TableExists = 0 */
					

						-- Re-check that table now exists (if not we failed creating it)	
						SET @TableExists = NULL;
						EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT;
						
						IF @TableExists = 1
							BEGIN
								SET @StringToExecute = 
									N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@
										(
											[run_id], 
											[run_datetime], 
											[server_name], 
											[database_name], 
											[schema_name], 
											[table_name], 
											[index_name],
                                            [Drop_Tsql],
                                            [Create_Tsql], 
											[index_id], 
											[db_schema_object_indexid], 
											[object_type], 
											[index_definition], 
											[key_column_names_with_sort_order], 
											[count_key_columns], 
											[include_column_names], 
											[count_included_columns], 
											[secret_columns], 
											[count_secret_columns], 
											[partition_key_column_name], 
											[filter_definition], 
											[is_indexed_view], 
											[is_primary_key],
											[is_unique_constraint],
											[is_XML], 
											[is_spatial], 
											[is_NC_columnstore], 
											[is_CX_columnstore], 
                                            [is_in_memory_oltp],
											[is_disabled], 
											[is_hypothetical], 
											[is_padded], 
											[fill_factor], 
											[is_referenced_by_foreign_key], 
											[last_user_seek], 
											[last_user_scan], 
											[last_user_lookup], 
											[last_user_update], 
											[total_reads], 
											[user_updates], 
											[reads_per_write], 
											[index_usage_summary], 
											[total_singleton_lookup_count],
											[total_range_scan_count],
											[total_leaf_delete_count],
											[total_leaf_update_count],
											[index_op_stats],
											[partition_count], 
											[total_rows], 
											[total_reserved_MB], 
											[total_reserved_LOB_MB], 
											[total_reserved_row_overflow_MB], 
											[index_size_summary], 
											[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], 
											[total_forwarded_fetch_count],
											[data_compression_desc], 
						                    [page_latch_wait_count],
								            [page_latch_wait_in_ms],
								            [page_io_latch_wait_count],								
								            [page_io_latch_wait_in_ms],
											[create_date], 
											[modify_date], 
											[more_info],
											[display_order]
										)
									SELECT ''@@@RunID@@@'',
										''@@@GETDATE@@@'',
										''@@@LocalServerName@@@'',
										-- Below should be a copy/paste of the real query
										-- Make sure all quotes are escaped
										i.[database_name] AS [Database Name], 
										i.[schema_name] AS [Schema Name], 
										i.[object_name] AS [Object Name], 
										ISNULL(i.index_name, '''') AS [Index Name],
                                        CASE 
						                    WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]''
							                    THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) +
							                         N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';''
						                    WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]''
							                    THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) +
							                         N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';''
						                    WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]''
						                        THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' +
							                         QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';''
						                ELSE N''''
						                END AS [Drop TSQL],
					                    CASE 
						                    WHEN i.index_definition = ''[HEAP]'' THEN N''''
					                            ELSE N''--'' + ict.create_tsql END AS [Create TSQL],
										CAST(i.index_id AS NVARCHAR(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], 
										LEFT(index_definition,4000) 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_unique_constraint AS [Is Unique Constraint],
										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_in_memory_oltp AS [Is In-Memory OLTP],
										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.total_singleton_lookup_count AS [Singleton Lookups],
										sz.total_range_scan_count AS [Range Scans],
										sz.total_leaf_delete_count AS [Leaf Deletes],
										sz.total_leaf_update_count AS [Leaf Updates],
										sz.index_op_stats AS [Index Op Stats],
										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.total_forwarded_fetch_count AS [Forwarded Fetches],
										sz.data_compression_desc AS [Data Compression],
						                sz.page_latch_wait_count,
								        sz.page_latch_wait_in_ms,
								        sz.page_io_latch_wait_count,								
								        sz.page_io_latch_wait_in_ms,
										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 #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id
                                    LEFT JOIN #IndexCreateTsql AS ict  ON i.index_sanity_id = ict.index_sanity_id
									ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID]
									OPTION (RECOMPILE);';
	
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName);
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID);
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE());
								SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)));
								EXEC(@StringToExecute);
							END; /* @TableExists = 1 */
						ELSE
							RAISERROR('Creation of the output table failed.', 16, 0);
					END; /* @TableExists = 0 */
				ELSE
					RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0);
			END; /* @ValidOutputLocation = 1 */
		ELSE
	
		IF(@OutputType <> 'NONE')
		BEGIN
			SELECT  i.[database_name] AS [Database Name], 
					i.[schema_name] AS [Schema Name], 
					i.[object_name] AS [Object Name], 
					ISNULL(i.index_name, '') AS [Index Name],
					CAST(i.index_id AS NVARCHAR(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_unique_constraint AS [Is Unique Constraint] ,
					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_in_memory_oltp AS [Is In-Memory OLTP],
					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.total_singleton_lookup_count AS [Singleton Lookups],
					sz.total_range_scan_count AS [Range Scans],
					sz.total_leaf_delete_count AS [Leaf Deletes],
					sz.total_leaf_update_count AS [Leaf Updates],
					sz.index_op_stats AS [Index Op Stats],
					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.page_latch_wait_count AS [Page Latch Wait Count],
					sz.page_latch_wait_in_ms AS [Page Latch Wait ms],
					sz.page_io_latch_wait_count AS [Page IO Latch Wait Count],								
					sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms],
                    sz.total_forwarded_fetch_count AS [Forwarded Fetches],
					sz.data_compression_desc AS [Data Compression],
					i.create_date AS [Create Date],
					i.modify_date AS [Modify Date],
					more_info AS [More Info],
                    CASE 
						 WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]'
							THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name])
							     + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';'
						 WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]'
							THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name])
							     + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';'
						 WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]'
						     THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + 
							     QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';'
						 ELSE N''
						 END AS [Drop TSQL],
					CASE 
						WHEN i.index_definition = '[HEAP]' THEN N''
					    ELSE N'--' + ict.create_tsql END AS [Create TSQL], 
					1 AS [Display Order]
            INTO #Mode2Temp
			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
            LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id
			OPTION(RECOMPILE);

			IF @@ROWCOUNT > 0
            BEGIN
			    SELECT
			        sz.*
			    FROM #Mode2Temp AS sz
			    ORDER BY    /* Shout out to DHutmacher */
			    			/*DESC*/
			    			CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.[Rows] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END DESC,
			    			CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END DESC,
			    			/*ASC*/
			    			CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.[Rows] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END ASC,
			    			CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END ASC,
			    	sz.[Database Name], [Schema Name], [Object Name], [Index ID]
			    OPTION (RECOMPILE);
			END
			ELSE
			BEGIN
    			SELECT
				    DatabaseDetails =
					    N'Database ' +
						ISNULL(@DatabaseName, DB_NAME()) +
						N' has ' +
						ISNULL(RTRIM(@Rowcount), 0) +
						N' partitions.',
					BringThePain =
					    CASE
						    WHEN @BringThePain IN (0, 1) AND ISNULL(@Rowcount, 0) = 0
							THEN N'Check the database name, it looks like nothing is here.'
							WHEN @BringThePain = 0 AND ISNULL(@Rowcount, 0) > 0
							THEN N'Please re-run with @BringThePain = 1'
						END;
			END
  		END;
    END; /* End @Mode=2 (index detail)*/








    ELSE IF (@Mode=3) /*Missing index Detail*/
    BEGIN
		IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL)
			BEGIN

				IF NOT @SchemaExists = 1
					BEGIN
						RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0);
						RETURN;
					END

				IF @TableExists = 0
					BEGIN
						SET @StringToExecute = 
							N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ 
								(
									[id] INT IDENTITY(1,1) NOT NULL, 
									[run_id] UNIQUEIDENTIFIER,
									[run_datetime] DATETIME, 
									[server_name] NVARCHAR(128),  
									[database_name] NVARCHAR(128), 
									[schema_name] NVARCHAR(128),
									[table_name] NVARCHAR(128),
									[magic_benefit_number] BIGINT,
									[missing_index_details] NVARCHAR(MAX),
									[avg_total_user_cost] NUMERIC(29,4),
									[avg_user_impact] NUMERIC(29,1),
									[user_seeks] BIGINT,
									[user_scans] BIGINT,
									[unique_compiles] BIGINT,
									[equality_columns_with_data_type] NVARCHAR(MAX),
									[inequality_columns_with_data_type] NVARCHAR(MAX),
									[included_columns_with_data_type] NVARCHAR(MAX),
									[index_estimated_impact] NVARCHAR(256),
									[create_tsql] NVARCHAR(MAX),
									[more_info] NVARCHAR(600),
									[display_order] INT,
									[is_low] BIT,
									[sample_query_plan] XML,
									CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC)
								);';
		
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
						SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); 
								
						IF @ValidOutputServer = 1
							BEGIN
								SET @StringToExecute = REPLACE(@StringToExecute,'''','''''');
								EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
							END;   
						ELSE
							BEGIN
								EXEC(@StringToExecute);
							END;
					END; /* @TableExists = 0 */

					-- Re-check that table now exists (if not we failed creating it)	
					SET @TableExists = NULL;
					EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT;
						
					IF NOT @TableExists = 1
						BEGIN
							RAISERROR('Creation of the output table failed.', 16, 0);
							RETURN;
						END;
					SET @StringToExecute = 
						N'WITH create_date AS (
									SELECT i.database_id,
										   i.schema_name,
										   i.[object_id], 
										   ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days
									FROM #IndexSanity AS i
									GROUP BY i.database_id, i.schema_name, i.object_id
									)
						INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@
							(
								[run_id], 
								[run_datetime], 
								[server_name], 
								[database_name], 
								[schema_name],
								[table_name],
								[magic_benefit_number],
								[missing_index_details],
								[avg_total_user_cost],
								[avg_user_impact],
								[user_seeks],
								[user_scans],
								[unique_compiles],
								[equality_columns_with_data_type],
								[inequality_columns_with_data_type],
								[included_columns_with_data_type],
								[index_estimated_impact],
								[create_tsql],
								[more_info],
								[display_order],
								[is_low],
								[sample_query_plan]
							)
						SELECT ''@@@RunID@@@'',
							''@@@GETDATE@@@'',
							''@@@LocalServerName@@@'',
							-- Below should be a copy/paste of the real query
							-- Make sure all quotes are escaped
							-- NOTE! information line is skipped from output and the query below
							-- NOTE! CTE block is above insert in the copied SQL
							mi.database_name AS [Database Name], 
							mi.[schema_name] AS [Schema], 
							mi.table_name AS [Table], 
							CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT)
								AS [Magic Benefit Number], 
							mi.missing_index_details AS [Missing Index Details], 
							mi.avg_total_user_cost AS [Avg Query Cost], 
							mi.avg_user_impact AS [Est Index Improvement], 
							mi.user_seeks AS [Seeks], 
							mi.user_scans AS [Scans],
							mi.unique_compiles AS [Compiles],
							mi.equality_columns_with_data_type AS [Equality Columns],
							mi.inequality_columns_with_data_type AS [Inequality Columns],
							mi.included_columns_with_data_type AS [Included Columns], 
							mi.index_estimated_impact AS [Estimated Impact], 
							mi.create_tsql AS [Create TSQL], 
							mi.more_info AS [More Info],
							1 AS [Display Order],
							mi.is_low,
							mi.sample_query_plan AS [Sample Query Plan]
						FROM #MissingIndexes AS mi
						LEFT JOIN create_date AS cd
						ON mi.[object_id] =  cd.object_id 
						AND mi.database_id = cd.database_id
						AND mi.schema_name = cd.schema_name
						/* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/
						WHERE @ShowAllMissingIndexRequests=1 
						OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000
						ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC
						OPTION (RECOMPILE);';
	
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName);
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName);
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); 
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); 
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID);
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE());
					SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)));
					EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests;

			END; /* @ValidOutputLocation = 1 */
		ELSE
			BEGIN
				IF(@OutputType <> 'NONE')
				BEGIN
			WITH create_date AS (
						SELECT i.database_id,
							   i.schema_name,
							   i.[object_id], 
							   ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days
						FROM #IndexSanity AS i
						GROUP BY i.database_id, i.schema_name, i.object_id
						)
			SELECT 
				mi.database_name AS [Database Name], 
				mi.[schema_name] AS [Schema], 
				mi.table_name AS [Table], 
				CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT)
					AS [Magic Benefit Number], 
				mi.missing_index_details AS [Missing Index Details], 
				mi.avg_total_user_cost AS [Avg Query Cost], 
				mi.avg_user_impact AS [Est Index Improvement], 
				mi.user_seeks AS [Seeks], 
				mi.user_scans AS [Scans],
				mi.unique_compiles AS [Compiles],
				mi.equality_columns_with_data_type AS [Equality Columns],
				mi.inequality_columns_with_data_type AS [Inequality Columns],
				mi.included_columns_with_data_type AS [Included Columns], 
				mi.index_estimated_impact AS [Estimated Impact], 
				mi.create_tsql AS [Create TSQL], 
				mi.more_info AS [More Info],
				1 AS [Display Order],
				mi.is_low,
				mi.sample_query_plan AS [Sample Query Plan]
			FROM #MissingIndexes AS mi
			LEFT JOIN create_date AS cd
			ON mi.[object_id] =  cd.object_id 
			AND mi.database_id = cd.database_id
			AND mi.schema_name = cd.schema_name
			/* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/
			WHERE @ShowAllMissingIndexRequests=1 
            OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000
			UNION ALL
			SELECT               
				@ScriptVersionName,   
				N'From Your Community Volunteers' ,   
				N'http://FirstResponderKit.org' ,
				100000000000,
				@DaysUptimeInsertValue,
				NULL,NULL,NULL,NULL,NULL,NULL,NULL,
				NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL
			ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC
			OPTION (RECOMPILE);
  				END;


				IF  (@BringThePain = 1
				AND @DatabaseName IS NOT NULL
				AND @GetAllDatabases = 0)

				BEGIN
					EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1;        
				END;

			END;





    END; /* End @Mode=3 (index detail)*/
    SET @d = CONVERT(VARCHAR(19), GETDATE(), 121);
    RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT;
END /* End @TableName IS NULL (mode 0/1/2/3/4) */
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
IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL
BEGIN
    EXECUTE ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;');
END;
GO

ALTER PROCEDURE
    dbo.sp_BlitzLock
(
    @DatabaseName sysname = NULL,
    @StartDate datetime = NULL,
    @EndDate datetime = NULL,
    @ObjectName nvarchar(1024) = NULL,
    @StoredProcName nvarchar(1024) = NULL,
    @AppName sysname = NULL,
    @HostName sysname = NULL,
    @LoginName sysname = NULL,
    @EventSessionName sysname = N'system_health',
    @TargetSessionType sysname = NULL,
    @VictimsOnly bit = 0,
    @DeadlockType nvarchar(20) = NULL,
    @TargetDatabaseName sysname = NULL,      
    @TargetSchemaName sysname = NULL,        
    @TargetTableName sysname = NULL,         
    @TargetColumnName sysname = NULL,       
    @TargetTimestampColumnName sysname = NULL,
    @Debug bit = 0,
    @Help bit = 0,
    @Version varchar(30) = NULL OUTPUT,
    @VersionDate datetime = NULL OUTPUT,
    @VersionCheckMode bit = 0,
    @OutputDatabaseName sysname = NULL,
    @OutputSchemaName sysname = N'dbo',      /*ditto as below*/
    @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/
    @ExportToExcel bit = 0
)
WITH RECOMPILE
AS
BEGIN
    SET STATISTICS XML OFF;
    SET NOCOUNT ON;
    SET XACT_ABORT OFF;
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT @Version = '8.29', @VersionDate = '20260203';

    IF @VersionCheckMode = 1
    BEGIN
        RETURN;
    END;

    IF @Help = 1
    BEGIN
        PRINT N'
    /*
    sp_BlitzLock from http://FirstResponderKit.org

    This script checks for and analyzes deadlocks from the system health session or a custom extended event path

    Variables you can use:

        /*Filtering parameters*/
        @DatabaseName: If you want to filter to a specific database

        @StartDate: The date you want to start searching on, defaults to last 7 days

        @EndDate: The date you want to stop searching on, defaults to current date

        @ObjectName: If you want to filter to a specific able.
                     The object name has to be fully qualified ''Database.Schema.Table''

        @StoredProcName: If you want to search for a single stored proc
                     The proc name has to be fully qualified ''Database.Schema.Sproc''

        @AppName: If you want to filter to a specific application

        @HostName: If you want to filter to a specific host

        @LoginName: If you want to filter to a specific login

        @DeadlockType: Search for regular or parallel deadlocks specifically

        /*Extended Event session details*/
        @EventSessionName: If you want to point this at an XE session rather than the system health session.

        @TargetSessionType: Can be ''ring_buffer'',  ''event_file'', or ''table''. Leave NULL to auto-detect.

        /*Output to a table*/
        @OutputDatabaseName: If you want to output information to a specific database

        @OutputSchemaName: Specify a schema name to output information to a specific Schema

        @OutputTableName: Specify table name to to output information to a specific table

        /*Point at a table containing deadlock XML*/
        @TargetDatabaseName: The database that contains the table with deadlock report XML

        @TargetSchemaName: The schema of the table containing deadlock report XML

        @TargetTableName: The name of the table containing deadlock report XML
       
        @TargetColumnName: The name of the XML column that contains the deadlock report
       
        @TargetTimestampColumnName: The name of the datetime column for filtering by date range (optional)

   
    To learn more, visit http://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 SQL Server 2012 and newer is supported
     - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml.
       I took a long look at this one, and:
        1) Trying to account for all the weird places these could crop up is a losing effort.
        2) Replace is slow af on lots of xml.

    Unknown limitations of this version:
     - None.  (If we knew them, they would be known. Duh.)

    MIT License

    Copyright (c) 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.

    */';

        RETURN;
    END; /* @Help = 1 */

    /*Declare local variables used in the procudure*/
    DECLARE
        @DatabaseId int =
            DB_ID(@DatabaseName),
        @ProductVersion nvarchar(128) =
            CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)),
        @ProductVersionMajor float =
            SUBSTRING
            (
                CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)),
                1,
                CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1
            ),
        @ProductVersionMinor int =
            PARSENAME
            (
                CONVERT
                (
                    varchar(32),
                    CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))
                ),
                2
            ),
        @ObjectFullName nvarchar(MAX) = N'',
        @Azure bit =
            CASE
                WHEN
                (
                    SELECT
                        CONVERT
                        (
                            integer,
                            SERVERPROPERTY('EngineEdition')
                        )
                ) = 5
                THEN 1
                ELSE 0
            END,
        @MI bit =
            CASE
                WHEN
                (
                    SELECT
                        CONVERT
                        (
                            integer,
                            SERVERPROPERTY('EngineEdition')
                        )
                ) = 8
                THEN 1
                ELSE 0
            END,
        @RDS bit =
            CASE
                WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-'
                AND  LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-'
                AND  DB_ID('rdsadmin') IS NULL
                THEN 0
                ELSE 1
            END,
        @d varchar(40) = '',
        @StringToExecute nvarchar(4000) = N'',
        @StringToExecuteParams nvarchar(500) = N'',
        @r sysname = NULL,
        @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]',
        @DeadlockCount int = 0,
        @ServerName sysname = @@SERVERNAME,
        @OutputDatabaseCheck bit = -1,
        @SessionId int = 0,
        @TargetSessionId int = 0,
        @FileName nvarchar(4000) = N'',
        @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0),
        @deadlock_result nvarchar(MAX) = N'',
        @StartDateOriginal datetime = @StartDate,
        @EndDateOriginal datetime = @EndDate,
        @StartDateUTC datetime,
        @EndDateUTC datetime,
        @extract_sql nvarchar(MAX),
        @validation_sql nvarchar(MAX),
        @xe bit,
        @xd bit;

    /*Temporary objects used in the procedure*/
    DECLARE
        @sysAssObjId AS table
    (
        database_id int,
        partition_id bigint,
        schema_name sysname,
        table_name sysname
    );

    CREATE TABLE
        #x
    (
        x xml NOT NULL
            DEFAULT N'<x>x</x>'
    );

    CREATE TABLE
        #deadlock_data
    (
        deadlock_xml xml NOT NULL
            DEFAULT N'<x>x</x>'
    );

    CREATE TABLE
        #t
    (
        id int NOT NULL
    );

    CREATE TABLE
        #deadlock_findings
    (
        id int IDENTITY PRIMARY KEY,
        check_id int NOT NULL,
        database_name nvarchar(256),
        object_name nvarchar(1000),
        finding_group nvarchar(100),
        finding nvarchar(4000),
        sort_order bigint
    );

    /*Set these to some sane defaults if NULLs are passed in*/
    /*Normally I'd hate this, but we RECOMPILE everything*/

    SELECT
        @StartDate =
            CASE
                WHEN @StartDate IS NULL
                THEN
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            SYSDATETIME(),
                            GETUTCDATE()
                        ),
                        DATEADD
                        (
                            DAY,
                            -7,
                            SYSDATETIME()
                        )
                    )
                ELSE
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            SYSDATETIME(),
                            GETUTCDATE()
                        ),
                        @StartDate
                    )
            END,
        @EndDate =
            CASE
                WHEN @EndDate IS NULL
                THEN
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            SYSDATETIME(),
                            GETUTCDATE()
                        ),
                        SYSDATETIME()
                    )
                ELSE
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            SYSDATETIME(),
                            GETUTCDATE()
                        ),
                        @EndDate
                    )
            END;

    SELECT
        @StartDateUTC = @StartDate,
        @EndDateUTC = @EndDate;

    IF
    (
            @MI = 1
        AND @EventSessionName = N'system_health'
        AND @TargetSessionType IS NULL
    )
    BEGIN
        SET
            @TargetSessionType = N'ring_buffer';
    END;

    IF  ISNULL(@TargetDatabaseName, DB_NAME()) IS NOT NULL
    AND ISNULL(@TargetSchemaName, N'dbo') IS NOT NULL
    AND @TargetTableName IS NOT NULL
    AND @TargetColumnName IS NOT NULL
    BEGIN
        SET @TargetSessionType = N'table';
    END;

    /* Add this after the existing parameter validations */
    IF @TargetSessionType = N'table'
    BEGIN     
        IF @TargetDatabaseName IS NULL
        BEGIN
            SET @TargetDatabaseName = DB_NAME();
        END;

        IF @TargetSchemaName IS NULL
        BEGIN
            SET @TargetSchemaName = N'dbo';
        END;

        IF @TargetTableName IS NULL
        OR @TargetColumnName IS NULL
        BEGIN
            RAISERROR(N'
            When using a table as a source, you must specify @TargetTableName, and @TargetColumnName.
            When @TargetDatabaseName or @TargetSchemaName is NULL, they default to DB_NAME() AND dbo',
            11, 1) WITH NOWAIT;
            RETURN;
        END;
   
        /* Check if target database exists */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.databases AS d
            WHERE d.name = @TargetDatabaseName
        )
        BEGIN
            RAISERROR(N'The specified @TargetDatabaseName %s does not exist.', 11, 1, @TargetDatabaseName) WITH NOWAIT;
            RETURN;
        END;
   
        /* Use dynamic SQL to validate schema, table, and column existence */
        SET @validation_sql = N'
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s
            WHERE s.name = @schema
        )
        BEGIN
            RAISERROR(N''The specified @TargetSchemaName %s does not exist in database %s.'', 11, 1, @schema, @database) WITH NOWAIT;
            RETURN;
        END;
   
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s
              ON t.schema_id = s.schema_id
            WHERE t.name = @table
            AND   s.name = @schema
        )
        BEGIN
            RAISERROR(N''The specified @TargetTableName %s does not exist in schema %s in database %s.'', 11, 1, @table, @schema, @database) WITH NOWAIT;
            RETURN;
        END;
   
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t
              ON c.object_id = t.object_id
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s
              ON t.schema_id = s.schema_id
            WHERE c.name = @column
            AND   t.name = @table
            AND   s.name = @schema
        )
        BEGIN
            RAISERROR(N''The specified @TargetColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @column, @schema, @table, @database) WITH NOWAIT;
            RETURN;
        END;
   
        /* Validate column is XML type */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty
              ON c.user_type_id = ty.user_type_id
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t
              ON c.object_id = t.object_id
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s
              ON t.schema_id = s.schema_id
            WHERE c.name = @column
            AND   t.name = @table
            AND   s.name = @schema
            AND   ty.name = N''xml''
        )
        BEGIN
            RAISERROR(N''The specified @TargetColumnName %s must be of XML data type.'', 11, 1, @column) WITH NOWAIT;
            RETURN;
        END;';
   
        /* Validate timestamp_column if specified */
        IF @TargetTimestampColumnName IS NOT NULL
        BEGIN
            SET @validation_sql = @validation_sql + N'
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t
              ON c.object_id = t.object_id
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s
              ON t.schema_id = s.schema_id
            WHERE c.name = @timestamp_column
            AND   t.name = @table
            AND   s.name = @schema
        )
        BEGIN
            RAISERROR(N''The specified @TargetTimestampColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @timestamp_column, @schema, @table, @database) WITH NOWAIT;
            RETURN;
        END;
   
        /* Validate timestamp column is datetime type */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty
              ON c.user_type_id = ty.user_type_id
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t
              ON c.object_id = t.object_id
            JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s
              ON t.schema_id = s.schema_id
            WHERE c.name = @timestamp_column
            AND   t.name = @table
            AND   s.name = @schema
            AND   ty.name LIKE ''%date%''
        )
        BEGIN
            RAISERROR(N''The specified @TargetTimestampColumnName %s must be of datetime data type.'', 11, 1, @timestamp_column) WITH NOWAIT;
            RETURN;
        END;';
        END;
   
        IF @Debug = 1 BEGIN PRINT @validation_sql; END;
       
        EXECUTE sys.sp_executesql
            @validation_sql,
          N'
            @database sysname,
            @schema sysname,
            @table sysname,
            @column sysname,
            @timestamp_column sysname
          ',
            @TargetDatabaseName,
            @TargetSchemaName,
            @TargetTableName,
            @TargetColumnName,
            @TargetTimestampColumnName;
    END;


    IF @Azure = 0
    AND LOWER(@TargetSessionType) <> N'table'
    BEGIN
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.server_event_sessions AS ses
            JOIN sys.dm_xe_sessions AS dxs
              ON dxs.name = ses.name
            WHERE ses.name = @EventSessionName
            AND   dxs.create_time IS NOT NULL
        )
        BEGIN
            RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT;
            RETURN;
        END;
    END;

    IF @Azure = 1
    AND LOWER(@TargetSessionType) <> N'table'
    BEGIN
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.database_event_sessions AS ses
            JOIN sys.dm_xe_database_sessions AS dxs
              ON dxs.name = ses.name
            WHERE ses.name = @EventSessionName
            AND   dxs.create_time IS NOT NULL
        )
        BEGIN
            RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT;
            RETURN;
        END;
    END;

    IF @OutputDatabaseName IS NOT NULL
    BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT;

        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.databases AS d
            WHERE d.name = @OutputDatabaseName
        ) /*If database is invalid raiserror and set bitcheck*/
        BEGIN
            RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT;
            SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */
        END;
        ELSE
        BEGIN
            SET @OutputDatabaseCheck = 0;

            SELECT
                @StringToExecute =
                    N'SELECT @r = o.name FROM ' +
                    @OutputDatabaseName +
                    N'.sys.objects AS o inner join ' +
                    @OutputDatabaseName +
                    N'.sys.schemas as s on o.schema_id = s.schema_id WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' +
                    QUOTENAME
                    (
                        @OutputTableName,
                        N''''
                    ) +
                    N' AND s.name =''' +
                    @OutputSchemaName +
                    N''';',
                @StringToExecuteParams =
                    N'@r sysname OUTPUT';

            IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
            EXECUTE sys.sp_executesql
                @StringToExecute,
                @StringToExecuteParams,
                @r OUTPUT;

            IF @Debug = 1
            BEGIN
                RAISERROR('@r is set to: %s for schema name %s  and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT;
            END;

            /*protection spells*/
            SELECT
                @ObjectFullName =
                    QUOTENAME(@OutputDatabaseName) +
                    N'.' +
                    QUOTENAME(@OutputSchemaName) +
                    N'.' +
                    QUOTENAME(@OutputTableName),
                @OutputDatabaseName =
                    QUOTENAME(@OutputDatabaseName),
                @OutputTableName =
                    QUOTENAME(@OutputTableName),
                @OutputSchemaName =
                    QUOTENAME(@OutputSchemaName);

            IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/
            BEGIN
                /* If the table doesn't have the new spid column, add it. See Github #3101. */
                SET @StringToExecute =
                        N'IF NOT EXISTS (SELECT 1/0 FROM ' +
                        @OutputDatabaseName +
                        N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' +
                        @ObjectFullName +
                        N''')) AND o.name = N''spid'')
                        /*Add spid column*/
                        ALTER TABLE ' +
                        @ObjectFullName +
                        N' ADD spid smallint NULL;';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;

                /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */
                SET @StringToExecute =
                        N'IF NOT EXISTS (SELECT 1/0 FROM ' +
                        @OutputDatabaseName +
                        N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' +
                        @ObjectFullName +
                        N''')) AND o.name = N''wait_resource'')
                        /*Add wait_resource column*/
                        ALTER TABLE ' +
                        @ObjectFullName +
                        N' ADD wait_resource nvarchar(MAX) NULL;';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;

                /* If the table doesn't have the new client option column, add it. See Github #3101. */
                SET @StringToExecute =
                        N'IF NOT EXISTS (SELECT 1/0 FROM ' +
                        @OutputDatabaseName +
                        N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' +
                        @ObjectFullName +
                        N''')) AND o.name = N''client_option_1'')
                        /*Add wait_resource column*/
                        ALTER TABLE ' +
                        @ObjectFullName +
                        N' ADD client_option_1 varchar(500) NULL;';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;

                /* If the table doesn't have the new client option column, add it. See Github #3101. */
                SET @StringToExecute =
                        N'IF NOT EXISTS (SELECT 1/0 FROM ' +
                        @OutputDatabaseName +
                        N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' +
                        @ObjectFullName +
                        N''')) AND o.name = N''client_option_2'')
                        /*Add wait_resource column*/
                        ALTER TABLE ' +
                        @ObjectFullName +
                        N' ADD client_option_2 varchar(500) NULL;';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;

                /* If the table doesn't have the new lock mode column, add it. See Github #3101. */
                SET @StringToExecute =
                        N'IF NOT EXISTS (SELECT 1/0 FROM ' +
                        @OutputDatabaseName +
                        N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' +
                        @ObjectFullName +
                        N''')) AND o.name = N''lock_mode'')
                        /*Add wait_resource column*/
                        ALTER TABLE ' +
                        @ObjectFullName +
                        N' ADD lock_mode nvarchar(256) NULL;';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;

                /* If the table doesn't have the new status column, add it. See Github #3101. */
                SET @StringToExecute =
                        N'IF NOT EXISTS (SELECT 1/0 FROM ' +
                        @OutputDatabaseName +
                        N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' +
                        @ObjectFullName +
                        N''')) AND o.name = N''status'')
                        /*Add wait_resource column*/
                        ALTER TABLE ' +
                        @ObjectFullName +
                        N' ADD status nvarchar(256) NULL;';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;
            END;
            ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */
            BEGIN
                SELECT
                    @StringToExecute =
                        N'USE ' +
                        @OutputDatabaseName +
                        N';
                        CREATE TABLE ' +
                        @OutputSchemaName +
                        N'.' +
                        @OutputTableName +
                        N'  (
                                ServerName nvarchar(256),
                                deadlock_type nvarchar(256),
                                event_date datetime,
                                database_name nvarchar(256),
                                spid smallint,
                                deadlock_group nvarchar(256),
                                query xml,
                                object_names xml,
                                isolation_level nvarchar(256),
                                owner_mode nvarchar(256),
                                waiter_mode nvarchar(256),
                                lock_mode nvarchar(256),
                                transaction_count bigint,
                                client_option_1 varchar(500),
                                client_option_2 varchar(500),
                                login_name nvarchar(256),
                                host_name nvarchar(256),
                                client_app nvarchar(1024),
                                wait_time bigint,
                                wait_resource nvarchar(max),
                                priority smallint,
                                log_used bigint,
                                last_tran_started datetime,
                                last_batch_started datetime,
                                last_batch_completed datetime,
                                transaction_name nvarchar(256),
                                status nvarchar(256),
                                owner_waiter_type nvarchar(256),
                                owner_activity nvarchar(256),
                                owner_waiter_activity nvarchar(256),
                                owner_merging nvarchar(256),
                                owner_spilling nvarchar(256),
                                owner_waiting_to_close nvarchar(256),
                                waiter_waiter_type nvarchar(256),
                                waiter_owner_activity nvarchar(256),
                                waiter_waiter_activity nvarchar(256),
                                waiter_merging nvarchar(256),
                                waiter_spilling nvarchar(256),
                                waiter_waiting_to_close nvarchar(256),
                                deadlock_graph xml
                            )';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;
            END;

            /*
            Check for BlitzLockFindings table - runs regardless of whether main table existed
            This was moved outside the ELSE block to fix issue where pre-existing deadlocks
            table would skip BlitzLockFindings creation, causing synonym errors
            Must filter by schema to avoid finding table in wrong schema
            Note: @OutputSchemaName is already QUOTENAMEd at this point, so use PARSENAME to get raw name
            */
            SET @r = NULL; /*Reset - SELECT with no rows doesn't overwrite variable*/

			SELECT
                @StringToExecute =
                    N'SELECT @r = o.name FROM ' +
                    @OutputDatabaseName +
                    N'.sys.objects AS o
                    INNER JOIN ' +
                    @OutputDatabaseName +
                    N'.sys.schemas AS s
                      ON o.schema_id = s.schema_id
                    WHERE o.type_desc = N''USER_TABLE''
                    AND o.name = N''BlitzLockFindings''
                    AND s.name = N''' +
                    PARSENAME(@OutputSchemaName, 1) +
                    N'''',
                @StringToExecuteParams =
                    N'@r sysname OUTPUT';

            IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
            EXECUTE sys.sp_executesql
                @StringToExecute,
                @StringToExecuteParams,
                @r OUTPUT;

            IF (@r IS NULL) /*if table does not exist*/
            BEGIN
                SELECT
                    @OutputTableFindings =
                        QUOTENAME(N'BlitzLockFindings'),
                    @StringToExecute =
                        N'USE ' +
                        @OutputDatabaseName +
                        N';
                        CREATE TABLE ' +
                        @OutputSchemaName +
                        N'.' +
                        @OutputTableFindings +
                        N' (
                               ServerName nvarchar(256),
                               check_id INT,
                               database_name nvarchar(256),
                               object_name nvarchar(1000),
                               finding_group nvarchar(100),
                               finding nvarchar(4000)
                           );';

                IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
                EXECUTE sys.sp_executesql
                    @StringToExecute;
            END;

            /*create synonym for deadlockfindings.*/
            IF EXISTS
            (
                SELECT
                    1/0
                FROM sys.objects AS o
                WHERE o.name = N'DeadlockFindings'
                AND   o.type_desc = N'SYNONYM'
            )
            BEGIN
                RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT;
                DROP SYNONYM dbo.DeadlockFindings;
            END;

            RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT;
            SET @StringToExecute =
                    N'CREATE SYNONYM dbo.DeadlockFindings FOR ' +
                    @OutputDatabaseName +
                    N'.' +
                    @OutputSchemaName +
                    N'.' +
                    @OutputTableFindings;

            IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
            EXECUTE sys.sp_executesql
                @StringToExecute;

            /*create synonym for deadlock table.*/
            IF EXISTS
            (
                SELECT
                    1/0
                FROM sys.objects AS o
                WHERE o.name = N'DeadLockTbl'
                AND   o.type_desc = N'SYNONYM'
            )
            BEGIN
                RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT;
                DROP SYNONYM dbo.DeadLockTbl;
            END;

            RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT;
            SET @StringToExecute =
                    N'CREATE SYNONYM dbo.DeadLockTbl FOR ' +
                    @OutputDatabaseName +
                    N'.' +
                    @OutputSchemaName +
                    N'.' +
                    @OutputTableName;

            IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
            EXECUTE sys.sp_executesql
                @StringToExecute;
        END;
    END;

    /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */
    IF @RDS = 0
    BEGIN;
        BEGIN TRY;
        RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT;
            UPDATE STATISTICS
                #t
            WITH
                ROWCOUNT  = 9223372036854775807,
                PAGECOUNT = 9223372036854775807;
        END TRY
        BEGIN CATCH;
            /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object".
                    Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */
            IF (ERROR_NUMBER() = 1088)
            BEGIN;
                SET @d = CONVERT(varchar(40), GETDATE(), 109);
                RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT;
            END;
            ELSE
            BEGIN;
                THROW;
            END;
        END CATCH;
    END;

    IF @DeadlockType IS NOT NULL
    BEGIN
        SELECT
            @DeadlockType =
                CASE
                    WHEN LOWER(@DeadlockType) LIKE 'regular%'
                    THEN N'Regular Deadlock'
                    WHEN LOWER(@DeadlockType) LIKE N'parallel%'
                    THEN N'Parallel Deadlock'
                    ELSE NULL
                END;
    END;

    /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/
    /*Azure has differently named views, so  we need to separate. Thanks, Azure.*/

    IF
    (
            @Azure = 0
        AND @TargetSessionType IS NULL
    )
    BEGIN
    RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT;

        SELECT TOP (1)
            @TargetSessionType = t.target_name
        FROM sys.dm_xe_sessions AS s
        JOIN sys.dm_xe_session_targets AS t
          ON s.address = t.event_session_address
        WHERE s.name = @EventSessionName
        AND   t.target_name IN (N'event_file', N'ring_buffer')
        ORDER BY t.target_name
        OPTION(RECOMPILE);

    RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT;
    END;

    IF
    (
            @Azure = 1
        AND @TargetSessionType IS NULL
        AND LOWER(@TargetSessionType) <> N'table'
    )
    BEGIN
    RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT;

        SELECT TOP (1)
            @TargetSessionType = t.target_name
        FROM sys.dm_xe_database_sessions AS s
        JOIN sys.dm_xe_database_session_targets AS t
          ON s.address = t.event_session_address
        WHERE s.name = @EventSessionName
        AND   t.target_name IN (N'event_file', N'ring_buffer')
        ORDER BY t.target_name
        OPTION(RECOMPILE);

    RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT;
    END;


    /*The system health stuff gets handled different from user extended events.*/
    /*These next sections deal with user events, dependent on target.*/

    /*If ring buffers*/
    IF
    (
           @TargetSessionType LIKE N'ring%'
       AND @EventSessionName NOT LIKE N'system_health%'
    )
    BEGIN
        IF @Azure = 0
        BEGIN
            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT;

            INSERT
                #x WITH(TABLOCKX)
            (
                x
            )
            SELECT
                x = TRY_CAST(t.target_data AS xml)
            FROM sys.dm_xe_session_targets AS t
            JOIN sys.dm_xe_sessions AS s
              ON s.address = t.event_session_address
            WHERE s.name = @EventSessionName
            AND   t.target_name = N'ring_buffer'
            OPTION(RECOMPILE);

            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
        END;

        IF @Azure = 1
        BEGIN
            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT;

            INSERT
                #x WITH(TABLOCKX)
            (
                x
            )
            SELECT
                x = TRY_CAST(t.target_data AS xml)
            FROM sys.dm_xe_database_session_targets AS t
            JOIN sys.dm_xe_database_sessions AS s
              ON s.address = t.event_session_address
            WHERE s.name = @EventSessionName
            AND   t.target_name = N'ring_buffer'
            OPTION(RECOMPILE);

            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
        END;
    END;


    /*If event file*/
    IF
    (
           @TargetSessionType LIKE N'event%'
       AND @EventSessionName NOT LIKE N'system_health%'
    )
    BEGIN
        IF @Azure = 0
        BEGIN
            RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT;

            SELECT
                @SessionId = t.event_session_id,
                @TargetSessionId = t.target_id
            FROM sys.server_event_session_targets AS t
            JOIN sys.server_event_sessions AS s
              ON s.event_session_id = t.event_session_id
            WHERE t.name = @TargetSessionType
            AND   s.name = @EventSessionName
            OPTION(RECOMPILE);

            /*We get the file name automatically, here*/
            RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT;
            SELECT
                @FileName =
                    CASE
                        WHEN f.file_name LIKE N'%.xel'
                        THEN REPLACE(f.file_name, N'.xel', N'*.xel')
                        ELSE f.file_name + N'*.xel'
                    END
            FROM
            (
                SELECT
                    file_name =
                        CONVERT(nvarchar(4000), f.value)
                FROM sys.server_event_session_fields AS f
                WHERE f.event_session_id = @SessionId
                AND   f.object_id = @TargetSessionId
                AND   f.name = N'filename'
            ) AS f
            OPTION(RECOMPILE);
        END;

        IF @Azure = 1
        BEGIN
            RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT;
            SELECT
                @SessionId =
                    t.event_session_address,
                @TargetSessionId =
                    t.target_name
            FROM sys.dm_xe_database_session_targets t
            JOIN sys.dm_xe_database_sessions s
              ON s.address = t.event_session_address
            WHERE t.target_name = @TargetSessionType
            AND   s.name = @EventSessionName
            OPTION(RECOMPILE);

            /*We get the file name automatically, here*/
            RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT;
            SELECT
                @FileName =
                    CASE
                        WHEN f.file_name LIKE N'%.xel'
                        THEN REPLACE(f.file_name, N'.xel', N'*.xel')
                        ELSE f.file_name + N'*.xel'
                    END
            FROM
            (
                SELECT
                    file_name =
                        CONVERT(nvarchar(4000), f.value)
                FROM sys.server_event_session_fields AS f
                WHERE f.event_session_id = @SessionId
                AND   f.object_id = @TargetSessionId
                AND   f.name = N'filename'
            ) AS f
            OPTION(RECOMPILE);
        END;

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT;

        INSERT
            #x WITH(TABLOCKX)
        (
            x
        )
        SELECT
            x = TRY_CAST(f.event_data AS xml)
        FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f
        LEFT JOIN #t AS t
          ON 1 = 1
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
    END;

    /*The XML is parsed differently if it comes from the event file or ring buffer*/

    /*If ring buffers*/
    IF
    (
           @TargetSessionType LIKE N'ring%'
       AND @EventSessionName NOT LIKE N'system_health%'
    )
    BEGIN
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT;

        INSERT
            #deadlock_data WITH(TABLOCKX)
        (
            deadlock_xml
        )
        SELECT
            deadlock_xml =
                e.x.query(N'.')
        FROM #x AS x
        LEFT JOIN #t AS t
          ON 1 = 1
        CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x)
        WHERE
          (
              e.x.exist('@name[ .= "xml_deadlock_report"]') = 1
           OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1
           OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1
          )
        AND   e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
    END;

    /*If event file*/
    IF
    (
           @TargetSessionType LIKE N'event_file%'
       AND @EventSessionName NOT LIKE N'system_health%'
    )
    BEGIN
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT;

        IF @Debug = 1 BEGIN SET STATISTICS XML ON; END;

        INSERT
            #deadlock_data WITH(TABLOCKX)
        (
            deadlock_xml
        )
        SELECT
            deadlock_xml =
                e.x.query('.')
        FROM #x AS x
        LEFT JOIN #t AS t
          ON 1 = 1
        CROSS APPLY x.x.nodes('/event') AS e(x)
        WHERE
          (
              e.x.exist('@name[ .= "xml_deadlock_report"]') = 1
           OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1
           OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1
          )
        AND   e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1
        OPTION(RECOMPILE);

        IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END;

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
    END;

    /*This section deals with event file*/
    IF
    (
           @TargetSessionType LIKE N'event%'
       AND @EventSessionName LIKE N'system_health%'
    )
    BEGIN
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT;

        IF @Debug = 1 BEGIN SET STATISTICS XML ON; END;

        SELECT
            xml.deadlock_xml
        INTO #xml
        FROM
        (
            SELECT
                deadlock_xml =
                    TRY_CAST(fx.event_data AS xml)
            FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx
            LEFT JOIN #t AS t
              ON 1 = 1
            WHERE fx.object_name = N'xml_deadlock_report'
        ) AS xml
        CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x)
        WHERE 1 = 1
        AND   e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1
        OPTION(RECOMPILE);

        INSERT
            #deadlock_data WITH(TABLOCKX)
        SELECT
            deadlock_xml =
                xml.deadlock_xml
        FROM #xml AS xml
        LEFT JOIN #t AS t
          ON 1 = 1
        WHERE xml.deadlock_xml IS NOT NULL
        OPTION(RECOMPILE);

        IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END;

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
    END;

    /* If table target */
    IF LOWER(@TargetSessionType) = N'table'
    BEGIN
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT;
   
        /*
        First, we need to heck the XML structure.
        Depending on the data source, the XML could
        contain either the /event or /deadlock nodes.
        When the /event nodes are not present, there
        is no @name attribute to evaluate.
        */

        SELECT
            @extract_sql = N'
        SELECT TOP (1)
            @xe = xe.e.exist(''.''),
            @xd = xd.e.exist(''.'')
        FROM ' +
        QUOTENAME(@TargetDatabaseName) +
        N'.' +
        QUOTENAME(@TargetSchemaName) +
        N'.' +
        QUOTENAME(@TargetTableName) +
        N' AS x
        OUTER APPLY x.' +
        QUOTENAME(@TargetColumnName) +
        N'.nodes(''/event'') AS xe(e)
        OUTER APPLY x.' +
        QUOTENAME(@TargetColumnName) +
        N'.nodes(''/deadlock'') AS xd(e) 
        OPTION(RECOMPILE);
        ';

        IF @Debug = 1 BEGIN PRINT @extract_sql; END;

        EXECUTE sys.sp_executesql
            @extract_sql,
          N'
            @xe bit OUTPUT,
            @xd bit OUTPUT
           ',
           @xe OUTPUT,
           @xd OUTPUT;

		/*This is a reasonable question to ask.*/
		IF  @xe IS NULL
		AND @xd IS NULL
		BEGIN
		    RAISERROR
			(
			    'No rows found in %s.%s.%s. Try again later.',
				11,
				1,
				@TargetDatabaseName,
				@TargetSchemaName,
				@TargetTableName
		    ) WITH NOWAIT;
			RETURN;
		END;

        /* Build dynamic SQL to extract the XML  */  
        IF  @xe = 1
        AND @xd IS NULL
        BEGIN
            SET @extract_sql = N'
        SELECT
            deadlock_xml = ' +
            QUOTENAME(@TargetColumnName) +
            N'
        FROM ' +
        QUOTENAME(@TargetDatabaseName) +
        N'.' +
        QUOTENAME(@TargetSchemaName) +
        N'.' +
        QUOTENAME(@TargetTableName) +
        N' AS x
        LEFT JOIN #t AS t
          ON 1 = 1
        CROSS APPLY x.' +
        QUOTENAME(@TargetColumnName) +
        N'.nodes(''/event'') AS e(x)
        WHERE
          (
              e.x.exist(''@name[ .= "xml_deadlock_report"]'') = 1
           OR e.x.exist(''@name[ .= "database_xml_deadlock_report"]'') = 1
           OR e.x.exist(''@name[ .= "xml_deadlock_report_filtered"]'') = 1
          )';
        END;

        IF  @xe IS NULL
        AND @xd = 1
        BEGIN
            SET @extract_sql = N'
        SELECT
            deadlock_xml = ' +
            QUOTENAME(@TargetColumnName) +
            N'
        FROM ' +
        QUOTENAME(@TargetDatabaseName) +
        N'.' +
        QUOTENAME(@TargetSchemaName) +
        N'.' +
        QUOTENAME(@TargetTableName) +
        N' AS x
        LEFT JOIN #t AS t
          ON 1 = 1
        CROSS APPLY x.' +
        QUOTENAME(@TargetColumnName) +
        N'.nodes(''/deadlock'') AS e(x)
        WHERE 1 = 1';
        END;
       
        /* Add timestamp filtering if specified */
        IF @TargetTimestampColumnName IS NOT NULL
        BEGIN
            SET @extract_sql = @extract_sql + N'
        AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' >= @StartDate
        AND x.' + QUOTENAME(@TargetTimestampColumnName) + N'  < @EndDate';
        END;
                   
        /* If no timestamp column but date filtering is needed, handle XML-based filtering when possible */
        IF  @TargetTimestampColumnName IS NULL
        AND @xe = 1
        AND @xd IS NULL
            BEGIN
                SET @extract_sql = @extract_sql + N'
        AND e.x.exist(''@timestamp[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1';
        END;

		/*Woof*/
		IF  @TargetTimestampColumnName IS NULL
        AND @xe IS NULL
        AND @xd = 1
            BEGIN
                SET @extract_sql = @extract_sql + N'
        AND e.x.exist(''(/deadlock/process-list/process/@lasttranstarted)[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1';
        END;

        SET @extract_sql += N'
        OPTION(RECOMPILE);
        ';
       
        IF @Debug = 1 BEGIN PRINT @extract_sql; END;
       
        /* Execute the dynamic SQL */
        INSERT
            #deadlock_data
        WITH
            (TABLOCKX)
        (
            deadlock_xml
        )
        EXECUTE sys.sp_executesql
            @extract_sql,
          N'
            @StartDate datetime,
            @EndDate datetime
           ',
            @StartDate,
            @EndDate;
       
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
    END;

        /*Parse process and input buffer xml*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT;

        SELECT
            d1.deadlock_xml,
            event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'),
            victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'),
            is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'),
            is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'),
            deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock')
        INTO #dd
        FROM #deadlock_data AS d1
        LEFT JOIN #t AS t
          ON 1 = 1
		WHERE @xe = 1
		OR    LOWER(@TargetSessionType) <> N'table'

		UNION ALL

        SELECT
            d1.deadlock_xml,
            event_date = d1.deadlock_xml.value('(/deadlock/process-list/process/@lasttranstarted)[1]', 'datetime2'),
            victim_id = d1.deadlock_xml.value('(/deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'),
            is_parallel = d1.deadlock_xml.exist('/deadlock/resource-list/exchangeEvent'),
            is_parallel_batch = d1.deadlock_xml.exist('/deadlock/resource-list/SyncPoint'),
            deadlock_graph = d1.deadlock_xml.query('.')
        FROM #deadlock_data AS d1
        LEFT JOIN #t AS t
          ON 1 = 1
		WHERE @xd = 1
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT;

        SELECT
            q.event_date,
            q.victim_id,
            is_parallel =
                CONVERT(bit, q.is_parallel),
            q.deadlock_graph,
            q.id,
            q.spid,
            q.database_id,
            database_name =
                ISNULL
                (
                    DB_NAME(q.database_id),
                    N'UNKNOWN'
                ),
            q.current_database_name,
            q.priority,
            q.log_used,
            q.wait_resource,
            q.wait_time,
            q.transaction_name,
            q.last_tran_started,
            q.last_batch_started,
            q.last_batch_completed,
            q.lock_mode,
            q.status,
            q.transaction_count,
            q.client_app,
            q.host_name,
            q.login_name,
            q.isolation_level,
            client_option_1 =
                SUBSTRING
                (
                    CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END +
                    CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END +
                    CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END +
                    CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END +
                    CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END +
                    CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END +
                    CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END +
                    CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END +
                    CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END +
                    CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END +
                    CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END +
                    CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END +
                    CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
                    CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END +
                    CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END,
                    3,
                    500
                ),
            client_option_2 =
                SUBSTRING
                (
                    CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END +
                    CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END +
                    CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END +
                    CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END +
                    CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END +
                    CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END +
                    CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END +
                    CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END +
                    CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END +
                    CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END +
                    CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END +
                    CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END +
                    CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END +
                    CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END +
                    CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END +
                    CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END,
                    3,
                    500
                ),
            q.process_xml
        INTO #deadlock_process
        FROM
        (
            SELECT
                dd.deadlock_xml,
                event_date =
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            GETUTCDATE(),
                            SYSDATETIME()
                        ),
                        dd.event_date
                    ),
                dd.victim_id,
                is_parallel =
                    CONVERT(tinyint, dd.is_parallel) +
                    CONVERT(tinyint, dd.is_parallel_batch),
                dd.deadlock_graph,
                id = ca.dp.value('@id', 'nvarchar(256)'),
                spid = ca.dp.value('@spid', 'smallint'),
                database_id = ca.dp.value('@currentdb', 'bigint'),
                current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'),
                priority = ca.dp.value('@priority', 'smallint'),
                log_used = ca.dp.value('@logused', 'bigint'),
                wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'),
                wait_time = ca.dp.value('@waittime', 'bigint'),
                transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'),
                last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'),
                last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'),
                last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'),
                lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'),
                status = ca.dp.value('@status', 'nvarchar(256)'),
                transaction_count = ca.dp.value('@trancount', 'bigint'),
                client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'),
                host_name = ca.dp.value('@hostname', 'nvarchar(256)'),
                login_name = ca.dp.value('@loginname', 'nvarchar(256)'),
                isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'),
                clientoption1 = ca.dp.value('@clientoption1', 'bigint'),
                clientoption2 = ca.dp.value('@clientoption2', 'bigint'),
                process_xml = ISNULL(ca.dp.query(N'.'), N'')
            FROM #dd AS dd
            CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp)
            WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL)
            AND   (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL)
            AND   (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL)
            AND   (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL)
        ) AS q
        LEFT JOIN #t AS t
          ON 1 = 1
        OPTION(RECOMPILE);


        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse execution stack xml*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT;

        SELECT DISTINCT
            dp.id,
            dp.event_date,
            proc_name = ca.dp.value('@procname', 'nvarchar(1024)'),
            sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)')
        INTO #deadlock_stack
        FROM #deadlock_process AS dp
        CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp)
        WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL)
        AND    ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Grab the full resource list*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);

        RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT;

        SELECT
            event_date =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    dr.event_date
                ),
            dr.victim_id,
            dr.resource_xml
        INTO
            #deadlock_resource
        FROM
        (
            SELECT
                event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'),
                victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'),
                resource_xml = ISNULL(ca.dp.query(N'.'), N'')
            FROM #deadlock_data AS dd
            CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp)
        ) AS dr
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse object locks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT;

        SELECT DISTINCT
            ca.event_date,
            ca.database_id,
            database_name =
                ISNULL
                (
                    DB_NAME(ca.database_id),
                    N'UNKNOWN'
                ),
            object_name =
                REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                    ca.object_name COLLATE Latin1_General_BIN2,
                NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'),
                NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'),
                NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'),
                NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'),
                NCHAR(8),  N'?'), NCHAR(7), N'?'),  NCHAR(6), N'?'),  NCHAR(5), N'?'),  NCHAR(4), N'?'),
                NCHAR(3),  N'?'), NCHAR(2), N'?'),  NCHAR(1), N'?'),  NCHAR(0), N'?'),
            ca.lock_mode,
            ca.index_name,
            ca.associatedObjectId,
            waiter_id = w.l.value('@id', 'nvarchar(256)'),
            waiter_mode = w.l.value('@mode', 'nvarchar(256)'),
            owner_id = o.l.value('@id', 'nvarchar(256)'),
            owner_mode = o.l.value('@mode', 'nvarchar(256)'),
            lock_type = CAST(N'OBJECT' AS nvarchar(100))
        INTO #deadlock_owner_waiter
        FROM
        (
            SELECT
                dr.event_date,
                database_id = ca.dr.value('@dbid', 'bigint'),
                object_name = ca.dr.value('@objectname', 'nvarchar(1024)'),
                lock_mode = ca.dr.value('@mode', 'nvarchar(256)'),
                index_name = ca.dr.value('@indexname', 'nvarchar(256)'),
                associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'),
                dr = ca.dr.query('.')
            FROM #deadlock_resource AS dr
            CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr)
            WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL)
        ) AS ca
        CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l)
        CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l)
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse page locks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_owner_waiter WITH(TABLOCKX)
        SELECT DISTINCT
            ca.event_date,
            ca.database_id,
            database_name =
                ISNULL
                (
                    DB_NAME(ca.database_id),
                    N'UNKNOWN'
                ),
            ca.object_name,
            ca.lock_mode,
            ca.index_name,
            ca.associatedObjectId,
            waiter_id = w.l.value('@id', 'nvarchar(256)'),
            waiter_mode = w.l.value('@mode', 'nvarchar(256)'),
            owner_id = o.l.value('@id', 'nvarchar(256)'),
            owner_mode = o.l.value('@mode', 'nvarchar(256)'),
            lock_type = N'PAGE'
        FROM
        (
            SELECT
                dr.event_date,
                database_id = ca.dr.value('@dbid', 'bigint'),
                object_name = ca.dr.value('@objectname', 'nvarchar(1024)'),
                lock_mode = ca.dr.value('@mode', 'nvarchar(256)'),
                index_name = ca.dr.value('@indexname', 'nvarchar(256)'),
                associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'),
                dr = ca.dr.query('.')
            FROM #deadlock_resource AS dr
            CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr)
            WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL)
        ) AS ca
        CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l)
        CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l)
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse key locks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_owner_waiter WITH(TABLOCKX)
        SELECT DISTINCT
            ca.event_date,
            ca.database_id,
            database_name =
                ISNULL
                (
                    DB_NAME(ca.database_id),
                    N'UNKNOWN'
                ),
            ca.object_name,
            ca.lock_mode,
            ca.index_name,
            ca.associatedObjectId,
            waiter_id = w.l.value('@id', 'nvarchar(256)'),
            waiter_mode = w.l.value('@mode', 'nvarchar(256)'),
            owner_id = o.l.value('@id', 'nvarchar(256)'),
            owner_mode = o.l.value('@mode', 'nvarchar(256)'),
            lock_type = N'KEY'
        FROM
        (
            SELECT
                dr.event_date,
                database_id = ca.dr.value('@dbid', 'bigint'),
                object_name = ca.dr.value('@objectname', 'nvarchar(1024)'),
                lock_mode = ca.dr.value('@mode', 'nvarchar(256)'),
                index_name = ca.dr.value('@indexname', 'nvarchar(256)'),
                associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'),
                dr = ca.dr.query('.')
            FROM #deadlock_resource AS dr
            CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr)
            WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL)
        ) AS ca
        CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l)
        CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l)
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse RID locks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_owner_waiter WITH(TABLOCKX)
        SELECT DISTINCT
            ca.event_date,
            ca.database_id,
            database_name =
                ISNULL
                (
                    DB_NAME(ca.database_id),
                    N'UNKNOWN'
                ),
            ca.object_name,
            ca.lock_mode,
            ca.index_name,
            ca.associatedObjectId,
            waiter_id = w.l.value('@id', 'nvarchar(256)'),
            waiter_mode = w.l.value('@mode', 'nvarchar(256)'),
            owner_id = o.l.value('@id', 'nvarchar(256)'),
            owner_mode = o.l.value('@mode', 'nvarchar(256)'),
            lock_type = N'RID'
        FROM
        (
            SELECT
                dr.event_date,
                database_id = ca.dr.value('@dbid', 'bigint'),
                object_name = ca.dr.value('@objectname', 'nvarchar(1024)'),
                lock_mode = ca.dr.value('@mode', 'nvarchar(256)'),
                index_name = ca.dr.value('@indexname', 'nvarchar(256)'),
                associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'),
                dr = ca.dr.query('.')
            FROM #deadlock_resource AS dr
            CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr)
            WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL)
        ) AS ca
        CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l)
        CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l)
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse row group locks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_owner_waiter WITH(TABLOCKX)
        SELECT DISTINCT
            ca.event_date,
            ca.database_id,
            database_name =
                ISNULL
                (
                    DB_NAME(ca.database_id),
                    N'UNKNOWN'
                ),
            ca.object_name,
            ca.lock_mode,
            ca.index_name,
            ca.associatedObjectId,
            waiter_id = w.l.value('@id', 'nvarchar(256)'),
            waiter_mode = w.l.value('@mode', 'nvarchar(256)'),
            owner_id = o.l.value('@id', 'nvarchar(256)'),
            owner_mode = o.l.value('@mode', 'nvarchar(256)'),
            lock_type = N'ROWGROUP'
        FROM
        (
            SELECT
                dr.event_date,
                database_id = ca.dr.value('@dbid', 'bigint'),
                object_name = ca.dr.value('@objectname', 'nvarchar(1024)'),
                lock_mode = ca.dr.value('@mode', 'nvarchar(256)'),
                index_name = ca.dr.value('@indexname', 'nvarchar(256)'),
                associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'),
                dr = ca.dr.query('.')
            FROM #deadlock_resource AS dr
            CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr)
            WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL)
        ) AS ca
        CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l)
        CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l)
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT;

        UPDATE
            d
        SET
            d.index_name =
                d.object_name + N'.HEAP'
        FROM #deadlock_owner_waiter AS d
        WHERE d.lock_type IN
              (
                  N'HEAP',
                  N'RID'
              )
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Parse parallel deadlocks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT;

        SELECT DISTINCT
            ca.id,
            ca.event_date,
            ca.wait_type,
            ca.node_id,
            ca.waiter_type,
            ca.owner_activity,
            ca.waiter_activity,
            ca.merging,
            ca.spilling,
            ca.waiting_to_close,
            waiter_id = w.l.value('@id', 'nvarchar(256)'),
            owner_id = o.l.value('@id', 'nvarchar(256)')
        INTO #deadlock_resource_parallel
        FROM
        (
            SELECT
                dr.event_date,
                id = ca.dr.value('@id', 'nvarchar(256)'),
                wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'),
                node_id = ca.dr.value('@nodeId', 'bigint'),
                /* These columns are in 2017 CU5+ ONLY */
                waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'),
                owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'),
                waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'),
                merging = ca.dr.value('@merging', 'nvarchar(256)'),
                spilling = ca.dr.value('@spilling', 'nvarchar(256)'),
                waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'),
                /* These columns are in 2017 CU5+ ONLY */
                dr = ca.dr.query('.')
            FROM #deadlock_resource AS dr
            CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr)
        ) AS ca
        CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l)
        CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l)
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Get rid of parallel noise*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT;

        WITH
            c AS
        (
            SELECT
                *,
                rn =
                    ROW_NUMBER() OVER
                    (
                        PARTITION BY
                            drp.owner_id,
                            drp.waiter_id
                        ORDER BY
                            drp.event_date
                    )
            FROM #deadlock_resource_parallel AS drp
        )
        DELETE
        FROM c
        WHERE c.rn > 1
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Get rid of nonsense*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT;

        DELETE dow
        FROM #deadlock_owner_waiter AS dow
        WHERE dow.owner_id = dow.waiter_id
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Add some nonsense*/
        ALTER TABLE
            #deadlock_process
        ADD
            waiter_mode nvarchar(256),
            owner_mode nvarchar(256),
            is_victim AS
                CONVERT
                (
                    bit,
                    CASE
                        WHEN id = victim_id
                        THEN 1
                        ELSE 0
                    END
                ) PERSISTED;

        /*Update some nonsense*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT;

        UPDATE
            dp
        SET
            dp.owner_mode = dow.owner_mode
        FROM #deadlock_process AS dp
        JOIN #deadlock_owner_waiter AS dow
          ON  dp.id = dow.owner_id
          AND dp.event_date = dow.event_date
        WHERE dp.is_victim = 0
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT;

        UPDATE
            dp
        SET
            dp.waiter_mode = dow.waiter_mode
        FROM #deadlock_process AS dp
        JOIN #deadlock_owner_waiter AS dow
          ON  dp.victim_id = dow.waiter_id
          AND dp.event_date = dow.event_date
        WHERE dp.is_victim = 1
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Get Agent Job and Step names*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT;

        SELECT
            x.event_date,
            x.victim_id,
            x.id,
            x.database_id,
            x.client_app,
            x.job_id,
            x.step_id,
            job_id_guid =
                CONVERT
                (
                    uniqueidentifier,
                    TRY_CAST
                    (
                        N''
                        AS xml
                    ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)')
                )
        INTO #agent_job
        FROM
        (
            SELECT
                dp.event_date,
                dp.victim_id,
                dp.id,
                dp.database_id,
                dp.client_app,
                job_id =
                    SUBSTRING
                    (
                        dp.client_app,
                        CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'),
                        32
                    ),
                step_id =
                    CASE
                        WHEN CHARINDEX(N': Step ', dp.client_app) > 0
                        AND  CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0
                        THEN
                            SUBSTRING
                            (
                                dp.client_app,
                                CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '),
                                CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) -
                                  (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '))
                            )
                        ELSE dp.client_app
                    END
            FROM #deadlock_process AS dp
            WHERE dp.client_app LIKE N'SQLAgent - %'
            AND   dp.client_app <> N'SQLAgent - Initial Boot Probe'
        ) AS x
        OPTION(RECOMPILE);

        ALTER TABLE
            #agent_job
        ADD
            job_name nvarchar(256),
            step_name nvarchar(256);

        IF
        (
                @Azure = 0
            AND @RDS = 0
        )
        BEGIN
            SET @StringToExecute =
                N'
    UPDATE
        aj
    SET
        aj.job_name = j.name,
        aj.step_name = s.step_name
    FROM msdb.dbo.sysjobs AS j
    JOIN msdb.dbo.sysjobsteps AS s
      ON j.job_id = s.job_id
    JOIN #agent_job AS aj
      ON  aj.job_id_guid = j.job_id
      AND aj.step_id = s.step_id
    OPTION(RECOMPILE);
                 ';

            IF @Debug = 1 BEGIN PRINT @StringToExecute; END;
            EXECUTE sys.sp_executesql
                @StringToExecute;

        END;

        UPDATE
            dp
        SET
            dp.client_app =
                CASE
                    WHEN dp.client_app LIKE N'SQLAgent - %'
                    THEN N'SQLAgent - Job: ' +
                         aj.job_name +
                         N' Step: ' +
                         aj.step_name
                    ELSE dp.client_app
                END
        FROM #deadlock_process AS dp
        JOIN #agent_job AS aj
          ON  dp.event_date = aj.event_date
          AND dp.victim_id = aj.victim_id
          AND dp.id = aj.id
        OPTION(RECOMPILE);

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Get each and every table of all databases*/
        IF @Azure = 0
        BEGIN
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT;

        INSERT INTO
            @sysAssObjId
        (
            database_id,
            partition_id,
            schema_name,
            table_name
        )
        EXECUTE sys.sp_MSforeachdb
            N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            USE [?];

            IF EXISTS
            (
                SELECT
                    1/0
                FROM #deadlock_process AS dp
                WHERE dp.database_id = DB_ID()
            )
            BEGIN
                SELECT
                    database_id =
                        DB_ID(),
                    p.partition_id,
                    schema_name =
                        s.name,
                    table_name =
                        t.name
                FROM sys.partitions p
                JOIN sys.tables t
                  ON t.object_id = p.object_id
                JOIN sys.schemas s
                  ON s.schema_id = t.schema_id
                WHERE s.name IS NOT NULL
                AND   t.name IS NOT NULL
                OPTION(RECOMPILE);
            END;
            ';

        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
        END;

        IF @Azure = 1
        BEGIN
            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT;

            INSERT INTO
                @sysAssObjId
            (
                database_id,
                partition_id,
                schema_name,
                table_name
            )
            SELECT
                database_id =
                    DB_ID(),
                p.partition_id,
                schema_name =
                    s.name,
                table_name =
                    t.name
            FROM sys.partitions p
            JOIN sys.tables t
              ON t.object_id = p.object_id
            JOIN sys.schemas s
              ON s.schema_id = t.schema_id
            WHERE s.name IS NOT NULL
            AND   t.name IS NOT NULL
            OPTION(RECOMPILE);

            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
        END;

        /*Begin checks based on parsed values*/

        /*
        First, revert these back since we already converted the event data to local time,
        and searches will break if we use the times converted over to UTC for the event data
        */
        SELECT
            @StartDate = @StartDateOriginal,
            @EndDate = @EndDateOriginal;

        /*Check 1 is deadlocks by database*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 1  database deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 1,
            dp.database_name,
            object_name = N'-',
            finding_group = N'Total Database Deadlocks',
            finding =
                N'This database had ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' deadlocks.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC)
        FROM #deadlock_process AS dp
        WHERE 1 = 1
        AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
        AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dp.event_date < @EndDate OR @EndDate IS NULL)
        AND (dp.client_app = @AppName OR @AppName IS NULL)
        AND (dp.host_name = @HostName OR @HostName IS NULL)
        AND (dp.login_name = @LoginName OR @LoginName IS NULL)
        GROUP BY dp.database_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 2 is deadlocks with selects*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 2,
            dow.database_name,
            object_name =
                CASE
                    WHEN EXISTS
                         (
                             SELECT
                                 1/0
                             FROM sys.databases AS d
                             WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT
                             AND   d.is_read_committed_snapshot_on = 1
                         )
                    THEN N'You already enabled RCSI, but...'
                    ELSE N'You Might Need RCSI'
                END,
            finding_group = N'Total Deadlocks Involving Selects',
            finding =
                N'There have been ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dow.event_date)
                ) +
                N' deadlock(s) between read queries and modification queries.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC)
        FROM #deadlock_owner_waiter AS dow
        WHERE 1 = 1
        AND dow.lock_mode IN
            (
                N'S',
                N'IS'
            )
        OR  dow.owner_mode IN
            (
                N'S',
                N'IS'
            )
        OR  dow.waiter_mode IN
            (
                N'S',
                N'IS'
            )
        AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
        AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dow.event_date < @EndDate OR @EndDate IS NULL)
        AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
        GROUP BY
            dow.database_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 3 is deadlocks by object*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 3,
            dow.database_name,
            object_name =
                ISNULL
                (
                    dow.object_name,
                    N'UNKNOWN'
                ),
            finding_group = N'Total Object Deadlocks',
            finding =
                N'This object was involved in ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dow.event_date)
                ) +
                N' deadlock(s).',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC)
        FROM #deadlock_owner_waiter AS dow
        WHERE 1 = 1
        AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
        AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dow.event_date < @EndDate OR @EndDate IS NULL)
        AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
        GROUP BY
            dow.database_name,
            dow.object_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 3 continuation, number of deadlocks per index*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 3,
            dow.database_name,
            index_name = dow.index_name,
            finding_group = N'Total Index Deadlocks',
            finding =
                N'This index was involved in ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dow.event_date)
                ) +
                N' deadlock(s).',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC)
        FROM #deadlock_owner_waiter AS dow
        WHERE 1 = 1
        AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
        AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dow.event_date < @EndDate OR @EndDate IS NULL)
        AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
        AND dow.lock_type NOT IN
            (
                N'HEAP',
                N'RID'
            )
        AND dow.index_name IS NOT NULL
        GROUP BY
            dow.database_name,
            dow.index_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 3 continuation, number of deadlocks per heap*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 3,
            dow.database_name,
            index_name = dow.index_name,
            finding_group = N'Total Heap Deadlocks',
            finding =
                N'This heap was involved in ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dow.event_date)
                ) +
                N' deadlock(s).',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC)
        FROM #deadlock_owner_waiter AS dow
        WHERE 1 = 1
        AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
        AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dow.event_date < @EndDate OR @EndDate IS NULL)
        AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
        AND dow.lock_type IN
            (
                N'HEAP',
                N'RID'
            )
        GROUP BY
            dow.database_name,
            dow.index_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 4 looks for Serializable deadlocks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 4,
            database_name =
                dp.database_name,
            object_name = N'-',
            finding_group = N'Serializable Deadlocking',
            finding =
                N'This database has had ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' instances of Serializable deadlocks.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC)
        FROM #deadlock_process AS dp
        WHERE dp.isolation_level LIKE N'serializable%'
        AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
        AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dp.event_date < @EndDate OR @EndDate IS NULL)
        AND (dp.client_app = @AppName OR @AppName IS NULL)
        AND (dp.host_name = @HostName OR @HostName IS NULL)
        AND (dp.login_name = @LoginName OR @LoginName IS NULL)
        GROUP BY dp.database_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 5 looks for Repeatable Read deadlocks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 5,
            dp.database_name,
            object_name = N'-',
            finding_group = N'Repeatable Read Deadlocking',
            finding =
                N'This database has had ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' instances of Repeatable Read deadlocks.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC)
        FROM #deadlock_process AS dp
        WHERE dp.isolation_level LIKE N'repeatable%'
        AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
        AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dp.event_date < @EndDate OR @EndDate IS NULL)
        AND (dp.client_app = @AppName OR @AppName IS NULL)
        AND (dp.host_name = @HostName OR @HostName IS NULL)
        AND (dp.login_name = @LoginName OR @LoginName IS NULL)
        GROUP BY dp.database_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 6 breaks down app, host, and login information*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 6,
            database_name =
                dp.database_name,
            object_name = N'-',
            finding_group = N'Login, App, and Host deadlocks',
            finding =
                N'This database has had ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' instances of deadlocks involving the login ' +
                ISNULL
                (
                    dp.login_name,
                    N'UNKNOWN'
                ) +
                N' from the application ' +
                ISNULL
                (
                    dp.client_app,
                    N'UNKNOWN'
                ) +
                N' on host ' +
                ISNULL
                (
                    dp.host_name,
                    N'UNKNOWN'
                ) +
                N'.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC)
        FROM #deadlock_process AS dp
        WHERE 1 = 1
        AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
        AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dp.event_date < @EndDate OR @EndDate IS NULL)
        AND (dp.client_app = @AppName OR @AppName IS NULL)
        AND (dp.host_name = @HostName OR @HostName IS NULL)
        AND (dp.login_name = @LoginName OR @LoginName IS NULL)
        GROUP BY
            dp.database_name,
            dp.login_name,
            dp.client_app,
            dp.host_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT;

        WITH
            lock_types AS
        (
            SELECT
                dp.database_name,
                dow.object_name,
                lock =
                    CASE
                        WHEN CHARINDEX(N':', dp.wait_resource) > 0
                        THEN LEFT(dp.wait_resource, CHARINDEX(N':', dp.wait_resource) - 1)
                        ELSE dp.wait_resource
                    END,
                lock_count = CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date))
            FROM #deadlock_process AS dp
            JOIN #deadlock_owner_waiter AS dow
              ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id)
             AND dp.event_date = dow.event_date
            WHERE (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
              AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
              AND (dp.event_date < @EndDate OR @EndDate IS NULL)
              AND (dp.client_app = @AppName OR @AppName IS NULL)
              AND (dp.host_name = @HostName OR @HostName IS NULL)
              AND (dp.login_name = @LoginName OR @LoginName IS NULL)
              AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
              AND dow.object_name IS NOT NULL
            GROUP BY
                dp.database_name,
                dow.object_name,
                CASE
                    WHEN CHARINDEX(N':', dp.wait_resource) > 0
                    THEN LEFT(dp.wait_resource, CHARINDEX(N':', dp.wait_resource) - 1)
                    ELSE dp.wait_resource
                END
        )
        INSERT #deadlock_findings WITH (TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id      = 7,
            lt.database_name,
            lt.object_name,
            finding_group = N'Types of locks by object',
            finding = 
                N'This object has had ' +
                STUFF(
                    (
                        SELECT
                            N', ' + lt2.lock_count + N' ' + lt2.lock
                        FROM lock_types AS lt2
                        WHERE lt2.database_name = lt.database_name
                          AND lt2.object_name   = lt.object_name
                        FOR XML PATH(''), TYPE
                    ).value('.', 'nvarchar(max)'),
                    1, 2, N''
                ) + N' locks.',
            sort_order = 
                ROW_NUMBER() OVER (
                    ORDER BY
                        MAX(CONVERT(bigint, lt.lock_count)) DESC
                )
        FROM lock_types AS lt
        GROUP BY
            lt.database_name,
            lt.object_name
        OPTION (RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT;

        WITH
            deadlock_stack AS
            (
                SELECT DISTINCT
                ds.id,
                ds.event_date,
                ds.proc_name,
                    database_name =
                        PARSENAME(ds.proc_name, 3),
                    schema_name =
                        PARSENAME(ds.proc_name, 2),
                    proc_only_name =
                        PARSENAME(ds.proc_name, 1),
                    sql_handle_csv =
                        N'''' +
                        STUFF
                        (
                            (
                                SELECT DISTINCT
                                    N',' +
                                    ds2.sql_handle
                                FROM #deadlock_stack AS ds2
                                WHERE ds2.id = ds.id
                                AND   ds2.event_date = ds.event_date
                                AND   ds2.sql_handle <> 0x
                                FOR XML
                                    PATH(N''),
                                    TYPE
                            ).value(N'.[1]', N'nvarchar(MAX)'),
                            1,
                            1,
                            N''
                        ) +
                        N''''
                FROM #deadlock_stack AS ds
                WHERE ds.sql_handle <> 0x
                GROUP BY
                    PARSENAME(ds.proc_name, 3),
                    PARSENAME(ds.proc_name, 2),
                    PARSENAME(ds.proc_name, 1),
                    ds.proc_name,
                    ds.id,
                    ds.event_date
            )
        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        SELECT DISTINCT
            check_id = 8,
            dow.database_name,
            object_name = ds.proc_name,
            finding_group = N'More Info - Query',
            finding = N'EXECUTE sp_BlitzCache ' +
                CASE
                    WHEN ds.proc_name = N'adhoc'
                    THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv
                    ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''')
                END + N';'
        FROM deadlock_stack AS ds
        JOIN #deadlock_owner_waiter AS dow
          ON dow.owner_id = ds.id
          AND dow.event_date = ds.event_date
        WHERE 1 = 1
        AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
        AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dow.event_date < @EndDate OR @EndDate IS NULL)
        AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL)
        AND ds.proc_name NOT LIKE 'Unknown%'
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        IF (@ProductVersionMajor >= 13 OR @Azure = 1)
        BEGIN
            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT;

            WITH
                deadlock_stack AS
            (
                SELECT DISTINCT
                    ds.id,
                    ds.sql_handle,
                    ds.proc_name,
                    ds.event_date,
                    database_name =
                        PARSENAME(ds.proc_name, 3),
                    schema_name =
                        PARSENAME(ds.proc_name, 2),
                    proc_only_name =
                        PARSENAME(ds.proc_name, 1)
                FROM #deadlock_stack AS ds
            )
            INSERT
                #deadlock_findings WITH(TABLOCKX)
            (
                check_id,
                database_name,
                object_name,
                finding_group,
                finding
            )
            SELECT DISTINCT
                check_id = 8,
                dow.database_name,
                object_name = ds.proc_name,
                finding_group = N'More Info - Query',
                finding =
                    N'EXECUTE sp_BlitzQueryStore ' +
                    N'@DatabaseName = ' +
                    QUOTENAME(ds.database_name, N'''') +
                    N', ' +
                    N'@StoredProcName = ' +
                    QUOTENAME(ds.proc_only_name, N'''') +
                    N';'
            FROM deadlock_stack AS ds
            JOIN #deadlock_owner_waiter AS dow
              ON dow.owner_id = ds.id
              AND dow.event_date = ds.event_date
            WHERE ds.proc_name <> N'adhoc'
            AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
            AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
            AND (dow.event_date < @EndDate OR @EndDate IS NULL)
            AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL)
            OPTION(RECOMPILE);

            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
        END;

        /*Check 9 gives you stored procedure deadlock counts*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 9,
            database_name =
                dp.database_name,
            object_name = ds.proc_name,
            finding_group = N'Stored Procedure Deadlocks',
            finding =
                N'The stored procedure ' +
                PARSENAME(ds.proc_name, 2) +
                N'.' +
                PARSENAME(ds.proc_name, 1) +
                N' has been involved in ' +
                CONVERT
                (
                    nvarchar(10),
                    COUNT_BIG(DISTINCT ds.id)
                ) +
                N' deadlocks.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC)
        FROM #deadlock_stack AS ds
        JOIN #deadlock_process AS dp
          ON dp.id = ds.id
          AND ds.event_date = dp.event_date
        WHERE ds.proc_name <> N'adhoc'
        AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL)
        AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
        AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
        AND (dp.event_date < @EndDate OR @EndDate IS NULL)
        AND (dp.client_app = @AppName OR @AppName IS NULL)
        AND (dp.host_name = @HostName OR @HostName IS NULL)
        AND (dp.login_name = @LoginName OR @LoginName IS NULL)
        GROUP BY
            dp.database_name,
            ds.proc_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 10 gives you more info queries for sp_BlitzIndex */
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 10 more info, BlitzIndex  %s', 0, 1, @d) WITH NOWAIT;

        WITH
            bi AS
            (
                SELECT DISTINCT
                    dow.object_name,
                    dow.database_name,
                    schema_name = s.schema_name,
                    table_name = s.table_name
                FROM #deadlock_owner_waiter AS dow
                JOIN @sysAssObjId AS s
                  ON  s.database_id = dow.database_id
                  AND s.partition_id = dow.associatedObjectId
                WHERE 1 = 1
                AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL)
                AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
                AND (dow.event_date < @EndDate OR @EndDate IS NULL)
                AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
                AND dow.object_name IS NOT NULL
            )
        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        SELECT DISTINCT
            check_id = 10,
            bi.database_name,
            bi.object_name,
            finding_group = N'More Info - Table',
            finding =
                N'EXECUTE sp_BlitzIndex ' +
                N'@DatabaseName = ' +
                QUOTENAME(bi.database_name, N'''') +
                N', @SchemaName = ' +
                QUOTENAME(bi.schema_name, N'''') +
                N', @TableName = ' +
                QUOTENAME(bi.table_name, N'''') +
                N';'
        FROM bi
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 11 gets total deadlock wait time per object*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT;

        WITH
            chopsuey AS
        (

         SELECT
             database_name =
                 dp.database_name,
             dow.object_name,
             wait_days =
                 CONVERT
                 (
                     nvarchar(30),
                     (
                         SUM
                         (
                             CONVERT
                             (
                                 bigint,
                                 dp.wait_time
                             )
                          ) / 1000 / 86400
                     )
                 ),
             wait_time_hms =
             /*the more wait time you rack up the less accurate this gets,
             it's either that or erroring out*/
            CASE
                WHEN
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            dp.wait_time
                        )
                    )/1000 > 2147483647
                THEN
                   CONVERT
                   (
                       nvarchar(30),
                       DATEADD
                       (
                            MINUTE,
                            (
                                 (
                                    SUM
                                    (
                                       CONVERT
                                       (
                                           bigint,
                                           dp.wait_time
                                       )
                                    )
                                 )/
                                 60000
                            ),
                            0
                       ),
                       14
                   )
                WHEN
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            dp.wait_time
                        )
                    ) BETWEEN 2147483648 AND 2147483647000
                THEN
                   CONVERT
                   (
                       nvarchar(30),
                       DATEADD
                       (
                            SECOND,
                            (
                                 (
                                    SUM
                                    (
                                       CONVERT
                                       (
                                           bigint,
                                           dp.wait_time
                                       )
                                    )
                                 )/
                                 1000
                            ),
                            0
                       ),
                       14
                   )
                ELSE
                 CONVERT
                 (
                     nvarchar(30),
                     DATEADD
                     (
                         MILLISECOND,
                         (
                             SUM
                             (
                                 CONVERT
                                 (
                                     bigint,
                                     dp.wait_time
                                  )
                             )
                         ),
                         0
                    ),
                    14
                 )
                 END,
                 total_waits =
                     SUM(CONVERT(bigint, dp.wait_time))
            FROM #deadlock_owner_waiter AS dow
            JOIN #deadlock_process AS dp
              ON (dp.id = dow.owner_id
                  OR dp.victim_id = dow.waiter_id)
              AND dp.event_date = dow.event_date
            WHERE 1 = 1
            AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
            AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
            AND (dp.event_date < @EndDate OR @EndDate IS NULL)
            AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
            AND (dp.client_app = @AppName OR @AppName IS NULL)
            AND (dp.host_name = @HostName OR @HostName IS NULL)
            AND (dp.login_name = @LoginName OR @LoginName IS NULL)
            GROUP BY
                dp.database_name,
                dow.object_name
        )
        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 11,
            cs.database_name,
            cs.object_name,
            finding_group = N'Total object deadlock wait time',
            finding =
                N'This object has had ' +
                CONVERT
                (
                    nvarchar(30),
                    cs.wait_days
                ) +
                N' ' +
                CONVERT
                (
                    nvarchar(30),
                    cs.wait_time_hms,
                    14
                ) +
                N' [dd hh:mm:ss:ms] of deadlock wait time.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY cs.total_waits DESC)
        FROM chopsuey AS cs
        WHERE cs.object_name IS NOT NULL
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 12 gets total deadlock wait time per database*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT;

        WITH
            wait_time AS
        (
            SELECT
                database_name =
                    dp.database_name,
                total_wait_time_ms =
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            dp.wait_time
                        )
                    )
            FROM #deadlock_owner_waiter AS dow
            JOIN #deadlock_process AS dp
              ON (dp.id = dow.owner_id
                  OR dp.victim_id = dow.waiter_id)
              AND dp.event_date = dow.event_date
            WHERE 1 = 1
            AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL)
            AND (dp.event_date >= @StartDate OR @StartDate IS NULL)
            AND (dp.event_date < @EndDate OR @EndDate IS NULL)
            AND (dp.client_app = @AppName OR @AppName IS NULL)
            AND (dp.host_name = @HostName OR @HostName IS NULL)
            AND (dp.login_name = @LoginName OR @LoginName IS NULL)
            GROUP BY
                dp.database_name
        )
        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 12,
            wt.database_name,
            object_name = N'-',
            finding_group = N'Total database deadlock wait time',
            N'This database has had ' +
            CONVERT
            (
                nvarchar(30),
                (
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            wt.total_wait_time_ms
                        )
                    ) / 1000 / 86400
                )
            ) +
            N' ' +
        /*the more wait time you rack up the less accurate this gets,
        it's either that or erroring out*/
            CASE
                WHEN
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            wt.total_wait_time_ms
                        )
                    )/1000 > 2147483647
                THEN
                   CONVERT
                   (
                       nvarchar(30),
                       DATEADD
                       (
                            MINUTE,
                            (
                                 (
                                    SUM
                                    (
                                       CONVERT
                                       (
                                           bigint,
                                           wt.total_wait_time_ms
                                       )
                                    )
                                 )/
                                 60000
                            ),
                            0
                       ),
                       14
                   )
                WHEN
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            wt.total_wait_time_ms
                        )
                    ) BETWEEN 2147483648 AND 2147483647000
                THEN
                   CONVERT
                   (
                       nvarchar(30),
                       DATEADD
                       (
                            SECOND,
                            (
                                 (
                                    SUM
                                    (
                                       CONVERT
                                       (
                                           bigint,
                                           wt.total_wait_time_ms
                                       )
                                    )
                                 )/
                                 1000
                            ),
                            0
                       ),
                       14
                   )
                ELSE
            CONVERT
              (
                  nvarchar(30),
                  DATEADD
                  (
                      MILLISECOND,
                      (
                          SUM
                          (
                              CONVERT
                              (
                                  bigint,
                                  wt.total_wait_time_ms
                              )
                          )
                      ),
                      0
                  ),
                  14
              ) END +
            N' [dd hh:mm:ss:ms] of deadlock wait time.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC)
        FROM wait_time AS wt
        GROUP BY
            wt.database_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 13 gets total deadlock wait time for SQL Agent*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding,
            sort_order
        )
        SELECT
            check_id = 13,
            database_name =
                DB_NAME(aj.database_id),
            object_name =
                N'SQLAgent - Job: ' +
                aj.job_name +
                N' Step: ' +
                aj.step_name,
            finding_group = N'Agent Job Deadlocks',
            finding =
                N'There have been ' +
                RTRIM(COUNT_BIG(DISTINCT aj.event_date)) +
                N' deadlocks from this Agent Job and Step.',
            sort_order = 
                ROW_NUMBER()
                OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC)
        FROM #agent_job AS aj
        GROUP BY
            DB_NAME(aj.database_id),
            aj.job_name,
            aj.step_name
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 14 is total parallel deadlocks*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        SELECT
            check_id = 14,
            database_name = N'-',
            object_name = N'-',
            finding_group = N'Total parallel deadlocks',
            finding =
                N'There have been ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT drp.event_date)
                ) +
                N' parallel deadlocks.'
        FROM #deadlock_resource_parallel AS drp
        HAVING COUNT_BIG(DISTINCT drp.event_date) > 0
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 15 is total deadlocks involving sleeping sessions*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        SELECT
            check_id = 15,
            database_name = N'-',
            object_name = N'-',
            finding_group = N'Total deadlocks involving sleeping sessions',
            finding =
                N'There have been ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' sleepy deadlocks.'
        FROM #deadlock_process AS dp
        WHERE dp.status = N'sleeping'
        HAVING COUNT_BIG(DISTINCT dp.event_date) > 0
        OPTION(RECOMPILE);

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        SELECT
            check_id = 15,
            database_name = N'-',
            object_name = N'-',
            finding_group = N'Total deadlocks involving background processes',
            finding =
                N'There have been ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' deadlocks with background task.'
        FROM #deadlock_process AS dp
        WHERE dp.status = N'background'
        HAVING COUNT_BIG(DISTINCT dp.event_date) > 0
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Check 16 is total deadlocks involving implicit transactions*/
        SET @d = CONVERT(varchar(40), GETDATE(), 109);
        RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT;

        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        SELECT
            check_id = 14,
            database_name = N'-',
            object_name = N'-',
            finding_group = N'Total implicit transaction deadlocks',
            finding =
                N'There have been ' +
                CONVERT
                (
                    nvarchar(20),
                    COUNT_BIG(DISTINCT dp.event_date)
                ) +
                N' implicit transaction deadlocks.'
        FROM #deadlock_process AS dp
        WHERE dp.transaction_name = N'implicit_transaction'
        HAVING COUNT_BIG(DISTINCT dp.event_date) > 0
        OPTION(RECOMPILE);

        RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

        /*Thank you goodnight*/
        INSERT
            #deadlock_findings WITH(TABLOCKX)
        (
            check_id,
            database_name,
            object_name,
            finding_group,
            finding
        )
        VALUES
        (
            -1,
            N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version),
            N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23),
            N'http://FirstResponderKit.org/',
            N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.'
        );

        RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT;

        /*Results*/
        BEGIN
            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT;

            CREATE CLUSTERED INDEX cx_whatever_dp  ON #deadlock_process (event_date, id);
            CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id);
            CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id);

            IF @Debug = 1 BEGIN SET STATISTICS XML ON; END;

            WITH
                deadlocks AS
            (
                SELECT
                    deadlock_type =
                        N'Regular Deadlock',
                    dp.event_date,
                    dp.id,
                    dp.victim_id,
                    dp.spid,
                    dp.database_id,
                    dp.database_name,
                    dp.current_database_name,
                    dp.priority,
                    dp.log_used,
                    wait_resource =
                        dp.wait_resource COLLATE DATABASE_DEFAULT,
                    object_names =
                        CONVERT
                        (
                            xml,
                            STUFF
                            (
                                (
                                    SELECT DISTINCT
                                        object_name =
                                            NCHAR(10) +
                                            N' <object>' +
                                            ISNULL(c.object_name, N'') +
                                            N'</object> ' COLLATE DATABASE_DEFAULT
                                    FROM #deadlock_owner_waiter AS c
                                    WHERE (dp.id = c.owner_id
                                           OR dp.victim_id = c.waiter_id)
                                    AND dp.event_date = c.event_date
                                    FOR XML
                                        PATH(N''),
                                        TYPE
                                ).value(N'.[1]', N'nvarchar(4000)'),
                                1,
                                1,
                                N''
                            )
                        ),
                    dp.wait_time,
                    dp.transaction_name,
                    dp.status,
                    dp.last_tran_started,
                    dp.last_batch_started,
                    dp.last_batch_completed,
                    dp.lock_mode,
                    dp.transaction_count,
                    dp.client_app,
                    dp.host_name,
                    dp.login_name,
                    dp.isolation_level,
                    dp.client_option_1,
                    dp.client_option_2,
                    inputbuf =
                        dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'),
                    en =
                        DENSE_RANK() OVER (ORDER BY dp.event_date),
                    qn =
                        ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1,
                    dn =
                        ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date),
                    dp.is_victim,
                    owner_mode =
                        ISNULL(dp.owner_mode, N'-'),
                    owner_waiter_type = NULL,
                    owner_activity = NULL,
                    owner_waiter_activity = NULL,
                    owner_merging = NULL,
                    owner_spilling = NULL,
                    owner_waiting_to_close = NULL,
                    waiter_mode =
                        ISNULL(dp.waiter_mode, N'-'),
                    waiter_waiter_type = NULL,
                    waiter_owner_activity = NULL,
                    waiter_waiter_activity = NULL,
                    waiter_merging = NULL,
                    waiter_spilling = NULL,
                    waiter_waiting_to_close = NULL,
                    dp.deadlock_graph
                FROM #deadlock_process AS dp
                WHERE dp.victim_id IS NOT NULL
                AND   dp.is_parallel = 0

                UNION ALL

                SELECT
                    deadlock_type =
                        N'Parallel Deadlock',
                    dp.event_date,
                    dp.id,
                    dp.victim_id,
                    dp.spid,
                    dp.database_id,
                    dp.database_name,
                    dp.current_database_name,
                    dp.priority,
                    dp.log_used,
                    dp.wait_resource COLLATE DATABASE_DEFAULT,
                    object_names =
                        CONVERT
                        (
                            xml,
                            STUFF
                            (
                                (
                                    SELECT DISTINCT
                                        object_name =
                                            NCHAR(10) +
                                            N' <object>' +
                                            ISNULL(c.object_name, N'') +
                                            N'</object> ' COLLATE DATABASE_DEFAULT
                                    FROM #deadlock_owner_waiter AS c
                                    WHERE (dp.id = c.owner_id
                                           OR dp.victim_id = c.waiter_id)
                                    AND dp.event_date = c.event_date
                                    FOR XML
                                        PATH(N''),
                                        TYPE
                                ).value(N'.[1]', N'nvarchar(4000)'),
                                1,
                                1,
                                N''
                            )
                        ),
                    dp.wait_time,
                    dp.transaction_name,
                    dp.status,
                    dp.last_tran_started,
                    dp.last_batch_started,
                    dp.last_batch_completed,
                    dp.lock_mode,
                    dp.transaction_count,
                    dp.client_app,
                    dp.host_name,
                    dp.login_name,
                    dp.isolation_level,
                    dp.client_option_1,
                    dp.client_option_2,
                    inputbuf =
                        dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'),
                    en =
                        DENSE_RANK() OVER (ORDER BY dp.event_date),
                    qn =
                        ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1,
                    dn =
                        ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date),
                    is_victim = 1,
                    owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT,
                    owner_waiter_type = cao.waiter_type,
                    owner_activity = cao.owner_activity,
                    owner_waiter_activity = cao.waiter_activity,
                    owner_merging = cao.merging,
                    owner_spilling = cao.spilling,
                    owner_waiting_to_close = cao.waiting_to_close,
                    waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT,
                    waiter_waiter_type = caw.waiter_type,
                    waiter_owner_activity = caw.owner_activity,
                    waiter_waiter_activity = caw.waiter_activity,
                    waiter_merging = caw.merging,
                    waiter_spilling = caw.spilling,
                    waiter_waiting_to_close = caw.waiting_to_close,
                    dp.deadlock_graph
                FROM #deadlock_process AS dp
                OUTER APPLY
                (
                    SELECT TOP (1)
                        drp.*
                    FROM #deadlock_resource_parallel AS drp
                    WHERE drp.owner_id = dp.id
                    AND drp.wait_type IN
                        (
                            N'e_waitPortOpen',
                            N'e_waitPipeNewRow'
                        )
                    ORDER BY drp.event_date
                ) AS cao
                OUTER APPLY
                (
                    SELECT TOP (1)
                        drp.*
                    FROM #deadlock_resource_parallel AS drp
                    WHERE drp.owner_id = dp.id
                    AND drp.wait_type IN
                        (
                            N'e_waitPortOpen',
                            N'e_waitPipeGetRow'
                        )
                    ORDER BY drp.event_date
                ) AS caw
                WHERE dp.is_parallel = 1
            )
            SELECT
                d.deadlock_type,
                d.event_date,
                d.id,
                d.victim_id,
                d.spid,
                deadlock_group =
                    N'Deadlock #' +
                    CONVERT
                    (
                        nvarchar(10),
                        d.en
                    ) +
                    N', Query #'
                    + CASE
                          WHEN d.qn = 0
                          THEN N'1'
                          ELSE CONVERT(nvarchar(10), d.qn)
                      END + CASE
                                WHEN d.is_victim = 1
                                THEN N' - VICTIM'
                                ELSE N''
                            END,
                d.database_id,
                d.database_name,
                d.current_database_name,
                d.priority,
                d.log_used,
                d.wait_resource,
                d.object_names,
                d.wait_time,
                d.transaction_name,
                d.status,
                d.last_tran_started,
                d.last_batch_started,
                d.last_batch_completed,
                d.lock_mode,
                d.transaction_count,
                d.client_app,
                d.host_name,
                d.login_name,
                d.isolation_level,
                d.client_option_1,
                d.client_option_2,
                inputbuf =
                    CASE
                        WHEN d.inputbuf
                             LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|'
                        THEN
                        OBJECT_SCHEMA_NAME
                        (
                                SUBSTRING
                                (
                                    d.inputbuf,
                                    CHARINDEX(N'Object Id = ', d.inputbuf) + 12,
                                    LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12)
                                )
                                ,
                                SUBSTRING
                                (
                                    d.inputbuf,
                                    CHARINDEX(N'Database Id = ', d.inputbuf) + 14,
                                    CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14)
                                )
                        ) +
                        N'.' +
                        OBJECT_NAME
                        (
                             SUBSTRING
                             (
                                 d.inputbuf,
                                 CHARINDEX(N'Object Id = ', d.inputbuf) + 12,
                                 LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12)
                             )
                             ,
                             SUBSTRING
                             (
                                 d.inputbuf,
                                 CHARINDEX(N'Database Id = ', d.inputbuf) + 14,
                                 CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14)
                             )
                        )
                        ELSE d.inputbuf
                    END COLLATE Latin1_General_BIN2,
                d.owner_mode,
                d.owner_waiter_type,
                d.owner_activity,
                d.owner_waiter_activity,
                d.owner_merging,
                d.owner_spilling,
                d.owner_waiting_to_close,
                d.waiter_mode,
                d.waiter_waiter_type,
                d.waiter_owner_activity,
                d.waiter_waiter_activity,
                d.waiter_merging,
                d.waiter_spilling,
                d.waiter_waiting_to_close,
                d.deadlock_graph,
                d.is_victim
            INTO #deadlocks
            FROM deadlocks AS d
            WHERE d.dn = 1
            AND  (d.is_victim = @VictimsOnly
            OR    @VictimsOnly = 0)
            AND d.qn < CASE
                           WHEN d.deadlock_type = N'Parallel Deadlock'
                           THEN 2
                           ELSE 2147483647
                       END
            AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL)
            AND (d.event_date >= @StartDate OR @StartDate IS NULL)
            AND (d.event_date < @EndDate OR @EndDate IS NULL)
            AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL)
            AND (d.client_app = @AppName OR @AppName IS NULL)
            AND (d.host_name = @HostName OR @HostName IS NULL)
            AND (d.login_name = @LoginName OR @LoginName IS NULL)
            AND (d.deadlock_type = @DeadlockType OR @DeadlockType IS NULL)
            OPTION (RECOMPILE, LOOP JOIN, HASH JOIN);

            UPDATE d
                SET d.inputbuf =
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            d.inputbuf,
                        NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'),
                        NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'),
                        NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'),
                        NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'),
                        NCHAR(8),  N'?'),  NCHAR(7), N'?'),  NCHAR(6), N'?'),  NCHAR(5), N'?'),  NCHAR(4), N'?'),
                        NCHAR(3),  N'?'),  NCHAR(2), N'?'),  NCHAR(1), N'?'),  NCHAR(0), N'?')
            FROM #deadlocks AS d
            OPTION(RECOMPILE);

            SELECT
                d.deadlock_type,
                d.event_date,
                database_name =
                    DB_NAME(d.database_id),
                database_name_x =
                    d.database_name,
                d.current_database_name,
                d.spid,
                d.deadlock_group,
                d.client_option_1,
                d.client_option_2,
                d.lock_mode,
                query_xml =
                    (
                        SELECT
                            [processing-instruction(query)] =
                                d.inputbuf
                        FOR XML
                            PATH(N''),
                            TYPE
                    ),
                query_string =
                    d.inputbuf,
                d.object_names,
                d.isolation_level,
                d.owner_mode,
                d.waiter_mode,
                d.transaction_count,
                d.login_name,
                d.host_name,
                d.client_app,
                d.wait_time,
                d.wait_resource,
                d.priority,
                d.log_used,
                d.last_tran_started,
                d.last_batch_started,
                d.last_batch_completed,
                d.transaction_name,
                d.status,
                /*These columns will be NULL for regular (non-parallel) deadlocks*/
                parallel_deadlock_details =
                    (
                        SELECT
                            d.owner_waiter_type,
                            d.owner_activity,
                            d.owner_waiter_activity,
                            d.owner_merging,
                            d.owner_spilling,
                            d.owner_waiting_to_close,
                            d.waiter_waiter_type,
                            d.waiter_owner_activity,
                            d.waiter_waiter_activity,
                            d.waiter_merging,
                            d.waiter_spilling,
                            d.waiter_waiting_to_close
                        FOR XML
                            PATH('parallel_deadlock_details'),
                            TYPE
                    ),
                d.owner_waiter_type,
                d.owner_activity,
                d.owner_waiter_activity,
                d.owner_merging,
                d.owner_spilling,
                d.owner_waiting_to_close,
                d.waiter_waiter_type,
                d.waiter_owner_activity,
                d.waiter_waiter_activity,
                d.waiter_merging,
                d.waiter_spilling,
                d.waiter_waiting_to_close,
                /*end parallel deadlock columns*/
                d.deadlock_graph,
                d.is_victim,
                d.id
            INTO #deadlock_results
            FROM #deadlocks AS d;

            IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END;
            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

            /*There's too much risk of errors sending the*/
            IF @OutputDatabaseCheck = 0
            BEGIN
                SET @ExportToExcel = 0;
            END;

            SET @deadlock_result += N'
            SELECT
                server_name =
                    @@SERVERNAME,
                dr.deadlock_type,
                dr.event_date,
                database_name =
                    COALESCE
                    (
                        dr.database_name,
                        dr.database_name_x,
                        dr.current_database_name
                    ),
                dr.spid,
                dr.deadlock_group,
                ' + CASE @ExportToExcel
                         WHEN 1
                         THEN N'
                query = dr.query_string,
                object_names =
                    REPLACE(
                    REPLACE(
                        CONVERT
                        (
                            nvarchar(MAX),
                            dr.object_names
                        ) COLLATE Latin1_General_BIN2,
                    ''<object>'', ''''),
                    ''</object>'', ''''),'
                         ELSE N'query = dr.query_xml,
                dr.object_names,'
                    END + N'
                dr.isolation_level,
                dr.owner_mode,
                dr.waiter_mode,
                dr.lock_mode,
                dr.transaction_count,
                dr.client_option_1,
                dr.client_option_2,
                dr.login_name,
                dr.host_name,
                dr.client_app,
                dr.wait_time,
                dr.wait_resource,
                dr.priority,
                dr.log_used,
                dr.last_tran_started,
                dr.last_batch_started,
                dr.last_batch_completed,
                dr.transaction_name,
                dr.status,' +
                    CASE
                        WHEN (@ExportToExcel = 1
                              OR @OutputDatabaseCheck = 0)
                        THEN N'
                dr.owner_waiter_type,
                dr.owner_activity,
                dr.owner_waiter_activity,
                dr.owner_merging,
                dr.owner_spilling,
                dr.owner_waiting_to_close,
                dr.waiter_waiter_type,
                dr.waiter_owner_activity,
                dr.waiter_waiter_activity,
                dr.waiter_merging,
                dr.waiter_spilling,
                dr.waiter_waiting_to_close,'
                        ELSE N'
                dr.parallel_deadlock_details,'
                        END +
                    CASE
                        @ExportToExcel
                        WHEN 1
                        THEN N'
                deadlock_graph =
                    REPLACE(REPLACE(
                    REPLACE(REPLACE(
                        CONVERT
                        (
                            nvarchar(MAX),
                            dr.deadlock_graph
                        ) COLLATE Latin1_General_BIN2,
                    ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''),
                    ''CHAR(10)'', ''''), ''CHAR(13)'', '''')'
                        ELSE N'
                dr.deadlock_graph'
                   END + N'
            FROM #deadlock_results AS dr
            ORDER BY
                dr.event_date,
                dr.is_victim DESC
            OPTION(RECOMPILE, LOOP JOIN, HASH JOIN);
            ';

        IF (@OutputDatabaseCheck = 0)
        BEGIN
            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT;

            IF @Debug = 1
            BEGIN
                PRINT @deadlock_result;
                SET STATISTICS XML ON;
            END;

            SET @StringToExecute = N'

                INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl
            (
                ServerName,
                deadlock_type,
                event_date,
                database_name,
                spid,
                deadlock_group,
                query,
                object_names,
                isolation_level,
                owner_mode,
                waiter_mode,
                lock_mode,
                transaction_count,
                client_option_1,
                client_option_2,
                login_name,
                host_name,
                client_app,
                wait_time,
                wait_resource,
                priority,
                log_used,
                last_tran_started,
                last_batch_started,
                last_batch_completed,
                transaction_name,
                status,
                owner_waiter_type,
                owner_activity,
                owner_waiter_activity,
                owner_merging,
                owner_spilling,
                owner_waiting_to_close,
                waiter_waiter_type,
                waiter_owner_activity,
                waiter_waiter_activity,
                waiter_merging,
                waiter_spilling,
                waiter_waiting_to_close,
                deadlock_graph
            )
            EXECUTE sys.sp_executesql
                @deadlock_result;';
            EXECUTE sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result;

            IF @Debug = 1
            BEGIN
                SET STATISTICS XML OFF;
            END;

            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

            DROP SYNONYM dbo.DeadLockTbl;

            SET @d = CONVERT(varchar(40), GETDATE(), 109);
            RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT;

            SET @StringToExecute = N'

                INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings
            (
                ServerName,
                check_id,
                database_name,
                object_name,
                finding_group,
                finding
            )
            SELECT
                @@SERVERNAME,
                df.check_id,
                df.database_name,
                df.object_name,
                df.finding_group,
                df.finding
            FROM #deadlock_findings AS df
            ORDER BY df.check_id
            OPTION(RECOMPILE);';
            EXECUTE sys.sp_executesql @StringToExecute;

            RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

            DROP SYNONYM dbo.DeadlockFindings; /*done with inserting.*/
        END;
        ELSE /*Output to database is not set output to client app*/
        BEGIN
                SET @d = CONVERT(varchar(40), GETDATE(), 109);
                RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT;
           
                IF @Debug = 1 BEGIN SET STATISTICS XML ON; END;
           
                EXECUTE sys.sp_executesql
                    @deadlock_result;
           
                IF @Debug = 1
                BEGIN
                    SET STATISTICS XML OFF;
                    PRINT @deadlock_result;
                END;
           
                RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;

                SET @d = CONVERT(varchar(40), GETDATE(), 109);
                RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT;
             
                SELECT DISTINCT
                    available_plans =
                        'available_plans',
                    ds.proc_name,
                    sql_handle =
                        CONVERT(varbinary(64), ds.sql_handle, 1),
                    dow.database_name,
                    dow.database_id,
                    dow.object_name,
                    query_xml =
                        TRY_CAST(dr.query_xml AS nvarchar(MAX))
                INTO #available_plans
                FROM #deadlock_stack AS ds
                JOIN #deadlock_owner_waiter AS dow
                  ON dow.owner_id = ds.id
                  AND dow.event_date = ds.event_date
                JOIN #deadlock_results AS dr
                  ON  dr.id = ds.id
                  AND dr.event_date = ds.event_date
                OPTION(RECOMPILE);

                SELECT
                    deqs.sql_handle,
                    deqs.plan_handle,
                    deqs.statement_start_offset,
                    deqs.statement_end_offset,
                    deqs.creation_time,
                    deqs.last_execution_time,
                    deqs.execution_count,
                    total_worker_time_ms =
                        deqs.total_worker_time / 1000.,
                    avg_worker_time_ms =
                        CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
                    total_elapsed_time_ms =
                        deqs.total_elapsed_time / 1000.,
                    avg_elapsed_time_ms =
                        CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count),
                    executions_per_second =
                        ISNULL
                        (
                            deqs.execution_count /
                                NULLIF
                                (
                                    DATEDIFF
                                    (
                                        SECOND,
                                        deqs.creation_time,
                                        NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
                                    ),
                                    0
                                ),
                                0
                        ),
                    total_physical_reads_mb =
                        deqs.total_physical_reads * 8. / 1024.,
                    total_logical_writes_mb =
                        deqs.total_logical_writes * 8. / 1024.,
                    total_logical_reads_mb =
                        deqs.total_logical_reads * 8. / 1024.,
                    min_grant_mb =
                        deqs.min_grant_kb * 8. / 1024.,
                    max_grant_mb =
                        deqs.max_grant_kb * 8. / 1024.,
                    min_used_grant_mb =
                        deqs.min_used_grant_kb * 8. / 1024.,
                    max_used_grant_mb =
                        deqs.max_used_grant_kb * 8. / 1024.,  
                    deqs.min_reserved_threads,
                    deqs.max_reserved_threads,
                    deqs.min_used_threads,
                    deqs.max_used_threads,
                    deqs.total_rows,
                    max_worker_time_ms =
                        deqs.max_worker_time / 1000.,
                    max_elapsed_time_ms =
                        deqs.max_elapsed_time / 1000.
                INTO #dm_exec_query_stats
                FROM sys.dm_exec_query_stats AS deqs
                WHERE EXISTS
                (
                    SELECT
                        1/0
                    FROM #available_plans AS ap
                    WHERE ap.sql_handle = deqs.sql_handle
                )
                AND deqs.query_hash IS NOT NULL;
              
                CREATE CLUSTERED INDEX
                    deqs
                ON #dm_exec_query_stats
                (
                    sql_handle,
                    plan_handle
                );
              
                SELECT
                    ap.available_plans,
                    ap.database_name,
                    query_text =
                        TRY_CAST(ap.query_xml AS xml),
                    ap.query_plan,
                    ap.creation_time,
                    ap.last_execution_time,
                    ap.execution_count,
                    ap.executions_per_second,
                    ap.total_worker_time_ms,
                    ap.avg_worker_time_ms,
                    ap.max_worker_time_ms,
                    ap.total_elapsed_time_ms,
                    ap.avg_elapsed_time_ms,
                    ap.max_elapsed_time_ms,
                    ap.total_logical_reads_mb,
                    ap.total_physical_reads_mb,
                    ap.total_logical_writes_mb,
                    ap.min_grant_mb,
                    ap.max_grant_mb,
                    ap.min_used_grant_mb,
                    ap.max_used_grant_mb,
                    ap.min_reserved_threads,
                    ap.max_reserved_threads,
                    ap.min_used_threads,
                    ap.max_used_threads,
                    ap.total_rows,
                    ap.sql_handle,
                    ap.statement_start_offset,
                    ap.statement_end_offset
                FROM
                (
             
                    SELECT
                        ap.*,
                        c.statement_start_offset,
                        c.statement_end_offset,
                        c.creation_time,
                        c.last_execution_time,
                        c.execution_count,
                        c.total_worker_time_ms,
                        c.avg_worker_time_ms,
                        c.total_elapsed_time_ms,
                        c.avg_elapsed_time_ms,
                        c.executions_per_second,
                        c.total_physical_reads_mb,
                        c.total_logical_writes_mb,
                        c.total_logical_reads_mb,
                        c.min_grant_mb,
                        c.max_grant_mb,
                        c.min_used_grant_mb,
                        c.max_used_grant_mb,
                        c.min_reserved_threads,
                        c.max_reserved_threads,
                        c.min_used_threads,
                        c.max_used_threads,
                        c.total_rows,
                        c.query_plan,
                        c.max_worker_time_ms,
                        c.max_elapsed_time_ms
                    FROM #available_plans AS ap
                    OUTER APPLY
                    (
                        SELECT
                            deqs.*,
                            query_plan =
                                TRY_CAST(deps.query_plan AS xml)
                        FROM #dm_exec_query_stats deqs
                        OUTER APPLY sys.dm_exec_text_query_plan
                        (
                            deqs.plan_handle,
                            deqs.statement_start_offset,
                            deqs.statement_end_offset
                        ) AS deps
                        WHERE deqs.sql_handle = ap.sql_handle
                        AND   deps.dbid = ap.database_id
                    ) AS c
                ) AS ap
                WHERE ap.query_plan IS NOT NULL
                ORDER BY
                    ap.avg_worker_time_ms DESC
                OPTION(RECOMPILE, LOOP JOIN, HASH JOIN);

                RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
           
                SET @d = CONVERT(varchar(40), GETDATE(), 109);
                RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT;
           
                SELECT
                    df.check_id,
                    df.database_name,
                    df.object_name,
                    df.finding_group,
                    df.finding
                FROM #deadlock_findings AS df
                ORDER BY
                    df.check_id,
                    df.sort_order
                OPTION(RECOMPILE);
           
                SET @d = CONVERT(varchar(40), GETDATE(), 109);
                RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT;
            END; /*done with output to client app.*/
        END;

        IF @Debug = 1
        BEGIN
            SELECT
                table_name = N'#deadlock_data',
                *
            FROM #deadlock_data AS dd
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#dd',
                *
            FROM #dd AS d
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlock_resource',
                *
            FROM #deadlock_resource AS dr
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlock_resource_parallel',
                *
            FROM #deadlock_resource_parallel AS drp
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlock_owner_waiter',
                *
            FROM #deadlock_owner_waiter AS dow
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlock_process',
                *
            FROM #deadlock_process AS dp
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlock_stack',
                *
            FROM #deadlock_stack AS ds
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlocks',
                *
            FROM #deadlocks AS d
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#deadlock_results',
                *
            FROM #deadlock_results AS dr
            OPTION(RECOMPILE);

            SELECT
                table_name = N'#x',
                *
            FROM #x AS x
            OPTION(RECOMPILE);

            SELECT
                table_name = N'@sysAssObjId',
                *
            FROM @sysAssObjId AS s
            OPTION(RECOMPILE);

            IF OBJECT_ID('tempdb..#available_plans') IS NOT NULL
            BEGIN
            SELECT
                table_name = N'#available_plans',
                *
            FROM #available_plans AS ap
            OPTION(RECOMPILE);
            END;

            IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL
            BEGIN   
            SELECT
                table_name = N'#dm_exec_query_stats',
                *
            FROM #dm_exec_query_stats
            OPTION(RECOMPILE);
            END;

            SELECT
                procedure_parameters =
                    'procedure_parameters',
            DatabaseName =
                @DatabaseName,
            StartDate =
                @StartDate,
            EndDate =
                @EndDate,
            ObjectName =
                @ObjectName,
            StoredProcName =
                @StoredProcName,
            AppName =
                @AppName,
            HostName =
                @HostName,
            LoginName =
                @LoginName,
            EventSessionName =
                @EventSessionName,
            TargetSessionType =
                @TargetSessionType,
            VictimsOnly =
                @VictimsOnly,
            DeadlockType =
                @DeadlockType,
            TargetDatabaseName =
                @TargetDatabaseName,
            TargetSchemaName =
                @TargetSchemaName,
            TargetTableName =
                @TargetTableName,
            TargetColumnName =
                @TargetColumnName,
            TargetTimestampColumnName =
                @TargetTimestampColumnName,
            Debug =
                @Debug,
            Help =
                @Help,
            Version =
                @Version,
            VersionDate =
                @VersionDate,
            VersionCheckMode =
                @VersionCheckMode,
            OutputDatabaseName =
                @OutputDatabaseName,
            OutputSchemaName =
                @OutputSchemaName,
            OutputTableName =
                @OutputTableName,
            ExportToExcel =
                @ExportToExcel;

        SELECT
            declared_variables =
                'declared_variables',
            DatabaseId =
                @DatabaseId,
            StartDateUTC =
                @StartDateUTC,
            EndDateUTC =
                @EndDateUTC,
            ProductVersion =
                @ProductVersion,
            ProductVersionMajor =
                @ProductVersionMajor,
            ProductVersionMinor =
                @ProductVersionMinor,
            ObjectFullName =
                @ObjectFullName,
            Azure =
                @Azure,
            RDS =
                @RDS,
            d =
                @d,
            StringToExecute =
                @StringToExecute,
            StringToExecuteParams =
                @StringToExecuteParams,
            r =
                @r,
            OutputTableFindings =
                @OutputTableFindings,
            DeadlockCount =
                @DeadlockCount,
            ServerName =
                @ServerName,
            OutputDatabaseCheck =
                @OutputDatabaseCheck,
            SessionId =
                @SessionId,
            TargetSessionId =
                @TargetSessionId,
            FileName =
                @FileName,
            inputbuf_bom =
                @inputbuf_bom,
            deadlock_result =
                @deadlock_result;
        END; /*End debug*/
    END; /*Final End*/
GO
IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL
	EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;')
GO

ALTER PROCEDURE dbo.sp_BlitzWho 
	@Help TINYINT = 0 ,
	@ShowSleepingSPIDs TINYINT = 0,
	@ExpertMode BIT = 0,
	@Debug BIT = 0,
	@OutputDatabaseName NVARCHAR(256) = NULL ,
	@OutputSchemaName NVARCHAR(256) = NULL ,
	@OutputTableName NVARCHAR(256) = NULL ,
	@OutputTableRetentionDays TINYINT = 3 ,
	@MinElapsedSeconds INT = 0 ,
	@MinCPUTime INT = 0 ,
	@MinLogicalReads INT = 0 ,
	@MinPhysicalReads INT = 0 ,
	@MinWrites INT = 0 ,
	@MinTempdbMB INT = 0 ,
	@MinRequestedMemoryKB INT = 0 ,
	@MinBlockingSeconds INT = 0 ,
	@CheckDateOverride DATETIMEOFFSET = NULL,
	@ShowActualParameters BIT = 0,
	@GetOuterCommand BIT = 0,
	@GetLiveQueryPlan BIT = NULL,
	@Version     VARCHAR(30) = NULL OUTPUT,
	@VersionDate DATETIME = NULL OUTPUT,
    @VersionCheckMode BIT = 0,
	@SortOrder NVARCHAR(256) = N'elapsed time'
AS
BEGIN
	SET NOCOUNT ON;
	SET STATISTICS XML OFF;
	SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
	
	SELECT @Version = '8.29', @VersionDate = '20260203';
    
	IF(@VersionCheckMode = 1)
	BEGIN
		RETURN;
	END;



	IF @Help = 1
	BEGIN
		PRINT '
sp_BlitzWho from http://FirstResponderKit.org

This script gives you a snapshot of everything currently executing on your SQL Server.

To learn more, visit http://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.
 - Outputting to table is only supported with SQL Server 2012 and higher.
 - If @OutputDatabaseName and @OutputSchemaName are populated, the database and
   schema must already exist. We will not create them, only the table.
   
MIT License

Copyright (c) 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.
';
RETURN;
END;    /* @Help = 1 */

/* Get the major and minor build numbers */
DECLARE  @ProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128))
		,@EngineEdition INT = CAST(SERVERPROPERTY('EngineEdition') AS INT)
		,@ProductVersionMajor DECIMAL(10,2)
		,@ProductVersionMinor DECIMAL(10,2)
		,@Platform NVARCHAR(8) /* Azure or NonAzure are acceptable */ = (SELECT CASE WHEN @@VERSION LIKE '%Azure%' THEN N'Azure' ELSE N'NonAzure' END AS [Platform])
		,@AzureSQLDB BIT = (SELECT CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END)
		,@EnhanceFlag BIT = 0
		,@BlockingCheck NVARCHAR(MAX)
		,@StringToSelect NVARCHAR(MAX)
		,@StringToExecute NVARCHAR(MAX)
		,@OutputTableCleanupDate DATE
		,@SessionWaits BIT = 0
		,@SessionWaitsSQL NVARCHAR(MAX) = 
						 N'LEFT JOIN ( SELECT DISTINCT
												wait.session_id ,
												( SELECT TOP  5 waitwait.wait_type + N'' (''
												     + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128))
												     + N'' ms), ''
												 FROM   sys.dm_exec_session_wait_stats AS waitwait
												 WHERE  waitwait.session_id = wait.session_id
												 GROUP BY  waitwait.wait_type
												 HAVING SUM(waitwait.wait_time_ms) > 5
												 ORDER BY 1												 
												 FOR
												 XML PATH('''') ) AS session_wait_info
										FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2
						ON   s.session_id = wt2.session_id
						LEFT JOIN sys.dm_exec_query_stats AS session_stats
						ON   r.sql_handle = session_stats.sql_handle
								AND r.plan_handle = session_stats.plan_handle
						  AND r.statement_start_offset = session_stats.statement_start_offset
						  AND r.statement_end_offset = session_stats.statement_end_offset' 
		,@ObjectFullName NVARCHAR(2000)
		,@OutputTableNameQueryStats_View NVARCHAR(256)
		,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/;

/* Let's get @SortOrder set to lower case here for comparisons later */
SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_');

SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ),
    @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2)

SELECT
	@OutputTableNameQueryStats_View = QUOTENAME(PARSENAME(@OutputTableName,1) + '_Deltas'),
	@OutputDatabaseName = QUOTENAME(PARSENAME(@OutputDatabaseName,1)),
	@OutputSchemaName = ISNULL(QUOTENAME(PARSENAME(@OutputSchemaName,1)),QUOTENAME(PARSENAME(@OutputTableName,2))),
	@OutputTableName = QUOTENAME(PARSENAME(@OutputTableName,1)),
	@LineFeed = CHAR(13) + CHAR(10);

IF @GetLiveQueryPlan IS NULL
	BEGIN
		IF @ProductVersionMajor >= 16 OR @EngineEdition NOT IN (1, 2, 3, 4)
			SET @GetLiveQueryPlan = 1;
		ELSE
			SET @GetLiveQueryPlan = 0;
	END

IF @OutputTableName IS NOT NULL AND (@OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL)
	BEGIN
		IF @OutputDatabaseName IS NULL AND @AzureSQLDB = 1
			BEGIN
			  /* If we're in Azure SQL DB then use the current database */
			  SET @OutputDatabaseName = QUOTENAME(DB_NAME());
			END;
		IF @OutputSchemaName IS NULL AND @OutputDatabaseName = QUOTENAME(DB_NAME())
			BEGIN
			  /* If we're inserting records in the current database use the default schema */
			  SET @OutputSchemaName = QUOTENAME(SCHEMA_NAME());
			END;
	END;

IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL
  AND EXISTS ( SELECT *
      FROM   sys.databases
      WHERE  QUOTENAME([name]) = @OutputDatabaseName)
	 BEGIN
	 SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */

	 /* Create the table if it doesn't exist */
	 SET @StringToExecute = N'USE '
	  + @OutputDatabaseName
	  + N'; IF EXISTS(SELECT * FROM '
	  + @OutputDatabaseName
	  + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
	  + @OutputSchemaName
	  + N''') AND NOT EXISTS (SELECT * FROM '
	  + @OutputDatabaseName
	  + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
	  + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = '''
	  + @OutputTableName + N''') CREATE TABLE '
	  + @OutputSchemaName + N'.'
	  + @OutputTableName
	  + N'(';
	SET @StringToExecute = @StringToExecute + N'
	ID INT IDENTITY(1,1) NOT NULL,
	ServerName NVARCHAR(128) NOT NULL,
	CheckDate DATETIMEOFFSET NOT NULL,
	[elapsed_time] [varchar](41) NULL,
	[session_id] [smallint] NOT NULL,
	[database_name] [nvarchar](128) NULL,
	[query_text] [nvarchar](max) NULL,
	[outer_command] NVARCHAR(4000) NULL,
	[query_plan] [xml] NULL,
	[live_query_plan] [xml] NULL,
	[cached_parameter_info] [nvarchar](max) NULL,
	[live_parameter_info] [nvarchar](max) NULL,
	[query_cost] [float] NULL,
	[status] [nvarchar](30) NOT NULL,
	[wait_info] [nvarchar](max) NULL,
	[wait_resource] [nvarchar](max) NULL,
	[top_session_waits] [nvarchar](max) NULL,
	[blocking_session_id] [smallint] NULL,
	[open_transaction_count] [int] NULL,
	[is_implicit_transaction] [int] NOT NULL,
	[nt_domain] [nvarchar](128) NULL,
	[host_name] [nvarchar](128) NULL,
	[login_name] [nvarchar](128) NOT NULL,
	[nt_user_name] [nvarchar](128) NULL,
	[program_name] [nvarchar](128) NULL,
	[fix_parameter_sniffing] [nvarchar](150) NULL,
	[client_interface_name] [nvarchar](32) NULL,
	[login_time] [datetime] NOT NULL,
	[start_time] [datetime] NULL,
	[request_time] [datetime] NULL,
	[request_cpu_time] [int] NULL,
	[request_logical_reads] [bigint] NULL,
	[request_writes] [bigint] NULL,
	[request_physical_reads] [bigint] NULL,
	[session_cpu] [int] NOT NULL,
	[session_logical_reads] [bigint] NOT NULL,
	[session_physical_reads] [bigint] NOT NULL,
	[session_writes] [bigint] NOT NULL,
	[tempdb_allocations_mb] [decimal](38, 2) NULL,
	[memory_usage] [int] NOT NULL,
	[estimated_completion_time] [bigint] NULL,
	[percent_complete] [real] NULL,
	[deadlock_priority] [int] NULL,
	[transaction_isolation_level] [varchar](33) NOT NULL,
	[degree_of_parallelism] [smallint] NULL,
	[last_dop] [bigint] NULL,
	[min_dop] [bigint] NULL,
	[max_dop] [bigint] NULL,
	[last_grant_kb] [bigint] NULL,
	[min_grant_kb] [bigint] NULL,
	[max_grant_kb] [bigint] NULL,
	[last_used_grant_kb] [bigint] NULL,
	[min_used_grant_kb] [bigint] NULL,
	[max_used_grant_kb] [bigint] NULL,
	[last_ideal_grant_kb] [bigint] NULL,
	[min_ideal_grant_kb] [bigint] NULL,
	[max_ideal_grant_kb] [bigint] NULL,
	[last_reserved_threads] [bigint] NULL,
	[min_reserved_threads] [bigint] NULL,
	[max_reserved_threads] [bigint] NULL,
	[last_used_threads] [bigint] NULL,
	[min_used_threads] [bigint] NULL,
	[max_used_threads] [bigint] NULL,
	[grant_time] [varchar](20) NULL,
	[requested_memory_kb] [bigint] NULL,
	[grant_memory_kb] [bigint] NULL,
	[is_request_granted] [varchar](39) NOT NULL,
	[required_memory_kb] [bigint] NULL,
	[query_memory_grant_used_memory_kb] [bigint] NULL,
	[ideal_memory_kb] [bigint] NULL,
	[is_small] [bit] NULL,
	[timeout_sec] [int] NULL,
	[resource_semaphore_id] [smallint] NULL,
	[wait_order] [varchar](20) NULL,
	[wait_time_ms] [varchar](20) NULL,
	[next_candidate_for_memory_grant] [varchar](3) NOT NULL,
	[target_memory_kb] [bigint] NULL,
	[max_target_memory_kb] [varchar](30) NULL,
	[total_memory_kb] [bigint] NULL,
	[available_memory_kb] [bigint] NULL,
	[granted_memory_kb] [bigint] NULL,
	[query_resource_semaphore_used_memory_kb] [bigint] NULL,
	[grantee_count] [int] NULL,
	[waiter_count] [int] NULL,
	[timeout_error_count] [bigint] NULL,
	[forced_grant_count] [varchar](30) NULL,
	[workload_group_name] [sysname] NULL,
	[resource_pool_name] [sysname] NULL,
	[context_info] [varchar](128) NULL,
	[query_hash] [binary](8) NULL,
	[query_plan_hash] [binary](8) NULL,
	[sql_handle] [varbinary] (64) NULL,
	[plan_handle] [varbinary] (64) NULL,
	[statement_start_offset] INT NULL,
	[statement_end_offset] INT NULL,
	JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)),
	PRIMARY KEY CLUSTERED (ID ASC));';
	IF @Debug = 1
		BEGIN
			PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000))
			PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000))
		END
	EXEC(@StringToExecute);

	/* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */
	SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
	SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
		WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'')
		ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));';
	EXEC(@StringToExecute);

	/* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */
	SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
	SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
		WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'')
		ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;';
	EXEC(@StringToExecute);

	/* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */
	SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
	SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
		WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'')
		ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;';
	EXEC(@StringToExecute);

	/* If the table doesn't have the new outer_command column, add it. See Github #2887. */
	SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
	SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
		WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'')
		ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;';
	EXEC(@StringToExecute);

	/* If the table doesn't have the new wait_resource column, add it. See Github #2970. */
	SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
	SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
		WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'')
		ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;';
	EXEC(@StringToExecute);

	/* Delete history older than @OutputTableRetentionDays */
	SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE);
	SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
		+ @OutputDatabaseName
		+ N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
		+ @OutputSchemaName + N''') DELETE '
		+ @OutputDatabaseName + '.'
		+ @OutputSchemaName + '.'
		+ @OutputTableName
		+ N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;';
	IF @Debug = 1
		BEGIN
			PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000))
			PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000))
		END
	EXEC sp_executesql @StringToExecute,
		N'@SrvName NVARCHAR(128), @CheckDate date',
		@@SERVERNAME, @OutputTableCleanupDate;

	SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableNameQueryStats_View;

        /* Create the view */
        IF OBJECT_ID(@ObjectFullName) IS NULL
            BEGIN
            SET @StringToExecute = N'USE '
                + @OutputDatabaseName
                + N'; EXEC (''CREATE VIEW '
                + @OutputSchemaName + '.'
                + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed
				+ N'WITH MaxQueryDuration AS ' + @LineFeed
				+ N'( ' + @LineFeed
				+ N'    SELECT ' + @LineFeed
				+ N'        MIN([ID]) AS [MinID], ' + @LineFeed
				+ N'		MAX([ID]) AS [MaxID] ' + @LineFeed
				+ N'    FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed
				+ N'    GROUP BY [ServerName], ' + @LineFeed
				+ N'    [session_id], ' + @LineFeed
				+ N'    [database_name], ' + @LineFeed
				+ N'    [request_time], ' + @LineFeed
				+ N'    [start_time], ' + @LineFeed
				+ N'    [sql_handle] ' + @LineFeed
				+ N') ' + @LineFeed
				+ N'SELECT ' + @LineFeed
				+ N'    [ID], ' + @LineFeed
				+ N'    [ServerName], ' + @LineFeed
				+ N'    [CheckDate], ' + @LineFeed
				+ N'    [elapsed_time], ' + @LineFeed 
				+ N'    [session_id], ' + @LineFeed 
				+ N'    [database_name], ' + @LineFeed 
				+ N'    [query_text_snippet], ' + @LineFeed 
				+ N'    [query_plan], ' + @LineFeed 
				+ N'    [live_query_plan], ' + @LineFeed 
				+ N'    [query_cost], ' + @LineFeed 
				+ N'    [status], ' + @LineFeed 
				+ N'    [wait_info], ' + @LineFeed 
				+ N'    [wait_resource], ' + @LineFeed 
				+ N'    [top_session_waits], ' + @LineFeed 
				+ N'    [blocking_session_id], ' + @LineFeed 
				+ N'    [open_transaction_count], ' + @LineFeed 
				+ N'    [is_implicit_transaction], ' + @LineFeed 
				+ N'    [nt_domain], ' + @LineFeed 
				+ N'    [host_name], ' + @LineFeed 
				+ N'    [login_name], ' + @LineFeed 
				+ N'    [nt_user_name], ' + @LineFeed 
				+ N'    [program_name], ' + @LineFeed 
				+ N'    [fix_parameter_sniffing], ' + @LineFeed 
				+ N'    [client_interface_name], ' + @LineFeed 
				+ N'    [login_time], ' + @LineFeed 
				+ N'    [start_time], ' + @LineFeed 
				+ N'    [request_time], ' + @LineFeed 
				+ N'    [request_cpu_time], ' + @LineFeed 
				+ N'    [degree_of_parallelism], ' + @LineFeed 
				+ N'    [request_logical_reads], ' + @LineFeed 
				+ N'    [Logical_Reads_MB], ' + @LineFeed 
				+ N'    [request_writes], ' + @LineFeed 
				+ N'    [Logical_Writes_MB], ' + @LineFeed 
				+ N'    [request_physical_reads], ' + @LineFeed 
				+ N'    [Physical_reads_MB], ' + @LineFeed 
				+ N'    [session_cpu], ' + @LineFeed 
				+ N'    [session_logical_reads], ' + @LineFeed 
				+ N'    [session_logical_reads_MB], ' + @LineFeed 
				+ N'    [session_physical_reads], ' + @LineFeed 
				+ N'    [session_physical_reads_MB], ' + @LineFeed 
				+ N'    [session_writes], ' + @LineFeed 
				+ N'    [session_writes_MB], ' + @LineFeed 
				+ N'    [tempdb_allocations_mb], ' + @LineFeed 
				+ N'    [memory_usage], ' + @LineFeed 
				+ N'    [estimated_completion_time], ' + @LineFeed 
				+ N'    [percent_complete], ' + @LineFeed 
				+ N'    [deadlock_priority], ' + @LineFeed 
				+ N'    [transaction_isolation_level], ' + @LineFeed 
				+ N'    [last_dop], ' + @LineFeed 
				+ N'    [min_dop], ' + @LineFeed 
				+ N'    [max_dop], ' + @LineFeed 
				+ N'    [last_grant_kb], ' + @LineFeed 
				+ N'    [min_grant_kb], ' + @LineFeed 
				+ N'    [max_grant_kb], ' + @LineFeed 
				+ N'    [last_used_grant_kb], ' + @LineFeed 
				+ N'    [min_used_grant_kb], ' + @LineFeed 
				+ N'    [max_used_grant_kb], ' + @LineFeed 
				+ N'    [last_ideal_grant_kb], ' + @LineFeed 
				+ N'    [min_ideal_grant_kb], ' + @LineFeed 
				+ N'    [max_ideal_grant_kb], ' + @LineFeed 
				+ N'    [last_reserved_threads], ' + @LineFeed 
				+ N'    [min_reserved_threads], ' + @LineFeed 
				+ N'    [max_reserved_threads], ' + @LineFeed 
				+ N'    [last_used_threads], ' + @LineFeed 
				+ N'    [min_used_threads], ' + @LineFeed 
				+ N'    [max_used_threads], ' + @LineFeed 
				+ N'    [grant_time], ' + @LineFeed 
				+ N'    [requested_memory_kb], ' + @LineFeed 
				+ N'    [grant_memory_kb], ' + @LineFeed 
				+ N'    [is_request_granted], ' + @LineFeed 
				+ N'    [required_memory_kb], ' + @LineFeed 
				+ N'    [query_memory_grant_used_memory_kb], ' + @LineFeed 
				+ N'    [ideal_memory_kb], ' + @LineFeed 
				+ N'    [is_small], ' + @LineFeed 
				+ N'    [timeout_sec], ' + @LineFeed 
				+ N'    [resource_semaphore_id], ' + @LineFeed 
				+ N'    [wait_order], ' + @LineFeed 
				+ N'    [wait_time_ms], ' + @LineFeed 
				+ N'    [next_candidate_for_memory_grant], ' + @LineFeed 
				+ N'    [target_memory_kb], ' + @LineFeed 
				+ N'    [max_target_memory_kb], ' + @LineFeed 
				+ N'    [total_memory_kb], ' + @LineFeed 
				+ N'    [available_memory_kb], ' + @LineFeed 
				+ N'    [granted_memory_kb], ' + @LineFeed 
				+ N'    [query_resource_semaphore_used_memory_kb], ' + @LineFeed 
				+ N'    [grantee_count], ' + @LineFeed 
				+ N'    [waiter_count], ' + @LineFeed 
				+ N'    [timeout_error_count], ' + @LineFeed 
				+ N'    [forced_grant_count], ' + @LineFeed 
				+ N'    [workload_group_name], ' + @LineFeed 
				+ N'    [resource_pool_name], ' + @LineFeed 
				+ N'    [context_info], ' + @LineFeed 
				+ N'    [query_hash], ' + @LineFeed 
				+ N'    [query_plan_hash], ' + @LineFeed 
				+ N'    [sql_handle], ' + @LineFeed 
				+ N'    [plan_handle], ' + @LineFeed 
				+ N'    [statement_start_offset], ' + @LineFeed 
				+ N'    [statement_end_offset] ' + @LineFeed
				+ N'    FROM ' + @LineFeed
				+ N'        ( ' + @LineFeed
				+ N'            SELECT ' + @LineFeed
				+ N'                   [ID], ' + @LineFeed
				+ N'			       [ServerName], ' + @LineFeed 
				+ N'			       [CheckDate], ' + @LineFeed 
				+ N'			       [elapsed_time], ' + @LineFeed 
				+ N'			       [session_id], ' + @LineFeed 
				+ N'			       [database_name], ' + @LineFeed 
				+ N'			       /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed
				+ N'			       CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed 
				+ N'			       [query_plan], ' + @LineFeed 
				+ N'			       [live_query_plan], ' + @LineFeed 
				+ N'			       [query_cost], ' + @LineFeed 
				+ N'			       [status], ' + @LineFeed 
				+ N'			       [wait_info], ' + @LineFeed 
				+ N'			       [wait_resource], ' + @LineFeed 
				+ N'			       [top_session_waits], ' + @LineFeed 
				+ N'			       [blocking_session_id], ' + @LineFeed 
				+ N'			       [open_transaction_count], ' + @LineFeed 
				+ N'			       [is_implicit_transaction], ' + @LineFeed 
				+ N'			       [nt_domain], ' + @LineFeed 
				+ N'			       [host_name], ' + @LineFeed 
				+ N'			       [login_name], ' + @LineFeed 
				+ N'			       [nt_user_name], ' + @LineFeed 
				+ N'			       [program_name], ' + @LineFeed 
				+ N'			       [fix_parameter_sniffing], ' + @LineFeed 
				+ N'			       [client_interface_name], ' + @LineFeed 
				+ N'			       [login_time], ' + @LineFeed 
				+ N'			       [start_time], ' + @LineFeed 
				+ N'			       [request_time], ' + @LineFeed 
				+ N'			       [request_cpu_time], ' + @LineFeed 
				+ N'			       [degree_of_parallelism], ' + @LineFeed 
				+ N'			       [request_logical_reads], ' + @LineFeed 
				+ N'			       ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed 
				+ N'			       [request_writes], ' + @LineFeed 
				+ N'			       ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed 
				+ N'			       [request_physical_reads], ' + @LineFeed 
				+ N'			       ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed 
				+ N'			       [session_cpu], ' + @LineFeed 
				+ N'			       [session_logical_reads], ' + @LineFeed 
				+ N'			       ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed 
				+ N'			       [session_physical_reads], ' + @LineFeed 
				+ N'			       ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed 
				+ N'			       [session_writes], ' + @LineFeed 
				+ N'			       ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed 
				+ N'			       [tempdb_allocations_mb], ' + @LineFeed 
				+ N'			       [memory_usage], ' + @LineFeed 
				+ N'			       [estimated_completion_time], ' + @LineFeed 
				+ N'			       [percent_complete], ' + @LineFeed 
				+ N'			       [deadlock_priority], ' + @LineFeed 
				+ N'			       [transaction_isolation_level], ' + @LineFeed 
				+ N'			       [last_dop], ' + @LineFeed 
				+ N'			       [min_dop], ' + @LineFeed 
				+ N'			       [max_dop], ' + @LineFeed 
				+ N'			       [last_grant_kb], ' + @LineFeed 
				+ N'			       [min_grant_kb], ' + @LineFeed 
				+ N'			       [max_grant_kb], ' + @LineFeed 
				+ N'			       [last_used_grant_kb], ' + @LineFeed 
				+ N'			       [min_used_grant_kb], ' + @LineFeed 
				+ N'			       [max_used_grant_kb], ' + @LineFeed 
				+ N'			       [last_ideal_grant_kb], ' + @LineFeed 
				+ N'			       [min_ideal_grant_kb], ' + @LineFeed 
				+ N'			       [max_ideal_grant_kb], ' + @LineFeed 
				+ N'			       [last_reserved_threads], ' + @LineFeed 
				+ N'			       [min_reserved_threads], ' + @LineFeed 
				+ N'			       [max_reserved_threads], ' + @LineFeed 
				+ N'			       [last_used_threads], ' + @LineFeed 
				+ N'			       [min_used_threads], ' + @LineFeed 
				+ N'			       [max_used_threads], ' + @LineFeed 
				+ N'			       [grant_time], ' + @LineFeed 
				+ N'			       [requested_memory_kb], ' + @LineFeed 
				+ N'			       [grant_memory_kb], ' + @LineFeed 
				+ N'			       [is_request_granted], ' + @LineFeed 
				+ N'			       [required_memory_kb], ' + @LineFeed 
				+ N'			       [query_memory_grant_used_memory_kb], ' + @LineFeed 
				+ N'			       [ideal_memory_kb], ' + @LineFeed 
				+ N'			       [is_small], ' + @LineFeed 
				+ N'			       [timeout_sec], ' + @LineFeed 
				+ N'			       [resource_semaphore_id], ' + @LineFeed 
				+ N'			       [wait_order], ' + @LineFeed 
				+ N'			       [wait_time_ms], ' + @LineFeed 
				+ N'			       [next_candidate_for_memory_grant], ' + @LineFeed 
				+ N'			       [target_memory_kb], ' + @LineFeed 
				+ N'			       [max_target_memory_kb], ' + @LineFeed 
				+ N'			       [total_memory_kb], ' + @LineFeed 
				+ N'			       [available_memory_kb], ' + @LineFeed 
				+ N'			       [granted_memory_kb], ' + @LineFeed 
				+ N'			       [query_resource_semaphore_used_memory_kb], ' + @LineFeed 
				+ N'			       [grantee_count], ' + @LineFeed 
				+ N'			       [waiter_count], ' + @LineFeed 
				+ N'			       [timeout_error_count], ' + @LineFeed 
				+ N'			       [forced_grant_count], ' + @LineFeed 
				+ N'			       [workload_group_name], ' + @LineFeed 
				+ N'			       [resource_pool_name], ' + @LineFeed 
				+ N'			       [context_info], ' + @LineFeed 
				+ N'			       [query_hash], ' + @LineFeed 
				+ N'			       [query_plan_hash], ' + @LineFeed 
				+ N'			       [sql_handle], ' + @LineFeed 
				+ N'			       [plan_handle], ' + @LineFeed 
				+ N'			       [statement_start_offset], ' + @LineFeed 
				+ N'			       [statement_end_offset] ' + @LineFeed
				+ N'            FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed 
				+ N'        ) AS [BlitzWho] ' + @LineFeed
				+ N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed
				+ N''');'

			IF @Debug = 1
				BEGIN
					PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000))
					PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000))
				END

				EXEC(@StringToExecute);
            END;

 END

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

CREATE TABLE #WhoReadableDBs 
(
database_id INT
);

IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states')
BEGIN
	RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT;

	EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;');
END

SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
   
						SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */
						DECLARE @blocked TABLE 
								(
								    dbid SMALLINT NOT NULL,
								    last_batch DATETIME NOT NULL,
								    open_tran SMALLINT NOT NULL,
								    sql_handle BINARY(20) NOT NULL,
								    session_id SMALLINT NOT NULL,
								    blocking_session_id SMALLINT NOT NULL,
								    lastwaittype NCHAR(32) NOT NULL,
								    waittime BIGINT NOT NULL,
								    cpu INT NOT NULL,
								    physical_io BIGINT NOT NULL,
								    memusage INT NOT NULL
								); 
						
						INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage )
						SELECT
							sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, 
							sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage
						FROM sys.sysprocesses AS sys1
						JOIN sys.sysprocesses AS sys2
						ON sys1.spid = sys2.blocked;
						'+CASE
							WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N'
						DECLARE @session_id SMALLINT;
						DECLARE @Sessions TABLE 
						(
							session_id INT
						);

						DECLARE @inputbuffer TABLE 
						(
						ID INT IDENTITY(1,1),
						session_id INT,
						event_type	NVARCHAR(30),
						parameters	SMALLINT,
						event_info	NVARCHAR(4000)
						);

						DECLARE inputbuffer_cursor
						
						CURSOR LOCAL FAST_FORWARD
						FOR 
						SELECT session_id
						FROM sys.dm_exec_sessions
						WHERE session_id <> @@SPID
						AND is_user_process = 1;
						
						OPEN inputbuffer_cursor;
						
						FETCH NEXT FROM inputbuffer_cursor INTO @session_id;
						
						WHILE (@@FETCH_STATUS = 0)
						BEGIN;
							BEGIN TRY;

								INSERT INTO @inputbuffer ([event_type],[parameters],[event_info])
								EXEC sp_executesql
									N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'',
									N''@session_id SMALLINT'',
									@session_id;
						
								UPDATE @inputbuffer 
								SET session_id = @session_id 
								WHERE ID = SCOPE_IDENTITY();

							END TRY
							BEGIN CATCH
								RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT;
							END CATCH;
						
							FETCH NEXT FROM inputbuffer_cursor INTO @session_id
						
						END;
						
						CLOSE inputbuffer_cursor;
						DEALLOCATE inputbuffer_cursor;'
						ELSE N''
						END+
						N' 

						DECLARE @LiveQueryPlans TABLE
						(
							Session_Id INT NOT NULL,
							Query_Plan XML NOT NULL
						);

						'
IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1)
BEGIN
	SET @BlockingCheck = @BlockingCheck + N'
							INSERT INTO @LiveQueryPlans
							SELECT	s.session_id, query_plan 
							FROM	sys.dm_exec_sessions AS s
							CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id)
							WHERE	s.session_id <> @@SPID;';
END


IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11
BEGIN
    /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select:
    SELECT @StringToExecute = N'SELECT  GETDATE() AS run_date ,
    */
    SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] ,
			       s.session_id ,
					CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL 
							THEN r.blocking_session_id
							WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id 
							THEN blocked.blocking_session_id
							WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id 
							THEN blocked.blocking_session_id
							WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id 
							THEN r.blocking_session_id
							ELSE NULL 
						END AS blocking_session_id,
						    COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name,
			       ISNULL(SUBSTRING(dest.text,
			            ( r.statement_start_offset / 2 ) + 1,
			            ( ( CASE r.statement_end_offset
			               WHEN -1 THEN DATALENGTH(dest.text)
			               ELSE r.statement_end_offset
			             END - r.statement_start_offset )
			              / 2 ) + 1), dest.text) AS query_text ,
						  '+CASE
								WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,'
							ELSE N''
							END+N'
			       derp.query_plan ,
						    qmg.query_cost ,										   		   
						    s.status ,
							CASE
								WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) 
								ELSE NULL
							END AS wait_info ,																					
							r.wait_resource ,
			       COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count ,
						    CASE WHEN EXISTS (  SELECT 1 
               FROM sys.dm_tran_active_transactions AS tat
               JOIN sys.dm_tran_session_transactions AS tst
               ON tst.transaction_id = tat.transaction_id
               WHERE tat.name = ''implicit_transaction''
               AND s.session_id = tst.session_id 
               )  THEN 1 
            ELSE 0 
          END AS is_implicit_transaction ,
					     s.nt_domain ,
			       s.host_name ,
			       s.login_name ,
			       s.nt_user_name ,'
		IF @Platform = 'NonAzure'
		BEGIN
		SET @StringToExecute +=
				   N'program_name = COALESCE((
					SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') 
					FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2)
					),s.program_name)'
		END
		ELSE
		BEGIN
		SET @StringToExecute += N's.program_name'
		END
						
    IF @ExpertMode = 1
    BEGIN
    SET @StringToExecute += 
			   N',
						''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing,						      		
			   s.client_interface_name ,
			   s.login_time ,
			   r.start_time ,
			   qmg.request_time ,
						COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time,
			   COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads,
			   COALESCE(r.writes, s.writes) AS request_writes,
			   COALESCE(r.reads, s.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,					
						tempdb_allocations.tempdb_allocations_mb,
			   s.memory_usage ,
			   r.estimated_completion_time , 	
						r.percent_complete , 
			   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.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation''
			     WHEN s.transaction_isolation_level = 2 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 ,				
						qmg.dop AS degree_of_parallelism ,
			   COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''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 AS query_memory_grant_used_memory_kb,
			   qmg.ideal_memory_kb ,
			   qmg.is_small ,
			   qmg.timeout_sec ,
			   qmg.resource_semaphore_id ,
			   COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order ,
			   COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)),
			      ''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(20)),
			      ''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 AS query_resource_semaphore_used_memory_kb,
			   qrs.grantee_count ,
			   qrs.waiter_count ,
			   qrs.timeout_error_count ,
			   COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)),
			      ''Small Query Resource Semaphore'') AS forced_grant_count,
						wg.name AS workload_group_name , 
						rp.name AS resource_pool_name,
 						CONVERT(VARCHAR(128), r.context_info)  AS context_info
						'
	END /* IF @ExpertMode = 1 */
				
    SET @StringToExecute += 			 
	    N'FROM sys.dm_exec_sessions AS s
			 '+
			 CASE
				WHEN @GetOuterCommand = 1 THEN CASE
													WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib'
													ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id'
												END
				ELSE N''
			 END+N'
			 LEFT 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(MAX(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
				LEFT JOIN sys.resource_governor_workload_groups wg 
				ON 		s.group_id = wg.group_id
				LEFT JOIN sys.resource_governor_resource_pools rp 
				ON		wg.pool_id = rp.pool_id
				OUTER APPLY (
								SELECT TOP 1
								b.dbid, b.last_batch, b.open_tran, b.sql_handle, 
								b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime
								FROM @blocked b
								WHERE (s.session_id = b.session_id
										OR s.session_id = b.blocking_session_id)
							) AS blocked				
				OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest
			 OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp
				OUTER APPLY (
						SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb
						FROM sys.dm_db_task_space_usage tsu
						WHERE tsu.request_id = r.request_id
						AND tsu.session_id = r.session_id
						AND tsu.session_id = s.session_id
				) as tempdb_allocations
			 WHERE s.session_id <> @@SPID 
				AND s.host_name IS NOT NULL
				'
				+ CASE WHEN @ShowSleepingSPIDs = 0 THEN
						N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL'
					  WHEN @ShowSleepingSPIDs = 1 THEN
						N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1'
					 ELSE N'' END;
END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */

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
			     WHEN @ProductVersionMajor > 13 THEN 1
		      ELSE 0 
	     END


    IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL
    BEGIN
	    SET @SessionWaits = 1
    END

    /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select:
    SELECT @StringToExecute = N'SELECT  GETDATE() AS run_date ,
    */
    SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] ,
			       s.session_id ,
					CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL 
					THEN r.blocking_session_id
					WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id 
					THEN blocked.blocking_session_id
					WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id 
					THEN blocked.blocking_session_id
					WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id 
					THEN r.blocking_session_id
					ELSE NULL 
					END AS blocking_session_id,
					COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name,
					ISNULL(SUBSTRING(dest.text,
			            ( r.statement_start_offset / 2 ) + 1,
			            ( ( CASE r.statement_end_offset
			               WHEN -1 THEN DATALENGTH(dest.text)
			               ELSE r.statement_end_offset
			             END - r.statement_start_offset )
			              / 2 ) + 1), dest.text) AS query_text ,
						  '+CASE
								WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,'
							ELSE N''
							END+N'
			       derp.query_plan ,
				   CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 
				   		THEN '''<?No live query plan available for this query in sys.dm_exec_query_statistics_xml.?>'''
						ELSE '''<?Live Query Plans were not retrieved. Set @GetLiveQueryPlan=1 to try and retrieve Live Query Plans ?>'''
						END
					+') AS XML
				   
				   
				   ) AS live_query_plan ,
					STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'')
						FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data)
						FOR XML PATH('''')), 1,2,'''')
						AS Cached_Parameter_Info,
						'
	IF @ShowActualParameters = 1
	BEGIN
		SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,'
	END

	SELECT @StringToExecute = @StringToExecute + N'
			       qmg.query_cost ,
			       s.status ,
					CASE
						WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) 
						ELSE NULL
					END AS wait_info ,
					r.wait_resource ,'
						    +
						    CASE @SessionWaits
							     WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,'
							     ELSE N' NULL AS top_session_waits ,'
						    END
						    +																	
						    N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count ,
						    CASE WHEN EXISTS (  SELECT 1 
               FROM sys.dm_tran_active_transactions AS tat
               JOIN sys.dm_tran_session_transactions AS tst
               ON tst.transaction_id = tat.transaction_id
               WHERE tat.name = ''implicit_transaction''
               AND s.session_id = tst.session_id 
               )  THEN 1 
            ELSE 0 
          END AS is_implicit_transaction ,
					     s.nt_domain ,
			       s.host_name ,
			       s.login_name ,
			       s.nt_user_name ,'
		IF @Platform = 'NonAzure'
		BEGIN
		SET @StringToExecute +=
				   N'program_name = COALESCE((
					SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') 
					FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2)
					),s.program_name)'
		END
		ELSE
		BEGIN
		SET @StringToExecute += N's.program_name'
		END

    IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */
    BEGIN
        SET @StringToExecute += 						
	        N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing,						      		
        s.client_interface_name ,
        s.login_time ,
        r.start_time ,		
        qmg.request_time ,										
		        COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time,
        COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads,
        COALESCE(r.writes, s.writes) AS request_writes,
        COALESCE(r.reads, s.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,
		        tempdb_allocations.tempdb_allocations_mb,
        s.memory_usage ,
        r.estimated_completion_time , 
		        r.percent_complete , 
        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.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation''
			WHEN s.transaction_isolation_level = 2 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 ,
		        qmg.dop AS degree_of_parallelism ,						'
		        + 
		        CASE @EnhanceFlag
				        WHEN 1 THEN 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,'
				        ELSE N' NULL AS last_dop,
        NULL AS min_dop,
        NULL AS max_dop,
        NULL AS last_grant_kb,
        NULL AS min_grant_kb,
        NULL AS max_grant_kb,
        NULL AS last_used_grant_kb,
        NULL AS min_used_grant_kb,
        NULL AS max_used_grant_kb,
        NULL AS last_ideal_grant_kb,
        NULL AS min_ideal_grant_kb,
        NULL AS max_ideal_grant_kb,
        NULL AS last_reserved_threads,
        NULL AS min_reserved_threads,
        NULL AS max_reserved_threads,
        NULL AS last_used_threads,
        NULL AS min_used_threads,
        NULL AS max_used_threads,'
		        END 

        SET @StringToExecute += 						
		        N'
        COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''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 AS query_memory_grant_used_memory_kb,
        qmg.ideal_memory_kb ,
        qmg.is_small ,
        qmg.timeout_sec ,
        qmg.resource_semaphore_id ,
        COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order ,
        COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)),
	        ''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(20)),
	        ''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 AS query_resource_semaphore_used_memory_kb,
        qrs.grantee_count ,
        qrs.waiter_count ,
        qrs.timeout_error_count ,
        COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)),
        ''Small Query Resource Semaphore'') AS forced_grant_count,
        wg.name AS workload_group_name, 
        rp.name AS resource_pool_name,
        CONVERT(VARCHAR(128), r.context_info)  AS context_info,
        r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset '
    END /* IF @ExpertMode = 1 */
					
    SET @StringToExecute += 	
	    N' FROM sys.dm_exec_sessions AS s'+
			 CASE
				WHEN @GetOuterCommand = 1 THEN CASE
													WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'
		OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib'
													ELSE N'
		LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id'
											   END
				ELSE N''
			 END+N'
	    LEFT 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(MAX(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
	    '
	    +
	    CASE @SessionWaits
			    WHEN 1 THEN @SessionWaitsSQL
			    ELSE N''
	    END
	    + 
	    N'
	    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
	    LEFT JOIN sys.resource_governor_workload_groups wg 
	    ON 		s.group_id = wg.group_id
	    LEFT JOIN sys.resource_governor_resource_pools rp 
	    ON		wg.pool_id = rp.pool_id
	    OUTER APPLY (
			    SELECT TOP 1
			    b.dbid, b.last_batch, b.open_tran, b.sql_handle, 
			    b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime
			    FROM @blocked b
			    WHERE (s.session_id = b.session_id
					    OR s.session_id = b.blocking_session_id)
		    ) AS blocked
	    OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest
	    OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp
	    OUTER APPLY (
			    SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb
			    FROM sys.dm_db_task_space_usage tsu
			    WHERE tsu.request_id = r.request_id
			    AND tsu.session_id = r.session_id
			    AND tsu.session_id = s.session_id
	    ) as tempdb_allocations

		OUTER APPLY (
			SELECT TOP 1 Query_Plan,
			STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')''
					FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data)
					FOR XML PATH('''')), 1,2,'''')
					AS Live_Parameter_Info
			FROM @LiveQueryPlans q
			WHERE (s.session_id = q.Session_Id)

		) AS qs_live

	    WHERE s.session_id <> @@SPID 
	    AND s.host_name IS NOT NULL
		AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs)
	    '
	    + CASE WHEN @ShowSleepingSPIDs = 0 THEN
			    N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL'
			    WHEN @ShowSleepingSPIDs = 1 THEN
			    N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1'
			    ELSE N'' END;


END /* IF @ProductVersionMajor >= 11  */

IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0
	BEGIN
	/* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */
	SET @StringToExecute += N' AND (1 = 0 ';
	IF @MinElapsedSeconds > 0
		SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20));
	IF @MinCPUTime > 0
		SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20));
	IF @MinLogicalReads > 0
		SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20));
	IF @MinPhysicalReads > 0
		SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20));
	IF @MinWrites > 0
		SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20));
	IF @MinTempdbMB > 0
		SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20));
	IF @MinRequestedMemoryKB > 0
		SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20));
	/* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */
	IF @MinBlockingSeconds > 0
		SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20));
	SET @StringToExecute += N' ) ';
	END

SET @StringToExecute +=
		N' ORDER BY ' + CASE	WHEN @SortOrder = 'session_id'		THEN '[session_id] DESC'
								WHEN @SortOrder = 'query_cost'		THEN '[query_cost] DESC'
								WHEN @SortOrder = 'database_name'		THEN '[database_name] ASC'
								WHEN @SortOrder = 'open_transaction_count'		THEN '[open_transaction_count] DESC'
								WHEN @SortOrder = 'is_implicit_transaction'		THEN '[is_implicit_transaction] DESC'
								WHEN @SortOrder = 'login_name'		THEN '[login_name] ASC'
								WHEN @SortOrder = 'program_name'		THEN '[program_name] ASC'
								WHEN @SortOrder = 'client_interface_name'		THEN '[client_interface_name] ASC'
								WHEN @SortOrder = 'request_cpu_time'		THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC'
								WHEN @SortOrder = 'request_logical_reads'		THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC'
								WHEN @SortOrder = 'request_writes'		THEN 'COALESCE(r.writes, s.writes) DESC'
								WHEN @SortOrder = 'request_physical_reads'		THEN 'COALESCE(r.reads, s.reads) DESC '
								WHEN @SortOrder = 'session_cpu'		THEN 's.cpu_time DESC'
								WHEN @SortOrder = 'session_logical_reads'		THEN 's.logical_reads DESC'
								WHEN @SortOrder = 'session_physical_reads'		THEN 's.reads DESC'
								WHEN @SortOrder = 'session_writes'		THEN 's.writes DESC'
								WHEN @SortOrder = 'tempdb_allocations_mb'		THEN '[tempdb_allocations_mb] DESC'
								WHEN @SortOrder = 'memory_usage'		THEN '[memory_usage] DESC'
								WHEN @SortOrder = 'deadlock_priority'		THEN 'r.deadlock_priority DESC'
								WHEN @SortOrder = 'transaction_isolation_level'		THEN 'r.[transaction_isolation_level] DESC'
								WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC'
								WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC'
								WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC'
								WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC'
								WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC'
								WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC'
								WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC'
								ELSE '[elapsed_time] DESC'
						END + '
		';


IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL
  AND EXISTS ( SELECT *
      FROM   sys.databases
      WHERE  QUOTENAME([name]) = @OutputDatabaseName)
	BEGIN
	SET @StringToExecute = N'USE '
	  + @OutputDatabaseName + N'; '
	  + @BlockingCheck + 
	  + ' INSERT INTO '
	  + @OutputSchemaName + N'.'
	  + @OutputTableName
	  + N'(ServerName
	,CheckDate
	,[elapsed_time]
	,[session_id]
	,[blocking_session_id]
	,[database_name]
	,[query_text]'
	+ CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N'
	,[query_plan]'
    + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END 
	+ CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]'  ELSE N'' END
	+ CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N'
	,[query_cost]
	,[status]
	,[wait_info]
	,[wait_resource]'
    + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N'
	,[open_transaction_count]
	,[is_implicit_transaction]
	,[nt_domain]
	,[host_name]
	,[login_name]
	,[nt_user_name]
	,[program_name]
	,[fix_parameter_sniffing]
	,[client_interface_name]
	,[login_time]
	,[start_time]
	,[request_time]
	,[request_cpu_time]
	,[request_logical_reads]
	,[request_writes]
	,[request_physical_reads]
	,[session_cpu]
	,[session_logical_reads]
	,[session_physical_reads]
	,[session_writes]
	,[tempdb_allocations_mb]
	,[memory_usage]
	,[estimated_completion_time]
	,[percent_complete]
	,[deadlock_priority]
	,[transaction_isolation_level]
	,[degree_of_parallelism]'
    + CASE WHEN @ProductVersionMajor >= 11 THEN N'
	,[last_dop]
	,[min_dop]
	,[max_dop]
	,[last_grant_kb]
	,[min_grant_kb]
	,[max_grant_kb]
	,[last_used_grant_kb]
	,[min_used_grant_kb]
	,[max_used_grant_kb]
	,[last_ideal_grant_kb]
	,[min_ideal_grant_kb]
	,[max_ideal_grant_kb]
	,[last_reserved_threads]
	,[min_reserved_threads]
	,[max_reserved_threads]
	,[last_used_threads]
	,[min_used_threads]
	,[max_used_threads]' ELSE N'' END + N'
	,[grant_time]
	,[requested_memory_kb]
	,[grant_memory_kb]
	,[is_request_granted]
	,[required_memory_kb]
	,[query_memory_grant_used_memory_kb]
	,[ideal_memory_kb]
	,[is_small]
	,[timeout_sec]
	,[resource_semaphore_id]
	,[wait_order]
	,[wait_time_ms]
	,[next_candidate_for_memory_grant]
	,[target_memory_kb]
	,[max_target_memory_kb]
	,[total_memory_kb]
	,[available_memory_kb]
	,[granted_memory_kb]
	,[query_resource_semaphore_used_memory_kb]
	,[grantee_count]
	,[waiter_count]
	,[timeout_error_count]
	,[forced_grant_count]
	,[workload_group_name]
	,[resource_pool_name]
	,[context_info]'
    + CASE WHEN @ProductVersionMajor >= 11 THEN N'
	,[query_hash]
	,[query_plan_hash]
	,[sql_handle]
	,[plan_handle]
	,[statement_start_offset]
	,[statement_end_offset]' ELSE N'' END + N'
) 
	SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , '
	+ @StringToExecute;
	END
ELSE
	SET @StringToExecute = @BlockingCheck + N' SELECT  GETDATE() AS run_date , ' + @StringToExecute;

/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */
IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020)
	OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 )
	OR (@ProductVersionMajor >= 13 )
	AND 50000000 < (SELECT cntr_value 			
						FROM sys.dm_os_performance_counters 
						WHERE object_name LIKE '%:Memory Manager%'
						AND counter_name LIKE 'Target Server Memory (KB)%')
	BEGIN
		SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) ';
	END
ELSE
	BEGIN
		SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) ';
	END

/* Be good: */
SET @StringToExecute = @StringToExecute + N' ; ';


IF @Debug = 1
	BEGIN
		PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000))
		PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000))
	END

EXEC sp_executesql @StringToExecute,
	N'@CheckDateOverride DATETIMEOFFSET',
	@CheckDateOverride;

END
GO 
IF OBJECT_ID('dbo.CommandExecute') IS NULL
BEGIN
	PRINT 'sp_DatabaseRestore is about to install, but you have not yet installed the Ola Hallengren maintenance scripts.'
	PRINT 'sp_DatabaseRestore will still install, but to use that stored proc, you will need to install this:'
	PRINT 'https://ola.hallengren.com'
	PRINT 'You will see a bunch of warnings below because the Ola scripts are not installed yet, and that is okay:'
END
GO


IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL
	EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;');
GO
ALTER PROCEDURE [dbo].[sp_DatabaseRestore]
    @Database NVARCHAR(128) = NULL,
    @RestoreDatabaseName NVARCHAR(128) = NULL,
    @BackupPathFull NVARCHAR(260) = NULL,
    @BackupPathDiff NVARCHAR(260) = NULL,
    @BackupPathLog NVARCHAR(260) = NULL,
    @MoveFiles BIT = 1,
    @MoveDataDrive NVARCHAR(260) = NULL,
    @MoveLogDrive NVARCHAR(260) = NULL,
    @MoveFilestreamDrive NVARCHAR(260) = NULL,
    @MoveFullTextCatalogDrive NVARCHAR(260) = NULL,
    @BufferCount INT = NULL,
    @MaxTransferSize INT = NULL,
    @BlockSize INT = NULL,
    @TestRestore BIT = 0,
    @RunCheckDB BIT = 0,
    @RestoreDiff BIT = 0,
    @ContinueLogs BIT = 0,
    @StandbyMode BIT = 0,
    @StandbyUndoPath NVARCHAR(MAX) = NULL,
    @RunRecovery BIT = 0,
    @ForceSimpleRecovery BIT = 0,
    @ExistingDBAction TINYINT = 0,
    @StopAt NVARCHAR(14) = NULL,
    @OnlyLogsAfter NVARCHAR(14) = NULL,
    @SimpleFolderEnumeration BIT = 0,
    @SkipBackupsAlreadyInMsdb BIT = 0,
    @DatabaseOwner sysname = NULL,
    @SetTrustworthyON BIT = 0,
    @FixOrphanUsers BIT = 0,
    @KeepCdc BIT = 0,
    @Execute CHAR(1) = Y,
    @FileExtensionDiff NVARCHAR(128) = NULL,
    @Debug INT = 0,
    @Help BIT = 0,
    @Version     VARCHAR(30) = NULL OUTPUT,
    @VersionDate DATETIME = NULL OUTPUT,
    @VersionCheckMode BIT = 0,
    @FileNamePrefix NVARCHAR(260) = NULL,
    @RunStoredProcAfterRestore NVARCHAR(260) = NULL,
    @EnableBroker BIT = 0
AS
SET NOCOUNT ON;
SET STATISTICS XML OFF;

/*Versioning details*/

SELECT @Version = '8.29', @VersionDate = '20260203';

IF(@VersionCheckMode = 1)
BEGIN
	RETURN;
END;


IF @Help = 1
BEGIN
	PRINT '
	/*
		sp_DatabaseRestore from http://FirstResponderKit.org

		This script will restore a database from a given file path.

		To learn more, visit http://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.
			- Tastes awful with marmite.

		Unknown limitations of this version:
			- None.  (If we knew them, they would be known. Duh.)

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

		MIT License

		Copyright (c) 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.

	*/
	';

	PRINT '
	/*
	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'',
		@BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'',
		@ContinueLogs = 0,
		@RunRecovery = 0;

	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'',
		@BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'',
		@ContinueLogs = 1,
		@RunRecovery = 0;

	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'',
		@BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'',
		@ContinueLogs = 1,
		@RunRecovery = 1;

	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'',
		@BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'',
		@ContinueLogs = 0,
		@RunRecovery = 1;

	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'',
		@BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'',
		@BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'',
		@RestoreDiff = 1,
		@ContinueLogs = 0,
		@RunRecovery = 1;

	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'',
		@BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'',
		@BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'',
		@RestoreDiff = 1,
		@ContinueLogs = 0,
		@RunRecovery = 1,
		@TestRestore = 1,
		@RunCheckDB = 1,
		@Debug = 0;

	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'',
		@BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'',
		@StandbyMode = 1,
		@StandbyUndoPath = ''D:\Data\'',
		@ContinueLogs = 1,
		@RunRecovery = 0,
		@Debug = 0;

	--Restore just through the latest DIFF, ignoring logs, and using a custom ".dif" file extension
	EXEC dbo.sp_DatabaseRestore
		@Database = ''LogShipMe'',
		@BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'',
		@BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'',
		@RestoreDiff = 1,
		@FileExtensionDiff = ''dif'',
		@ContinueLogs = 0,
		@RunRecovery = 1;

	-- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths
	EXEC dbo.sp_DatabaseRestore
		@Database = ''DBA'',
		@BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'',
		@BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'',
		@StandbyMode = 0,
		@ContinueLogs = 1,
		@RunRecovery = 0,
		@Debug = 0;

	--This example will restore the latest differential backup, and stop transaction logs at the specified date time.  It will execute and print debug information.
	EXEC dbo.sp_DatabaseRestore
		@Database = ''DBA'',
		@BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'',
		@BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'',
		@BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'',
		@RestoreDiff = 1,
		@ContinueLogs = 0,
		@RunRecovery = 1,
		@StopAt = ''20170508201501'',
		@Debug = 1;

	--This example will NOT execute the restore.  Commands will be printed in a copy/paste ready format only
	EXEC dbo.sp_DatabaseRestore
		@Database = ''DBA'',
		@BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'',
		@BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'',
		@BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'',
		@RestoreDiff = 1,
		@ContinueLogs = 0,
		@RunRecovery = 1,
		@TestRestore = 1,
		@RunCheckDB = 1,
		@Debug = 0,
		@Execute = ''N'';
	';

    RETURN;
END;

-- Get the SQL Server version number because the columns returned by RESTORE commands vary by version
-- Based on: https://www.brentozar.com/archive/2015/05/sql-server-version-detection/
-- Need to capture BuildVersion because RESTORE HEADERONLY changed with 2014 CU1, not RTM
DECLARE @ProductVersion AS NVARCHAR(20) = CAST(SERVERPROPERTY ('productversion') AS NVARCHAR(20));
DECLARE @MajorVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 4) AS SMALLINT);
DECLARE @MinorVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 3) AS SMALLINT);
DECLARE @BuildVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 2) AS SMALLINT);

IF @MajorVersion < 10
BEGIN
    RAISERROR('Sorry, DatabaseRestore doesn''t work on versions of SQL prior to 2008.', 15, 1);
    RETURN;
END;

BEGIN TRY
DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME());
DECLARE @CommandExecuteCheck VARCHAR(400);

SET @CommandExecuteCheck = 'IF NOT EXISTS (SELECT name FROM ' +@CurrentDatabaseContext+'.sys.objects WHERE type = ''P'' AND name = ''CommandExecute'')
BEGIN
	RAISERROR (''DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?'', 15, 1);
	RETURN;
END;'
EXEC (@CommandExecuteCheck)
END TRY
BEGIN CATCH
THROW;
END CATCH

DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command
        @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands
        @LastFullBackup NVARCHAR(500) = N'', --Last full backup name
        @LastDiffBackup NVARCHAR(500) = N'', --Last diff backup name
        @LastDiffBackupDateTime NVARCHAR(500) = N'', --Last diff backup date
        @BackupFile NVARCHAR(500) = N'', --Name of backup file
        @BackupDateTime AS CHAR(15) = N'', --Used for comparisons to generate ordered backup files/create a stopat point
        @FullLastLSN NUMERIC(25, 0), --LSN for full
        @DiffLastLSN NUMERIC(25, 0), --LSN for diff
		@HeadersSQL AS NVARCHAR(4000) = N'', --Dynamic insert into #Headers table (deals with varying results from RESTORE FILELISTONLY across different versions)
		@MoveOption AS NVARCHAR(MAX) = N'', --If you need to move restored files to a different directory
		@LogRecoveryOption AS NVARCHAR(MAX) = N'', --Holds the option to cause logs to be restored in standby mode or with no recovery
		@DatabaseLastLSN NUMERIC(25, 0), --redo_start_lsn of the current database
		@i TINYINT = 1,  --Maintains loop to continue logs
		@LogRestoreRanking INT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped
		@LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers
		@LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers
		@LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored
		@FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters
		@BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount
        @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName
		@UnquotedRestoreDatabaseName NVARCHAR(128);   --Holds the unquoted @RestoreDatabaseName

DECLARE @FileListSimple TABLE (
    BackupFile NVARCHAR(255) NOT NULL,
    depth INT NOT NULL,
    [file] INT NOT NULL
);

DECLARE @FileList TABLE (
	BackupPath NVARCHAR(255) NULL,
    BackupFile NVARCHAR(255) NULL
);

DECLARE @PathItem TABLE (
	PathItem NVARCHAR(512)
);


IF OBJECT_ID(N'tempdb..#FileListParameters') IS NOT NULL DROP TABLE #FileListParameters;
CREATE TABLE #FileListParameters
(
    LogicalName NVARCHAR(128) NOT NULL,
    PhysicalName NVARCHAR(260) NOT NULL,
    [Type] CHAR(1) NOT NULL,
    FileGroupName NVARCHAR(120) NULL,
    Size NUMERIC(20, 0) NOT NULL,
    MaxSize NUMERIC(20, 0) NOT NULL,
    FileID BIGINT NULL,
    CreateLSN NUMERIC(25, 0) NULL,
    DropLSN NUMERIC(25, 0) NULL,
    UniqueID UNIQUEIDENTIFIER NULL,
    ReadOnlyLSN NUMERIC(25, 0) NULL,
    ReadWriteLSN NUMERIC(25, 0) NULL,
    BackupSizeInBytes BIGINT NULL,
    SourceBlockSize INT NULL,
    FileGroupID INT NULL,
    LogGroupGUID UNIQUEIDENTIFIER NULL,
    DifferentialBaseLSN NUMERIC(25, 0) NULL,
    DifferentialBaseGUID UNIQUEIDENTIFIER NULL,
    IsReadOnly BIT NULL,
    IsPresent BIT NULL,
    TDEThumbprint VARBINARY(32) NULL,
    SnapshotUrl NVARCHAR(360) NULL
);

IF OBJECT_ID(N'tempdb..#Headers') IS NOT NULL DROP TABLE #Headers;
CREATE TABLE #Headers
(
    BackupName NVARCHAR(256),
    BackupDescription NVARCHAR(256),
    BackupType NVARCHAR(256),
    ExpirationDate NVARCHAR(256),
    Compressed NVARCHAR(256),
    Position NVARCHAR(256),
    DeviceType NVARCHAR(256),
    UserName NVARCHAR(256),
    ServerName NVARCHAR(256),
    DatabaseName NVARCHAR(256),
    DatabaseVersion NVARCHAR(256),
    DatabaseCreationDate NVARCHAR(256),
    BackupSize NVARCHAR(256),
    FirstLSN NVARCHAR(256),
    LastLSN NVARCHAR(256),
    CheckpointLSN NVARCHAR(256),
    DatabaseBackupLSN NVARCHAR(256),
    BackupStartDate NVARCHAR(256),
    BackupFinishDate NVARCHAR(256),
    SortOrder NVARCHAR(256),
    [CodePage] NVARCHAR(256),
    UnicodeLocaleId NVARCHAR(256),
    UnicodeComparisonStyle NVARCHAR(256),
    CompatibilityLevel NVARCHAR(256),
    SoftwareVendorId NVARCHAR(256),
    SoftwareVersionMajor NVARCHAR(256),
    SoftwareVersionMinor NVARCHAR(256),
    SoftwareVersionBuild NVARCHAR(256),
    MachineName NVARCHAR(256),
    Flags NVARCHAR(256),
    BindingID NVARCHAR(256),
    RecoveryForkID NVARCHAR(256),
    Collation NVARCHAR(256),
    FamilyGUID NVARCHAR(256),
    HasBulkLoggedData NVARCHAR(256),
    IsSnapshot NVARCHAR(256),
    IsReadOnly NVARCHAR(256),
    IsSingleUser NVARCHAR(256),
    HasBackupChecksums NVARCHAR(256),
    IsDamaged NVARCHAR(256),
    BeginsLogChain NVARCHAR(256),
    HasIncompleteMetaData NVARCHAR(256),
    IsForceOffline NVARCHAR(256),
    IsCopyOnly NVARCHAR(256),
    FirstRecoveryForkID NVARCHAR(256),
    ForkPointLSN NVARCHAR(256),
    RecoveryModel NVARCHAR(256),
    DifferentialBaseLSN NVARCHAR(256),
    DifferentialBaseGUID NVARCHAR(256),
    BackupTypeDescription NVARCHAR(256),
    BackupSetGUID NVARCHAR(256),
    CompressedBackupSize NVARCHAR(256),
    Containment NVARCHAR(256),
    KeyAlgorithm NVARCHAR(32),
    EncryptorThumbprint VARBINARY(20),
    EncryptorType NVARCHAR(32),
	LastValidRestoreTime DATETIME,
	TimeZone NVARCHAR(32),
	CompressionAlgorithm NVARCHAR(32),
    --
    -- Seq added to retain order by
    --
    Seq INT NOT NULL IDENTITY(1, 1)
);

/*
Correct paths in case people forget a final "\" or "/"
*/
/*Full*/
IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "/"', 0, 1) WITH NOWAIT;
	SET @BackupPathFull += N'/';
END;
ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT;
	SET @BackupPathFull += N'\';
END;
/*Diff*/
IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "/"', 0, 1) WITH NOWAIT;
	SET @BackupPathDiff += N'/';
END;
ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT;
	SET @BackupPathDiff += N'\';
END;
/*Log*/
IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "/"', 0, 1) WITH NOWAIT;
	SET @BackupPathLog += N'/';
END;
IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT;
	SET @BackupPathLog += N'\';
END;
/*Move Data File*/
IF NULLIF(@MoveDataDrive, '') IS NULL
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default data drive for @MoveDataDrive', 0, 1) WITH NOWAIT;
	SET @MoveDataDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260));
END;
IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "/"', 0, 1) WITH NOWAIT;
	SET @MoveDataDrive += N'/';
END;
ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT;
	SET @MoveDataDrive += N'\';
END;
/*Move Log File*/
IF NULLIF(@MoveLogDrive, '') IS NULL
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default log drive for @MoveLogDrive', 0, 1) WITH NOWAIT;
	SET @MoveLogDrive  = CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260));
END;
IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing@MoveLogDrive to add a "/"', 0, 1) WITH NOWAIT;
	SET @MoveLogDrive += N'/';
END;
ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT;
	SET @MoveLogDrive += N'\';
END;
/*Move Filestream File*/
IF NULLIF(@MoveFilestreamDrive, '') IS NULL
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFilestreamDrive', 0, 1) WITH NOWAIT;
	SET @MoveFilestreamDrive  = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260));
END;
IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT;
	SET @MoveFilestreamDrive += N'/';
END;
ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT;
	SET @MoveFilestreamDrive += N'\';
END;
/*Move FullText Catalog File*/
IF NULLIF(@MoveFullTextCatalogDrive, '') IS NULL
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFullTextCatalogDrive', 0, 1) WITH NOWAIT;
	SET @MoveFullTextCatalogDrive  = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260));
END;
IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "/"', 0, 1) WITH NOWAIT;
	SET @MoveFullTextCatalogDrive += N'/';
END;
IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT;
	SET @MoveFullTextCatalogDrive += N'\';
END;
/*Standby Undo File*/
IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "/"', 0, 1) WITH NOWAIT;
	SET @StandbyUndoPath += N'/';
END;
IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' --Has to end in a '\'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT;
	SET @StandbyUndoPath += N'\';
END;

IF @RestoreDatabaseName IS NULL OR @RestoreDatabaseName LIKE N'' /*use LIKE instead of =, otherwise N'' = N' '. See: https://www.brentozar.com/archive/2017/04/surprising-behavior-trailing-spaces/ */
BEGIN
	SET @RestoreDatabaseName = @Database;
END;

/*check input parameters*/
IF NOT @MaxTransferSize IS NULL
BEGIN
	IF @MaxTransferSize > 4194304
	BEGIN
		RAISERROR('@MaxTransferSize can not be greater then 4194304', 0, 1) WITH NOWAIT;
	END

	IF @MaxTransferSize % 64 <> 0
	BEGIN
		RAISERROR('@MaxTransferSize has to be a multiple of 65536', 0, 1) WITH NOWAIT;
	END
END;

IF NOT @BlockSize IS NULL
BEGIN
	IF @BlockSize NOT IN (512, 1024, 2048, 4096, 8192, 16384, 32768, 65536)
	BEGIN
		RAISERROR('Supported values for @BlockSize are 512, 1024, 2048, 4096, 8192, 16384, 32768, and 65536', 0, 1) WITH NOWAIT;
	END
END

--File Extension cleanup
IF @FileExtensionDiff LIKE '%.%'
BEGIN
	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Removing "." from @FileExtensionDiff', 0, 1) WITH NOWAIT;
	SET @FileExtensionDiff = REPLACE(@FileExtensionDiff,'.','');
END

SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName);
SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName);
SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1);

--If xp_cmdshell is disabled, force use of xp_dirtree
IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1)
    SET @SimpleFolderEnumeration = 1;

SET @HeadersSQL =
N'INSERT INTO #Headers WITH (TABLOCK)
  (BackupName, BackupDescription, BackupType, ExpirationDate, Compressed, Position, DeviceType, UserName, ServerName
  ,DatabaseName, DatabaseVersion, DatabaseCreationDate, BackupSize, FirstLSN, LastLSN, CheckpointLSN, DatabaseBackupLSN
  ,BackupStartDate, BackupFinishDate, SortOrder, CodePage, UnicodeLocaleId, UnicodeComparisonStyle, CompatibilityLevel
  ,SoftwareVendorId, SoftwareVersionMajor, SoftwareVersionMinor, SoftwareVersionBuild, MachineName, Flags, BindingID
  ,RecoveryForkID, Collation, FamilyGUID, HasBulkLoggedData, IsSnapshot, IsReadOnly, IsSingleUser, HasBackupChecksums
  ,IsDamaged, BeginsLogChain, HasIncompleteMetaData, IsForceOffline, IsCopyOnly, FirstRecoveryForkID, ForkPointLSN
  ,RecoveryModel, DifferentialBaseLSN, DifferentialBaseGUID, BackupTypeDescription, BackupSetGUID, CompressedBackupSize';

IF @MajorVersion >= 11
  SET @HeadersSQL += NCHAR(13) + NCHAR(10) + N', Containment';

IF @MajorVersion >= 13 OR (@MajorVersion = 12 AND @BuildVersion >= 2342)
  SET @HeadersSQL += N', KeyAlgorithm, EncryptorThumbprint, EncryptorType';

IF @MajorVersion >= 16
  SET @HeadersSQL += N', LastValidRestoreTime, TimeZone, CompressionAlgorithm';

SET @HeadersSQL += N')' + NCHAR(13) + NCHAR(10);
SET @HeadersSQL += N'EXEC (''RESTORE HEADERONLY FROM DISK=''''{Path}'''''')';

IF @BackupPathFull IS NOT NULL
BEGIN
	DECLARE @CurrentBackupPathFull NVARCHAR(255);

	 -- Split CSV string logic has taken from Ola Hallengren's :)
	WITH BackupPaths (
	StartPosition, EndPosition, PathItem
	)
	AS (
		   SELECT 1																															   AS StartPosition,
				  ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, 1 ), 0 ), LEN( @BackupPathFull ) + 1 )									   AS EndPosition,
				  SUBSTRING( @BackupPathFull, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) - 1 ) AS PathItem
		   WHERE @BackupPathFull IS NOT NULL
		   UNION ALL
		   SELECT CAST( EndPosition AS INT ) + 1																																		 AS StartPosition,
				  ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, EndPosition + 1 ), 0 ), LEN( @BackupPathFull ) + 1 )																	 AS EndPosition,
				  SUBSTRING( @BackupPathFull, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, EndPosition + 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) - EndPosition - 1 ) AS PathItem
		   FROM BackupPaths
		   WHERE EndPosition < LEN( @BackupPathFull ) + 1
	 )
	INSERT INTO @PathItem
	SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths;

	WHILE 1 = 1
	BEGIN

		SELECT TOP 1 @CurrentBackupPathFull = PathItem FROM @PathItem
		WHERE PathItem > COALESCE( @CurrentBackupPathFull, '' ) ORDER BY PathItem;
		IF @@rowcount = 0 BREAK;

		IF @SimpleFolderEnumeration = 1
		BEGIN    -- Get list of files
			INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathFull, 1, 1;
			INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathFull, BackupFile FROM @FileListSimple;
			DELETE FROM @FileListSimple;
		END
		ELSE
		BEGIN
			SET @cmd = N'DIR /b "' + @CurrentBackupPathFull + N'"';
			IF @Debug = 1
			BEGIN
				IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathFull';
				PRINT @cmd;
			END;
			INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd;
			UPDATE @FileList SET BackupPath = @CurrentBackupPathFull
			WHERE BackupPath IS NULL;
		END;

		IF @Debug = 1
		BEGIN
			SELECT BackupPath, BackupFile FROM @FileList;
		END;
		IF @SimpleFolderEnumeration = 1
		BEGIN
			/*Check what we can*/
			IF NOT EXISTS (SELECT * FROM @FileList)
			BEGIN
				RAISERROR('(FULL) No rows were returned for that database in path %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT;
				RETURN;
			END;
		END
		ELSE
		BEGIN
			/*Full Sanity check folders*/
			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile = 'The system cannot find the path specified.'
				OR fl.BackupFile = 'File Not Found'
				) = 1
			BEGIN
				RAISERROR('(FULL) No rows or bad value for path %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT;
				RETURN;
			END;
			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile = 'Access is denied.'
				) = 1
			BEGIN
				RAISERROR('(FULL) Access is denied to %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT;
				RETURN;
			END;
			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				) = 1
				AND
				(
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile IS NULL
				) = 1
				BEGIN
					RAISERROR('(FULL) Empty directory %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT;
					RETURN;
				END
			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile = 'The user name or password is incorrect.'
				) = 1
				BEGIN
					RAISERROR('(FULL) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT;
					RETURN;
				END;
		END;
	END
	/*End folder sanity check*/

	IF @StopAt IS NOT NULL
		BEGIN
			DELETE
			FROM @FileList
			WHERE BackupFile LIKE N'%[_][0-9].bak'
			AND	BackupFile LIKE N'%' + @Database + N'%'
			AND	(REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt);

			DELETE
			FROM @FileList
			WHERE BackupFile LIKE N'%[_][0-9][0-9].bak'
			AND	BackupFile LIKE N'%' + @Database + N'%'
			AND	(REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 18 ), '_', '' ) > @StopAt);
		END;

    -- Find latest full backup
    -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command
    SELECT TOP 1 @LastFullBackup = BackupFile, @CurrentBackupPathFull = BackupPath
    FROM @FileList
    WHERE BackupFile LIKE N'%.bak'
        AND
        BackupFile LIKE N'%' + @Database + N'%'
	    AND
	    (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt)
    ORDER BY BackupFile DESC;

    /*	To get all backups that belong to the same set we can do two things:
		    1.	RESTORE HEADERONLY of ALL backup files in the folder and look for BackupSetGUID.
			    Backups that belong to the same split will have the same BackupSetGUID.
		    2.	Olla Hallengren's solution appends file index at the end of the name:
			    SQLSERVER1_TEST_DB_FULL_20180703_213211_1.bak
			    SQLSERVER1_TEST_DB_FULL_20180703_213211_2.bak
			    SQLSERVER1_TEST_DB_FULL_20180703_213211_N.bak
			    We can and find all related files with the same timestamp but different index.
			    This option is simpler and requires less changes to this procedure */

    IF @LastFullBackup IS NULL
    BEGIN
        RAISERROR('No backups for "%s" found in "%s"', 16, 1, @Database, @BackupPathFull) WITH NOWAIT;
        RETURN;
    END;

	SELECT BackupPath, BackupFile INTO #SplitFullBackups
	FROM @FileList
	WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastFullBackup, LEN( @LastFullBackup ) - PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) )
	AND PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Ola only supports up to 64 file split.
	ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC;

    -- File list can be obtained by running RESTORE FILELISTONLY of any file from the given BackupSet therefore we do not have to cater for split backups when building @FileListParamSQL

    SET @FileListParamSQL =
      N'INSERT INTO #FileListParameters WITH (TABLOCK)
       (LogicalName, PhysicalName, Type, FileGroupName, Size, MaxSize, FileID, CreateLSN, DropLSN
       ,UniqueID, ReadOnlyLSN, ReadWriteLSN, BackupSizeInBytes, SourceBlockSize, FileGroupID, LogGroupGUID
       ,DifferentialBaseLSN, DifferentialBaseGUID, IsReadOnly, IsPresent, TDEThumbprint';

    IF @MajorVersion >= 13
    BEGIN
	    SET @FileListParamSQL += N', SnapshotUrl';
    END;

    SET @FileListParamSQL += N')' + NCHAR(13) + NCHAR(10);
    SET @FileListParamSQL += N'EXEC (''RESTORE FILELISTONLY FROM DISK=''''{Path}'''''')';

    SET @sql = REPLACE(@FileListParamSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup);

    IF @Debug = 1
    BEGIN
	    IF @sql IS NULL PRINT '@sql is NULL for INSERT to #FileListParameters: @BackupPathFull + @LastFullBackup';
	    PRINT @sql;
    END;

    EXEC (@sql);
    IF @Debug = 1
    BEGIN
	    SELECT '#FileListParameters' AS table_name, * FROM #FileListParameters;
	    SELECT '@FileList' AS table_name, BackupPath, BackupFile FROM @FileList WHERE BackupFile IS NOT NULL;
    END

    --get the backup completed data so we can apply tlogs from that point forwards
    SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup);

    IF @Debug = 1
    BEGIN
	    IF @sql IS NULL PRINT '@sql is NULL for get backup completed data: @BackupPathFull, @LastFullBackup';
	    PRINT @sql;
    END;
    EXECUTE (@sql);
    IF @Debug = 1
    BEGIN
	    SELECT '#Headers' AS table_name, @LastFullBackup AS FullBackupFile, * FROM #Headers
    END;

    --Ensure we are looking at the expected backup, but only if we expect to restore a FULL backups
    IF NOT EXISTS (SELECT * FROM #Headers h WHERE h.DatabaseName = @Database)
    BEGIN
        RAISERROR('Backupfile "%s" does not match @Database parameter "%s"', 16, 1, @LastFullBackup, @Database) WITH NOWAIT;
        RETURN;
    END;

	IF NOT @BufferCount IS NULL
	BEGIN
		SET @BackupParameters += N', BufferCount=' + cast(@BufferCount as NVARCHAR(10))
	END

	IF NOT @MaxTransferSize IS NULL
	BEGIN
		SET @BackupParameters += N', MaxTransferSize=' + cast(@MaxTransferSize as NVARCHAR(7))
	END

	IF NOT @BlockSize IS NULL
	BEGIN
		SET @BackupParameters += N', BlockSize=' + cast(@BlockSize as NVARCHAR(5))
	END

    IF @MoveFiles = 1
    BEGIN
	    IF @Execute = 'Y' RAISERROR('@MoveFiles = 1, adjusting paths', 0, 1) WITH NOWAIT;

	    WITH Files
	    AS (
		    SELECT
			    CASE
				    WHEN Type = 'D' THEN @MoveDataDrive
				    WHEN Type = 'L' THEN @MoveLogDrive
				    WHEN Type = 'S' THEN @MoveFilestreamDrive
					WHEN Type = 'F' THEN @MoveFullTextCatalogDrive
			    END + COALESCE(@FileNamePrefix, '') + CASE
                        WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1))
					    ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2))
					    END AS TargetPhysicalName,
                    PhysicalName,
                    LogicalName
		    FROM #FileListParameters)
	    SELECT @MoveOption = @MoveOption + N', MOVE ''' + Files.LogicalName + N''' TO ''' + Files.TargetPhysicalName + ''''
	    FROM Files
        WHERE Files.TargetPhysicalName <> Files.PhysicalName;

	    IF @Debug = 1 PRINT @MoveOption
    END;

    /*Process @ExistingDBAction flag */
    IF @ExistingDBAction BETWEEN 1 AND 4
    BEGIN
        IF @RestoreDatabaseID IS NOT NULL
        BEGIN
            IF @ExistingDBAction = 1
            BEGIN
                RAISERROR('Setting single user', 0, 1) WITH NOWAIT;
                SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ' + NCHAR(13);
                IF @Debug = 1 OR @Execute = 'N'
		        BEGIN
			        IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER';
			        PRINT @sql;
		        END;
		        IF @Debug IN (0, 1) AND @Execute = 'Y'
				BEGIN
					IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING'
					BEGIN
						EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
					END
					ELSE IF @Debug = 1
					BEGIN
						IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skipping setting database to SINGLE_USER';
						ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database to SINGLE_USER';
			        END
		        END
            END
            IF @ExistingDBAction IN (2, 3)
            BEGIN
                RAISERROR('Killing connections', 0, 1) WITH NOWAIT;
                SET @sql = N'/* Kill connections */' + NCHAR(13);
                SELECT
                    @sql = @sql + N'KILL ' + CAST(spid as nvarchar(5)) + N';' + NCHAR(13)
                FROM
                    --database_ID was only added to sys.dm_exec_sessions in SQL Server 2012 but we need to support older
                    sys.sysprocesses
                WHERE
                    dbid = @RestoreDatabaseID;
                IF @Debug = 1 OR @Execute = 'N'
		        BEGIN
			        IF @sql IS NULL PRINT '@sql is NULL for Kill connections';
			        PRINT @sql;
		        END;
                IF @Debug IN (0, 1) AND @Execute = 'Y'
			       EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
            END
            IF @ExistingDBAction = 3
            BEGIN
                RAISERROR('Dropping database', 0, 1) WITH NOWAIT;

                SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13);
                IF @Debug = 1 OR @Execute = 'N'
		        BEGIN
			        IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE';
			        PRINT @sql;
		        END;
		        IF @Debug IN (0, 1) AND @Execute = 'Y'
			        EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
            END
			IF @ExistingDBAction = 4
			BEGIN
				RAISERROR ('Offlining database', 0, 1) WITH NOWAIT;

				SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + SPACE( 1 ) + 'SET OFFLINE WITH ROLLBACK IMMEDIATE';
				IF @Debug = 1 OR @Execute = 'N'
				BEGIN
					IF @sql IS NULL PRINT '@sql is NULL for Offline database';
					PRINT @sql;
				END;
		        IF @Debug IN (0, 1) AND @Execute = 'Y'
				BEGIN
					IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING'
					BEGIN
						EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
					END
					ELSE IF @Debug = 1
					BEGIN
						IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skipping setting database OFFLINE';
						ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database OFFLINE';
			        END
		        END
			END;
        END
        ELSE
            RAISERROR('@ExistingDBAction > 0, but no existing @RestoreDatabaseName', 0, 1) WITH NOWAIT;
    END
    ELSE
        IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@ExistingDBAction %u so do nothing', 0, 1, @ExistingDBAction) WITH NOWAIT;

    IF @ContinueLogs = 0
    BEGIN
	    IF @Execute = 'Y' RAISERROR('@ContinueLogs set to 0', 0, 1) WITH NOWAIT;

	    /* now take split backups into account */
	    IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0
        BEGIN
            IF @Debug = 1 RAISERROR('Split backups found', 0, 1) WITH NOWAIT;

			SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM '
                       + STUFF(
                             (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + ''''
							  FROM #SplitFullBackups
							  ORDER BY BackupFile
							  FOR XML PATH ('')),
                             1,
                             2,
                             '') + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
        END;
	    ELSE
		BEGIN
			SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
		END
	    IF (@StandbyMode = 1)
	    BEGIN
		    IF (@StandbyUndoPath IS NULL)
			BEGIN
			    IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT;
			END
	        ELSE IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0
			BEGIN
				SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
			END
			ELSE
	        BEGIN
		        SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH  REPLACE' + @BackupParameters + @MoveOption + N' , STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
	        END
        END;
		IF @Debug = 1 OR @Execute = 'N'
		BEGIN
			IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathFull, @LastFullBackup, @MoveOption';
			PRINT @sql;
		END;

		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';

		-- We already loaded #Headers above

	    --setting the @BackupDateTime to a numeric string so that it can be used in comparisons
		SET @BackupDateTime = REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' );

		SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1;
		IF @Debug = 1
		BEGIN
			IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastFullBackup';
			PRINT @BackupDateTime;
		END;

	END;
    ELSE
	BEGIN

		SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0))
		FROM master.sys.databases d
		JOIN master.sys.master_files f ON d.database_id = f.database_id
		WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1;

	END;
END;

IF @BackupPathFull IS NULL AND @ContinueLogs = 1
BEGIN

	SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0))
	FROM master.sys.databases d
	JOIN master.sys.master_files f ON d.database_id = f.database_id
	WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1;

END;

IF @BackupPathDiff IS NOT NULL
BEGIN
    DELETE FROM @FileList;
    DELETE FROM @FileListSimple;
	DELETE FROM @PathItem;

	DECLARE @CurrentBackupPathDiff NVARCHAR(512);
	-- Split CSV string logic has taken from Ola Hallengren's :)
	WITH BackupPaths (
	StartPosition, EndPosition, PathItem
	)
	AS (
		   SELECT 1																															   AS StartPosition,
				  ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, 1 ), 0 ), LEN( @BackupPathDiff ) + 1 )									   AS EndPosition,
				  SUBSTRING( @BackupPathDiff, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) - 1 ) AS PathItem
		   WHERE @BackupPathDiff IS NOT NULL
		   UNION ALL
		   SELECT CAST( EndPosition AS INT ) + 1																																		 AS StartPosition,
				  ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, EndPosition + 1 ), 0 ), LEN( @BackupPathDiff ) + 1 )																	 AS EndPosition,
				  SUBSTRING( @BackupPathDiff, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, EndPosition + 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) - EndPosition - 1 ) AS PathItem
		   FROM BackupPaths
		   WHERE EndPosition < LEN( @BackupPathDiff ) + 1
	 )
	INSERT INTO @PathItem
	SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths;

	WHILE 1 = 1
	BEGIN

		SELECT TOP 1 @CurrentBackupPathDiff = PathItem FROM @PathItem
		WHERE PathItem > COALESCE( @CurrentBackupPathDiff, '' ) ORDER BY PathItem;
		IF @@rowcount = 0 BREAK;

		IF @SimpleFolderEnumeration = 1
		BEGIN    -- Get list of files
			INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathDiff, 1, 1;
			INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathDiff, BackupFile FROM @FileListSimple;
			DELETE FROM @FileListSimple;
		END
		ELSE
		BEGIN
			SET @cmd = N'DIR /b "' + @CurrentBackupPathDiff + N'"';
			IF @Debug = 1
			BEGIN
				IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathDiff';
				PRINT @cmd;
			END;
			INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd;
			UPDATE @FileList SET BackupPath = @CurrentBackupPathDiff WHERE BackupPath IS NULL;
		END;

		IF @Debug = 1
		BEGIN
			SELECT BackupPath,BackupFile FROM @FileList WHERE BackupFile IS NOT NULL;
		END;
		IF @SimpleFolderEnumeration = 0
    BEGIN
        /*Full Sanity check folders*/
        IF (
		    SELECT COUNT(*)
		    FROM @FileList AS fl
		    WHERE fl.BackupFile = 'The system cannot find the path specified.'
		    ) = 1
	    BEGIN
		    RAISERROR('(DIFF) Bad value for path %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT;
            RETURN;
	    END;
	    IF (
		    SELECT COUNT(*)
		    FROM @FileList AS fl
		    WHERE fl.BackupFile = 'Access is denied.'
		    ) = 1
	    BEGIN
		    RAISERROR('(DIFF) Access is denied to %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT;
            RETURN;
	    END;
	    IF (
		    SELECT COUNT(*)
		    FROM @FileList AS fl
		    WHERE fl.BackupFile = 'The user name or password is incorrect.'
		    ) = 1
		    BEGIN
			    RAISERROR('(DIFF) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT;
                RETURN;
		    END;
    END;
	END
    /*End folder sanity check*/
    -- Find latest diff backup
	IF @FileExtensionDiff IS NULL
	BEGIN
		IF @Execute = 'Y' OR @Debug = 1 RAISERROR('No @FileExtensionDiff given, assuming "bak".', 0, 1) WITH NOWAIT;
		SET @FileExtensionDiff = 'bak';
	END

	SELECT TOP 1 @LastDiffBackup = BackupFile, @CurrentBackupPathDiff = BackupPath
    FROM @FileList
    WHERE BackupFile LIKE N'%.' + @FileExtensionDiff
        AND
        BackupFile LIKE N'%' + @Database + '%'
	    AND
	    (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt)
	ORDER BY BackupFile DESC;

	 -- Load FileList data into Temp Table sorted by DateTime Stamp desc
	 SELECT BackupPath, BackupFile INTO #SplitDiffBackups
	 FROM @FileList
	 WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastDiffBackup, LEN( @LastDiffBackup ) - PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) )
	 AND PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Olla only supports up to 64 file split.
	 ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC;

    --No file = no backup to restore
	SET @LastDiffBackupDateTime = REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' );

    IF @RestoreDiff = 1 AND @BackupDateTime < @LastDiffBackupDateTime
	BEGIN

	    IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0
		BEGIN
			IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT;
			SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM '
									   + STUFF(
											 (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + ''''
											 FROM #SplitDiffBackups
											 ORDER BY BackupFile
											 FOR XML PATH ('')),
									   1,
									   2,
									   '' ) + N' WITH NORECOVERY, REPLACE' +  @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
		END;
		ELSE
			SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathDiff + @LastDiffBackup + N''' WITH NORECOVERY' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);

	    IF (@StandbyMode = 1)
		BEGIN
		    IF (@StandbyUndoPath IS NULL)
			    BEGIN
				    IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT;
			    END
		    ELSE IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0
				SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
			ELSE
			    SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @BackupPathDiff + @LastDiffBackup + N''' WITH STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
	    END;
		IF @Debug = 1 OR @Execute = 'N'
		BEGIN
			IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathDiff, @LastDiffBackup';
			PRINT @sql;
		END;
		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';

		--get the backup completed data so we can apply tlogs from that point forwards
		SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup);

		IF @Debug = 1
		BEGIN
			IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @CurrentBackupPathDiff, @LastDiffBackup';
			PRINT @sql;
		END;

		EXECUTE (@sql);
		IF @Debug = 1
		BEGIN
			SELECT '#Headers' AS table_name, @LastDiffBackup AS DiffbackupFile, * FROM #Headers AS h WHERE h.BackupType = 5;
		END

		--set the @BackupDateTime to the date time on the most recent differential
		SET @BackupDateTime = ISNULL( @LastDiffBackupDateTime, @BackupDateTime );
		IF @Debug = 1
		BEGIN
			IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastDiffBackupDateTime';
			PRINT @BackupDateTime;
		END;
		SELECT @DiffLastLSN = CAST(LastLSN AS NUMERIC(25, 0))
		FROM #Headers
		WHERE BackupType = 5;
	END;

	IF @DiffLastLSN IS NULL
	BEGIN
		SET @DiffLastLSN=@FullLastLSN
	END
END


IF @BackupPathLog IS NOT NULL
BEGIN
    DELETE FROM @FileList;
    DELETE FROM @FileListSimple;
	DELETE FROM @PathItem;

	DECLARE @CurrentBackupPathLog NVARCHAR(512);
	-- Split CSV string logic has taken from Ola Hallengren's :)
	WITH BackupPaths (
	StartPosition, EndPosition, PathItem
	)
	AS (
		   SELECT 1																															 AS StartPosition,
				  ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, 1 ), 0 ), LEN( @BackupPathLog ) + 1 )									     AS EndPosition,
				  SUBSTRING( @BackupPathLog, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) - 1 )  AS PathItem
		   WHERE @BackupPathLog IS NOT NULL
		   UNION ALL
		   SELECT CAST( EndPosition AS INT ) + 1																																		 AS StartPosition,
				  ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, EndPosition + 1 ), 0 ), LEN( @BackupPathLog ) + 1 )																	 AS EndPosition,
				  SUBSTRING( @BackupPathLog, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, EndPosition + 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) - EndPosition - 1 )    AS PathItem
		   FROM BackupPaths
		   WHERE EndPosition < LEN( @BackupPathLog ) + 1
	 )
	INSERT INTO @PathItem
	SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths;

	WHILE 1 = 1
	BEGIN
		SELECT TOP 1 @CurrentBackupPathLog = PathItem FROM @PathItem
		WHERE PathItem > COALESCE( @CurrentBackupPathLog, '' ) ORDER BY PathItem;
		IF @@rowcount = 0 BREAK;

		IF @SimpleFolderEnumeration = 1
		BEGIN    -- Get list of files
			INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @BackupPathLog, 1, 1;
			INSERT @FileList (BackupPath, BackupFile) SELECT @CurrentBackupPathLog, BackupFile FROM @FileListSimple;
			DELETE FROM @FileListSimple;
		END
		ELSE
		BEGIN
			SET @cmd = N'DIR /b "' + @CurrentBackupPathLog + N'"';
			IF @Debug = 1
			BEGIN
				IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathLog';
				PRINT @cmd;
			END;
			INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd;
			UPDATE @FileList SET BackupPath = @CurrentBackupPathLog
			WHERE BackupPath IS NULL;
		END;

		IF @SimpleFolderEnumeration = 1
		BEGIN
			/*Check what we can*/
			IF NOT EXISTS (SELECT * FROM @FileList)
			BEGIN
				RAISERROR('(LOG) No rows were returned for that database %s in path %s', 16, 1, @Database, @CurrentBackupPathLog) WITH NOWAIT;
				RETURN;
			END;
		END
		ELSE
		BEGIN
			/*Full Sanity check folders*/
			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile = 'The system cannot find the path specified.'
				OR fl.BackupFile = 'File Not Found'
				) = 1
			BEGIN
				RAISERROR('(LOG) No rows or bad value for path %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT;
				RETURN;
			END;

			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile = 'Access is denied.'
				) = 1
			BEGIN
				RAISERROR('(LOG) Access is denied to %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT;
				RETURN;
			END;

			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				) = 1
				AND
				(
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile IS NULL
				) = 1
				BEGIN
					RAISERROR('(LOG) Empty directory %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT;
					RETURN;
				END

			IF (
				SELECT COUNT(*)
				FROM @FileList AS fl
				WHERE fl.BackupFile = 'The user name or password is incorrect.'
				) = 1
				BEGIN
					RAISERROR('(LOG) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT;
					RETURN;
				END;
		END;
	END
    /*End folder sanity check*/

IF @Debug = 1
BEGIN
	SELECT * FROM @FileList WHERE BackupFile IS NOT NULL;
END

IF @SkipBackupsAlreadyInMsdb = 1
BEGIN

	SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name
		FROM msdb.dbo.backupmediafamily bf
		INNER JOIN msdb.dbo.backupset bs ON bs.media_set_id = bf.media_set_id
		INNER JOIN msdb.dbo.restorehistory rh ON rh.backup_set_id = bs.backup_set_id
		WHERE physical_device_name like @BackupPathLog + '%'
		AND rh.destination_database_name = @UnquotedRestoreDatabaseName
		ORDER BY physical_device_name DESC

	IF @Debug = 1
	BEGIN
		SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS
	END

	DELETE fl
	FROM @FileList AS fl
	WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS
END



IF (@OnlyLogsAfter IS NOT NULL)
BEGIN

	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT;

    DELETE fl
	FROM @FileList AS fl
	WHERE BackupFile LIKE N'%.trn'
	AND BackupFile LIKE N'%' + @Database + N'%'
	AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) < @OnlyLogsAfter;

END

-- Check for log backups
IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '')
	BEGIN
		DELETE FROM @FileList
		WHERE BackupFile LIKE N'%.trn'
		AND BackupFile LIKE N'%' + @Database + N'%'
		AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime));
	END;


IF (@StandbyMode = 1)
	BEGIN
		IF (@StandbyUndoPath IS NULL)
			BEGIN
				IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. Logs will not be restored in standby mode.', 0, 1) WITH NOWAIT;
			END;
		ELSE
			SET @LogRecoveryOption = N'STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''';
	END;

IF (@LogRecoveryOption = N'')
	BEGIN
		SET @LogRecoveryOption = N'NORECOVERY';
	END;

IF (@StopAt IS NOT NULL)
BEGIN

	IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@StopAt is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT;

	IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0
	BEGIN
		RAISERROR('@StopAt parameter is incorrect. It should contain exactly 14 digits in the format yyyyMMddhhmmss.', 16, 1) WITH NOWAIT;
		RETURN
	END

	IF ISDATE(STUFF(STUFF(STUFF(@StopAt, 13, 0, ':'), 11, 0, ':'), 9, 0, ' ')) = 0
	BEGIN
		RAISERROR('@StopAt is not a valid datetime.', 16, 1) WITH NOWAIT;
		RETURN
	END

	-- Add the STOPAT parameter to the log recovery options but change the value to a valid DATETIME, e.g. '20211118040230' -> '20211118 04:02:30'
	SET @LogRecoveryOption += ', STOPAT = ''' + STUFF(STUFF(STUFF(@StopAt, 13, 0, ':'), 11, 0, ':'), 9, 0, ' ') + ''''

	IF @BackupDateTime = @StopAt
	BEGIN
		IF @Debug = 1
		BEGIN
			RAISERROR('@StopAt is the end time of a FULL backup, no log files will be restored.', 0, 1) WITH NOWAIT;
		END
	END
	ELSE
	BEGIN
		DECLARE @ExtraLogFile NVARCHAR(255)
		SELECT TOP 1 @ExtraLogFile = fl.BackupFile
		FROM @FileList AS fl
		WHERE BackupFile LIKE N'%.trn'
		AND BackupFile LIKE N'%' + @Database + N'%'
		AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt
		ORDER BY BackupFile;
	END

	IF @ExtraLogFile IS NULL
	BEGIN
		DELETE fl
		FROM @FileList AS fl
		WHERE BackupFile LIKE N'%.trn'
		AND BackupFile LIKE N'%' + @Database + N'%'
		AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt;
	END
	ELSE
	BEGIN
		-- If this is a split backup, @ExtraLogFile contains only the first split backup file, either _1.trn or _01.trn
		-- Change @ExtraLogFile to the max split backup file, then delete all log files greater than this
		SET @ExtraLogFile = REPLACE(REPLACE(@ExtraLogFile, '_1.trn', '_9.trn'), '_01.trn', '_64.trn')

		DELETE fl
		FROM @FileList AS fl
		WHERE BackupFile LIKE N'%.trn'
		AND BackupFile LIKE N'%' + @Database + N'%'
		AND fl.BackupFile >	@ExtraLogFile
	END
END


 -- Group Ordering based on Backup File Name excluding Index {#} to construct coma separated string in "Restore Log" Command
SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' )) AS DenseRank INTO #SplitLogBackups
FROM @FileList
WHERE BackupFile IS NOT NULL;

-- Loop through all the files for the database
	WHILE 1 = 1
		BEGIN

			-- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement
			SELECT TOP 1 @CurrentBackupPathLog = BackupPath, @BackupFile = BackupFile FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking;
			IF @@rowcount = 0 BREAK;

			IF @i = 1

			BEGIN
		    SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathLog + @BackupFile);

				IF @Debug = 1
				BEGIN
					IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @HeadersSQL, @CurrentBackupPathLog, @BackupFile';
					PRINT @sql;
				END;

			EXECUTE (@sql);

			SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)),
				   @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0))
			FROM #Headers
			WHERE BackupType = 2;

			IF (@ContinueLogs = 0 AND @LogFirstLSN <= @FullLastLSN AND @FullLastLSN <= @LogLastLSN AND @RestoreDiff = 0) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 0)
				SET @i = 2;

			IF (@ContinueLogs = 0 AND @LogFirstLSN <= @DiffLastLSN AND @DiffLastLSN <= @LogLastLSN AND @RestoreDiff = 1) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 1)
				SET @i = 2;

			DELETE FROM #Headers WHERE BackupType = 2;


			END;

			IF @i = 1
				BEGIN
					IF @Debug = 1 RAISERROR('No Log to Restore', 0, 1) WITH NOWAIT;
				END

			IF @i = 2
			BEGIN
				IF @Execute = 'Y' RAISERROR('@i set to 2, restoring logs', 0, 1) WITH NOWAIT;

				IF (SELECT COUNT( * ) FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking) > 1
				BEGIN
					IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT;
					SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM '
							   + STUFF(
									(SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + ''''
									 FROM #SplitLogBackups
									 WHERE DenseRank = @LogRestoreRanking
									 ORDER BY BackupFile
									 FOR XML PATH ('')),
								1,
								2,
								'' ) + N' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10);
				END;
				ELSE
				SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathLog + @BackupFile + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10);

					IF @Debug = 1 OR @Execute = 'N'
					BEGIN
						IF @sql IS NULL PRINT '@sql is NULL for RESTORE LOG: @RestoreDatabaseName, @CurrentBackupPathLog, @BackupFile';
						PRINT @sql;
					END;

					IF @Debug IN (0, 1) AND @Execute = 'Y'
						EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
			END;

			SET @LogRestoreRanking += 1;
		END;


	IF @Debug = 1
	BEGIN
	    SELECT '#SplitLogBackups' AS table_name, BackupPath, BackupFile FROM #SplitLogBackups;
	END
END

-- Put database in a useable state
IF @RunRecovery = 1
	BEGIN
		SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY';

		IF @KeepCdc = 1
			SET @sql = @sql + N', KEEP_CDC';

		IF @EnableBroker = 1
			SET @sql = @sql + N', ENABLE_BROKER';

		SET @sql = @sql + NCHAR(13);

		IF @Debug = 1 OR @Execute = 'N'
		BEGIN
			IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName';
			PRINT @sql;
		END;

		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
	END;

-- Ensure simple recovery model
IF @ForceSimpleRecovery = 1
	BEGIN
		SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + N' SET RECOVERY SIMPLE' + NCHAR(13);

			IF @Debug = 1 OR @Execute = 'N'
			BEGIN
				IF @sql IS NULL PRINT '@sql is NULL for SET RECOVERY SIMPLE: @RestoreDatabaseName';
				PRINT @sql;
			END;

		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
	END;

 -- Run checkdb against this database
IF @RunCheckDB = 1
	BEGIN
		SET @sql = N'DBCC CHECKDB (' + @RestoreDatabaseName + N') WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;';

			IF @Debug = 1 OR @Execute = 'N'
			BEGIN
				IF @sql IS NULL PRINT '@sql is NULL for Run Integrity Check: @RestoreDatabaseName';
				PRINT @sql;
			END;

		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
	END;


IF @DatabaseOwner IS NOT NULL
	BEGIN
		IF @RunRecovery = 1
		BEGIN
			IF EXISTS (SELECT * FROM master.dbo.syslogins WHERE syslogins.loginname = @DatabaseOwner)
			BEGIN
				SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @DatabaseOwner + ']';

					IF @Debug = 1 OR @Execute = 'N'
					BEGIN
						IF @sql IS NULL PRINT '@sql is NULL for Set Database Owner';
						PRINT @sql;
					END;

				IF @Debug IN (0, 1) AND @Execute = 'Y'
					EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
			END
			ELSE
			BEGIN
				PRINT @DatabaseOwner + ' is not a valid Login. Database Owner not set.';
			END
		END
		ELSE
		BEGIN
			PRINT @RestoreDatabaseName + ' is still in Recovery, so we are unable to change the database owner to [' + @DatabaseOwner + '].';
		END
	END;

	IF @SetTrustworthyON = 1
		BEGIN
			IF @RunRecovery = 1
			BEGIN
				IF IS_SRVROLEMEMBER('sysadmin') = 1
				BEGIN
					SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + N' SET TRUSTWORTHY ON;';

						IF @Debug = 1 OR @Execute = 'N'
						BEGIN
							IF @sql IS NULL PRINT '@sql is NULL for SET TRUSTWORTHY ON';
							PRINT @sql;
						END;

					IF @Debug IN (0, 1) AND @Execute = 'Y'
						EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
				END
				ELSE
				BEGIN
					PRINT 'Current user''s login is NOT a member of the sysadmin role. Database TRUSTWORHY bit has not been enabled.';
				END
			END
			ELSE
			BEGIN
				PRINT @RestoreDatabaseName + ' is still in Recovery, so we are unable to enable the TRUSTWORHY bit.';
			END
		END;

-- Link a user entry in the sys.database_principals system catalog view in the restored database to a SQL Server login of the same name
IF @FixOrphanUsers = 1
	BEGIN
		SET @sql = N'
-- Fixup Orphan Users by setting database user sid to match login sid
DECLARE @FixOrphansSql NVARCHAR(MAX);
DECLARE @OrphanUsers TABLE (SqlToExecute NVARCHAR(MAX));
USE ' + @RestoreDatabaseName + ';

INSERT @OrphanUsers
SELECT ''ALTER USER ['' + d.name + ''] WITH LOGIN = ['' + d.name + '']; ''
	FROM  sys.database_principals d
	INNER JOIN master.sys.server_principals s ON d.name COLLATE DATABASE_DEFAULT = s.name COLLATE DATABASE_DEFAULT
	WHERE d.type_desc = ''SQL_USER''
		AND d.name NOT IN (''guest'',''dbo'')
		AND d.sid <> s.sid
	ORDER BY d.name;

SELECT @FixOrphansSql = (SELECT SqlToExecute AS [text()] FROM @OrphanUsers FOR XML PATH (''''), TYPE).value(''text()[1]'',''NVARCHAR(MAX)'');

IF @FixOrphansSql IS NULL
	PRINT ''No orphan users require a sid fixup.'';
ELSE
BEGIN
	PRINT ''Fix Orphan Users: '' + @FixOrphansSql;
	EXECUTE(@FixOrphansSql);
END;'

		IF @Debug = 1 OR @Execute = 'N'
		BEGIN
			IF @sql IS NULL PRINT '@sql is NULL for Fix Orphan Users';
			PRINT @sql;
		END;

		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';
	END;

IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0
BEGIN
	PRINT 'Attempting to run ' + @RunStoredProcAfterRestore
	SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @RunStoredProcAfterRestore

	IF @Debug = 1 OR @Execute = 'N'
	BEGIN
		IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore'
		PRINT @sql
	END

	IF @RunRecovery = 0
	BEGIN
		PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1'
	END
	ELSE
    BEGIN
		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXEC sp_executesql  @sql
	END
END

-- If test restore then blow the database away (be careful)
IF @TestRestore = 1
	BEGIN
		SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13);

			IF @Debug = 1 OR @Execute = 'N'
			BEGIN
				IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE: @RestoreDatabaseName';
				PRINT @sql;
			END;

		IF @Debug IN (0, 1) AND @Execute = 'Y'
			EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';

	END;

-- Clean-Up Tempdb Objects
IF OBJECT_ID( 'tempdb..#SplitFullBackups' ) IS NOT NULL DROP TABLE #SplitFullBackups;
IF OBJECT_ID( 'tempdb..#SplitDiffBackups' ) IS NOT NULL DROP TABLE #SplitDiffBackups;
IF OBJECT_ID( 'tempdb..#SplitLogBackups' ) IS NOT NULL DROP TABLE #SplitLogBackups;
GO
IF OBJECT_ID('dbo.sp_ineachdb') IS NULL
    EXEC ('CREATE PROCEDURE dbo.sp_ineachdb AS RETURN 0')
GO

ALTER PROCEDURE [dbo].[sp_ineachdb]
  -- mssqltips.com/sqlservertip/5694/execute-a-command-in-the-context-of-each-database-in-sql-server--part-2/
  @command              nvarchar(max) = NULL,
  @replace_character    nchar(1)      = N'?',
  @print_dbname         bit = 0,
  @select_dbname        bit = 0, 
  @print_command        bit = 0, 
  @print_command_only   bit = 0,
  @suppress_quotename   bit = 0, -- use with caution
  @system_only          bit = 0,
  @user_only            bit = 0,
  @name_pattern         nvarchar(300)  = N'%', 
  @database_list        nvarchar(max)  = NULL,
  @exclude_pattern      nvarchar(300)  = NULL,
  @exclude_list         nvarchar(max)  = NULL,
  @recovery_model_desc  nvarchar(120)  = NULL,
  @compatibility_level  tinyint        = NULL,
  @state_desc           nvarchar(120)  = N'ONLINE',
  @is_read_only         bit = 0,
  @is_auto_close_on     bit = NULL,
  @is_auto_shrink_on    bit = NULL,
  @is_broker_enabled    bit = NULL,
  @user_access          nvarchar(128)  = NULL, 
  @Help                 bit = 0,
  @Version              varchar(30)    = NULL OUTPUT,
  @VersionDate          datetime       = NULL OUTPUT,
  @VersionCheckMode     bit            = 0,
  @is_ag_writeable_copy bit            = 0,
  @is_query_store_on	bit            = NULL
-- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system
AS
BEGIN
  SET NOCOUNT ON;
  SET STATISTICS XML OFF;

  SELECT @Version = '8.29', @VersionDate = '20260203';
  
  IF(@VersionCheckMode = 1)
  BEGIN
	RETURN;
  END;
  
  IF @Help = 1

	BEGIN
	
		PRINT '
		/*
			sp_ineachdb from http://FirstResponderKit.org
			
			This script will execute a command against multiple databases.
		
			To learn more, visit http://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.
			 - Tastes awful with marmite.
		
			Unknown limitations of this version:
			 - None.  (If we knew them, they would be known. Duh.)
		
		     Changes - for the full list of improvements and fixes in this version, see:
		     https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/
		
		    MIT License
			
			Copyright (c) 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.
		
		*/
		';
		
		RETURN -1;
	END

  DECLARE @exec   nvarchar(150),
          @sx     nvarchar(18) = N'.sys.sp_executesql',
          @db     sysname,
          @dbq    sysname,
          @cmd    nvarchar(max),
          @thisdb sysname,
          @cr     char(2) = CHAR(13) + CHAR(10),
		  @SQLVersion	AS tinyint = (@@microsoftversion / 0x1000000) & 0xff,	     -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019)
		  @ServerName	AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')), -- Stores the SQL Server Instance name.
		  @NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%'; --Pattern for PATINDEX


  CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit);


/*
  -- first, let's limit to only DBs the caller is interested in
  IF @database_list > N''
  -- comma-separated list of potentially valid/invalid/quoted/unquoted names
  BEGIN
    ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@database_list)),
    names AS
    (
      SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@database_list, n, 
        CHARINDEX(N',', @database_list + N',', n) - n), 1)))
      FROM n 
      WHERE SUBSTRING(N',' + @database_list, n, 1) = N','
    ) 
    INSERT #ineachdb(id,name,is_distributor) 
    SELECT d.database_id, d.name, d.is_distributor
      FROM sys.databases AS d
      WHERE EXISTS (SELECT 1 FROM names WHERE name = d.name)
      OPTION (MAXRECURSION 0);
  END
  ELSE
  BEGIN
    INSERT #ineachdb(id,name,is_distributor) SELECT database_id, name, is_distributor FROM sys.databases;
  END

  -- now delete any that have been explicitly excluded - exclude trumps include
  IF @exclude_list > N'' 
  -- comma-separated list of potentially valid/invalid/quoted/unquoted names
  BEGIN
    ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@exclude_list)),
    names AS
    (
      SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@exclude_list, n, 
        CHARINDEX(N',', @exclude_list + N',', n) - n), 1)))
      FROM n 
      WHERE SUBSTRING(N',' + @exclude_list, n, 1) = N','
    )
    DELETE d 
      FROM #ineachdb AS d
      INNER JOIN names
      ON names.name = d.name
      OPTION (MAXRECURSION 0);
  END
*/

/*
@database_list and @exclude_list are are processed at the same time
1)Read the list searching for a comma or [
2)If we find a comma, save the name
3)If we find a [, we begin to accumulate the result until we reach closing ], (jumping over escaped ]]).
4)Finally, tabs, line breaks and spaces are removed from unquoted names
*/
WITH C
AS (SELECT V.SrcList
         , CAST('' AS nvarchar(MAX)) AS Name
         , V.DBList
         , 0 AS InBracket
         , 0 AS Quoted
    FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList)
    UNION ALL
	SELECT C.SrcList									
--       , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/
         , CASE WHEN V.Found = '[' THEN '' ELSE SUBSTRING(C.DBList, 1, V.Place - 1)  END  /*remove initial [*/
         , STUFF(C.DBList, 1, V.Place, '')
--       , IIF(V.Found = '[', 1, 0)
		 ,Case WHEN V.Found = '[' THEN 1 ELSE 0 END
         , 0
    FROM C
        CROSS APPLY
    (   VALUES (PATINDEX('%[,[]%', C.DBList), SUBSTRING(C.DBList, PATINDEX('%[,[]%', C.DBList), 1))) AS V (Place, Found)
    WHERE C.DBList > ''
          AND C.InBracket = 0
    UNION ALL
    SELECT C.SrcList
--       , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ 
	     , ISNULL(C.Name,'') + ISNULL(SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1),'') /*Accumulates only one ] if escaped]] or none if end]*/ 
         , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '')
         , W.DoubleBracket
         , 1
    FROM C
        CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place)
	--  CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket)
        CROSS APPLY (VALUES (CASE WHEN SUBSTRING(C.DBList, V.Place + 1, 1) = ']' THEN 1 ELSE 0 END)) AS W (DoubleBracket)							 				 
    WHERE C.DBList > ''
          AND C.InBracket = 1)
   , F
AS (SELECT C.SrcList
         , CASE WHEN C.Quoted = 0 THEN 
                SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2)
             ELSE C.Name END						   
 AS name
    FROM C
    WHERE C.InBracket = 0
          AND C.Name > '')																	 
INSERT #ineachdb(id,name,is_distributor)
SELECT d.database_id
     , d.name
     , d.is_distributor
FROM sys.databases AS d
WHERE (   EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'In')
          OR @database_list IS NULL)
      AND NOT EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'Out')
OPTION (MAXRECURSION 0);
;
  -- next, let's delete any that *don't* match various criteria passed in
  DELETE dbs FROM #ineachdb AS dbs
  WHERE (@system_only = 1 AND (id NOT IN (1,2,3,4) AND is_distributor <> 1))
     OR (@user_only   = 1 AND (id     IN (1,2,3,4) OR  is_distributor =  1))
     OR name NOT LIKE @name_pattern
     OR name LIKE @exclude_pattern
     OR EXISTS
     (
       SELECT 1 
         FROM sys.databases AS d
         WHERE d.database_id = dbs.id
         AND NOT
         (
           recovery_model_desc     = COALESCE(@recovery_model_desc, recovery_model_desc)
           AND compatibility_level = COALESCE(@compatibility_level, compatibility_level)
           AND is_read_only        = COALESCE(@is_read_only,        is_read_only)
           AND is_auto_close_on    = COALESCE(@is_auto_close_on,    is_auto_close_on)
           AND is_auto_shrink_on   = COALESCE(@is_auto_shrink_on,   is_auto_shrink_on)
           AND is_broker_enabled   = COALESCE(@is_broker_enabled,   is_broker_enabled)
         )
     );

  -- delete any databases that don't match query store criteria
  IF @SQLVersion >= 13
  BEGIN
    DELETE dbs FROM #ineachdb AS dbs
	WHERE EXISTS
	 (
       SELECT 1 
         FROM sys.databases AS d
         WHERE d.database_id = dbs.id
         AND NOT
         (
           is_query_store_on   = COALESCE(@is_query_store_on,   is_query_store_on)
		   AND NOT (@is_query_store_on = 1 AND d.database_id = 3) OR (@is_query_store_on = 0 AND d.database_id = 3) -- Excluding the model database which shows QS enabled in SQL2022+
		 )
     );
  END
  
  -- if a user access is specified, remove any that are NOT in that state
  IF @user_access IN (N'SINGLE_USER', N'MULTI_USER', N'RESTRICTED_USER')
  BEGIN
    DELETE #ineachdb WHERE 
      CONVERT(nvarchar(128), DATABASEPROPERTYEX(name, 'UserAccess')) <> @user_access;
  END  

  -- finally, remove any that are not *fully* online or we can't access
  DELETE dbs FROM #ineachdb AS dbs
  WHERE EXISTS
  (
    SELECT 1 FROM sys.databases
      WHERE database_id = dbs.id
      AND
      ( 
        @state_desc = N'ONLINE' AND
        (
          [state] & 992 <> 0         -- inaccessible
          OR state_desc <> N'ONLINE' -- not online
          OR HAS_DBACCESS(name) = 0  -- don't have access
          OR DATABASEPROPERTYEX(name, 'Collation') IS NULL -- not fully online. See "status" here:
          -- https://docs.microsoft.com/en-us/sql/t-sql/functions/databasepropertyex-transact-sql
        )
        OR (@state_desc <> N'ONLINE' AND state_desc <> @state_desc)
      )
  );

  -- from Andy Mallon / First Responders Kit. Make sure that if we're an 
  -- AG secondary, we skip any database where allow connections is off
  IF @SQLVersion >= 11 AND 3 = (SELECT COUNT(*) FROM sys.all_objects WHERE name IN('availability_replicas','dm_hadr_availability_group_states','dm_hadr_database_replica_states'))
  BEGIN
    DELETE dbs FROM #ineachdb AS dbs
    WHERE EXISTS
          (
            SELECT 1 FROM sys.dm_hadr_database_replica_states AS drs 
              INNER JOIN sys.availability_replicas AS ar
              ON ar.replica_id = drs.replica_id
              INNER JOIN sys.dm_hadr_availability_group_states ags 
              ON ags.group_id = ar.group_id
              WHERE drs.database_id = dbs.id
              AND ar.secondary_role_allow_connections = 0
              AND ags.primary_replica <> @ServerName
          );
	/* Remove databases which are not the writeable copies in an AG. */
    IF @is_ag_writeable_copy = 1
    BEGIN
      DELETE dbs FROM #ineachdb AS dbs
      WHERE EXISTS
          (
          SELECT 1 FROM sys.dm_hadr_database_replica_states AS drs 
            INNER JOIN sys.availability_replicas AS ar
            ON ar.replica_id = drs.replica_id
            INNER JOIN sys.dm_hadr_availability_group_states AS ags 
            ON ags.group_id = ar.group_id
            WHERE drs.database_id = dbs.id
            AND drs.is_primary_replica <> 1
            AND ags.primary_replica <> @ServerName
          );
    END
  END

  -- Well, if we deleted them all...
  IF NOT EXISTS (SELECT 1 FROM #ineachdb)
  BEGIN
    RAISERROR(N'No databases to process.', 1, 0);
    RETURN;
  END

  -- ok, now, let's go through what we have left
  DECLARE dbs CURSOR LOCAL FAST_FORWARD
    FOR SELECT DB_NAME(id), QUOTENAME(DB_NAME(id))
    FROM #ineachdb;

  OPEN dbs;

  FETCH NEXT FROM dbs INTO @db, @dbq;

  DECLARE @msg1 nvarchar(512) = N'Could not run against %s : %s.',
          @msg2 nvarchar(max);

  WHILE @@FETCH_STATUS <> -1
  BEGIN
    SET @thisdb = CASE WHEN @suppress_quotename = 1 THEN @db ELSE @dbq END;
    SET @cmd = REPLACE(@command, @replace_character, REPLACE(@thisdb,'''',''''''));

    BEGIN TRY
      IF @print_dbname = 1
      BEGIN
        PRINT N'/* ' + @thisdb + N' */';
      END

      IF @select_dbname = 1
      BEGIN
        SELECT [ineachdb current database] = @thisdb;
      END

      IF 1 IN (@print_command, @print_command_only)
      BEGIN
        PRINT N'/* For ' + @thisdb + ': */' + @cr + @cr + @cmd + @cr + @cr;
      END

      IF COALESCE(@print_command_only,0) = 0
      BEGIN
        SET @exec = @dbq + @sx;
        EXEC @exec @cmd;
      END
    END TRY

    BEGIN CATCH
      SET @msg2 = ERROR_MESSAGE();
      RAISERROR(@msg1, 1, 0, @db, @msg2);
    END CATCH

    FETCH NEXT FROM dbs INTO @db, @dbq;
  END

  CLOSE dbs; 
  DEALLOCATE dbs;
END
GO

IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL)
BEGIN

    CREATE TABLE dbo.SqlServerVersions
    (
        MajorVersionNumber tinyint not null,
        MinorVersionNumber smallint not null,
        Branch varchar(34) not null,
        [Url] varchar(99) not null,
        ReleaseDate date not null,
        MainstreamSupportEndDate date not null,
        ExtendedSupportEndDate date not null,
        MajorVersionName varchar(19) not null,
        MinorVersionName varchar(67) not null,

        CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED
        (
            MajorVersionNumber ASC,
            MinorVersionNumber ASC,
            ReleaseDate ASC
        )
    );
	
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName'
	EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions'

END;
GO

DELETE FROM dbo.SqlServerVersions;

INSERT INTO dbo.SqlServerVersions
    (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName)
VALUES
    /*2025*/
    (17, 4006, 'CU1 v2', 'https://learn.microsoft.com/troubleshoot/sql/releases/sqlserver-2025/cumulativeupdate1', '2026-01-29', '2031-01-06', '2036-01-06', 'SQL Server 2025', 'Cumulative Update 1 v2'),
    (17, 4005, 'CU1', 'https://learn.microsoft.com/troubleshoot/sql/releases/sqlserver-2025/cumulativeupdate1', '2026-01-15', '2031-01-06', '2036-01-06', 'SQL Server 2025', 'Cumulative Update 1 (Removed)'),
    (17, 1000, 'RTM', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-11-18', '2031-01-06', '2036-01-06', 'SQL Server 2025', 'RTM'),
    (17, 925, 'RC1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-09-17', '2025-11-18', '2025-11-18', 'SQL Server 2025', 'Preview RC1'),
    (17, 900, 'RC0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-08-20', '2025-11-18', '2025-11-18', 'SQL Server 2025', 'Preview RC0'),
    (17, 800, 'CTP 2.1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-06-16', '2025-11-18', '2025-11-18', 'SQL Server 2025', 'Preview CTP 2.1'),
    (17, 700, 'CTP 2.0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-05-19', '2025-11-18', '2025-11-18', 'SQL Server 2025', 'Preview CTP 2.0'),
    /*2022*/
    (16, 4236, 'CU23 v2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate23', '2026-01-29', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 23 v2'),
    (16, 4235, 'CU23', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate23', '2026-01-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 23 (Removed)'),
    (16, 4225, 'CU22', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate22', '2025-11-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 22'),
    (16, 4215, 'CU21', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate21', '2025-09-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 21'),
    (16, 4205, 'CU20', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate20', '2025-07-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 20'),
    (16, 4200, 'CU19 GDR', 'https://support.microsoft.com/en-us/help/5058721', '2025-07-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19 GDR'),
    (16, 4195, 'CU19', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate19', '2025-05-19', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19'),
    (16, 4185, 'CU18', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate18', '2025-03-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 18'),
    (16, 4175, 'CU17', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate17', '2025-01-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 17'),
    (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'),
    (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'),
    (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'),
    (16, 4140, 'CU14 GDR', 'https://support.microsoft.com/en-us/help/5042578', '2024-09-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14 GDR'),
    (16, 4135, 'CU14', 'https://support.microsoft.com/en-us/help/5038325', '2024-07-23', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14'),
    (16, 4125, 'CU13', 'https://support.microsoft.com/en-us/help/5036432', '2024-05-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 13'),
    (16, 4115, 'CU12', 'https://support.microsoft.com/en-us/help/5033663', '2024-03-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 12'),
    (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'),
    (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'),
    (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'),
    (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'),
    (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'),
    (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'),
    (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'),
    (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'),
    (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'),
    (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'),
    (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'),
    (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'),
    (16, 1050, 'RTM GDR', 'https://support.microsoft.com/kb/5021522', '2023-02-14', '2028-01-11', '2033-01-11', 'SQL Server 2022 GDR', 'RTM'),
    (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'),
    /*2019*/
    (15, 4455, 'CU32 GDR', 'https://support.microsoft.com/help/5068404', '2025-09-09', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'),
    (15, 4445, 'CU32 GDR', 'https://support.microsoft.com/kb/5065222', '2025-09-09', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'),
    (15, 4440, 'CU32 GDR', 'https://support.microsoft.com/kb/5063757', '2025-08-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'),
    (15, 4435, 'CU32 GDR', 'https://support.microsoft.com/kb/5058722', '2025-07-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'),
    (15, 4430, 'CU32', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate32', '2025-02-27', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32'),
    (15, 4420, 'CU31', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate31', '2025-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 31'),
    (15, 4415, 'CU30', 'https://support.microsoft.com/kb/5049235', '2024-12-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 30'),
    (15, 4405, 'CU29', 'https://support.microsoft.com/kb/5046365', '2024-10-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 29'),
    (15, 4395, 'CU28 GDR', 'https://support.microsoft.com/kb/5046060', '2024-10-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'),
    (15, 4390, 'CU28 GDR', 'https://support.microsoft.com/kb/5042749', '2024-09-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'),
    (15, 4385, 'CU28', 'https://support.microsoft.com/kb/5039747', '2024-08-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28'),
    (15, 4375, 'CU27', 'https://support.microsoft.com/kb/5037331', '2024-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 27'),
    (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'),
    (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'),
    (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'),
    (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'),
    (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'),
    (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'),
    (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'),
    (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'),
    (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'),
    (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'),
    (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'),
    (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'),
    (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'),
    (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'),
    (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'),
    (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'),
    (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'),
    (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'),
    (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'),
    (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '),
    (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '),
    (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '),
    (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '),
    (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '),
    (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '),
    (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '),
    (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '),
    (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '),
    (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '),
    (15, 2070, 'GDR', 'https://support.microsoft.com/en-us/help/4517790', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM GDR '),
    (15, 2000, 'RTM ', '', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM '),
    /*2017*/
    (14, 3515, 'RTM CU31 GDR', 'https://support.microsoft.com/help/5068402', '2025-09-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3505, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5065225', '2025-09-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3500, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5063759', '2025-08-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3495, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5058714', '2025-07-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3485, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046858', '2024-11-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3480, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046061', '2024-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3475, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5042215', '2024-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3471, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5040940', '2024-07-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'),
    (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'),
    (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'),
    (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'),
    (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'),
    (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'),
    (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'),
    (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'),
    (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'),
    (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'),
    (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'),
    (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'),
    (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'),
    (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'),
    (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'),
    (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'),
    (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'),
    (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'),
    (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'),
    (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'),
    (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'),
    (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'),
    (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'),
    (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'),
    (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'),
    (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'),
    (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'),
    (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'),
    (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'),
    (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'),
    (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'),
    (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'),
    (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'),
    (14, 3006, 'RTM CU1', 'https://support.microsoft.com/en-us/help/4038634', '2017-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 1'),
    (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '),
    /*2016*/
    (13, 7055, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5058717', '2025-07-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7045, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5046062', '2024-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7040, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5042209', '2024-09-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7037, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5040944', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7029, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5029187', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7024, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5021128', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7016, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5015371', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'),
    (13, 7000, 'SP3 Azure Feature Pack', 'https://support.microsoft.com/en-us/help/5014242', '2022-05-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack'),
    (13, 6470, 'SP3 GDR', 'https://support.microsoft.com/kb/5065226', '2025-09-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6465, 'SP3 GDR', 'https://support.microsoft.com/kb/5063762', '2025-08-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6460, 'SP3 GDR', 'https://support.microsoft.com/kb/5058718', '2025-07-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6455, 'SP3 GDR', 'https://support.microsoft.com/kb/5046855', '2024-11-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6450, 'SP3 GDR', 'https://support.microsoft.com/kb/5046063', '2024-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6445, 'SP3 GDR', 'https://support.microsoft.com/kb/5042207', '2024-09-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6441, 'SP3 GDR', 'https://support.microsoft.com/kb/5040946', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6435, 'SP3 GDR', 'https://support.microsoft.com/kb/5029186', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'),
    (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'),
    (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'),
    (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'),
    (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'),
    (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'),
    (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'),
    (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'),
    (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'),
    (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'),
    (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'),
    (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'),
    (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'),
    (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'),
    (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'),
    (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'),
    (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'),
    (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'),
    (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'),
    (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'),
    (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'),
    (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'),
    (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '),
    (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'),
    (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'),
    (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'),
    (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'),
    (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'),
    (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'),
    (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'),
    (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'),
    (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'),
    (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'),
    (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'),
    (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'),
    (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'),
    (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'),
    (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'),
    (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'),
    (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '),
    (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'),
    (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'),
    (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'),
    (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'),
    (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'),
    (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'),
    (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'),
    (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'),
    (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'),
    (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '),
    /*2014*/
    (12, 6449, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5029185', '2023-10-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'),
    (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'),
    (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'),
    (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'),
    (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'),
    (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'),
    (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'),
    (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'),
    (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'),
    (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'),
    (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '),
    (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'),
    (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'),
    (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'),
    (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'),
    (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'),
    (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'),
    (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'),
    (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'),
    (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'),
    (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'),
    (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'),
    (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'),
    (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'),
    (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'),
    (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'),
    (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'),
    (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'),
    (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'),
    (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '),
    (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'),
    (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'),
    (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'),
    (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'),
    (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'),
    (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'),
    (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'),
    (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'),
    (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'),
    (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'),
    (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'),
    (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'),
    (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'),
    (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'),
    (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'),
    (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '),
    (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'),
    (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'),
    (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'),
    (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'),
    (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'),
    (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'),
    (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'),
    (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'),
    (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'),
    (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'),
    (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'),
    (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'),
    (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'),
    (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'),
    (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'),
    (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'),
    (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '),
    (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'),
    (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '),
    /*2012*/
    (11, 7512, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/5021123', '2023-02-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'),
    (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'),
    (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'),
    (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'),
    (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'),
    (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '),
    (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'),
    (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'),
    (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'),
    (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'),
    (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'),
    (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'),
    (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'),
    (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'),
    (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'),
    (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'),
    (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '),
    (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'),
    (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'),
    (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'),
    (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'),
    (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'),
    (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'),
    (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'),
    (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'),
    (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'),
    (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'),
    (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'),
    (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'),
    (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'),
    (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'),
    (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'),
    (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'),
    (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'),
    (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'),
    (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '),
    (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'),
    (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'),
    (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'),
    (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '),
    (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'),
    (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'),
    (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'),
    (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'),
    (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'),
    (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'),
    (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'),
    (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'),
    (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'),
    (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'),
    (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'),
    (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'),
    (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'),
    (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '),
    (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'),
    (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'),
    (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'),
    (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'),
    (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'),
    (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'),
    (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'),
    (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'),
    (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'),
    (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'),
    (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'),
    (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'),
    (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'),
    (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '),
    /*2008 R2*/
    (10, 6560, 'SP3 GDR: January 6 2018', 'http://support.microsoft.com/help/4057113', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'SP3 GDR: January 6 2018'),
    (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'),
    (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'),
    (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '),
    (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'),
    (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'),
    (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'),
    (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'),
    (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'),
    (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'),
    (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'),
    (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'),
    (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'),
    (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'),
    (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'),
    (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'),
    (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'),
    (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'),
    (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'),
    (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'),
    (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'),
    (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '),
    (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'),
    (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'),
    (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'),
    (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'),
    (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'),
    (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'),
    (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'),
    (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'),
    (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'),
    (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'),
    (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'),
    (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'),
    (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'),
    (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'),
    (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'),
    (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'),
    (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '),
    (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'),
    (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'),
    (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'),
    (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'),
    (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'),
    (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'),
    (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'),
    (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'),
    (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'),
    (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'),
    (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'),
    (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'),
    (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'),
    (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'),
    (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'),
    (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '),
    /*2008*/
    (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'),
    (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'),
    (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'),
    (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'),
    (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'),
    (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'),
    (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'),
    (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'),
    (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'),
    (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'),
    (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'),
    (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'),
    (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'),
    (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'),
    (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'),
    (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'),
    (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'),
    (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'),
    (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'),
    (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'),
    (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'),
    (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'),
    (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'),
    (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'),
    (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'),
    (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '),
    (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'),
    (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'),
    (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'),
    (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'),
    (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'),
    (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'),
    (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'),
    (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'),
    (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'),
    (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'),
    (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'),
    (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'),
    (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'),
    (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'),
    (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'),
    (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '),
    (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'),
    (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'),
    (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'),
    (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'),
    (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'),
    (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'),
    (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'),
    (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'),
    (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'),
    (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'),
    (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'),
    (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'),
    (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'),
    (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'),
    (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'),
    (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'),
    (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'),
    (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'),
    (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '),
    (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'),
    (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'),
    (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'),
    (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'),
    (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'),
    (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'),
    (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'),
    (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'),
    (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'),
    (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'),
    (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ')
;
GO
IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL
  EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;');
GO


ALTER PROCEDURE [dbo].[sp_BlitzFirst]
    @LogMessage NVARCHAR(4000) = 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 ,
    @OutputTableNameBlitzCache NVARCHAR(256) = NULL ,
    @OutputTableNameBlitzWho NVARCHAR(256) = NULL ,
    @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' ,
    @OutputTableRetentionDays TINYINT = 7 ,
    @OutputXMLasNVARCHAR TINYINT = 0 ,
    @FilterPlansByDatabase VARCHAR(MAX) = NULL ,
    @CheckProcedureCache TINYINT = 0 ,
    @CheckServerInfo TINYINT = 1 ,
    @FileLatencyThresholdMS INT = 100 ,
    @SinceStartup TINYINT = 0 ,
    @ShowSleepingSPIDs TINYINT = 0 ,
    @BlitzCacheSkipAnalysis BIT = 1 ,
	@MemoryGrantThresholdPct DECIMAL(5,2) = 15.00,
    @LogMessageCheckID INT = 38,
    @LogMessagePriority TINYINT = 1,
    @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message',
    @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst',
    @LogMessageURL VARCHAR(200) = '',
    @LogMessageCheckDate DATETIMEOFFSET = NULL,
    @Debug BIT = 0,
	@Version     VARCHAR(30) = NULL OUTPUT,
	@VersionDate DATETIME = NULL OUTPUT,
    @VersionCheckMode BIT = 0
    WITH EXECUTE AS CALLER, RECOMPILE
AS
BEGIN
SET NOCOUNT ON;
SET STATISTICS XML OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT @Version = '8.29', @VersionDate = '20260203';

IF(@VersionCheckMode = 1)
BEGIN
	RETURN;
END;

IF @Help = 1 
BEGIN
PRINT '
sp_BlitzFirst from http://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 http://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.
 - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets,
   the write to table may silently fail. Look, I never said I was good at this.

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:
https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/


MIT License

Copyright (c) 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.

';
RETURN;
END;    /* @Help = 1 */

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(MAX) = N'',
    @StockWarningFooter NVARCHAR(MAX) = N'',
    @StockDetailsHeader NVARCHAR(MAX) = N'',
    @StockDetailsFooter NVARCHAR(MAX) = N'',
    @StartSampleTime DATETIMEOFFSET,
    @FinishSampleTime DATETIMEOFFSET,
	@FinishSampleTimeWaitFor DATETIME,
	@AsOf1 DATETIMEOFFSET,
	@AsOf2 DATETIMEOFFSET,
    @ServiceName sysname,
    @OutputTableNameFileStats_View NVARCHAR(256),
    @OutputTableNamePerfmonStats_View NVARCHAR(256),
    @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256),
    @OutputTableNameWaitStats_View NVARCHAR(256),
    @OutputTableNameWaitStats_Categories NVARCHAR(256),
	@OutputTableCleanupDate DATE,
    @ObjectFullName NVARCHAR(2000),
	@BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';',
    @BlitzCacheMinutesBack INT,
    @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName ,
    @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName ,
    @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName ,
    @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)),
	@dm_exec_query_statistics_xml BIT = 0,
	@total_cpu_usage BIT = 0,
	@get_thread_time_ms NVARCHAR(MAX) = N'',
	@thread_time_ms FLOAT = 0,
	@logical_processors INT = 0,
	@max_worker_threads INT = 0,
    @is_windows_operating_system BIT = 1;

IF EXISTS
(
    SELECT  1
    FROM    sys.all_objects
    WHERE   name = 'dm_os_host_info'
)
BEGIN
    SELECT @is_windows_operating_system = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info;
END;

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

SELECT
    @OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
    @OutputSchemaName = QUOTENAME(@OutputSchemaName),
    @OutputTableName = QUOTENAME(@OutputTableName),
    @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats),
    @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats),
    @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats),
	@OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE),
    /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache),  We purposely don't sanitize this because sp_BlitzCache will */
    /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho),  We purposely don't sanitize this because sp_BlitzWho will */
    @LineFeed = CHAR(13) + CHAR(10),
    @OurSessionID = @@SPID,
    @OutputType                     = UPPER(@OutputType);

IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL))
BEGIN
    RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1);
    RETURN;
END;

IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10';
IF @OutputType = 'Top10' SET @SinceStartup = 1;

/* Logged Message  - CheckID 38 */
IF @LogMessage IS NOT NULL
    BEGIN

    RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT;

    /* Try to set the output table parameters if they don't exist */
    IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL
        BEGIN
            SET @OutputSchemaName = N'[dbo]';
            SET @OutputTableName = N'[BlitzFirst]';

            /* Look for the table in the current database */
            SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG)
                FROM INFORMATION_SCHEMA.TABLES
                WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst';

            IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools')
                SET @OutputDatabaseName = '[DBAtools]';

        END;

    IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL
            OR NOT EXISTS ( SELECT *
                     FROM   sys.databases
                     WHERE  QUOTENAME([name]) = @OutputDatabaseName)
		BEGIN
        RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT;
		RETURN;
        END;
    IF @LogMessageCheckDate IS NULL
        SET @LogMessageCheckDate = SYSDATETIMEOFFSET();
    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, Details, URL) VALUES( '
        + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)';

    EXECUTE sp_executesql @StringToExecute,
        N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)',
        @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL;

    RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT;

    RETURN;
    END;

IF @SinceStartup = 1
    BEGIN
    SET @Seconds = 0
    IF @ExpertMode = 0
        SET @ExpertMode = 1
    END;


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 @AsOf1= DATEADD(mi, -15, @AsOf);
		SET @AsOf2= DATEADD(mi, +15, @AsOf);

        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 >= @AsOf1'
            + ' AND CheckDate <= @AsOf2'
            + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;';
		EXEC sp_executesql @StringToExecute,
			N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET',
			@AsOf1, @AsOf2


END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */
ELSE IF @LogMessage 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 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%'
    BEGIN
		IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL
		BEGIN
			PRINT N'sp_BlitzWho is not installed in the current database_files.  You can get a copy from http://FirstResponderKit.org';
		END;
		ELSE
		BEGIN
			EXEC (@BlitzWho);
		END;
    END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE'   -   What's running right now? This is the first and last result set. */

    /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */
    IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/
		BEGIN
		/* Use the most accurate (but undocumented) DMV if it's available: */
		IF EXISTS(SELECT * FROM sys.all_columns ac WHERE ac.object_id = OBJECT_ID('sys.dm_cloud_database_epoch') AND ac.name = 'last_role_transition_time')
			SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),last_role_transition_time) , @FinishSampleTime = SYSDATETIMEOFFSET()
				FROM sys.dm_cloud_database_epoch;
		ELSE
			WITH WaitTimes AS (
				SELECT wait_type, wait_time_ms,
					NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper
					FROM sys.dm_os_wait_stats w
					WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP',
										'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT')
			)
			SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET()
				FROM WaitTimes
				WHERE grouper = 2;
		END
    ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> '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()),
                @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE());


	SELECT @logical_processors = COUNT(*)
		FROM sys.dm_os_schedulers
		WHERE status = 'VISIBLE ONLINE';

    IF EXISTS
	   (

	       SELECT
		       1/0
		   FROM sys.all_columns AS ac
		   WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers')
		   AND   ac.name = 'total_cpu_usage_ms'

	   )
	BEGIN

	    SELECT
		    @total_cpu_usage = 1,
			@get_thread_time_ms +=
			    N'
			     SELECT 
				     @thread_time_ms = 
				         CONVERT
					     (
					         FLOAT,
				             SUM(s.total_cpu_usage_ms)
					     )
                 FROM sys.dm_os_schedulers AS s
                 WHERE  s.status = ''VISIBLE ONLINE''
                 AND    s.is_online = 1
				 OPTION(RECOMPILE);
			     ';

	END
	ELSE
	BEGIN
	    SELECT
		    @total_cpu_usage = 0,
			@get_thread_time_ms +=
			    N'
			     SELECT 
				     @thread_time_ms = 
				         CONVERT
					     (
					         FLOAT,
				             SUM(s.total_worker_time / 1000.)
					     )
                 FROM sys.dm_exec_query_stats AS s
                 OPTION(RECOMPILE);
			     ';
	END

    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 the temp table. 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 http://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(MAX) 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,
          QueryHash BINARY(8)
        );

    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,
		thread_time_ms FLOAT,
		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..#checkversion') IS NOT NULL
		DROP TABLE #checkversion;
	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)
	);

    IF OBJECT_ID('tempdb..##WaitCategories') IS NULL
		BEGIN
			/* We reuse this one by default rather than recreate it every time. */
			CREATE TABLE ##WaitCategories
			(
				WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED,
				WaitCategory NVARCHAR(128) NOT NULL,
				Ignorable BIT DEFAULT 0
			);
		END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */

	IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)
		BEGIN
		    TRUNCATE TABLE ##WaitCategories;
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POPULATE_LOCK_ORDINALS','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1);
			INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1);
		END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */



    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 (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */
         AND (OBJECT_ID('sys.master_files') IS NULL))
        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 DISTINCT db.database_id
                FROM a
                INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name
                WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL
                OPTION (MAXRECURSION 0);
            END;
        END;

	IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL 
		DROP TABLE #ReadableDBs;
	CREATE TABLE #ReadableDBs (
	database_id INT
	);

	IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states')
    BEGIN
		RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT;

        SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;';
        EXEC(@StringToExecute);
		
	END

    DECLARE	@v DECIMAL(6,2),
		@build INT,
		@memGrantSortSupported BIT = 1;

	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 (@v < 11)
	OR (@v = 11 AND @build < 6020) 
	OR (@v = 12 AND @build < 5000) 
	OR (@v = 13 AND @build < 1601)
		SET @memGrantSortSupported = 0;

	IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml')
		AND ((@v = 13 AND @build >= 5337)	/* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */
				OR (@v = 14 AND @build >= 3162) 
				OR (@v >= 15)
				OR (@v <= 12)) /* Azure */
		SET @dm_exec_query_statistics_xml = 1;


    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 = @StockWarningFooter + @LineFeed + @LineFeed + '-- ?>',
           @StockDetailsHeader = @StockDetailsHeader + '<?ClickToSeeDetails -- ' + @LineFeed,
           @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>';

    /* Get the instance name to use as a Perfmon counter prefix. */
    IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*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 + ':Availability Group','Active Hadr Threads','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total');
        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 + ':Database Replica','Database Flow Control Delay', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total');
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_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 Pool LogWriter Pushes/sec', '_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 + ':Databases','Transactions/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL);
        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);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/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);
        /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention.
           And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly.
           For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group
        */
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL);
        INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL);
        END;


    IF @total_cpu_usage IN (0, 1)
	BEGIN
	    EXEC sys.sp_executesql
		    @get_thread_time_ms,
			N'@thread_time_ms FLOAT OUTPUT',
			@thread_time_ms OUTPUT;		    
	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;
	SET @StringToExecute = N'
		INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_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, 
			CASE @Seconds
			     WHEN 0
			     THEN 0
			     ELSE @thread_time_ms
			END AS thread_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 ';

	IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/
		SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os ';
	ELSE
		SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os ';

	SET @StringToExecute = @StringToExecute + N'
		) x
		   WHERE NOT EXISTS 
		   (
                SELECT *
				FROM ##WaitCategories AS wc
				WHERE wc.WaitType = x.wait_type
				AND wc.Ignorable = 1
		   )
		GROUP BY x.Pass, x.SampleTime, x.wait_type
		ORDER BY sum_wait_time_ms DESC;'
		
		EXEC sys.sp_executesql
	        @StringToExecute,
          N'@StartSampleTime DATETIMEOFFSET,
	        @Seconds INT,
	        @thread_time_ms FLOAT',
	        @StartSampleTime,
	        @Seconds,
	        @thread_time_ms;
		
    WITH w AS
	(
	    SELECT
		    total_waits =
			    CONVERT
				(
				    FLOAT,
			        SUM(ws.wait_time_ms)
				)
		FROM #WaitStats AS ws
		WHERE Pass = 1
	)
    UPDATE ws
	    SET	ws.thread_time_ms += w.total_waits
	FROM #WaitStats AS ws
	CROSS JOIN w
	WHERE ws.Pass = 1
	OPTION(RECOMPILE);
	
    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);

	/* For Github #2743: */
	CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED,
		forwarded_fetch_count BIGINT);
	INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count)
		SELECT object_id, forwarded_fetch_count
		FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os
	WHERE os.database_id = DB_ID('tempdb')
		AND os.forwarded_fetch_count > 100;


	/* If they want to run sp_BlitzWho and export to table, go for it. */
	IF @OutputTableNameBlitzWho IS NOT NULL
		AND @OutputDatabaseName IS NOT NULL
		AND @OutputSchemaName IS NOT NULL
		AND EXISTS ( SELECT *
						FROM   sys.databases
						WHERE  QUOTENAME([name]) = @OutputDatabaseName)
	BEGIN
		RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT;
		EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays;
	END

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


    /* Maintenance Tasks Running - Backup Running - CheckID 1 */
    IF @Seconds > 0
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 1',10,1) WITH NOWAIT;
		END

        IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%')
            INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash)
            SELECT 1 AS CheckID,
                1 AS Priority,
                'Maintenance Tasks Running' AS FindingGroup,
                'Backup Running' AS Finding,
                'https://www.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) ' + @LineFeed
                    + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed
                    + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details,
                'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt,
                pl.query_plan AS QueryPlan,
                r.start_time AS StartTime,
                s.loginame AS LoginName,
                s.nt_username AS NTUserName,
                s.[program_name] AS ProgramName,
                s.[hostname] AS HostName,
                db.[resource_database_id] AS DatabaseID,
                DB_NAME(db.resource_database_id) AS DatabaseName,
                0 AS OpenTransactionCount,
                r.query_hash
            FROM sys.dm_exec_requests r
            INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id
            INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0
            INNER JOIN
            (
                SELECT DISTINCT
                    t.request_session_id,
                    t.resource_database_id
                FROM sys.dm_tran_locks AS t
                WHERE t.resource_type = N'DATABASE'
                AND   t.request_mode = N'S'
                AND   t.request_status = N'GRANT'
                AND   t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE'
            ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id
            CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl
            WHERE r.command LIKE 'BACKUP%'
            AND r.start_time <= DATEADD(minute, -5, GETDATE())
            AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);
	END

    /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */
    IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*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 CHECK* Running - CheckID 2 */
    IF @Seconds > 0
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 2',10,1) WITH NOWAIT;
		END

        IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000)
            INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash)
            SELECT 2 AS CheckID,
                1 AS Priority,
                'Maintenance Tasks Running' AS FindingGroup,
                'DBCC CHECK* Running' AS Finding,
                'https://www.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,
                r.query_hash
            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
            OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl
            OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
            WHERE r.command LIKE 'DBCC%'
            AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%'
            AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%'
            AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%'
            AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);
	END

    /* Maintenance Tasks Running - Restore Running - CheckID 3 */
    IF @Seconds > 0
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 3',10,1) WITH NOWAIT;
		END

        IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000)
            INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash)
            SELECT 3 AS CheckID,
                1 AS Priority,
                'Maintenance Tasks Running' AS FindingGroup,
                'Restore Running' AS Finding,
                'https://www.brentozar.com/askbrent/backups/' AS URL,
                'Restore of ' + COALESCE(DB_NAME(db.resource_database_id),
					(SELECT db1.name FROM sys.databases db1
					LEFT OUTER JOIN sys.databases db2 ON db1.name <> db2.name AND db1.state_desc = db2.state_desc
					WHERE db1.state_desc = 'RESTORING' AND db2.name IS NULL),
					'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown ') + '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,
                COALESCE(db.[resource_database_id],0) AS DatabaseID,
                COALESCE(DB_NAME(db.resource_database_id), 'Unknown') AS DatabaseName,
                0 AS OpenTransactionCount,
                r.query_hash
            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
            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') 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%'
            AND s.program_name <> 'SQL Server Log Shipping'
            AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);
	END

    /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */
    IF @Seconds > 0
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 4',10,1) WITH NOWAIT;
		END

		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,
		    'https://www.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 https://www.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'
		AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);
	END

    /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */
    IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000)
    BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 5',10,1) WITH NOWAIT;
		END

        SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash)
            SELECT 5 AS CheckID,
                1 AS Priority,
                ''Query Problems'' AS FindingGroup,
                ''Long-Running Query Blocking Others'' AS Finding,
                ''https://www.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,
                r.query_hash
            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
              /* And the blocking session ID is not blocked by anyone else: */
              AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL)
			  AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);';
		EXECUTE sp_executesql @StringToExecute;
    END;
	
    /* Query Problems - Plan Cache Erased Recently - CheckID 7 */
    IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time)
    BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 7',10,1) WITH NOWAIT;
		END

        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,
            'https://www.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
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 8',10,1) WITH NOWAIT;
		END

        IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0)
           
            IF OBJECT_ID('tempdb..#BlitzFirstTmpSession', 'U') IS NOT NULL
				DROP TABLE #BlitzFirstTmpSession;

            SELECT DISTINCT  request_session_id, resource_database_id
            INTO #BlitzFirstTmpSession
            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';

           
            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,
                'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL,
                'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details,
                'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt,
                s.last_batch AS StartTime,
                s.loginame AS LoginName,
                s.nt_username AS NTUserName,
                s.[program_name] AS ProgramName,
                s.hostname 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,
                s.open_tran AS OpenTransactionCount
            FROM sys.sysprocesses s
            INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id
            INNER JOIN #BlitzFirstTmpSession AS db ON s.spid = db.request_session_id
            WHERE s.status = 'sleeping'
            AND s.open_tran > 0
            AND s.last_batch < DATEADD(ss, -10, SYSDATETIME())
            AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid
            AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE'));
	END

    /*Query Problems - Clients using implicit transactions - CheckID 37 */
    IF @Seconds > 0 
		AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%'
		AND	  @@VERSION NOT LIKE 'Microsoft SQL Server 2008%'
		AND	  @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' )
     BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 37',10,1) WITH NOWAIT;
		END

        SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount)
		SELECT  37 AS CheckId,
		        50 AS Priority,
		        ''Query Problems'' AS FindingsGroup,
		        ''Implicit Transactions'',
		        ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL,
		        ''Database: '' + DB_NAME(s.database_id)  + '' '' + CHAR(13) + CHAR(10) +
				''Host: '' + s.[host_name]  + '' '' + CHAR(13) + CHAR(10) +
				''Program: '' + s.[program_name]  + '' '' + CHAR(13) + CHAR(10) +
				CONVERT(NVARCHAR(10), s.open_transaction_count) + 
				'' open transactions since: '' + 
				CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' 
					AS Details,
				''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits.
If one of them is a lead blocker, consider killing that query.'' AS HowToStopit,
		        tat.transaction_begin_time,
		        s.login_name,
		        s.nt_user_name,
		        s.program_name,
		        s.host_name,
		        s.database_id,
		        DB_NAME(s.database_id) AS DatabaseName,
		        NULL AS Querytext,
		        s.open_transaction_count AS OpenTransactionCount
		FROM    sys.dm_tran_active_transactions AS tat
		LEFT JOIN sys.dm_tran_session_transactions AS tst
		ON tst.transaction_id = tat.transaction_id
		LEFT JOIN sys.dm_exec_sessions AS s
		ON s.session_id = tst.session_id
		WHERE tat.name = ''implicit_transaction'';
		'
		EXECUTE sp_executesql @StringToExecute;
    END;

    /* Query Problems - Query Rolling Back - CheckID 9 */
    IF @Seconds > 0
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 9',10,1) WITH NOWAIT;
		END

		IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0)
            INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash)
            SELECT 9 AS CheckID,
                1 AS Priority,
                'Query Problems' AS FindingGroup,
                'Query Rolling Back' AS Finding,
                'https://www.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,
                r.query_hash
            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';
	END

	IF @Seconds > 0
	BEGIN
    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
    SELECT
        47 AS CheckId,
    	50 AS Priority,
    	'Query Problems' AS FindingsGroup,
    	'High Percentage Of Runnable Queries' AS Finding, 
    	'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 
    	'On the ' 
    	+ CASE WHEN y.pass = 1 
    	       THEN '1st' 
    		   ELSE '2nd'
    	   END
    	+ ' pass, '
    	+ RTRIM(y.runnable_pct)
    	+ '% of your queries were waiting to get on a CPU to run. '
    	+ ' This can indicate CPU pressure.'
    FROM
    (
        SELECT 
            1 AS pass,
            x.total, 
        	x.runnable,
            CONVERT(decimal(5,2),
                (
                    x.runnable / 
                        (1. * NULLIF(x.total, 0))
                )
            ) * 100. AS runnable_pct
        FROM 
        (
            SELECT 
                COUNT_BIG(*) AS total, 
                SUM(CASE WHEN status = 'runnable' 
        		         THEN 1 
        				 ELSE 0 
        		    END) AS runnable
            FROM sys.dm_exec_requests
            WHERE session_id > 50
        ) AS x
    ) AS y
    WHERE y.runnable_pct > 20.;
	END

    /* Server Performance - Too Much Free Memory - CheckID 34 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 34',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
    SELECT 34 AS CheckID,
        50 AS Priority,
        'Server Performance' AS FindingGroup,
        'Too Much Free Memory' AS Finding,
        'https://www.brentozar.com/go/freememory' AS URL,
		CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details,
        'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt
		FROM sys.dm_os_performance_counters cFree
		INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%'
			AND cTotal.counter_name = N'Total Server Memory (KB)                                                                                                        '
		WHERE cFree.object_name LIKE N'%Memory Manager%'
			AND cFree.counter_name = N'Free Memory (KB)                                                                                                                '
			AND CAST(cFree.cntr_value AS BIGINT) > 20480000000
			AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT)
            AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%';

    /* Server Performance - Target Memory Lower Than Max - CheckID 35 */
	IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 35',10,1) WITH NOWAIT;
		END

		INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
		SELECT 35 AS CheckID,
			10 AS Priority,
			'Server Performance' AS FindingGroup,
			'Target Memory Lower Than Max' AS Finding,
			'https://www.brentozar.com/go/target' AS URL,
			N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed
				+ N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details,
			'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt
			FROM sys.configurations cMax
			INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%'
				AND cTarget.counter_name = N'Target Server Memory (KB)                                                                                                       '
			WHERE cMax.name = 'max server memory (MB)'
				AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024)
				AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */
				AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */
	END

    /* Server Info - Database Size, Total GB - CheckID 21 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 21',10,1) WITH NOWAIT;
	END

    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,
        'https://www.brentozar.com/askbrent/' AS URL
    FROM #MasterFiles
    WHERE database_id > 4;

    /* Server Info - Database Count - CheckID 22 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 22',10,1) WITH NOWAIT;
	END

    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,
        'https://www.brentozar.com/askbrent/' AS URL
    FROM sys.databases
    WHERE database_id > 4;


	/* Server Info - Memory Grants pending - CheckID 39 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 39',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
    SELECT 39 AS CheckID,
        50 AS Priority,
		'Server Performance' AS FindingGroup,
        'Memory Grants Pending' AS Finding,
		CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details,
		PendingGrants.DetailsInt,
		'https://www.brentozar.com/blitz/memory-grants/' AS URL
	FROM 
	(
		SELECT 
		COUNT(1) AS Details,
		COUNT(1) AS DetailsInt
	FROM sys.dm_exec_query_memory_grants AS Grants
	WHERE queue_id IS NOT NULL
	) AS PendingGrants
	WHERE PendingGrants.Details > 0;

	/* Server Info - Memory Grant/Workspace info - CheckID 40 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 40',10,1) WITH NOWAIT;
	END

	DECLARE @MaxWorkspace BIGINT
	SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)')
	
	IF (@MaxWorkspace IS NULL
	    OR @MaxWorkspace = 0)
	BEGIN
		SET @MaxWorkspace = 1
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
    SELECT 40 AS CheckID,
        251 AS Priority,
        'Server Info' AS FindingGroup,
        'Memory Grant/Workspace info' AS Finding,
		+ 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed
		+ 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed
		+ 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed  
		+ 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY)
		                              / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed
		+ 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details,
		(SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt,
		'https://www.brentozar.com/askbrent/' AS URL
	FROM sys.dm_exec_query_memory_grants AS Grants;

	/* Query Problems - Queries with high memory grants - CheckID 46 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 46',10,1) WITH NOWAIT;
	END

	INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan)
	SELECT 46 AS CheckID,
	    100 AS Priority,
	    'Query Problems' AS FindingGroup,
	    'Query with a memory grant exceeding '
		+CAST(@MemoryGrantThresholdPct AS NVARCHAR(15))
		+'%' AS Finding,
		'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50))
		+N'MB '
		 + @LineFeed
		+N'Granted pct of max workspace: ' 
		+ CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY)
		                              / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' 
		+ @LineFeed
		+N'SQLHandle: '
		+CONVERT(NVARCHAR(128),Grants.[sql_handle],1),
		'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL,
		SQLText.[text],
		QueryPlan.query_plan
	FROM sys.dm_exec_query_memory_grants AS Grants
	OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText
	OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan
	WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024));

    /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */
    IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb')
        BEGIN
			IF (@Debug = 1)
			BEGIN
				RAISERROR('Running CheckID 45',10,1) WITH NOWAIT;
			END

        /* SQL 2012+ version */
        SET @StringToExecute = N'
        INSERT  INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL)
        SELECT 45 AS CheckID,
                50 AS Priority,
                ''Query Problems'' AS FindingsGroup,
                ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding,
                N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) 
                    + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.''
                AS details,
                ''https://www.BrentOzar.com/go/userstore'' AS URL
        FROM sys.dm_os_memory_clerks
        HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1					
            AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */';
        EXEC sp_executesql @StringToExecute;
        END
    ELSE
        BEGIN
        /* Antiques Roadshow SQL 2008R2 - version */
			IF (@Debug = 1)
			BEGIN
				RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT;
			END

        SET @StringToExecute = N'
        INSERT  INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL)
        SELECT 45 AS CheckID,
                50 AS Priority,
                ''Performance'' AS FindingsGroup,
                ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding,
                N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) 
                    + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.''
                AS details,
                ''https://www.BrentOzar.com/go/userstore'' AS URL
        FROM sys.dm_os_memory_clerks
        HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1
            AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */';
        EXEC sp_executesql @StringToExecute;
        END



    IF @Seconds > 0
    BEGIN

    IF EXISTS ( SELECT 1/0
                FROM sys.all_objects AS ao
                WHERE ao.name = 'dm_exec_query_profiles' )
    BEGIN

        IF EXISTS( SELECT 1/0
                   FROM sys.dm_exec_requests AS r
                   JOIN sys.dm_exec_sessions AS s
                       ON r.session_id = s.session_id
                   WHERE s.host_name IS NOT NULL
                   AND r.total_elapsed_time > 5000
                   AND r.request_id > 0 )
			BEGIN

                   SET @StringToExecute = N'
                   DECLARE @bad_estimate TABLE 
                     ( 
                       session_id INT, 
                       request_id INT, 
                       estimate_inaccuracy BIT 
                     );
                   
                   INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy )
                   SELECT x.session_id, 
                          x.request_id, 
                          x.estimate_inaccuracy
                   FROM (
                         SELECT deqp.session_id,
                                deqp.request_id,
                                CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count
                                     THEN 1
                                     ELSE 0
                                END AS estimate_inaccuracy
                         FROM   sys.dm_exec_query_profiles AS deqp
                         INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id
						 WHERE deqp.session_id <> @@SPID
                           AND r.total_elapsed_time > 5000
                   ) AS x
                   WHERE x.estimate_inaccuracy = 1
                   GROUP BY x.session_id, 
                            x.request_id, 
                            x.estimate_inaccuracy;
                   
                   DECLARE @parallelism_skew TABLE
                     (
                       session_id INT, 
                       request_id INT, 
                       parallelism_skew BIT     
                     );
                   
                   INSERT @parallelism_skew ( session_id, request_id, parallelism_skew )
                   SELECT y.session_id,
                          y.request_id,
                          y.parallelism_skew
                   FROM (
                         SELECT x.session_id, 
                                x.request_id, 
                                x.node_id, 
                                x.thread_id, 
                                x.row_count, 
                                x.sum_node_rows, 
                                x.node_dop,
                                x.sum_node_rows / x.node_dop AS even_distribution,
                                x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent,
                                CASE 
                                    WHEN x.row_count > 10000
                                    AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2.
                                    THEN 1
                                    WHEN x.row_count > 10000
                                    AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5
                                    THEN 1
                                    ELSE 0 
                         	   END AS parallelism_skew
                         FROM (
                         	       SELECT deqp.session_id,
                                              deqp.request_id,
                                              deqp.node_id,
                                              deqp.thread_id,
                         	       	   deqp.row_count,
                         	       	   SUM(deqp.row_count) 
                         	       		OVER ( PARTITION BY deqp.session_id,
                                                                   deqp.request_id,
                         	       		                    deqp.node_id
                         	       			   ORDER BY deqp.row_count
                         	       			   ROWS BETWEEN UNBOUNDED PRECEDING 
                         	       			   AND UNBOUNDED FOLLOWING ) 
                         	       			   AS sum_node_rows,
                         	       	   COUNT(*) 
                         	       		OVER ( PARTITION BY deqp.session_id,
                                                                   deqp.request_id,
                         	       		                    deqp.node_id
                         	       			   ORDER BY deqp.row_count
                         	       			   ROWS BETWEEN UNBOUNDED PRECEDING 
                         	       			   AND UNBOUNDED FOLLOWING ) 
                         	       			   AS node_dop
                         	       FROM sys.dm_exec_query_profiles AS deqp
                         	       WHERE deqp.thread_id > 0
								   AND deqp.session_id <> @@SPID
                         	       AND EXISTS 
                         	       	(
                         	       		SELECT 1/0
                         	       		FROM   sys.dm_exec_query_profiles AS deqp2
                         	       		WHERE deqp.session_id = deqp2.session_id
                         	       		AND   deqp.node_id = deqp2.node_id
                         	       		AND   deqp2.thread_id > 0
                         	       		GROUP BY deqp2.session_id, deqp2.node_id
                         	       		HAVING COUNT(deqp2.node_id) > 1
                         	       	)
                         	   ) AS x
                         ) AS y
                   WHERE y.parallelism_skew = 1
                   GROUP BY y.session_id, 
                            y.request_id, 
                            y.parallelism_skew;
                   
                   /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */
				   IF (@Debug = 1)
				   BEGIN
				        RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT;
				   END

                   INSERT INTO #BlitzFirstResults 
                   (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan)
                   SELECT 42 AS CheckID,
                          100 AS Priority,
                          ''Query Performance'' AS FindingsGroup,
                          ''Queries with 10000x cardinality misestimations'' AS Findings,
                          ''https://www.brentozar.com/go/skewedup'' AS URL,
                          ''The query on SPID '' 
                              + RTRIM(b.session_id) 
                              + '' has been running for ''
                              + RTRIM(r.total_elapsed_time / 1000)
                              + '' seconds,  with a large cardinality misestimate'' AS Details,
                          ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt,
                          r.start_time,
                          s.login_name,
                          s.nt_user_name,
                          s.program_name,
                          s.host_name,
                          r.database_id,
                          DB_NAME(r.database_id),
                          dest.text,
                          s.open_transaction_count,
                          r.query_hash, ';

				IF @dm_exec_query_statistics_xml = 1
					SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan ';
				ELSE
					SET @StringToExecute = @StringToExecute + N' qp.query_plan ';

				SET @StringToExecute = @StringToExecute + N'
                  FROM @bad_estimate AS b
                  JOIN sys.dm_exec_requests AS r
                  ON r.session_id = b.session_id
                  AND r.request_id = b.request_id
                  JOIN sys.dm_exec_sessions AS s
                  ON s.session_id = b.session_id
                  CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest
				  CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp ';

				IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml')
				/* GitHub #3210 */
					SET @StringToExecute = N'
                   SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live ';
				  
				SET @StringToExecute = @StringToExecute + N';

                   /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */
				   IF (@Debug = 1)
				   BEGIN
				        RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT;
				   END

                   INSERT INTO #BlitzFirstResults 
                   (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan)
                   SELECT 43 AS CheckID,
                          100 AS Priority,
                          ''Query Performance'' AS FindingsGroup,
                          ''Queries with 10000x skewed parallelism'' AS Findings,
                          ''https://www.brentozar.com/go/skewedup'' AS URL,
                          ''The query on SPID '' 
                              + RTRIM(p.session_id) 
                              + '' has been running for ''
                              + RTRIM(r.total_elapsed_time / 1000)
                              + '' seconds,  with a parallel threads doing uneven work.'' AS Details,
                          ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt,
                          r.start_time,
                          s.login_name,
                          s.nt_user_name,
                          s.program_name,
                          s.host_name,
                          r.database_id,
                          DB_NAME(r.database_id),
                          dest.text,
                          s.open_transaction_count,
                          r.query_hash, ';

				IF @dm_exec_query_statistics_xml = 1
					SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan ';
				ELSE
					SET @StringToExecute = @StringToExecute + N' qp.query_plan ';

				SET @StringToExecute = @StringToExecute + N'
                  FROM @parallelism_skew AS p
                  JOIN sys.dm_exec_requests AS r
                  ON r.session_id = p.session_id
                  AND r.request_id = p.request_id
                  JOIN sys.dm_exec_sessions AS s
                  ON s.session_id = p.session_id
                  CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest
				  CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp ';

				IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml')
					SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live ';
				  
				  
				SET @StringToExecute = @StringToExecute + N';';

	          EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug;
			END
   
        END
    END

    /* 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. */
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 24',10,1) WITH NOWAIT;
		END

        /* Traditionally, we use 100 - SystemIdle here.
        However, SystemIdle is always 0 on Linux.
        So if we are on Linux, we use ProcessUtilization instead.
		This is the approach found in
		https://techcommunity.microsoft.com/blog/sqlserver/sql-server-cpu-usage-available-in-sys-dm-os-ring-buffers-dmv-starting-sql-server/825361 */
        INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
        SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(CpuUsage AS NVARCHAR(20)) + N'%.', CpuUsage, 'https://www.brentozar.com/go/cpu'
        FROM (
            SELECT CASE WHEN @is_windows_operating_system = 1 THEN 100 - SystemIdle ELSE ProcessUtilization END AS CpuUsage
            FROM (
                SELECT record,
                    record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle,
                    record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS ProcessUtilization
                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 ShreddedCpuXml
        ) AS OsCpu
        WHERE CpuUsage >= 50;

        /* CPU Utilization - CheckID 23 */
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 23',10,1) WITH NOWAIT;
		END

        IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/
			WITH y
				AS
				 (
                     /* See earlier comments about SystemIdle on Linux. */
					 SELECT      CONVERT(VARCHAR(5), CASE WHEN @is_windows_operating_system = 1 THEN 100 - ca.c.value('.', 'INT') ELSE ca2.p.value('.', 'INT') END) AS cpu_usage,
								 CONVERT(VARCHAR(30), rb.event_date) AS event_date,
								 CONVERT(VARCHAR(8000), rb.record) AS record,
								 event_date as event_date_raw
					 FROM
								 (   SELECT CONVERT(XML, dorb.record) AS record,
											DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date
									 FROM   sys.dm_os_ring_buffers AS dorb
									 CROSS JOIN
											( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts
									 WHERE  dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
									 AND    record LIKE '%<SystemHealth>%' ) AS rb
					 CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c)
                     CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization') AS ca2(p)
				 )
			INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt)
			SELECT TOP 1 
					23, 
					250, 
					'Server Info', 
					'CPU Utilization', 
					y.cpu_usage + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), 
					y.cpu_usage	, 
					'https://www.brentozar.com/go/cpu',
					STUFF(( SELECT TOP 2147483647
							  CHAR(10) + CHAR(13)
							+ y2.cpu_usage
							+ '% ON ' 
							+ y2.event_date 
							+ ' Ring buffer details:  '
							+ y2.record
					FROM   y AS y2
					ORDER BY y2.event_date_raw DESC
					FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query
			FROM   y
			ORDER BY y.event_date_raw DESC;

		
		/* Highlight if non SQL processes are using >25% CPU - CheckID 28 */
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 28',10,1) WITH NOWAIT;
		END

		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), 'https://www.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
            /* SystemIdle is always 0 on Linux, as described earlier.
            We therefore cannot distinguish between a totally idle Linux server and
            a Linux server where SQL Server is being crushed by other CPU-heavy processes.
            We therefore disable this check on Linux. */
            AND @is_windows_operating_system = 1;
		
        END; /* IF @Seconds < 30 */

    /* Query Problems - Statistics Updated Recently - CheckID 44 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 44',10,1) WITH NOWAIT;
	END

	IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb'))
		AND @Seconds > 0
	BEGIN
		CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT);
		IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties')
		BEGIN
			/* We don't want to hang around to obtain locks */
			SET LOCK_TIMEOUT 0;

			IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/
				SET @StringToExecute = N'USE [?];' + @LineFeed;
			ELSE
				SET @StringToExecute = N'';

            SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed +
                                    'BEGIN TRY' + @LineFeed +
                                    '    INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed +
                                    '    SELECT HowToStopIt = ' + @LineFeed +
                                    '                QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed +
                                    '                QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed +
                                    '                QUOTENAME(obj.name) +' + @LineFeed +
                                    '                N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed +
                                    '                N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed +
                                    '                N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed +
                                    '                CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' +  ' + @LineFeed +
                                    '                N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed +
                                    '        sp.rows' + @LineFeed +
                                    '    FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed +
                                    '    INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id  ' + @LineFeed +
                                    '    CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp  ' + @LineFeed +
                                    '    WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed +
                                    '    AND obj.is_ms_shipped = 0' + @LineFeed +
                                    '    AND ''[?]'' <> ''[tempdb]'';' + @LineFeed +
                                    'END TRY' + @LineFeed +
                                    'BEGIN CATCH' + @LineFeed +
                                    '    IF (ERROR_NUMBER() = 1222)' + @LineFeed +
                                    '    BEGIN ' + @LineFeed +
                                    '        INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed +
                                    '        SELECT HowToStopIt = ' + @LineFeed +
                                    '                    QUOTENAME(DB_NAME()) +' + @LineFeed +
                                    '                    N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed +
                                    '                    N''  this is likely due to an Index operation in Progress'',' + @LineFeed +
                                    '            -1' + @LineFeed +
                                    '    END' + @LineFeed +
                                    '    ELSE' + @LineFeed +
                                    '    BEGIN' + @LineFeed +
                                    '        INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed +
                                    '        SELECT HowToStopIt = ' + @LineFeed +
                                    '                    QUOTENAME(DB_NAME()) +' + @LineFeed +
                                    '                    N'' No information could be retrieved as a result of error: ''+' + @LineFeed +
                                    '                    CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed +
                                    '                    N'' with message: ''+' + @LineFeed +
                                    '                    CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed +
                                    '            -1' + @LineFeed +
                                    '    END' + @LineFeed +
                                    'END CATCH'                          
                                    ;

			IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/
			BEGIN
				BEGIN TRY
					EXEC sp_MSforeachdb @StringToExecute;
				END TRY
				BEGIN CATCH
					IF (ERROR_NUMBER() = 1222)
					BEGIN
						INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)
						SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' +
											 N' this is likely due to an Index operation in Progress', -1;
					END
					ELSE
					BEGIN;
						THROW;
					END
				END CATCH
			END
			ELSE
				EXEC(@StringToExecute);

			/* Set timeout back to a default value of -1 */
			SET LOCK_TIMEOUT -1;
		END;
		
		/* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */
		IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1)
			INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
			SELECT 44 AS CheckId,
					50 AS Priority,
					'Query Problems' AS FindingGroup,
					'Statistics Updated Recently' AS Finding,
					'https://www.brentozar.com/go/stats' AS URL,
					'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed
						+ 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed
						+ 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed
						+ 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed
						+ 'Be on the lookout for sudden parameter sniffing issues after this time range.',
					HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10))
						FROM #UpdatedStats
						ORDER BY RowsForSorting DESC
						FOR XML PATH(''));

	END

    /* Server Performance - Azure Operation Ongoing  - CheckID 53 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 53',10,1) WITH NOWAIT;
	END
	IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_operation_status')
		BEGIN
			INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			SELECT 53 AS CheckID,
				50 AS Priority,
				'Server Performance' AS FindingGroup,
				'Azure Operation ' + CASE WHEN state IN (2, 3, 5) THEN 'Ended Recently' ELSE 'Ongoing' END AS Finding,
				'https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-operation-status-azure-sql-database' AS URL,
				N'Operation: ' + operation + N' State: ' + state_desc + N' Percent Complete: ' + CAST(percent_complete AS NVARCHAR(10)) + @LineFeed
                    + N' On: ' + CAST(resource_type_desc AS NVARCHAR(100)) + N':' + CAST(major_resource_id AS NVARCHAR(100)) + @LineFeed
                    + N' Started: ' + CAST(start_time AS NVARCHAR(100)) + N' Last Modified Time: ' + CAST(last_modify_time AS NVARCHAR(100)) + @LineFeed
                    + N' For more information, query SELECT * FROM sys.dm_operation_status; ' AS Details
			FROM sys.dm_operation_status
		END


    /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 49',10,1) WITH NOWAIT;
	END
	IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%64%' AND SERVERPROPERTY('EngineEdition') <> 5
		BEGIN
		IF @logical_processors <= 4
			SET @max_worker_threads = 512;
		ELSE IF @logical_processors > 64 AND 
			((@v = 13 AND @build >= 5026) OR @v >= 14)
			SET @max_worker_threads = 512 + ((@logical_processors - 4) * 32)
		ELSE
			SET @max_worker_threads = 512 + ((@logical_processors - 4) * 16)

		IF @max_worker_threads > 0
			BEGIN
				INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
				SELECT 49 AS CheckID,
					210 AS Priority,
					'Potential Upcoming Problems' AS FindingGroup,
					'High Number of Connections' AS Finding,
					'https://www.brentozar.com/archive/2014/05/connections-slow-sql-server-threadpool/' AS URL,
					'There are ' + CAST(SUM(1) AS VARCHAR(20)) + ' open connections, which would lead to ' + @LineFeed + 'worker thread exhaustion and THREADPOOL waits' + @LineFeed + 'if they all ran queries at the same time.' AS Details
			FROM sys.dm_exec_connections c
			HAVING SUM(1) > @max_worker_threads;
			END
		END



    /* Server Performance - Memory Dangerously Low Recently - CheckID 52 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 52',10,1) WITH NOWAIT;
	END
	IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history')
		BEGIN
			INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
			SELECT TOP 1 52 AS CheckID,
				10 AS Priority,
				'Server Performance' AS FindingGroup,
				'Memory Dangerously Low Recently' AS Finding,
				'https://www.brentozar.com/go/memhist' AS URL,
				N'As recently as ' + CONVERT(NVARCHAR(19), snapshot_time, 120) + N', memory health issues are being reported in sys.dm_os_memory_health_history, indicating extreme memory pressure.' AS Details
			FROM sys.dm_os_memory_health_history
			WHERE severity_level > 1;
		END


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


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

    IF @total_cpu_usage IN (0, 1)
	BEGIN
	    EXEC sys.sp_executesql
		    @get_thread_time_ms,
			N'@thread_time_ms FLOAT OUTPUT',
			@thread_time_ms OUTPUT;		    
	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. */
	SET @StringToExecute = N'
		INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_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, 
			@thread_time_ms AS thread_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 ';

	IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/
		SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os ';
	ELSE
		SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os ';

	SET @StringToExecute = @StringToExecute + N'
		) x
		   WHERE NOT EXISTS 
		   (
                SELECT *
				FROM ##WaitCategories AS wc
				WHERE wc.WaitType = x.wait_type
				AND wc.Ignorable = 1
		   )
		GROUP BY x.Pass, x.SampleTime, x.wait_type
		ORDER BY sum_wait_time_ms DESC;';
		
		EXEC sys.sp_executesql
	        @StringToExecute,
          N'@StartSampleTime DATETIMEOFFSET,
	        @Seconds INT,
	        @thread_time_ms FLOAT',
	        @StartSampleTime,
	        @Seconds,
	        @thread_time_ms;

    WITH w AS
	(
	    SELECT
		    total_waits =
			    CONVERT
				(
				    FLOAT,
			        SUM(ws.wait_time_ms)
				)
		FROM #WaitStats AS ws
		WHERE Pass = 2
	)
    UPDATE ws
	    SET	ws.thread_time_ms += w.total_waits
	FROM #WaitStats AS ws
	CROSS JOIN w
	WHERE ws.Pass = 2
	OPTION(RECOMPILE);
	

    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;


    /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */
    IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1
        BEGIN
			IF (@Debug = 1)
			BEGIN
				RAISERROR('Running CheckID 18',10,1) WITH NOWAIT;
			END

            INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
            VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.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 - Most Resource-Intensive Queries - CheckID 17 */
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 17',10,1) WITH NOWAIT;
		END

        INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash)
        SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.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,
            qsNow.query_hash
            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. */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 6',10,1) WITH NOWAIT;
	END

    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, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */
        N'https://www.sqlskills.com/help/waits/' + LOWER(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(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) 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 - Poison Wait Detected - CheckID 30 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 30',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt)
    SELECT 30 AS CheckID,
        10 AS Priority,
        'Server Performance' AS FindingGroup,
        'Poison Wait Detected: ' + wNow.wait_type AS Finding,
        N'https://www.brentozar.com/go/poison/#' + 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(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) 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_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') 
	  AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000);


    /* Server Performance - Slow Data File Reads - CheckID 11 */
	IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%')
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 11',10,1) WITH NOWAIT;
		END

		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,
			'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL,
			'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed
				+ '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;
	END;	

    /* Server Performance - Slow Log File Writes - CheckID 12 */
	IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%')
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 12',10,1) WITH NOWAIT;
		END

		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,
			'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL,
			'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed
				+ '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;
	END;


    /* Query Problems - Deadlocks - CheckID 51 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 51',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
    SELECT 51 AS CheckID,
        100 AS Priority,
        'Query Problems' AS FindingGroup,
        'Deadlocks' AS Finding,
        ' https://www.brentozar.com/go/deadlocks' AS URL,
        'Number of deadlocks 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,
        'Check sp_BlitzLock to find which indexes and queries to tune.'  AS HowToStopIt
    FROM #PerfmonStats ps
    WHERE ps.Pass = 2
        AND counter_name = 'Number of Deadlocks/sec'
        AND instance_name LIKE '_Total%'
        AND value_delta > 0;


    /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 13',10,1) WITH NOWAIT;
	END

    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,
        'https://www.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 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 14',10,1) WITH NOWAIT;
	END

    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,
        'https://www.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 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 15',10,1) WITH NOWAIT;
	END

    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,
        'https://www.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,
        'To find the queries that are compiling, start with:' + @LineFeed
            + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed
            + 'If dynamic SQL or non-parameterized strings are involved, 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 psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */
        AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */
        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 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 16',10,1) WITH NOWAIT;
	END

    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,
        'https://www.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,
        'To find the queries that are being forced to recompile, start with:' + @LineFeed
            + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed
            + 'Examine those plans to 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 psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */
        AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */
        AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */

    /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 29',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
    SELECT 29 AS CheckID,
        40 AS Priority,
        'Table Problems' AS FindingGroup,
        'Forwarded Fetches/Sec High' AS Finding,
        'https://www.brentozar.com/go/fetch/' AS URL,
        CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed
            + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details,
        'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt
    FROM #PerfmonStats ps
        INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100
    WHERE ps.Pass = 2
        AND ps.object_name = @ServiceName + ':Access Methods'
        AND ps.counter_name = 'Forwarded Records/sec'
        AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */

	/* Check for temp objects with high forwarded fetches.
		This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */
	IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29)
		BEGIN
		SET @StringToExecute = N'
		INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
		SELECT TOP 10 29 AS CheckID,
			40 AS Priority,
			''Table Problems'' AS FindingGroup,
			''Forwarded Fetches/Sec High: TempDB Object'' AS Finding,
			''https://www.brentozar.com/go/fetch/'' AS URL,
			CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' +
				CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table ''
				WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id)
				ELSE ''a temp table '' + OBJECT_NAME(os.object_id)
				END AS Details,
			''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt
		FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os
			LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id
				AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count
		WHERE os.database_id = DB_ID(''tempdb'')
			AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100
		ORDER BY os.forwarded_fetch_count DESC;'

		EXECUTE sp_executesql @StringToExecute;
		END

    /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 31',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
    SELECT 31 AS CheckID,
        50 AS Priority,
        'In-Memory OLTP' AS FindingGroup,
        'Garbage Collection in Progress' AS Finding,
        'https://www.brentozar.com/go/garbage/' AS URL,
        CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)'  + @LineFeed 
            + 'This can happen for a few reasons: ' + @LineFeed
            + 'Memory-Optimized TempDB, or ' + @LineFeed
            + 'transactional workloads that constantly insert/delete data in In-Memory OLTP tables, or ' + @LineFeed
            + 'memory pressure (causing In-Memory OLTP to shrink its footprint) or' AS Details,
        'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt
    FROM #PerfmonStats ps
        INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100
    WHERE ps.Pass = 2
        AND ps.object_name LIKE '%XTP Garbage Collection'
        AND ps.counter_name = 'Rows processed/sec'
        AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */

    /* In-Memory OLTP - Transactions Aborted - CheckID 32 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 32',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
    SELECT 32 AS CheckID,
        100 AS Priority,
        'In-Memory OLTP' AS FindingGroup,
        'Transactions Aborted' AS Finding,
        'https://www.brentozar.com/go/aborted/' AS URL,
        CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)'  + @LineFeed 
            + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details,
        'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt
    FROM #PerfmonStats ps
        INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100
    WHERE ps.Pass = 2
        AND ps.object_name LIKE '%XTP Transactions'
        AND ps.counter_name = 'Transactions aborted/sec'
        AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */

    /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 33',10,1) WITH NOWAIT;
	END

    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
    SELECT 32 AS CheckID,
        100 AS Priority,
        'Query Problems' AS FindingGroup,
        'Suboptimal Plans/Sec High' AS Finding,
        'https://www.brentozar.com/go/suboptimal/' AS URL,
        CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)'  + @LineFeed 
            + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details,
        'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt
    FROM #PerfmonStats ps
        INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100
    WHERE ps.Pass = 2
        AND ps.object_name = @ServiceName + ':Workload GroupStats' 
        AND ps.counter_name = 'Suboptimal plans/sec'
        AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */

    /* Azure Performance - Database is Maxed Out - CheckID 41 */
    IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 41',10,1) WITH NOWAIT;
		END

        INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt)
        SELECT 41 AS CheckID,
            10 AS Priority,
            'Azure Performance' AS FindingGroup,
            'Database is Maxed Out' AS Finding,
            'https://www.brentozar.com/go/maxedout' AS URL,
            N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed
                + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed
                + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed
                + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed
                + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed
                + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details,
            'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt
        FROM sys.dm_db_resource_stats s
        WHERE s.end_time >= DATEADD(MI, -5, GETDATE())
          AND (avg_cpu_percent > 90
               OR avg_data_io_percent >= 90
               OR avg_log_write_percent >=90
               OR max_worker_percent >= 90
               OR max_session_percent >= 90);
	END

    /* Server Info - Thread Time - CheckID 50 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 50',10,1) WITH NOWAIT;
	END

    ;WITH max_batch AS (
        SELECT MAX(SampleTime) AS SampleTime
        FROM #WaitStats
    )
    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
    SELECT TOP 1 
        50 AS CheckID,
        251 AS Priority,
        'Server Info' AS FindingGroup,
        'Thread Time' AS Finding,
        LTRIM(
            CASE 
                WHEN c.[TotalThreadTimeSeconds] >= 86400 THEN 
                    CAST(c.[TotalThreadTimeSeconds] / 86400 AS VARCHAR) + 'd '
                ELSE '' 
            END +
            CASE 
                WHEN c.[TotalThreadTimeSeconds] % 86400 >= 3600 THEN 
                    CAST((c.[TotalThreadTimeSeconds] % 86400) / 3600 AS VARCHAR) + 'h '
                ELSE '' 
            END +
            CASE 
                WHEN c.[TotalThreadTimeSeconds] % 3600 >= 60 THEN 
                    CAST((c.[TotalThreadTimeSeconds] % 3600) / 60 AS VARCHAR) + 'm '
                ELSE '' 
            END +
            CASE 
                WHEN c.[TotalThreadTimeSeconds] % 60 > 0 OR c.[TotalThreadTimeSeconds] = 0 THEN 
                    CAST(c.[TotalThreadTimeSeconds] % 60 AS VARCHAR) + 's'
                ELSE '' 
            END
        ) AS Details,
        CAST(c.[TotalThreadTimeSeconds] AS DECIMAL(18,1)) AS DetailsInt,
        'https://www.brentozar.com/go/threadtime' 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 CAST((wd2.thread_time_ms - wd1.thread_time_ms) / 1000 AS INT) AS TotalThreadTimeSeconds
    ) AS c;

    /* Server Info - Batch Requests per Sec - CheckID 19 */
	IF (@Debug = 1)
	BEGIN
		RAISERROR('Running CheckID 19',10,1) WITH NOWAIT;
	END

    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,
        'https://www.brentozar.com/go/measure' AS URL,
        CAST(CAST(ps.value_delta AS MONEY) / (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
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 25',10,1) WITH NOWAIT;
		END

		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,
		    'https://www.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';
	END

    /* Server Info - SQL Re-Compilations/sec - CheckID 26 */
    IF @ExpertMode >= 1
	BEGIN
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 26',10,1) WITH NOWAIT;
		END

		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,
		    'https://www.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';
	END

    /* Server Info - Wait Time per Core per Sec - CheckID 20 */
    IF @Seconds > 0
    BEGIN;
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 20',10,1) WITH NOWAIT;
		END;

        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 20 AS CheckID,
            250 AS Priority,
            'Server Info' AS FindingGroup,
            'Wait Time per Core per Sec' AS Finding,
            'https://www.brentozar.com/go/measure' AS URL,
            CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details,
            (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt
        FROM cores i
          CROSS JOIN waits1
          CROSS JOIN waits2;
    END;

	IF @Seconds > 0
	BEGIN
    INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
    SELECT
        47 AS CheckId,
    	50 AS Priority,
    	'Query Problems' AS FindingsGroup,
    	'High Percentage Of Runnable Queries' AS Finding, 
    	'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 
    	'On the ' 
    	+ CASE WHEN y.pass = 1 
    	       THEN '1st' 
    		   ELSE '2nd'
    	   END
    	+ ' pass, '
    	+ RTRIM(y.runnable_pct)
    	+ '% of your queries were waiting to get on a CPU to run. '
    	+ ' This can indicate CPU pressure.'
    FROM
    (
        SELECT 
            2 AS pass,
            x.total, 
        	x.runnable,
            CONVERT(decimal(5,2),
                (
                    x.runnable / 
                        (1. * NULLIF(x.total, 0))
                )
            ) * 100. AS runnable_pct
        FROM 
        (
            SELECT 
                COUNT_BIG(*) AS total, 
                SUM(CASE WHEN status = 'runnable' 
        		         THEN 1 
        				 ELSE 0 
        		    END) AS runnable
            FROM sys.dm_exec_requests
            WHERE session_id > 50
        ) AS x
    ) AS y
    WHERE y.runnable_pct > 20.;
	END

    /* If we're waiting 30+ seconds, run these checks 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. */
    IF @Seconds >= 30
    BEGIN
        /* Server Performance - High CPU Utilization CheckID 24 */
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 24',10,1) WITH NOWAIT;
		END

        INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
        SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(CpuUsage AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), CpuUsage, 'https://www.brentozar.com/go/cpu'
        FROM (
            SELECT record,
                CASE WHEN @is_windows_operating_system = 1 THEN 100 - SystemIdle ELSE ProcessUtilization END AS CpuUsage
            FROM (
                SELECT record,
                    record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle,
                    /* See earlier comments about SystemIdle on Linux. */
                    record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS ProcessUtilization
                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 ShreddedCpuXml
        ) AS OsCpu
        WHERE CpuUsage >= 50;

        /* Server Performance - CPU Utilization CheckID 23 */
		IF (@Debug = 1)
		BEGIN
			RAISERROR('Running CheckID 23',10,1) WITH NOWAIT;
		END

        INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL)
        SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(CpuUsage AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), CpuUsage, 'https://www.brentozar.com/go/cpu'
        FROM (
            SELECT record,
                CASE WHEN @is_windows_operating_system = 1 THEN 100 - SystemIdle ELSE ProcessUtilization END AS CpuUsage
            FROM (
                SELECT record,
                    record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle,
                    /* See earlier comments about SystemIdle on Linux. */
                    record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS ProcessUtilization
                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 ShreddedCpuXml
        ) AS OsCpu;

	END; /* IF @Seconds >= 30 */

	IF /* Let people on <2016 know about the thread time column */
	(
	    @Seconds > 0
		AND @total_cpu_usage = 0
	)
	BEGIN
	    INSERT INTO
		    #BlitzFirstResults
		(
		    CheckID,
			Priority,
			FindingsGroup,
			Finding,
			Details,
			URL
		)
		SELECT
		    48,
			254,
			N'Informational',
			N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable',
			N'The oldest plan in your cache is from ' +
			CONVERT(nvarchar(30), MIN(s.creation_time)) +
			N' and your server was last restarted on ' +
			CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)),
			N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql'
        FROM sys.dm_exec_query_stats AS s
        CROSS JOIN sys.dm_os_sys_info AS o
        OPTION(RECOMPILE);
	END /* Let people on <2016 know about the thread time column */

    /* 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' ,
                  'http://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' ,
                  'http://FirstResponderKit.org/' ,
                  'To get help or add your own contributions, join us at http://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' ,
                  'http://FirstResponderKit.org/' ,
                  'We hope you found this tool useful.'
                );

                /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */
				IF (@Debug = 1)
				BEGIN
					RAISERROR('Running CheckID 27',10,1) WITH NOWAIT;
				END

                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 ,
                                        'http://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;

    IF @CheckServerInfo = 0 /* Github #1680 */
        BEGIN
        DELETE #BlitzFirstResults
          WHERE FindingsGroup = 'Server Info';
        END

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


    /* If they want to run sp_BlitzCache and export to table, go for it. */
    IF @OutputTableNameBlitzCache IS NOT NULL
        AND @OutputDatabaseName IS NOT NULL
        AND @OutputSchemaName IS NOT NULL
        AND EXISTS ( SELECT *
                     FROM   sys.databases
                     WHERE  QUOTENAME([name]) = @OutputDatabaseName)
    BEGIN


		RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT;


        /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */
        IF EXISTS (SELECT * FROM sys.objects o 
                        INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack'
                        INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride'
                        WHERE o.name = 'sp_BlitzCache')
            BEGIN
                /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */
                SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
                    + @OutputDatabaseName
                    + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
                    + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
                    + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM '
                    + @OutputDatabaseName + '.'
                    + @OutputSchemaName + '.'
                    + QUOTENAME(@OutputTableNameBlitzCache)
                    + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;';
                EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT;

                /* If there's no data, let's just analyze the last 15 minutes of the plan cache */
                IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60
                    SET @BlitzCacheMinutesBack = 15;

                IF(@OutputType = 'NONE')
				BEGIN 
                    EXEC sp_BlitzCache
                        @OutputDatabaseName = @UnquotedOutputDatabaseName,
                        @OutputSchemaName = @UnquotedOutputSchemaName,
                        @OutputTableName = @OutputTableNameBlitzCache,
                        @CheckDateOverride = @StartSampleTime,
                        @SortOrder = 'all',
                        @SkipAnalysis = @BlitzCacheSkipAnalysis,
                        @MinutesBack = @BlitzCacheMinutesBack,
                        @Debug = @Debug,
					    @OutputType = @OutputType
				    ;
				END;
				ELSE
				BEGIN
				
				    EXEC sp_BlitzCache
                        @OutputDatabaseName = @UnquotedOutputDatabaseName,
                        @OutputSchemaName = @UnquotedOutputSchemaName,
                        @OutputTableName = @OutputTableNameBlitzCache,
                        @CheckDateOverride = @StartSampleTime,
                        @SortOrder = 'all',
                        @SkipAnalysis = @BlitzCacheSkipAnalysis,
                        @MinutesBack = @BlitzCacheMinutesBack,
                        @Debug = @Debug
				    ;
				END;

                /* Delete history older than @OutputTableRetentionDays */
                SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
                    + @OutputDatabaseName
                    + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
                    + @OutputSchemaName + ''') DELETE '
                    + @OutputDatabaseName + '.'
                    + @OutputSchemaName + '.'
                    + QUOTENAME(@OutputTableNameBlitzCache)
                    + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;';
                EXEC sp_executesql @StringToExecute,
					N'@SrvName NVARCHAR(128), @CheckDate date',
					@LocalServerName, @OutputTableCleanupDate;


            END;

        ELSE 
            BEGIN
                /* No sp_BlitzCache found, or it's outdated - CheckID 36 */
				IF (@Debug = 1)
				BEGIN
					RAISERROR('Running CheckID 36',10,1) WITH NOWAIT;
				END

                INSERT  INTO #BlitzFirstResults
                        ( CheckID ,
                            Priority ,
                            FindingsGroup ,
                            Finding ,
                            URL ,
                            Details
                        )
                        SELECT 36 AS CheckID ,
                                0 AS Priority ,
                                'Outdated or Missing sp_BlitzCache' AS FindingsGroup ,
                                'Update Your sp_BlitzCache' AS Finding ,
                                'http://FirstResponderKit.org/' AS URL ,
                                'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details;
            END;

    	RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT;

    END; /* End running sp_BlitzCache */

    /* @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,
                QueryHash BINARY(8) NULL,
                JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)),
                PRIMARY KEY CLUSTERED (ID ASC));';

        EXEC(@StringToExecute);

        /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */
        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
        SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
            WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'')
            ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;';
        EXEC(@StringToExecute);

        /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */
        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableName;
        SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
            WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'')
            ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));';
        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, QueryHash) SELECT '
            + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details';
		
		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;

        /* Delete history older than @OutputTableRetentionDays */
        SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
            + @OutputDatabaseName
            + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
            + @OutputSchemaName + ''') DELETE '
            + @OutputDatabaseName + '.'
            + @OutputSchemaName + '.'
            + @OutputTableName
            + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;';
		
		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate date',
			@LocalServerName, @OutputTableCleanupDate;

    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,
                QueryHash BINARY(8) NULL,
                JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)),
                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 '
            + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details';
		
		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;
    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) ,
                PRIMARY KEY CLUSTERED (ID ASC));';

		EXEC(@StringToExecute);

        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableNameFileStats_View;

        /* If the view exists without the most recently added columns, drop it. See Github #2162. */
        IF OBJECT_ID(@ObjectFullName) IS NOT NULL
            BEGIN
            SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'')
                DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';';

            EXEC(@StringToExecute);
            END

        /* Create the view */
        IF OBJECT_ID(@ObjectFullName) IS NULL
            BEGIN
            SET @StringToExecute = 'USE '
                + @OutputDatabaseName
                + '; EXEC (''CREATE VIEW '
                + @OutputSchemaName + '.'
                + @OutputTableNameFileStats_View + ' AS ' + @LineFeed
                + 'WITH RowDates as' + @LineFeed
                + '(' + @LineFeed
                + '        SELECT ' + @LineFeed
                + '                ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed
                + '                [CheckDate]' + @LineFeed
                + '        FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed
                + '        GROUP BY [ServerName], [CheckDate]' + @LineFeed
                + '),' + @LineFeed
                + 'CheckDates as' + @LineFeed
                + '(' + @LineFeed
                + '        SELECT ThisDate.CheckDate,' + @LineFeed
                + '               LastDate.CheckDate as PreviousCheckDate' + @LineFeed
                + '        FROM RowDates ThisDate' + @LineFeed
                + '        JOIN RowDates LastDate' + @LineFeed
                + '        ON ThisDate.ID = LastDate.ID + 1' + @LineFeed
                + ')' + @LineFeed
                + '     SELECT f.ServerName,' + @LineFeed
                + '            f.CheckDate,' + @LineFeed
                + '            f.DatabaseID,' + @LineFeed
                + '            f.DatabaseName,' + @LineFeed
                + '            f.FileID,' + @LineFeed
                + '            f.FileLogicalName,' + @LineFeed
                + '            f.TypeDesc,' + @LineFeed
                + '            f.PhysicalName,' + @LineFeed
                + '            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' + @LineFeed
                + '                                           WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed
                + '                                           THEN 0' + @LineFeed
                + '                                           ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) /     (f.num_of_reads   -           fPrior.num_of_reads)' + @LineFeed
                + '                                       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' + @LineFeed
                + '                                            WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed
                + '                                            THEN 0' + @LineFeed
                + '                                            ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) /         (f.num_of_writes   -       fPrior.num_of_writes)' + @LineFeed
                + '                                        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
                + '            f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + '     FROM   ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed
                + '            INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed
                + '            INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName =                 fPrior.ServerName' + @LineFeed
                + '                                                              AND f.DatabaseID = fPrior.DatabaseID' +     @LineFeed
                + '                                                              AND f.FileID = fPrior.FileID' + @LineFeed
                + '                                                              AND fPrior.CheckDate =   DATES.PreviousCheckDate'   +           @LineFeed
                + '' + @LineFeed
                + '     WHERE  f.num_of_reads >= fPrior.num_of_reads' + @LineFeed
                + '            AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed
                + '            AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')'

			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 '
            + ' @SrvName, @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 FROM #FileStats WHERE Pass = 2';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;

        /* Delete history older than @OutputTableRetentionDays */
        SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
            + @OutputDatabaseName
            + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
            + @OutputSchemaName + ''') DELETE '
            + @OutputDatabaseName + '.'
            + @OutputSchemaName + '.'
            + @OutputTableNameFileStats
            + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate date',
			@LocalServerName, @OutputTableCleanupDate;

    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,
                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 '
            + ' @SrvName, @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 FROM #FileStats WHERE Pass = 2';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;
    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,
                PRIMARY KEY CLUSTERED (ID ASC));';

		EXEC(@StringToExecute);

        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableNamePerfmonStats_View;

        /* If the view exists without the most recently added columns, drop it. See Github #2162. */
        IF OBJECT_ID(@ObjectFullName) IS NOT NULL
            BEGIN
            SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'')
                DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';';

            EXEC(@StringToExecute);
            END

        /* Create the view */
        IF OBJECT_ID(@ObjectFullName) IS NULL
            BEGIN
            SET @StringToExecute = 'USE '
                + @OutputDatabaseName
                + '; EXEC (''CREATE VIEW '
                + @OutputSchemaName + '.'
                + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed
                + 'WITH RowDates as' + @LineFeed
                + '(' + @LineFeed
                + '        SELECT ' + @LineFeed
                + '                ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed
                + '                [CheckDate]' + @LineFeed
                + '        FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed
                + '        GROUP BY [ServerName], [CheckDate]' + @LineFeed
                + '),' + @LineFeed
                + 'CheckDates as' + @LineFeed
                + '(' + @LineFeed
                + '        SELECT ThisDate.CheckDate,' + @LineFeed
                + '               LastDate.CheckDate as PreviousCheckDate' + @LineFeed
                + '        FROM RowDates ThisDate' + @LineFeed
                + '        JOIN RowDates LastDate' + @LineFeed
                + '        ON ThisDate.ID = LastDate.ID + 1' + @LineFeed
                + ')' + @LineFeed
                + 'SELECT' + @LineFeed
                + '       pMon.[ServerName]' + @LineFeed
                + '      ,pMon.[CheckDate]' + @LineFeed
                + '      ,pMon.[object_name]' + @LineFeed
                + '      ,pMon.[counter_name]' + @LineFeed
                + '      ,pMon.[instance_name]' + @LineFeed
                + '      ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed
                + '      ,pMon.[cntr_value]' + @LineFeed
                + '      ,pMon.[cntr_type]' + @LineFeed
                + '      ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed
                + '      ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed
                + '      ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + '  FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed
                + '  INNER HASH JOIN CheckDates Dates' + @LineFeed
                + '  ON Dates.CheckDate = pMon.CheckDate' + @LineFeed
                + '  JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed
                + '  ON  Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed
                + '      AND pMon.[ServerName]    = pMonPrior.[ServerName]   ' + @LineFeed
                + '      AND pMon.[object_name]   = pMonPrior.[object_name]  ' + @LineFeed
                + '      AND pMon.[counter_name]  = pMonPrior.[counter_name] ' + @LineFeed
                + '      AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed
                + '    WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')'

			EXEC(@StringToExecute);
            END

        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableNamePerfmonStatsActuals_View;

        /* If the view exists without the most recently added columns, drop it. See Github #2162. */
        IF OBJECT_ID(@ObjectFullName) IS NOT NULL
            BEGIN
            SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'')
                DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';';

            EXEC(@StringToExecute);
            END

        /* Create the second view */
        IF OBJECT_ID(@ObjectFullName) IS NULL
            BEGIN
            SET @StringToExecute = 'USE '
                + @OutputDatabaseName
                + '; EXEC (''CREATE VIEW '
                + @OutputSchemaName + '.'
                + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed
                + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed
                + '(' + @LineFeed
                + '    SELECT ServerName,' + @LineFeed
                + '           object_name,' + @LineFeed
                + '           instance_name,' + @LineFeed
                + '           counter_name,' + @LineFeed
                + '           CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END    AS   counter_join,' + @LineFeed
                + '           CheckDate,' + @LineFeed
                + '           cntr_delta' + @LineFeed
                + '    FROM   ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed
                + '    WHERE  cntr_type IN(1073874176)' + @LineFeed
                + '    AND cntr_delta <> 0' + @LineFeed
                + '),' + @LineFeed
                + 'PERF_LARGE_RAW_BASE AS' + @LineFeed
                + '(' + @LineFeed
                + '    SELECT ServerName,' + @LineFeed
                + '           object_name,' + @LineFeed
                + '           instance_name,' + @LineFeed
                + '           LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed
                + '           CheckDate,' + @LineFeed
                + '           cntr_delta' + @LineFeed
                + '    FROM   ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed
                + '    WHERE  cntr_type IN(1073939712)' + @LineFeed
                + '    AND cntr_delta <> 0' + @LineFeed
                + '),' + @LineFeed
                + 'PERF_AVERAGE_FRACTION AS' + @LineFeed
                + '(' + @LineFeed
                + '    SELECT ServerName,' + @LineFeed
                + '           object_name,' + @LineFeed
                + '           instance_name,' + @LineFeed
                + '           counter_name,' + @LineFeed
                + '           counter_name AS counter_join,' + @LineFeed
                + '           CheckDate,' + @LineFeed
                + '           cntr_delta' + @LineFeed
                + '    FROM   ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed
                + '    WHERE  cntr_type IN(537003264)' + @LineFeed
                + '    AND cntr_delta <> 0' + @LineFeed
                + '),' + @LineFeed
                + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed
                + '(' + @LineFeed
                + '    SELECT ServerName,' + @LineFeed
                + '           object_name,' + @LineFeed
                + '           instance_name,' + @LineFeed
                + '           counter_name,' + @LineFeed
                + '           CheckDate,' + @LineFeed
                + '           cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed
                + '    FROM   ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed
                + '    WHERE  cntr_type IN(272696576, 272696320)' + @LineFeed
                + '    AND cntr_delta <> 0' + @LineFeed
                + '),' + @LineFeed
                + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed
                + '(' + @LineFeed
                + '    SELECT ServerName,' + @LineFeed
                + '           object_name,' + @LineFeed
                + '           instance_name,' + @LineFeed
                + '           counter_name,' + @LineFeed
                + '           CheckDate,' + @LineFeed
                + '           cntr_value' + @LineFeed
                + '    FROM   ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed
                + '    WHERE  cntr_type IN(65792, 65536)' + @LineFeed
                + ')' + @LineFeed
                + '' + @LineFeed
                + 'SELECT NUM.ServerName,' + @LineFeed
                + '       NUM.object_name,' + @LineFeed
                + '       NUM.counter_name,' + @LineFeed
                + '       NUM.instance_name,' + @LineFeed
                + '       NUM.CheckDate,' + @LineFeed
                + '       NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed
                + '       NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + '       ' + @LineFeed
                + 'FROM   PERF_AVERAGE_BULK AS NUM' + @LineFeed
                + '       JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed
                + '                                          AND NUM.CheckDate = DEN.CheckDate' + @LineFeed
                + '                                          AND NUM.ServerName = DEN.ServerName' + @LineFeed
                + '                                          AND NUM.object_name = DEN.object_name' + @LineFeed
                + '                                          AND NUM.instance_name = DEN.instance_name' + @LineFeed
                + '                                          AND DEN.cntr_delta <> 0' + @LineFeed
                + '' + @LineFeed
                + 'UNION ALL' + @LineFeed
                + '' + @LineFeed
                + 'SELECT NUM.ServerName,' + @LineFeed
                + '       NUM.object_name,' + @LineFeed
                + '       NUM.counter_name,' + @LineFeed
                + '       NUM.instance_name,' + @LineFeed
                + '       NUM.CheckDate,' + @LineFeed
                + '       CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3))  AS cntr_value,' +         @LineFeed
                + '       NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + 'FROM   PERF_AVERAGE_FRACTION AS NUM' + @LineFeed
                + '       JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed
                + '                                          AND NUM.CheckDate = DEN.CheckDate' + @LineFeed
                + '                                          AND NUM.ServerName = DEN.ServerName' + @LineFeed
                + '                                          AND NUM.object_name = DEN.object_name' + @LineFeed
                + '                                          AND NUM.instance_name = DEN.instance_name' + @LineFeed
                + '                                          AND DEN.cntr_delta <> 0' + @LineFeed
                + 'UNION ALL' + @LineFeed
                + '' + @LineFeed
                + 'SELECT ServerName,' + @LineFeed
                + '       object_name,' + @LineFeed
                + '       counter_name,' + @LineFeed
                + '       instance_name,' + @LineFeed
                + '       CheckDate,' + @LineFeed
                + '       cntr_value,' + @LineFeed
                + '       ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + 'FROM   PERF_COUNTER_BULK_COUNT' + @LineFeed
                + '' + @LineFeed
                + 'UNION ALL' + @LineFeed
                + '' + @LineFeed
                + 'SELECT ServerName,' + @LineFeed
                + '       object_name,' + @LineFeed
                + '       counter_name,' + @LineFeed
                + '       instance_name,' + @LineFeed
                + '       CheckDate,' + @LineFeed
                + '       cntr_value,' + @LineFeed
                + '       ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + 'FROM   PERF_COUNTER_RAWCOUNT;'')';

			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 '
            + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;

        /* Delete history older than @OutputTableRetentionDays */
        SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
            + @OutputDatabaseName
            + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
            + @OutputSchemaName + ''') DELETE '
            + @OutputDatabaseName + '.'
            + @OutputSchemaName + '.'
            + @OutputTableNamePerfmonStats
            + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate date',
			@LocalServerName, @OutputTableCleanupDate;



    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,
                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))
            + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;
    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 + ''') ' + @LineFeed
			+ 'BEGIN' + @LineFeed
			+ '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 ,
                PRIMARY KEY CLUSTERED (ID));' + @LineFeed
			+ 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed
			+ '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed
			+ 'END';

        EXEC(@StringToExecute);

        /* Create the wait stats category table */
        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableNameWaitStats_Categories;
        IF OBJECT_ID(@ObjectFullName) IS NULL
            BEGIN
            SET @StringToExecute = 'USE '
                + @OutputDatabaseName
                + '; EXEC (''CREATE TABLE '
                + @OutputSchemaName + '.'
                + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')';

			EXEC(@StringToExecute);
            END;

		/* Make sure the wait stats category table has the current number of rows */
		SET @StringToExecute = 'USE '
            + @OutputDatabaseName
            + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed
			+ 'BEGIN ' + @LineFeed
			+ 'TRUNCATE TABLE '  + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed
			+ 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed
			+ 'END'')';

		EXEC(@StringToExecute);


        SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' +  @OutputTableNameWaitStats_View;

        /* If the view exists without the most recently added columns, drop it. See Github #2162. */
        IF OBJECT_ID(@ObjectFullName) IS NOT NULL
            BEGIN
            SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns 
                WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'')
                DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';';

            EXEC(@StringToExecute);
            END


        /* Create the wait stats view */
        IF OBJECT_ID(@ObjectFullName) IS NULL
            BEGIN
            SET @StringToExecute = 'USE '
                + @OutputDatabaseName
                + '; EXEC (''CREATE VIEW '
                + @OutputSchemaName + '.'
                + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed
                + 'WITH RowDates as' + @LineFeed
                + '(' + @LineFeed
                + '        SELECT ' + @LineFeed
                + '                ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed
                + '                [CheckDate]' + @LineFeed
                + '        FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed
                + '        GROUP BY [ServerName], [CheckDate]' + @LineFeed
                + '),' + @LineFeed
                + 'CheckDates as' + @LineFeed
                + '(' + @LineFeed
                + '        SELECT ThisDate.CheckDate,' + @LineFeed
                + '               LastDate.CheckDate as PreviousCheckDate' + @LineFeed
                + '        FROM RowDates ThisDate' + @LineFeed
                + '        JOIN RowDates LastDate' + @LineFeed
                + '        ON ThisDate.ID = LastDate.ID + 1' + @LineFeed
                + ')' + @LineFeed
                + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @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.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed
                + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @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
                + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed
                + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed
                + 'INNER HASH JOIN CheckDates Dates' + @LineFeed
                + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed
                + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed
			 + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed
                + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed
                + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')'

			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 '
            + ' @SrvName, @CheckDate, 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 sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;

        /* Delete history older than @OutputTableRetentionDays */
        SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
            + @OutputDatabaseName
            + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
            + @OutputSchemaName + ''') DELETE '
            + @OutputDatabaseName + '.'
            + @OutputSchemaName + '.'
            + @OutputTableNameWaitStats
            + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;';

		EXEC sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate date',
			@LocalServerName, @OutputTableCleanupDate;

    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 ,
                PRIMARY KEY CLUSTERED (ID ASC));'
            + ' INSERT '
            + @OutputTableNameWaitStats
            + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT '
            + ' @SrvName, @CheckDate, 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 sp_executesql @StringToExecute,
			N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset',
			@LocalServerName, @StartSampleTime;
    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 AND @OutputResultSets LIKE N'%Findings%'
        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 AND @OutputResultSets LIKE N'%Findings%'
        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 @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%'
            BEGIN
                /* Measure waits in hours */
                ;WITH max_batch AS (
                    SELECT MAX(SampleTime) AS SampleTime
                    FROM #WaitStats
                )
                SELECT TOP 10
                    CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample],
					CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)],
                    wd1.wait_type,
					COALESCE(wcat.WaitCategory, 'Other') AS wait_category,
                    CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)],
                    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],
					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 Hour],
                    (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits]
                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 DECIMAL(18,1)) AS [Wait Time (Seconds)],
                    CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)],
					CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)]
					) AS c
				LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType
                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 IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%'
        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,
					CAST(Details AS NVARCHAR(4000));
        END;
        ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%'
        BEGIN
            SELECT  [Priority] ,
                    [FindingsGroup] ,
                    [Finding] ,
                    [URL] ,
                    CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details,
                    CAST(LEFT([HowToStopIt],32000) AS TEXT) 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,
					CAST(Details AS NVARCHAR(4000));
        END;
        ELSE IF @ExpertMode >= 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%'
        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,
						CAST(r.Details AS NVARCHAR(4000));

            -------------------------
            --What happened: #WaitStats
            -------------------------
            IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%'
                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. AS DECIMAL(18,1)) AS [Hours Sample],
					CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)],
                    wd1.wait_type,
					COALESCE(wcat.WaitCategory, 'Other') AS wait_category,
                    CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)],
                    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],
                    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 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],
                    N'https://www.sqlskills.com/help/waits/' + LOWER(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 DECIMAL(18,1)) AS [Wait Time (Seconds)],
                    CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)],
					CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)]
					) AS c
				LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType
                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 IF @OutputResultSets LIKE N'%WaitStats%'
                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],
					c.[Total Thread Time (Seconds)],
                    wd1.wait_type,
					COALESCE(wcat.WaitCategory, 'Other') AS wait_category,
                    c.[Wait Time (Seconds)],
                    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],
                    CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 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],
                    N'https://www.sqlskills.com/help/waits/' + LOWER(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 DECIMAL(18,1)) AS [Wait Time (Seconds)],
                    CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)],
					CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)]
					) AS c
				LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType
                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
            -------------------------
            IF @OutputResultSets LIKE N'%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], [DatabaseName], [StallRank]
            FROM readstats
            WHERE StallRank <=20 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], [DatabaseName], [StallRank]
            FROM writestats
            WHERE StallRank <=20 AND [MB Read/Written] > 0
            ORDER BY Pattern, StallRank;


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

            IF @OutputResultSets LIKE N'%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
                ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name;


            -------------------------
            --What happened: #QueryStats
            -------------------------
            IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%'
			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 IF @OutputResultSets LIKE N'%BlitzCache%'
			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 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%'
    BEGIN
		IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL
		BEGIN
			PRINT N'sp_BlitzWho is not installed in the current database_files.  You can get a copy from http://FirstResponderKit.org';
		END;
		ELSE
		BEGIN
			EXEC (@BlitzWho);
		END;
    END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE'   -   What's running right now? This is the first and last result set. */

END; /* IF @LogMessage IS NULL */
END; /* ELSE IF @OutputType = 'SCHEMA' */

SET NOCOUNT OFF;
GO



/* How to run it:
EXEC dbo.sp_BlitzFirst

With extra diagnostic info:
EXEC dbo.sp_BlitzFirst @ExpertMode = 1;

Saving output to tables:
EXEC sp_BlitzFirst
  @OutputDatabaseName = 'DBAtools'
, @OutputSchemaName = 'dbo'
, @OutputTableName = 'BlitzFirst'
, @OutputTableNameFileStats = 'BlitzFirst_FileStats'
, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats'
, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats'
, @OutputTableNameBlitzCache = 'BlitzCache'
, @OutputTableNameBlitzWho = 'BlitzWho'
, @OutputType = 'none'
*/
