/****************************************************************************************************
Function: dbo._as_Health_Overview_DBChecks
Purpose: Collect database-level health checks (D1 - D14) separately.
Author: Asamco BV - Alex
Date: 25-01-2025
****************************************************************************************************/
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER FUNCTION dbo._as_Health_Overview_DBChecks
(
@checkDate DATE
)
RETURNS @tblDBHealth TABLE
(
ErrorType NVARCHAR(150), -- e.g. 'Critical', 'High', 'Warning', 'OK'
Module NVARCHAR(20), -- e.g. 'DB', 'AR', 'AP', etc
HealthMessage NVARCHAR(MAX),
PossibleImpact NVARCHAR(MAX),
NoOfIssues INT,
DetailQuery NVARCHAR(MAX),
DetailData NVARCHAR(MAX),
LinkToResolve NVARCHAR(MAX),
IsInfoOnly BIT DEFAULT 0
)
AS
BEGIN
/*Usage:
select * from dbo._as_Health_Overview_DBChecks(null)
*/
/***************************************************************************************
D1. Missing indices, more than X impact
(Threshold ~ 500 for avg_total_user_cost. Adjust as needed.)
***************************************************************************************/
;WITH dD1 AS
(
SELECT
ObjectName = OBJECT_NAME(mid.object_id),
IndexGroupHandle = mig.index_group_handle,
AvgTotalUserCost = migs.avg_total_user_cost,
AvgUserImpact = migs.avg_user_impact,
EqualityColumns = mid.equality_columns,
InequalityColumns = mid.inequality_columns,
IncludedColumns = mid.included_columns
FROM sys.dm_db_missing_index_group_stats AS migs
INNER JOIN sys.dm_db_missing_index_groups AS mig
ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details AS mid
ON mig.index_handle = mid.index_handle
WHERE migs.avg_total_user_cost > 5000
AND mid.database_id = DB_ID() -- If you only want checks for current DB
)
INSERT INTO @tblDBHealth
(
ErrorType, Module, HealthMessage, PossibleImpact,
NoOfIssues, DetailQuery, DetailData, LinkToResolve, IsInfoOnly
)
SELECT
CASE WHEN EXISTS (SELECT * FROM dD1) THEN 'WARNING' ELSE 'OK' END AS ErrorType,
'DB' AS Module,
CASE WHEN EXISTS (SELECT * FROM dD1)
THEN CONCAT('Missing indexes (cost>5000). ', (SELECT COUNT(*) FROM dD1), ' potential index(es).')
ELSE 'OK. No significant missing indexes found.'
END AS HealthMessage,
CASE WHEN EXISTS (SELECT * FROM dD1)
THEN 'Impact: Potential performance degradation due to missing indexes.'
ELSE ''
END AS PossibleImpact,
COALESCE((SELECT COUNT(*) FROM dD1), 0) AS NoOfIssues,
N'
SELECT
OBJECT_NAME(mid.object_id) AS TableName,
migs.avg_total_user_cost AS AvgTotalCost,
migs.avg_user_impact AS AvgImpact,
mid.equality_columns,
mid.inequality_columns,
mid.included_columns
FROM sys.dm_db_missing_index_group_stats migs
INNER JOIN sys.dm_db_missing_index_groups mig ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE migs.avg_total_user_cost > 5000
AND mid.database_id = DB_ID()
' AS DetailQuery,
(SELECT STRING_AGG(ObjectName, ', ')
FROM (SELECT DISTINCT TOP 5 ObjectName FROM dD1) AS x) AS DetailData,
NULL AS LinkToResolve,
CASE WHEN EXISTS (SELECT * FROM dD1) THEN 0 ELSE 1 END AS IsInfoOnly;
/***************************************************************************************
D2. Fragmented indices (>30%).
***************************************************************************************/
;WITH dD2 AS
(
SELECT
DBName = DB_NAME(ps.database_id),
SchemaName = OBJECT_SCHEMA_NAME(ps.object_id, ps.database_id),
TableName = OBJECT_NAME(ps.object_id, ps.database_id),
IndexName = i.name,
ps.index_id,
FragmentPct = ps.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') AS ps
INNER JOIN sys.indexes i
ON ps.object_id = i.object_id
AND ps.index_id = i.index_id
WHERE ps.avg_fragmentation_in_percent > 30
AND i.name IS NOT NULL
AND i.is_disabled = 0
AND i.index_id > 0
)
INSERT INTO @tblDBHealth
(
ErrorType, Module, HealthMessage, PossibleImpact,
NoOfIssues, DetailQuery, DetailData, LinkToResolve, IsInfoOnly
)
SELECT
CASE WHEN EXISTS (SELECT * FROM dD2) THEN 'WARNING' ELSE 'OK' END,
'DB',
CASE WHEN EXISTS (SELECT * FROM dD2)
THEN CONCAT('High index fragmentation (>30%). ', (SELECT COUNT(*) FROM dD2), ' index(es).')
ELSE 'OK. No significant fragmentation.'
END,
CASE WHEN EXISTS (SELECT * FROM dD2)
THEN 'Impact: Poor query performance, heavy IO usage.'
ELSE ''
END,
COALESCE((SELECT COUNT(*) FROM dD2), 0),
N'
SELECT
DB_NAME(ps.database_id) AS DBName,
OBJECT_SCHEMA_NAME(ps.object_id) AS SchemaName,
OBJECT_NAME(ps.object_id) AS TableName,
i.name AS IndexName,
ps.avg_fragmentation_in_percent AS Fragmentation
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, ''LIMITED'') ps
INNER JOIN sys.indexes i
ON ps.object_id = i.object_id
AND ps.index_id = i.index_id
WHERE ps.avg_fragmentation_in_percent > 30
AND i.name IS NOT NULL
AND i.is_disabled = 0
AND i.index_id > 0
',
(SELECT STRING_AGG(TableName + '.' + IndexName, ', ')
FROM (SELECT TOP 5 TableName, IndexName FROM dD2 ORDER BY FragmentPct DESC) t),
NULL,
CASE WHEN EXISTS (SELECT * FROM dD2) THEN 0 ELSE 1 END;
/***************************************************************************************
D3. Free space if Express Edition (simple check ~10GB limit).
***************************************************************************************/
IF CAST(SERVERPROPERTY('Edition') as nvarchar(1000)) NOT LIKE '%Express%'
BEGIN
INSERT INTO @tblDBHealth
(
ErrorType, Module, HealthMessage, PossibleImpact,
NoOfIssues, DetailQuery, DetailData, LinkToResolve, IsInfoOnly
)
SELECT
'OK',
'DB',
'Not Express Edition. Free space limit check skipped.',
'',
0,
NULL,
NULL,
NULL,
1;
END
ELSE
BEGIN
;WITH dD3 AS
(
SELECT
DBName = DB_NAME(),
FileName = name,
FileSizeMB = (size * 8) / 1024.0,
MaxSizeMB = CASE WHEN max_size = -1 THEN 10240
ELSE (max_size * 8) / 1024.0 END,
UsedSpaceMB= ((size - FILEPROPERTY(name, 'SpaceUsed')) * 8) / 1024.0,
FreeSpaceMB= (FILEPROPERTY(name, 'SpaceUsed') * 8) / 1024.0
FROM sys.database_files
WHERE type_desc = 'ROWS'
)
INSERT INTO @tblDBHealth
(
ErrorType, Module, HealthMessage, PossibleImpact,
NoOfIssues, DetailQuery, DetailData, LinkToResolve, IsInfoOnly
)
SELECT
CASE WHEN (SELECT TOP 1 (MaxSizeMB - FileSizeMB)
FROM dD3 ORDER BY FileSizeMB DESC) < 1000
THEN 'ERROR'
ELSE 'OK'
END,
'DB',
CASE WHEN (SELECT TOP 1 (MaxSizeMB - FileSizeMB)
FROM dD3 ORDER BY FileSizeMB DESC) < 1000
THEN 'Express Edition nearing 10GB limit. <1GB free.'
ELSE 'OK. Sufficient free space.'
END,
CASE WHEN (SELECT TOP 1 (MaxSizeMB - FileSizeMB)
FROM dD3 ORDER BY FileSizeMB DESC) < 1000
THEN 'Impact: DB may run out of space soon, insert/update failures.'
ELSE ''
END,
CASE WHEN EXISTS
(
SELECT 1
FROM dD3
WHERE (MaxSizeMB - FileSizeMB) < 1000
)
THEN 1
ELSE 0
END,
N'
SELECT
name AS FileName,
(size*8)/1024.0 AS FileSizeMB,
CASE WHEN max_size=-1 THEN 10240
ELSE (max_size*8)/1024.0 END AS MaxSizeMB,
((size - FILEPROPERTY(name, ''SpaceUsed''))*8)/1024.0 AS UsedSpaceMB,
(FILEPROPERTY(name, ''SpaceUsed'')*8)/1024.0 AS FreeSpaceMB
FROM sys.database_files
WHERE type_desc = ''ROWS''
',
(SELECT STRING_AGG(FileName + ': '
+ CONVERT(VARCHAR(10),(MaxSizeMB - FileSizeMB)) + 'MB free', '; ')
FROM dD3),
NULL,
CASE WHEN (SELECT TOP 1 (MaxSizeMB - FileSizeMB) FROM dD3 ORDER BY FileSizeMB DESC) < 1000
THEN 0
ELSE 1
END;
END
/***************************************************************************************
D4. Placeholder
***************************************************************************************/
INSERT INTO @tblDBHealth
(
ErrorType, Module, HealthMessage, PossibleImpact,
NoOfIssues, DetailQuery, DetailData, LinkToResolve, IsInfoOnly
)
SELECT
'OK',
'DB',
'D4 placeholder: No additional checks implemented here.',
'',
0,
NULL,
NULL,
NULL,
1;
/***************************************************************************************
D5. Last Successful FULL Backup (example: older than 1 day => WARNING)
***************************************************************************************/
;WITH LastFullBackup AS
(
SELECT
dbname = bs.database_name,
last_backup_date = MAX(bs.backup_finish_date)
FROM msdb.dbo.backupset bs
WHERE bs.type = 'D' -- D = Full backup
AND bs.database_name = DB_NAME()
GROUP BY bs.database_name
)
INSERT INTO @tblDBHealth
SELECT
CASE
WHEN EXISTS(SELECT * FROM LastFullBackup
WHERE last_backup_date < DATEADD(DAY, -1, GETDATE()))
THEN 'WARNING'
ELSE 'OK'
END,
'DB',
CASE
WHEN EXISTS(SELECT * FROM LastFullBackup
WHERE last_backup_date < DATEADD(DAY, -1, GETDATE()))
THEN 'No full backup in the last 24 hours.'
ELSE 'OK. Recent full backup found.'
END,
CASE
WHEN EXISTS(SELECT * FROM LastFullBackup
WHERE last_backup_date < DATEADD(DAY, -1, GETDATE()))
THEN 'Impact: Potential data loss if a crash occurs.'
ELSE ''
END,
/* If there is no row at all for the DB, consider that even worse => Insert logic or
treat as "no backups exist"? For demonstration, let's do basic. */
CASE
WHEN NOT EXISTS(SELECT * FROM LastFullBackup)
THEN 1 -- no backup at all
WHEN EXISTS(SELECT * FROM LastFullBackup
WHERE last_backup_date < DATEADD(DAY, -1, GETDATE()))
THEN 1
ELSE 0
END,
N'
SELECT TOP 1
database_name,
backup_finish_date
FROM msdb.dbo.backupset
WHERE type = ''D''
AND database_name = DB_NAME()
ORDER BY backup_finish_date DESC
',
(SELECT CONVERT(NVARCHAR(30), last_backup_date, 120)
FROM LastFullBackup),
NULL,
CASE
WHEN NOT EXISTS(SELECT * FROM LastFullBackup) THEN 0
WHEN EXISTS(SELECT * FROM LastFullBackup
WHERE last_backup_date < DATEADD(DAY, -1, GETDATE())) THEN 0
ELSE 1
END;
/***************************************************************************************
D6. DBCC CheckDB / Consistency Check (using msdb..suspect_pages as a simple indicator)
***************************************************************************************/
;WITH suspect AS
(
SELECT sp.*
FROM msdb.dbo.suspect_pages sp
WHERE sp.database_id = DB_ID()
AND sp.event_type IN (1,2,3)
/* 1=bad page, 2=checksum error, 3=tear, etc. See MS docs for others. */
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN EXISTS(SELECT * FROM suspect) THEN 'ERROR' ELSE 'OK' END,
'DB',
CASE WHEN EXISTS(SELECT * FROM suspect)
THEN 'Possible corruption detected in suspect_pages.'
ELSE 'OK. No pages marked suspect.'
END,
CASE WHEN EXISTS(SELECT * FROM suspect)
THEN 'Impact: Potential data corruption/integrity issues.'
ELSE ''
END,
COALESCE((SELECT COUNT(*) FROM suspect), 0),
N'
SELECT *
FROM msdb.dbo.suspect_pages
WHERE database_id = DB_ID()
AND event_type IN (1,2,3)
',
NULL,
NULL,
CASE WHEN EXISTS(SELECT * FROM suspect) THEN 0 ELSE 1 END;
/***************************************************************************************
D7. Transaction Log Usage (>80%)
***************************************************************************************/
;WITH logUse AS
(
SELECT
DBName = DB_NAME(database_id),
total_log_mb = total_log_size_in_bytes / 1024,
used_log_mb = used_log_space_in_bytes / 1024,
used_pct = CASE WHEN (total_log_size_in_bytes / 1024)=0 THEN 0
ELSE ((used_log_space_in_bytes / 1024) * 100.0 / (total_log_size_in_bytes / 1024))
END
FROM sys.dm_db_log_space_usage
WHERE database_id = DB_ID()
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN EXISTS(SELECT * FROM logUse WHERE used_pct > 80)
THEN 'WARNING'
ELSE 'OK'
END,
'DB',
CASE WHEN EXISTS(SELECT * FROM logUse WHERE used_pct > 80)
THEN 'Transaction log is >80% used.'
ELSE 'OK. Log usage within normal range.'
END,
CASE WHEN EXISTS(SELECT * FROM logUse WHERE used_pct > 80)
THEN 'Impact: Risk of running out of log space, blocking, or forced autogrowth.'
ELSE ''
END,
CASE WHEN EXISTS(SELECT * FROM logUse WHERE used_pct > 80)
THEN 1
ELSE 0
END,
N'
SELECT
DB_NAME(database_id) AS DBName,
total_log_size_mb,
used_log_space_mb,
(used_log_space_mb*100.0/total_log_size_mb) AS used_pct
FROM sys.dm_db_log_space_usage
WHERE database_id = DB_ID()
',
(SELECT CONCAT(used_pct,'%') FROM logUse),
NULL,
CASE WHEN EXISTS(SELECT * FROM logUse WHERE used_pct > 80) THEN 0 ELSE 1 END;
/***************************************************************************************
D8. Long-Running Transactions (>60 minutes)
***************************************************************************************/
;WITH longTrans AS
(
SELECT s.session_id,
s.login_time,
DATEDIFF(MINUTE, s.login_time, GETDATE()) AS MinutesActive
FROM sys.dm_exec_sessions s
WHERE s.is_user_process = 1
AND s.database_id = DB_ID()
AND DATEDIFF(MINUTE, s.login_time, GETDATE()) > 60
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN EXISTS(SELECT * FROM longTrans) THEN 'WARNING' ELSE 'OK' END,
'DB',
CASE WHEN EXISTS(SELECT * FROM longTrans)
THEN CONCAT('Long-running transactions (>60 min). Found: ', (SELECT COUNT(*) FROM longTrans), '.')
ELSE 'OK. No user transactions older than 60 min.'
END,
CASE WHEN EXISTS(SELECT * FROM longTrans)
THEN 'Impact: Potential blocking, locks, or delays.'
ELSE ''
END,
COALESCE((SELECT COUNT(*) FROM longTrans), 0),
N'
SELECT
s.session_id,
s.login_time,
DATEDIFF(MINUTE, s.login_time, GETDATE()) AS MinutesActive
FROM sys.dm_exec_sessions s
WHERE s.is_user_process = 1
AND s.database_id = DB_ID()
AND DATEDIFF(MINUTE, s.login_time, GETDATE()) > 60
',
NULL,
NULL,
CASE WHEN EXISTS(SELECT * FROM longTrans) THEN 0 ELSE 1 END;
/***************************************************************************************
D9. TempDB Usage (simple check if current DB is tempdb; otherwise skip or check usage).
For demonstration, let's check if 'tempdb' usage > 80% allocated vs. its total size.
***************************************************************************************/
IF DB_NAME() = 'tempdb'
BEGIN
;WITH TempDBFiles AS
(
SELECT
FileID = file_id,
FileName = name,
SizeMB = CAST((size*8)/1024.0 AS DECIMAL(10,2)) ,
MaxSizeMB = CASE WHEN max_size=-1 THEN 0 -- or unlimited
ELSE (max_size*8)/1024.0 END ,
UsedMB = CAST(FILEPROPERTY(name, 'SpaceUsed')*8/1024.0 AS DECIMAL(10,2))
FROM sys.database_files
WHERE type_desc = 'ROWS'
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN (SELECT SUM(UsedMB)*100.0/SUM(SizeMB) FROM TempDBFiles) > 80
THEN 'WARNING'
ELSE 'OK'
END,
'DB',
CASE WHEN (SELECT SUM(UsedMB)*100.0/SUM(SizeMB) FROM TempDBFiles) > 80
THEN 'TempDB usage >80% allocated.'
ELSE 'OK. TempDB usage is within normal range.'
END,
CASE WHEN (SELECT SUM(UsedMB)*100.0/SUM(SizeMB) FROM TempDBFiles) > 80
THEN 'Impact: Potential out-of-space issues for temp objects, queries, sorts.'
ELSE ''
END,
CASE WHEN (SELECT SUM(UsedMB)*100.0/SUM(SizeMB) FROM TempDBFiles) > 80
THEN 1
ELSE 0
END,
N'
SELECT
name AS FileName,
(size*8)/1024.0 AS SizeMB,
FILEPROPERTY(name, ''SpaceUsed'')*8/1024.0 AS UsedMB
FROM sys.database_files
WHERE type_desc = ''ROWS''
',
NULL,
NULL,
CASE WHEN (SELECT SUM(UsedMB)*100.0/SUM(SizeMB) FROM TempDBFiles) > 80 THEN 0 ELSE 1 END;
END
ELSE
BEGIN
-- Not tempdb, just add an info row (or omit entirely).
INSERT INTO @tblDBHealth
SELECT
'OK',
'DB',
'TempDB usage check skipped (current DB is not tempdb).',
'',
0,
NULL,
NULL,
NULL,
1;
END
/***************************************************************************************
D10. VLF Count (Virtual Log Files). If >1000 => WARNING
***************************************************************************************/
/* For SQL Server 2019 or above: sys.dm_db_log_info(DB_ID())
For older versions: DBCC LOGINFO() approach (which is more cumbersome). */
;WITH vlf AS
(
SELECT COUNT(*) AS VLFCount
FROM sys.dm_db_log_info(DB_ID())
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN (SELECT VLFCount FROM vlf) > 1000 THEN 'WARNING' ELSE 'OK' END,
'DB',
CASE WHEN (SELECT VLFCount FROM vlf) > 1000
THEN CONCAT('High VLF count (', (SELECT VLFCount FROM vlf), ').')
ELSE 'OK. Acceptable VLF count.'
END,
CASE WHEN (SELECT VLFCount FROM vlf) > 1000
THEN 'Impact: Slower recovery times, potential performance degradation.'
ELSE ''
END,
(SELECT VLFCount FROM vlf),
N'
SELECT DB_NAME() AS DBName, COUNT(*) AS VLFCount
FROM sys.dm_db_log_info(DB_ID())
',
NULL,
NULL,
CASE WHEN (SELECT VLFCount FROM vlf) > 1000 THEN 0 ELSE 1 END;
/***************************************************************************************
D11. High CPU queries (simplified).
E.g. if any query has total_worker_time > 100,000,000 (~100s CPU) => WARNING
***************************************************************************************/
;WITH topCPU AS
(
SELECT
qs.plan_handle,
qs.total_worker_time AS TotalCPU,
qs.execution_count AS ExecCount,
qs.total_worker_time / qs.execution_count AS AvgCPU,
SUBSTRING(
qt.TEXT,
(qs.statement_start_offset/2)+1,
(
CASE
WHEN qs.statement_end_offset = -1
THEN LEN(CONVERT(NVARCHAR(MAX), qt.TEXT)) * 2
ELSE qs.statement_end_offset
END - qs.statement_start_offset
)/2
) AS SqlText --select top 10 *
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qt.dbid = DB_ID()
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN EXISTS (
SELECT 1 FROM topCPU WHERE TotalCPU > 100000000 -- ~100 CPU seconds
)
THEN 'WARNING' ELSE 'OK' END,
'DB',
CASE WHEN EXISTS (
SELECT 1 FROM topCPU WHERE TotalCPU > 100000000
)
THEN 'One or more queries used >100s total CPU time.'
ELSE 'OK. No extremely high CPU queries found.'
END,
CASE WHEN EXISTS (
SELECT 1 FROM topCPU WHERE TotalCPU > 100000000
)
THEN 'Impact: Potential performance bottleneck from CPU-heavy queries.'
ELSE ''
END,
CASE WHEN EXISTS (
SELECT 1 FROM topCPU WHERE TotalCPU > 100000000
)
THEN 1 ELSE 0 END,
N'
SELECT TOP 5
qs.total_worker_time AS TotalCPU,
qs.execution_count AS ExecCount,
(qs.total_worker_time / qs.execution_count) AS AvgCPU,
SUBSTRING(qt.TEXT,
(qs.statement_start_offset/2)+1,
(CASE WHEN qs.statement_end_offset=-1
THEN LEN(CONVERT(NVARCHAR(MAX), qt.TEXT))*2
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2
) AS SqlText
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qs.database_id = DB_ID()
ORDER BY qs.total_worker_time DESC
',
NULL,
NULL,
CASE WHEN EXISTS (
SELECT 1 FROM topCPU WHERE TotalCPU > 100000000
) THEN 0 ELSE 1 END;
/***************************************************************************************
D12. Collation mismatch vs. DB collation (simple example)
***************************************************************************************/
;WITH colMismatch AS
(
SELECT
t.name AS TableName,
c.name AS ColumnName,
c.collation_name,
DBLevelColl = DATABASEPROPERTYEX(DB_NAME(), 'Collation')
FROM sys.tables t
JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.collation_name IS NOT NULL
AND c.collation_name <> cast(DATABASEPROPERTYEX(DB_NAME(), 'Collation') as nvarchar(100))
)
INSERT INTO @tblDBHealth
SELECT
CASE WHEN EXISTS (SELECT * FROM colMismatch) THEN 'WARNING' ELSE 'OK' END,
'DB',
CASE WHEN EXISTS (SELECT * FROM colMismatch)
THEN CONCAT('Collation mismatches found. ', (SELECT COUNT(*) FROM colMismatch), ' columns differ.')
ELSE 'OK. No collation mismatches vs. DB collation.'
END,
CASE WHEN EXISTS (SELECT * FROM colMismatch)
THEN 'Impact: Potential errors or performance issues with string comparisons.'
ELSE ''
END,
COALESCE((SELECT COUNT(*) FROM colMismatch), 0),
N'
SELECT t.name AS TableName,
c.name AS ColumnName,
c.collation_name,
DATABASEPROPERTYEX(DB_NAME(), ''Collation'') AS DBLevelColl
FROM sys.tables t
JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.collation_name IS NOT NULL
AND c.collation_name <> cast(DATABASEPROPERTYEX(DB_NAME(), ''Collation'') as nvarchar(100))
',
NULL,
NULL,
CASE WHEN EXISTS (SELECT * FROM colMismatch) THEN 0 ELSE 1 END;
/***************************************************************************************
D13. Database compatibility level
For example, if DB is < 150 on a SQL 2019 instance => Warning
***************************************************************************************/
DECLARE @SQLProductVersion nvarchar(1000) = CAST(SERVERPROPERTY('ProductVersion') as nvarchar(1000))
INSERT INTO @tblDBHealth
SELECT
CASE WHEN (d.compatibility_level < 150 AND @SQLProductVersion LIKE '15%')
THEN 'WARNING'
WHEN (d.compatibility_level < 160 AND @SQLProductVersion LIKE '16%')
THEN 'WARNING'
ELSE 'OK'
END,
'DB',
CASE WHEN (d.compatibility_level < 150 AND @SQLProductVersion LIKE '15%')
THEN CONCAT('DB Compatibility level is ', CONVERT(VARCHAR(5), d.compatibility_level),
', recommended 150 for SQL 2019.')
WHEN (d.compatibility_level < 160 AND @SQLProductVersion LIKE '16%')
THEN CONCAT('DB Compatibility level is ', CONVERT(VARCHAR(5), d.compatibility_level),
', recommended 160 for SQL 2022.')
ELSE 'OK. Compatibility level is at or above recommended.'
END,
CASE WHEN (d.compatibility_level < 150 AND @SQLProductVersion LIKE '15%')
OR (d.compatibility_level < 160 AND @SQLProductVersion LIKE '16%')
THEN 'Impact: Missing potential performance improvements, T-SQL features.'
ELSE ''
END,
CASE WHEN (d.compatibility_level < 150 AND @SQLProductVersion LIKE '15%')
OR (d.compatibility_level < 160 AND @SQLProductVersion LIKE '16%')
THEN 1
ELSE 0
END,
N'
SELECT name, compatibility_level
FROM sys.databases
WHERE database_id = DB_ID()
',
CONCAT('Current level: ', d.compatibility_level),
NULL,
CASE WHEN (d.compatibility_level < 150 AND @SQLProductVersion LIKE '15%')
OR (d.compatibility_level < 160 AND @SQLProductVersion LIKE '16%')
THEN 0
ELSE 1
END
FROM sys.databases d
WHERE d.database_id = DB_ID();
/***************************************************************************************
D14. Critical Errors / Alerts (Placeholder)
Real implementation might parse SQL Error Log or Extended Events.
***************************************************************************************/
INSERT INTO @tblDBHealth
(
ErrorType, Module, HealthMessage, PossibleImpact,
NoOfIssues, DetailQuery, DetailData, LinkToResolve, IsInfoOnly
)
SELECT
'OK',
'DB',
'D14 placeholder: No check implemented for critical errors/alerts in logs.',
'',
0,
NULL,
NULL,
NULL,
1;
RETURN;
END
GO
|