SQL Optima guide

Assessment reports should drive tickets

Health checks that die in slide decks waste the engagement.

SQL diagnosticSQL Server health overviewsql_server_server_health_overview.sql
Download .sql
/*
================================================================================
sql_server_server_health_overview — One-Script Daily Snapshot
================================================================================
Description:
    Simple, standalone T-SQL script that returns overall SQL Server health in
    one pass. No framework install required. Safe for production (read-only).
    PostgreSQL counterpart: postgres/pg_server_health_overview.sql

Reports:
    1.  Instance identity & uptime
    2.  CPU / scheduler pressure
    3.  Memory health
    4.  Key instance configuration
    5.  Database inventory & risk flags
    6.  Database sizes
    7.  TempDB configuration
    8.  Top file I/O latency
    9.  Backup currency
    10. Failed Agent jobs (last 24 hours)
    11. Active blocking
    12. Top wait types (since startup)
    13. Session / connection summary
    14. Always On AG status (when configured)
    15. Quick findings summary

Parameters (edit before running):
    @BackupHoursSLA     - full/diff backup SLA in hours (default 24)
    @LogBackupHoursSLA  - log backup SLA in hours for FULL recovery (default 1)

Prerequisites:
    VIEW SERVER STATE; access to msdb for backups/jobs.
    SQL Server 2016+.

Criticality: High — daily / on-call triage
Author:      Ravi Sharma
================================================================================
*/

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON;
SET QUOTED_IDENTIFIER ON;

DECLARE @BackupHoursSLA    INT = 24;
DECLARE @LogBackupHoursSLA INT = 1;
DECLARE @Now               DATETIME2(0) = SYSDATETIME();

-------------------------------------------------------------------------------
-- 1. Instance identity & uptime
-------------------------------------------------------------------------------
PRINT '=== 1. INSTANCE IDENTITY ===';
SELECT
    CAST(SERVERPROPERTY(N'MachineName') AS NVARCHAR(128))        AS [Machine_Name],
    CAST(SERVERPROPERTY(N'ServerName') AS NVARCHAR(128))         AS [Server_Name],
    CAST(SERVERPROPERTY(N'InstanceName') AS NVARCHAR(128))       AS [Instance_Name],
    CAST(SERVERPROPERTY(N'ProductVersion') AS NVARCHAR(128))     AS [Product_Version],
    CAST(SERVERPROPERTY(N'ProductLevel') AS NVARCHAR(128))       AS [Product_Level],
    CAST(SERVERPROPERTY(N'ProductUpdateLevel') AS NVARCHAR(128)) AS [CU_Level],
    CAST(SERVERPROPERTY(N'Edition') AS NVARCHAR(256))            AS [Edition],
    CAST(SERVERPROPERTY(N'Collation') AS NVARCHAR(128))          AS [Collation],
    si.sqlserver_start_time                                      AS [SQL_Start_Time],
    DATEDIFF(DAY, si.sqlserver_start_time, @Now)                 AS [Uptime_Days],
    DATEDIFF(MINUTE, si.sqlserver_start_time, @Now)              AS [Uptime_Minutes],
    si.cpu_count                                                 AS [Logical_CPUs],
    si.hyperthread_ratio                                         AS [Hyperthread_Ratio],
    CAST(si.physical_memory_kb / 1024.0 AS DECIMAL(18, 1))       AS [Physical_RAM_MB],
    CASE
        WHEN COL_LENGTH(N'sys.dm_os_sys_info', N'sql_memory_model_desc') IS NOT NULL
            THEN (SELECT sql_memory_model_desc FROM sys.dm_os_sys_info)
        ELSE N'N/A'
    END AS [SQL_Memory_Model]
FROM sys.dm_os_sys_info AS si;

-------------------------------------------------------------------------------
-- 2. CPU / scheduler pressure
-------------------------------------------------------------------------------
PRINT '=== 2. CPU / SCHEDULER ===';
;WITH Ring AS
(
    SELECT TOP (1)
        record.value(N'(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', N'int') AS SystemIdle,
        record.value(N'(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', N'int') AS SQLProcessUtilization
    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 N'%<SystemHealth>%'
        ORDER BY [timestamp] DESC
    ) AS x
)
SELECT
    r.SQLProcessUtilization AS [SQL_CPU_Pct],
    r.SystemIdle            AS [Idle_CPU_Pct],
    100 - r.SystemIdle - r.SQLProcessUtilization AS [Other_Process_CPU_Pct],
    sched.Online_Schedulers,
    sched.Total_Runnable_Tasks,
    sched.Max_Runnable_On_Any_Scheduler,
    CAST(
        CASE
            WHEN r.SQLProcessUtilization > 80 THEN N'WARNING: High SQL CPU'
            WHEN sched.Max_Runnable_On_Any_Scheduler > 10 THEN N'CRITICAL: CPU queue (runnable tasks)'
            WHEN (100 - r.SystemIdle - r.SQLProcessUtilization) > 20 THEN N'WARNING: External CPU contention'
            ELSE N'OK'
        END AS NVARCHAR(100)
    ) AS [CPU_Status]
FROM Ring AS r
CROSS JOIN
(
    SELECT
        SUM(CASE WHEN status = N'VISIBLE ONLINE' THEN 1 ELSE 0 END) AS Online_Schedulers,
        SUM(runnable_tasks_count) AS Total_Runnable_Tasks,
        MAX(runnable_tasks_count) AS Max_Runnable_On_Any_Scheduler
    FROM sys.dm_os_schedulers
    WHERE scheduler_id < 255
) AS sched;

-------------------------------------------------------------------------------
-- 3. Memory health
-------------------------------------------------------------------------------
PRINT '=== 3. MEMORY ===';
DECLARE @TargetMemMB BIGINT =
(
    SELECT cntr_value / 1024
    FROM sys.dm_os_performance_counters
    WHERE counter_name = N'Target Server Memory (KB)'
);
DECLARE @TotalMemMB BIGINT =
(
    SELECT cntr_value / 1024
    FROM sys.dm_os_performance_counters
    WHERE counter_name = N'Total Server Memory (KB)'
);
DECLARE @PLE INT =
(
    SELECT MIN(cntr_value)
    FROM sys.dm_os_performance_counters
    WHERE object_name LIKE N'%Buffer Manager%'
      AND counter_name = N'Page life expectancy'
);
DECLARE @PLEThreshold INT =
    CASE WHEN @TotalMemMB > 0 THEN (@TotalMemMB / 1024 / 4) * 150 ELSE 300 END;
DECLARE @MaxServerMemMB BIGINT =
(
    SELECT CAST(value_in_use AS BIGINT)
    FROM sys.configurations
    WHERE name = N'max server memory (MB)'
);
DECLARE @PhysicalRamMB BIGINT =
(
    SELECT physical_memory_kb / 1024
    FROM sys.dm_os_sys_info
);

SELECT
    @PhysicalRamMB AS [Physical_RAM_MB],
    @MaxServerMemMB AS [Max_Server_Memory_MB],
    @TargetMemMB AS [Target_Server_Memory_MB],
    @TotalMemMB AS [Total_Server_Memory_MB],
    @PLE AS [Page_Life_Expectancy_Sec],
    @PLEThreshold AS [PLE_Threshold_Sec],
    (
        SELECT COUNT(*)
        FROM sys.dm_exec_query_memory_grants
    ) AS [Active_Memory_Grant_Waits],
    CAST(
        CASE
            WHEN EXISTS (SELECT 1 FROM sys.dm_exec_query_memory_grants)
                THEN N'CRITICAL: Queries waiting on memory grants'
            WHEN @PLE < @PLEThreshold
                THEN N'WARNING: Low PLE (buffer pool churn)'
            WHEN @TotalMemMB < (@TargetMemMB * 0.9)
                THEN N'WARNING: Not reaching target memory'
            WHEN @MaxServerMemMB >= @PhysicalRamMB
                THEN N'WARNING: max server memory leaves little/no OS reserve'
            ELSE N'OK'
        END AS NVARCHAR(120)
    ) AS [Memory_Status];

-------------------------------------------------------------------------------
-- 4. Key instance configuration
-------------------------------------------------------------------------------
PRINT '=== 4. KEY CONFIGURATION ===';
SELECT
    c.name AS [Setting],
    c.value_in_use AS [Value_In_Use],
    CAST(
        CASE c.name
            WHEN N'max degree of parallelism' THEN
                CASE WHEN c.value_in_use = 0 THEN N'Review: MAXDOP 0 can over-parallelize'
                     ELSE N'OK / review per NUMA guidance' END
            WHEN N'cost threshold for parallelism' THEN
                CASE WHEN c.value_in_use = 5 THEN N'Review: default CTFP often too low (consider 50+)'
                     ELSE N'OK' END
            WHEN N'optimize for ad hoc workloads' THEN
                CASE WHEN c.value_in_use = 0 THEN N'Review: enable to reduce plan-cache bloat'
                     ELSE N'OK' END
            WHEN N'backup compression default' THEN
                CASE WHEN c.value_in_use = 0 THEN N'Review: enable for smaller/faster backups'
                     ELSE N'OK' END
            WHEN N'remote admin connections' THEN
                CASE WHEN c.value_in_use = 0 THEN N'Review: enable DAC for hung-instance access'
                     ELSE N'OK' END
            WHEN N'max server memory (MB)' THEN
                CASE WHEN c.value_in_use >= (SELECT physical_memory_kb / 1024 FROM sys.dm_os_sys_info)
                         THEN N'WARNING: little/no OS memory reserve'
                     ELSE N'OK' END
            ELSE N'Review'
        END AS NVARCHAR(200)
    ) AS [Note]
FROM sys.configurations AS c
WHERE c.name IN
(
    N'max server memory (MB)',
    N'min server memory (MB)',
    N'max degree of parallelism',
    N'cost threshold for parallelism',
    N'optimize for ad hoc workloads',
    N'backup compression default',
    N'remote admin connections',
    N'fill factor (%)',
    N'blocked process threshold (s)'
)
ORDER BY c.name;

-------------------------------------------------------------------------------
-- 5. Database inventory & risk flags
-------------------------------------------------------------------------------
PRINT '=== 5. DATABASE INVENTORY & RISK FLAGS ===';
SELECT
    d.name AS [Database_Name],
    d.state_desc AS [State],
    d.recovery_model_desc AS [Recovery_Model],
    d.compatibility_level AS [Compat_Level],
    d.page_verify_option_desc AS [Page_Verify],
    d.is_auto_close_on AS [Auto_Close],
    d.is_auto_shrink_on AS [Auto_Shrink],
    d.is_auto_create_stats_on AS [Auto_Create_Stats],
    d.is_auto_update_stats_on AS [Auto_Update_Stats],
    d.is_read_only AS [Read_Only],
    d.is_trustworthy_on AS [Trustworthy],
    d.is_query_store_on AS [Query_Store],
    CAST(
        CASE
            WHEN d.state <> 0 THEN N'CRITICAL: Not ONLINE'
            WHEN d.is_auto_shrink_on = 1 OR d.is_auto_close_on = 1 THEN N'CRITICAL: Auto-Shrink/Close ON'
            WHEN d.page_verify_option_desc <> N'CHECKSUM' THEN N'CRITICAL: Page Verify not CHECKSUM'
            WHEN d.is_trustworthy_on = 1 AND d.database_id > 4 THEN N'WARNING: TRUSTWORTHY ON'
            WHEN d.is_auto_update_stats_on = 0 THEN N'WARNING: Auto Update Stats OFF'
            ELSE N'OK'
        END AS NVARCHAR(80)
    ) AS [Health_Flag]
FROM sys.databases AS d
ORDER BY
    CASE
        WHEN d.state <> 0 THEN 0
        WHEN d.is_auto_shrink_on = 1 OR d.is_auto_close_on = 1 THEN 1
        WHEN d.page_verify_option_desc <> N'CHECKSUM' THEN 2
        ELSE 3
    END,
    d.name;

-------------------------------------------------------------------------------
-- 6. Database sizes
-------------------------------------------------------------------------------
PRINT '=== 6. DATABASE SIZES ===';
SELECT
    d.name AS [Database_Name],
    CAST(SUM(CASE WHEN mf.type_desc = N'ROWS' THEN mf.size END) * 8.0 / 1024 AS DECIMAL(18, 1)) AS [Data_MB],
    CAST(SUM(CASE WHEN mf.type_desc = N'LOG' THEN mf.size END) * 8.0 / 1024 AS DECIMAL(18, 1)) AS [Log_MB],
    CAST(SUM(mf.size) * 8.0 / 1024 AS DECIMAL(18, 1)) AS [Total_MB]
FROM sys.databases AS d
INNER JOIN sys.master_files AS mf ON d.database_id = mf.database_id
WHERE d.state = 0
GROUP BY d.name
ORDER BY [Total_MB] DESC;

-------------------------------------------------------------------------------
-- 7. TempDB configuration
-------------------------------------------------------------------------------
PRINT '=== 7. TEMPDB ===';
SELECT
    mf.name AS [Logical_Name],
    mf.type_desc AS [File_Type],
    mf.physical_name AS [Physical_Path],
    CAST(mf.size * 8.0 / 1024 AS DECIMAL(18, 1)) AS [Size_MB],
    CASE
        WHEN mf.is_percent_growth = 1 THEN CAST(mf.growth AS VARCHAR(20)) + N'%'
        ELSE CAST(mf.growth * 8 / 1024 AS VARCHAR(20)) + N' MB'
    END AS [Autogrowth],
    mf.max_size AS [Max_Size_Pages]
FROM sys.master_files AS mf
WHERE mf.database_id = 2
ORDER BY mf.type_desc, mf.file_id;

SELECT
    COUNT(*) AS [TempDB_Data_Files],
    COUNT(DISTINCT size) AS [Distinct_Sizes],
    CAST(
        CASE
            WHEN COUNT(*) < (SELECT CASE WHEN cpu_count > 8 THEN 8 ELSE cpu_count END FROM sys.dm_os_sys_info)
                THEN N'Review: fewer data files than recommended (often 1 per logical CPU, cap 8)'
            WHEN COUNT(DISTINCT size) > 1
                THEN N'WARNING: data files are not equal size'
            ELSE N'OK'
        END AS NVARCHAR(120)
    ) AS [TempDB_Status]
FROM sys.master_files
WHERE database_id = 2
  AND type_desc = N'ROWS';

-------------------------------------------------------------------------------
-- 8. Top file I/O latency
-------------------------------------------------------------------------------
PRINT '=== 8. TOP FILE I/O LATENCY ===';
SELECT TOP (15)
    DB_NAME(vfs.database_id) AS [Database_Name],
    mf.name AS [Logical_File],
    mf.type_desc AS [File_Type],
    vfs.num_of_reads AS [Reads],
    vfs.num_of_writes AS [Writes],
    CAST(vfs.io_stall_read_ms * 1.0 / NULLIF(vfs.num_of_reads, 0) AS DECIMAL(18, 1)) AS [Avg_Read_Stall_ms],
    CAST(vfs.io_stall_write_ms * 1.0 / NULLIF(vfs.num_of_writes, 0) AS DECIMAL(18, 1)) AS [Avg_Write_Stall_ms],
    CAST(vfs.io_stall * 1.0 / NULLIF(vfs.num_of_reads + vfs.num_of_writes, 0) AS DECIMAL(18, 1)) AS [Avg_IO_Stall_ms],
    CAST(
        CASE
            WHEN vfs.io_stall * 1.0 / NULLIF(vfs.num_of_reads + vfs.num_of_writes, 0) > 20 THEN N'CRITICAL'
            WHEN vfs.io_stall * 1.0 / NULLIF(vfs.num_of_reads + vfs.num_of_writes, 0) > 15 THEN N'WARNING'
            ELSE N'OK'
        END AS NVARCHAR(20)
    ) AS [IO_Status]
FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs
INNER JOIN sys.master_files AS mf
    ON vfs.database_id = mf.database_id
   AND vfs.file_id = mf.file_id
WHERE vfs.num_of_reads + vfs.num_of_writes > 0
ORDER BY [Avg_IO_Stall_ms] DESC;

-------------------------------------------------------------------------------
-- 9. Backup currency
-------------------------------------------------------------------------------
PRINT '=== 9. BACKUP CURRENCY ===';
;WITH LastBackups AS
(
    SELECT
        database_name,
        MAX(CASE WHEN type = N'D' THEN backup_finish_date END) AS Last_Full_Backup,
        MAX(CASE WHEN type = N'I' THEN backup_finish_date END) AS Last_Diff_Backup,
        MAX(CASE WHEN type = N'L' THEN backup_finish_date END) AS Last_Log_Backup
    FROM msdb.dbo.backupset WITH (NOLOCK)
    GROUP BY database_name
)
SELECT
    d.name AS [Database_Name],
    d.recovery_model_desc AS [Recovery_Model],
    lb.Last_Full_Backup,
    lb.Last_Diff_Backup,
    lb.Last_Log_Backup,
    DATEDIFF(HOUR, COALESCE(lb.Last_Diff_Backup, lb.Last_Full_Backup), @Now) AS [Hours_Since_Full_Or_Diff],
    DATEDIFF(HOUR, lb.Last_Log_Backup, @Now) AS [Hours_Since_Log],
    CAST(
        CASE
            WHEN d.state <> 0 THEN N'N/A (not online)'
            WHEN lb.Last_Full_Backup IS NULL THEN N'CRITICAL: No full backup found'
            WHEN DATEDIFF(HOUR, COALESCE(lb.Last_Diff_Backup, lb.Last_Full_Backup), @Now) > @BackupHoursSLA
                THEN N'CRITICAL: Full/diff older than SLA'
            WHEN d.recovery_model_desc = N'FULL' AND lb.Last_Log_Backup IS NULL
                THEN N'WARNING: FULL recovery with no log backup'
            WHEN d.recovery_model_desc = N'FULL'
             AND DATEDIFF(HOUR, lb.Last_Log_Backup, @Now) > @LogBackupHoursSLA
                THEN N'WARNING: Log backup older than SLA'
            ELSE N'OK'
        END AS NVARCHAR(80)
    ) AS [Backup_Status]
FROM sys.databases AS d
LEFT JOIN LastBackups AS lb ON d.name = lb.database_name
WHERE d.database_id > 4
ORDER BY
    CASE
        WHEN lb.Last_Full_Backup IS NULL THEN 0
        WHEN DATEDIFF(HOUR, COALESCE(lb.Last_Diff_Backup, lb.Last_Full_Backup), @Now) > @BackupHoursSLA THEN 1
        ELSE 2
    END,
    d.name;

-------------------------------------------------------------------------------
-- 10. Failed Agent jobs (last 24 hours)
-------------------------------------------------------------------------------
PRINT '=== 10. FAILED AGENT JOBS (LAST 24 HOURS) ===';
SELECT
    j.name AS [Job_Name],
    h.step_id AS [Step_Id],
    h.step_name AS [Step_Name],
    msdb.dbo.agent_datetime(h.run_date, h.run_time) AS [Run_DateTime],
    CASE h.run_status
        WHEN 0 THEN N'FAILED'
        WHEN 2 THEN N'RETRY'
        WHEN 3 THEN N'CANCELLED'
        ELSE CAST(h.run_status AS NVARCHAR(10))
    END AS [Status],
    LEFT(h.message, 400) AS [Message]
FROM msdb.dbo.sysjobhistory AS h
INNER JOIN msdb.dbo.sysjobs AS j ON h.job_id = j.job_id
WHERE h.run_status IN (0, 2, 3)
  AND h.step_id > 0
  AND msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(HOUR, -24, @Now)
ORDER BY [Run_DateTime] DESC;

IF @@ROWCOUNT = 0
    PRINT 'No failed/retry/cancelled job steps in the last 24 hours.';

-------------------------------------------------------------------------------
-- 11. Active blocking
-------------------------------------------------------------------------------
PRINT '=== 11. ACTIVE BLOCKING ===';
SELECT
    r.session_id AS [Session_Id],
    r.blocking_session_id AS [Blocking_Session_Id],
    r.wait_type AS [Wait_Type],
    r.wait_time AS [Wait_ms],
    r.wait_resource AS [Wait_Resource],
    DB_NAME(r.database_id) AS [Database_Name],
    s.login_name AS [Login_Name],
    s.host_name AS [Host_Name],
    s.program_name AS [Program_Name],
    SUBSTRING
    (
        st.text,
        (r.statement_start_offset / 2) + 1,
        CASE
            WHEN r.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), st.text))
            ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1
        END
    ) AS [Current_Statement]
FROM sys.dm_exec_requests AS r
INNER JOIN sys.dm_exec_sessions AS s ON r.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
WHERE r.blocking_session_id <> 0
   OR r.session_id IN
   (
       SELECT DISTINCT blocking_session_id
       FROM sys.dm_exec_requests
       WHERE blocking_session_id <> 0
   )
ORDER BY r.wait_time DESC;

IF @@ROWCOUNT = 0
    PRINT 'No active blocking at this moment.';

-------------------------------------------------------------------------------
-- 12. Top wait types (since startup)
-------------------------------------------------------------------------------
PRINT '=== 12. TOP WAIT TYPES (SINCE STARTUP) ===';
SELECT TOP (15)
    ws.wait_type AS [Wait_Type],
    ws.waiting_tasks_count AS [Wait_Count],
    CAST(ws.wait_time_ms / 1000.0 AS DECIMAL(18, 1)) AS [Total_Wait_Sec],
    CAST((ws.wait_time_ms - ws.signal_wait_time_ms) / 1000.0 AS DECIMAL(18, 1)) AS [Resource_Wait_Sec],
    CAST(ws.signal_wait_time_ms / 1000.0 AS DECIMAL(18, 1)) AS [Signal_Wait_Sec],
    CAST(ws.wait_time_ms * 100.0 / NULLIF(SUM(ws.wait_time_ms) OVER (), 0) AS DECIMAL(5, 2)) AS [Pct_Of_Total],
    CAST(
        CASE
            WHEN ws.wait_type LIKE N'LCK%' THEN N'Blocking / locking'
            WHEN ws.wait_type LIKE N'PAGEIOLATCH%' OR ws.wait_type IN (N'WRITELOG', N'IO_COMPLETION')
                THEN N'Storage / log I/O'
            WHEN ws.wait_type LIKE N'RESOURCE_SEMAPHORE%' THEN N'Memory grant pressure'
            WHEN ws.wait_type IN (N'CXPACKET', N'CXCONSUMER', N'EXECSYNC') THEN N'Parallelism'
            WHEN ws.wait_type = N'SOS_SCHEDULER_YIELD' THEN N'CPU pressure'
            WHEN ws.wait_type = N'THREADPOOL' THEN N'CRITICAL: worker exhaustion'
            WHEN ws.wait_type = N'ASYNC_NETWORK_IO' THEN N'Client / network slow consume'
            WHEN ws.wait_type LIKE N'HADR%' THEN N'Always On'
            ELSE N'Review wait meaning'
        END AS NVARCHAR(80)
    ) AS [Category]
FROM sys.dm_os_wait_stats AS ws
WHERE ws.waiting_tasks_count > 0
  AND ws.wait_type NOT IN
  (
      N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH',
      N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT',
      N'CLR_SEMAPHORE', N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE',
      N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
      N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
      N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE',
      N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP',
      N'LOGMGR_QUEUE', N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE',
      N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE', N'PARALLEL_REDO_TRAN_LIST',
      N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK',
      N'PREEMPTIVE_XE_GETTARGETSTATE', N'QDS_ASYNC_QUEUE',
      N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
      N'QDS_SHUTDOWN_QUEUE', N'REDO_THREAD_PENDING_WORK', N'REQUEST_FOR_DEADLOCK_SEARCH',
      N'RESOURCE_QUEUE', N'SERVER_IDLE_TASK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP',
      N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', N'SLEEP_MASTERUPGRADED',
      N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', N'SLEEP_TEMPDBSTARTUP',
      N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', N'SP_SERVER_DIAGNOSTICS_SLEEP',
      N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES',
      N'WAIT_FOR_RESULTS', N'WAITFOR', N'XE_DISPATCHER_JOIN', N'XE_DISPATCHER_WAIT',
      N'XE_TIMER_EVENT'
  )
ORDER BY ws.wait_time_ms DESC;

-------------------------------------------------------------------------------
-- 13. Session / connection summary
-------------------------------------------------------------------------------
PRINT '=== 13. SESSIONS / CONNECTIONS ===';
SELECT
    COUNT(*) AS [User_Sessions],
    SUM(CASE WHEN s.status = N'running' THEN 1 ELSE 0 END) AS [Running],
    SUM(CASE WHEN s.status = N'sleeping' THEN 1 ELSE 0 END) AS [Sleeping],
    SUM(CASE WHEN r.status = N'suspended' THEN 1 ELSE 0 END) AS [Suspended_Requests],
    SUM(CASE WHEN r.blocking_session_id > 0 THEN 1 ELSE 0 END) AS [Blocked_Requests],
    MAX(r.open_transaction_count) AS [Max_Open_Tran_Count]
FROM sys.dm_exec_sessions AS s
LEFT JOIN sys.dm_exec_requests AS r ON s.session_id = r.session_id
WHERE s.is_user_process = 1;

SELECT TOP (20)
    s.session_id AS [Session_Id],
    s.login_name AS [Login_Name],
    s.host_name AS [Host_Name],
    s.program_name AS [Program_Name],
    DB_NAME(r.database_id) AS [Database_Name],
    r.status AS [Request_Status],
    r.command AS [Command],
    r.wait_type AS [Wait_Type],
    r.wait_time AS [Wait_ms],
    r.cpu_time AS [CPU_ms],
    r.logical_reads AS [Logical_Reads],
    r.total_elapsed_time AS [Elapsed_ms]
FROM sys.dm_exec_sessions AS s
INNER JOIN sys.dm_exec_requests AS r ON s.session_id = r.session_id
WHERE s.is_user_process = 1
  AND s.session_id <> @@SPID
ORDER BY r.total_elapsed_time DESC;

-------------------------------------------------------------------------------
-- 14. Always On AG status (when configured)
-------------------------------------------------------------------------------
PRINT '=== 14. ALWAYS ON AG STATUS ===';
IF SERVERPROPERTY(N'IsHadrEnabled') = 1
BEGIN
    SELECT
        ag.name AS [AG_Name],
        ar.replica_server_name AS [Replica],
        ars.role_desc AS [Role],
        ars.operational_state_desc AS [Operational_State],
        ars.connected_state_desc AS [Connected_State],
        ars.synchronization_health_desc AS [Sync_Health],
        ars.last_connect_error_description AS [Last_Connect_Error]
    FROM sys.availability_groups AS ag
    INNER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id
    INNER JOIN sys.dm_hadr_availability_replica_states AS ars
        ON ar.replica_id = ars.replica_id
    ORDER BY ag.name, ars.role_desc;

    SELECT
        DB_NAME(drs.database_id) AS [Database_Name],
        ar.replica_server_name AS [Replica],
        drs.synchronization_state_desc AS [Sync_State],
        drs.synchronization_health_desc AS [Sync_Health],
        drs.is_suspended AS [Is_Suspended],
        drs.suspend_reason_desc AS [Suspend_Reason],
        drs.log_send_queue_size AS [Log_Send_Queue_KB],
        drs.redo_queue_size AS [Redo_Queue_KB]
    FROM sys.dm_hadr_database_replica_states AS drs
    INNER JOIN sys.availability_replicas AS ar ON drs.replica_id = ar.replica_id
    ORDER BY [Database_Name], ar.replica_server_name;
END
ELSE
    PRINT 'Always On AG is not enabled on this instance.';

-------------------------------------------------------------------------------
-- 15. Quick findings summary
-------------------------------------------------------------------------------
PRINT '=== 15. QUICK FINDINGS SUMMARY ===';
;WITH Issues AS
(
    SELECT N'CPU' AS [Area],
           CASE
               WHEN EXISTS
               (
                   SELECT 1
                   FROM sys.dm_os_schedulers
                   WHERE status = N'VISIBLE ONLINE' AND runnable_tasks_count > 10
               ) THEN N'CRITICAL'
               ELSE N'OK'
           END AS [Status],
           N'Runnable tasks > 10 on a scheduler' AS [Check]
    UNION ALL
    SELECT N'Memory',
           CASE
               WHEN EXISTS (SELECT 1 FROM sys.dm_exec_query_memory_grants) THEN N'CRITICAL'
               WHEN @PLE < @PLEThreshold THEN N'WARNING'
               ELSE N'OK'
           END,
           N'Memory grants / low PLE'
    UNION ALL
    SELECT N'Config',
           CASE
               WHEN EXISTS
               (
                   SELECT 1
                   FROM sys.databases
                   WHERE database_id > 4
                     AND state = 0
                     AND (is_auto_shrink_on = 1 OR is_auto_close_on = 1 OR page_verify_option_desc <> N'CHECKSUM')
               ) THEN N'CRITICAL'
               ELSE N'OK'
           END,
           N'Auto-shrink/close or page verify'
    UNION ALL
    SELECT N'Backup',
           CASE
               WHEN EXISTS
               (
                   SELECT 1
                   FROM sys.databases AS d
                   LEFT JOIN
                   (
                       SELECT
                           database_name,
                           MAX(CASE WHEN type IN (N'D', N'I') THEN backup_finish_date END) AS Last_Data_Backup
                       FROM msdb.dbo.backupset WITH (NOLOCK)
                       GROUP BY database_name
                   ) AS b ON d.name = b.database_name
                   WHERE d.database_id > 4
                     AND d.state = 0
                     AND
                     (
                         b.Last_Data_Backup IS NULL
                         OR DATEDIFF(HOUR, b.Last_Data_Backup, @Now) > @BackupHoursSLA
                     )
               ) THEN N'CRITICAL'
               ELSE N'OK'
           END,
           N'Full/diff backup within SLA'
    UNION ALL
    SELECT N'Blocking',
           CASE
               WHEN EXISTS
               (
                   SELECT 1
                   FROM sys.dm_exec_requests
                   WHERE blocking_session_id <> 0
               ) THEN N'WARNING'
               ELSE N'OK'
           END,
           N'Active blocking chains'
    UNION ALL
    SELECT N'Jobs',
           CASE
               WHEN EXISTS
               (
                   SELECT 1
                   FROM msdb.dbo.sysjobhistory AS h
                   WHERE h.run_status = 0
                     AND h.step_id > 0
                     AND msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(HOUR, -24, @Now)
               ) THEN N'WARNING'
               ELSE N'OK'
           END,
           N'Failed job steps in last 24h'
)
SELECT
    [Area],
    [Status],
    [Check],
    CASE [Status]
        WHEN N'CRITICAL' THEN 1
        WHEN N'WARNING' THEN 2
        ELSE 3
    END AS [Sort]
FROM Issues
ORDER BY [Sort], [Area];

PRINT '=== SERVER HEALTH OVERVIEW COMPLETE ===';
PRINT 'For scored findings with recommendations: EXEC dbo.sp_DBA_HealthCheck @DeepDive = 0;';
PRINT 'For deeper area drills, use folders 01_Server_OS through 09_Maintenance.';

Review the full script and confirm the connected SQL Server instance before execution. The run instructions and output guide appear later in this article.

Diagram for Assessment reports should drive tickets
Assessment reports should drive tickets

A server assessment only creates value when findings become tickets with owners and due dates.

Rank by blast radius, not by how interesting the finding is. SQLOptima Assessment is designed to hand off into that remediation loop — not a PDF graveyard.

Why assessment follow-through deserves a method

The central principle is that an assessment creates value only when findings become owned, testable work. A single metric, recommendation, or successful command is not enough. The decision has to connect an observed symptom to a plausible cause, a controlled change, and a result that can be verified under the workload that matters.

Evidence to collect

Use evidence from the same time window whenever possible:

  • severity and business impact
  • evidence that reproduces the finding
  • the affected server, database, or workload
  • a measurable remediation outcome

A practical workflow

  1. Normalize findings into a backlog.
  2. Separate urgent risk from planned improvement.
  3. Add acceptance and rollback criteria.
  4. Review closure evidence with the owner.

Common mistakes

Avoid these shortcuts:

  • copying report text without context
  • creating one oversized remediation ticket
  • closing work without a verification result

Verification and completion

Define completion before making the change. Confirm correctness first, then compare latency, throughput, reads, CPU, waits, storage behavior, or recovery time as appropriate for Assessment reports should drive tickets. Test negative and boundary cases, not only the path expected to succeed. Watch the system long enough to cover the relevant workload pattern.

Download and run the diagnostic

Before you run it

  • Review every statement and confirm the connection points to the intended SQL Server database server.
  • Run through a non-production or read-only diagnostic account first when possible.
  • Required access: VIEW SERVER STATE and read access to system metadata.
  • Save the output with the server name, database name, collection time, and workload window so another engineer can reproduce the analysis.

What it does

Collects configuration, workload, storage, backup, and operational evidence suitable for assessment findings. The script is diagnostic and does not apply an automatic remediation. Result values still need workload context, a second supporting signal, and a before-and-after comparison.

How to read the output values

Convert rows into tickets only when the server, database, metric, evidence time, business impact, acceptance test, and owner are known. Prioritize critical or repeated conditions instead of turning every informational row into work.