Learn more about SQL Server tools

mssqltips logo
 

Tutorials          DBA          Dev          BI          Career          Categories          Webcasts          Whitepapers          Today's Tip          Join

Tutorials      DBA      Dev      BI      Categories      Webcasts

DBA    Dev    BI    Categories

 

Simple DBCC CHECKDB process to report on database corruption for all SQL Server databases


By:   |   Last Updated: 2018-09-25   |   Comments (1)   |   Related Tips: More > Database Console Commands DBCCs

Problem

As DBAs, we all know how important it is to check all of the SQL Server databases under our care for potential corruption issues. However, to accomplish that, we might not have the time to do any of the following:

  • Setup a maintenance plan across all X SQL Server instances under our support.
  • Setup a custom job to run a consistency check across all databases in all X SQL Server instances (which this tip particularly aims to address).
  • Build a PowerShell solution (if you want to get a bit fancy here).

Regardless of the way you choose to go, you have to consider other important factors as well:

  • Environment type (production / non-production)
  • When is the maintenance window?
  • How much time do you have during the maintenance window?
Solution

I have come up with a custom *simple solution that lets you check the consistency of all the databases under your care, save only the end result (without that much overhead) with a few additional important values, while having to modify only a couple of things that are very specific to your use case.

*By simple I’m not implying that the amount of code used is small, but instead simple in terms of what it accomplishes in the end.

SQL Server DBCC CHECKDB Script as a whole

The entire script consists of 3 sections:

  • Creation of the table to store the final results.
  • Creation of the custom stored procedure.
  • Creation of the job to automate it.

You can find complete "plug-and-play" script, in case you want to test it right away, attached within this tip.

I will be detailing each section in the next sub-sections.

Creation of the SQL Server DBCC CheckDB table

This will be used to store the result of the execution of the DBCC CHECKDB statement against each database.

  • You can use a different database, I used the master just as an example.
  • Here’s the description for the fields that might not be so obvious at a first glance:
    • [checkdb_type] -> ‘FULL’ or ‘PHYSICAL ONLY’ (will explain later the reason for these 2 values in the Stored Procedure).
    • [completion_time] in seconds because it is very useful to know how long a CHECKDB against each database took. You can even use this information to plan your checks in a more efficient way because you can get an average of the time it takes to complete for each database.
    • [last_good_dbcc] tells you when was the last time a successful CHECKDB took place, something very important to know so that later on you can prioritize those that haven’t had a check in a while (for whatever reason).
USE [master]
GO
CREATE TABLE [dbo].[CheckDB](
   [instance] [varchar](255) NOT NULL,
   [database] [varchar](255) NOT NULL,
   [size] [int] NOT NULL,
   [result] [varchar](max) NULL,
   [checkdb_type] [varchar](255) NULL,
   [data_collection_timestamp] [smalldatetime] NULL,
   [completion_time] [int] NULL,
   [last_good_dbcc] [datetime] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

Creation of the SQL Server DBCC CheckDB Stored Procedure

At a first glance the stored procedure might seem quite big, but it is only because it contains some validations and considerations that you probably won’t find in similar versions.

Here are the most important points to note:

  • From the very start, you can see that the stored procedure can accept 2 parameters:
    • @dbSizeThreshold: by default, it is set at 512,000 MB (500GB) and it is used to determine if the CHECKDB will use the "PHYSICAL_ONLY" option for databases larger than 500GB (you can set it to whatever you think fits your case). Let’s keep in mind that your maintenance window might not be enough to accommodate a full CHECKDB for a big database (or databases), and a run with "PHYSICAL_ONLY" option might represent at least a small victory for you.
    • @force: this parameter is designed to override the @dbSizeThreshold behavior. For instance, you might have an instance with only 1 big database (bigger than the threshold value) but you feel confident enough that a full CHECKDB can be successful within your maintenance window, so you go for it and set it to 1 to proceed with a full run of the CHECKDB.
  • This stored procedure takes into account the SQL Server version where it’s being executed, so you don’t have to worry to deploy a different version depending on how old yours is.
    • Since we are executing the CHECKDB command WITH TABLERESULTS, there is a slight variation in the output obtained starting from SQL Server 2012 (since it adds more fields to the mix), but the stored procedure creates the right table for you.
  • You might notice that I use a cursor to traverse the database list and run the CHECKDB against each one of them.
    • I know that cursors are somewhat of a "taboo" thing within our profession, for their low performance, but its purpose is purely for control.
    • First I populate the table "master.dbo.CheckDB" with the databases to be checked by the stored procedure. As each one is checked successfully, then the respective record is updated in the table.
    • By doing this, you know if a particular database was not a part of the process.
      • Server might have been patched and rebooted while the execution was taking place.
      • Server was unexpectedly shutdown while the execution was taking place.
      • The SQL Server instance was restarted while the execution was taking place.
    • When CHECKDB is executed against the master database, the output for the ResourceDB is also obtained, so I also make sure to capture that one as well.
USE [master]
GO

/****** Object: StoredProcedure [dbo].[Simple_CHECKDB]  Script Date: 8/30/2018 3:45:26 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:      Alejandro Cobar
-- Create date: 8/22/2018
-- Description: Runs DBCC CHECKDB ON each database and stores the output message
-- =============================================
CREATE PROCEDURE [dbo].[Simple_CHECKDB]
@dbSizeThreshold INT = 512000,
@force INT = 0
AS
BEGIN
   SET NOCOUNT ON;

   -- Temporal table to obtain the "Last Known Good DBCC CHECKDB" execution for each database
   IF NOT OBJECT_ID('tempdb..#DBInfo') IS NULL
   DROP TABLE #DBInfo;

   CREATE TABLE #DBInfo
   ([ParentObject] VARCHAR(255)
   ,[Object] VARCHAR(255)
   ,[Field] VARCHAR(255)
   ,[Value] VARCHAR(255)
   )

   -- Depending on the SQL Server version, the respective temporal table will be created to store the CHECKDB results
   DECLARE @version INT;
   SELECT @version = RIGHT(LEFT(@@VERSION,25),4);

   --Starting from SQL Server 2012, new fields were introduced to the output of DBCC CHECKDB WITH TABLERESULTS
   IF NOT OBJECT_ID('tempdb..#CheckDB_old') IS NULL
   DROP TABLE #CheckDB_old;

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

   IF @version >= 2012
   CREATE TABLE #CheckDB_new
   ([Error] INT
   ,[Level] INT
   ,[State] INT
   ,[MessageText] VARCHAR(MAX)
   ,[RepairLevel] INT
   ,[Status] INT
   ,[DbId] INT
   ,[DbFragId] INT
   ,[ObjectID] INT
   ,[IndexId] INT
   ,[PartitionId] INT
   ,[AllocUnitId] INT
   ,[RidDbId] INT
   ,[RidPruId] INT
   ,[File] INT
   ,[Page] INT
   ,[Slot] INT
   ,[RefDbID] INT
   ,[RefPruId] INT
   ,[RefFile] INT
   ,[RefPage] INT
   ,[RefSlot] INT
   ,[Allocation] INT);
   ELSE
   CREATE TABLE #CheckDB_old
   ([Error] INT
   ,[Level] INT
   ,[State] INT
   ,[MessageText] VARCHAR(MAX)
   ,[RepairLevel] INT
   ,[Status] INT
   ,[DbId] INT
   ,[ObjectID] INT
   ,[IndexId] INT
   ,[PartitionId] INT
   ,[AllocUnitId] INT
   ,[File] INT
   ,[Page] INT
   ,[Slot] INT
   ,[RefFile] INT
   ,[RefPage] INT
   ,[RefSlot] INT
   ,[Allocation] INT);
   
   -- We don't want to keep all the CHECKDB results here for a very long time...
   TRUNCATE TABLE master.dbo.CheckDB;

   -- Insert all the databases that will be checked
   -- Only consider those in ONLINE state and exclude the SNAPSHOTS
   INSERT INTO master.dbo.CheckDB
   SELECT CAST(SERVERPROPERTY('SERVERNAME') AS VARCHAR(255)), DB_NAME(mf.database_id), SUM(mf.size*8)/1024, NULL, NULL, NULL, NULL,NULL
   FROM sys.master_files mf
   JOIN sys.databases db ON mf.database_id = db.database_id
   WHERE mf.state_desc = 'ONLINE' AND db.source_database_id IS NULL
   GROUP BY mf.database_id;
   
   -- Prepare a cursor to have a better control of which databases where checked and which weren't
   -- A sudden server or instance reboot might affect this whole process...
   DECLARE @db VARCHAR(255);

   DECLARE checkdb_cursor CURSOR FOR
   SELECT [database]
   FROM master.dbo.CheckDB
   WHERE result IS NULL;

   OPEN checkdb_cursor
   FETCH NEXT FROM checkdb_cursor INTO @db
   
   WHILE @@FETCH_STATUS = 0
   BEGIN
      DECLARE @startTime DATETIME;
      DECLARE @endTime DATETIME;

      DECLARE @databaseSize INT;
      SELECT @databaseSize = size FROM master.dbo.CheckDB WHERE [database] = @db;
      
      SET @startTime = GETDATE();
   
      IF @databaseSize <= @dbSizeThreshold OR @force = 1
      BEGIN
         IF @version >= 2012
         BEGIN
            INSERT INTO #CheckDB_new
            ([Error], [Level], [State], [MessageText], [RepairLevel],
            [Status], [DbId], [DbFragId], [ObjectID], [IndexId], [PartitionId],
            [AllocUnitId], [RidDbId], [RidPruId], [File], [Page], [Slot], [RefDbID],
            [RefPruId], [RefFile], [RefPage], [RefSlot], [Allocation])
            EXEC ('DBCC CHECKDB('[email protected]+') WITH TABLERESULTS');
            
            SET @endTime = GETDATE();

            UPDATE master.dbo.CheckDB
            SET result = MessageText, checkdb_type = 'FULL', data_collection_timestamp = GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime)
            FROM #CheckDB_new
            WHERE [Error] = 8989 AND [database] = @db;

            IF @db = 'master'
            INSERT INTO master.dbo.CheckDB
            SELECT CAST(SERVERPROPERTY('SERVERNAME') AS VARCHAR(255)), 'mssqlsystemresource', ISNULL((SELECT CONVERT(DECIMAL(10,2),SUM(size / 1024.0)) AS 'size' FROM sys.sysaltfiles WHERE DBID = 32767),0), MessageText, 'FULL', GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime), NULL
            FROM #CheckDB_new
            WHERE [Error] = 8989 AND DbId = 32767;

            TRUNCATE TABLE #CheckDB_new;
         END;
         ELSE
         BEGIN
            INSERT INTO #CheckDB_old
            ([Error], [Level], [State], [MessageText], [RepairLevel],
            [Status], [DbId], [ObjectID], [IndexId], [PartitionId],
            [AllocUnitId], [File], [Page], [Slot], [RefFile], [RefPage], [RefSlot], [Allocation])
            EXEC ('DBCC CHECKDB('[email protected]+') WITH TABLERESULTS');

            SET @endTime = GETDATE();

            UPDATE master.dbo.CheckDB
            SET result = MessageText, checkdb_type = 'FULL', data_collection_timestamp = GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime)
            FROM #CheckDB_old
            WHERE [Error] = 8989 AND [database] = @db;

            IF @db = 'master'
            INSERT INTO master.dbo.CheckDB
            SELECT CAST(SERVERPROPERTY('SERVERNAME') AS VARCHAR(255)), 'mssqlsystemresource', ISNULL((SELECT CONVERT(DECIMAL(10,2),SUM(size / 1024.0)) AS 'size' FROM sys.sysaltfiles WHERE DBID = 32767),0), MessageText, 'FULL', GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime), NULL
            FROM #CheckDB_old
            WHERE [Error] = 8989 AND DbId = 32767;

            TRUNCATE TABLE #CheckDB_old;
         END;
      END;
      ELSE
      BEGIN
         IF @version >= 2012
         BEGIN
            INSERT INTO #CheckDB_new
            ([Error], [Level], [State], [MessageText], [RepairLevel],
            [Status], [DbId], [DbFragId], [ObjectID], [IndexId], [PartitionId],
            [AllocUnitId], [RidDbId], [RidPruId], [File], [Page], [Slot], [RefDbID],
            [RefPruId], [RefFile], [RefPage], [RefSlot], [Allocation])
            EXEC ('DBCC CHECKDB('[email protected]+') WITH TABLERESULTS, PHYSICAL_ONLY');
            
            SET @endTime = GETDATE();

            UPDATE master.dbo.CheckDB
            SET result = MessageText, checkdb_type = 'PHYSICAL ONLY', data_collection_timestamp = GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime)
            FROM #CheckDB_new
            WHERE [Error] = 8989 AND [database] = @db;

            IF @db = 'master'
            INSERT INTO master.dbo.CheckDB
            SELECT CAST(SERVERPROPERTY('SERVERNAME') AS VARCHAR(255)), 'mssqlsystemresource', ISNULL((SELECT CONVERT(DECIMAL(10,2),SUM(size / 1024.0)) AS 'size' FROM sys.sysaltfiles WHERE DBID = 32767),0), MessageText, 'PHYSICAL ONLY', GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime), NULL
            FROM #CheckDB_new
            WHERE [Error] = 8989 AND DbId = 32767;

            TRUNCATE TABLE #CheckDB_new;
         END;
         ELSE
         BEGIN
            INSERT INTO #CheckDB_old
            ([Error], [Level], [State], [MessageText], [RepairLevel],
            [Status], [DbId], [ObjectID], [IndexId], [PartitionId],
            [AllocUnitId], [File], [Page], [Slot], [RefFile], [RefPage], [RefSlot], [Allocation])
            EXEC ('DBCC CHECKDB('[email protected]+') WITH TABLERESULTS, PHYSICAL_ONLY');

            SET @endTime = GETDATE();

            UPDATE master.dbo.CheckDB
            SET result = MessageText, checkdb_type = 'PHYSICAL ONLY', data_collection_timestamp = GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime)
            FROM #CheckDB_old
            WHERE [Error] = 8989 AND [database] = @db;

            IF @db = 'master'
            INSERT INTO master.dbo.CheckDB
            SELECT CAST(SERVERPROPERTY('SERVERNAME') AS VARCHAR(255)), 'mssqlsystemresource', ISNULL((SELECT CONVERT(DECIMAL(10,2),SUM(size / 1024.0)) AS 'size' FROM sys.sysaltfiles WHERE DBID = 32767),0), MessageText, 'PHYSICAL ONLY', GETDATE(), completion_time = DATEDIFF(ss, @startTime, @endTime), NULL
            FROM #CheckDB_old
            WHERE [Error] = 8989 AND DbId = 32767;

            TRUNCATE TABLE #CheckDB_old;
         END;
      END;
      
      -- Get the information for the "Last Known Good DBCC CHECKDB" execution
      INSERT INTO #DBInfo ([ParentObject], [Object], [Field], [Value])
     EXEC ('DBCC DBINFO('[email protected]+') WITH TABLERESULTS');

      UPDATE master.dbo.CheckDB
      SET last_good_dbcc = [Value]
      FROM #DBInfo
      WHERE [Field] = 'dbi_dbccLastKnownGood' AND [database] = @db;
      
      IF @db = 'master'
      UPDATE master.dbo.CheckDB
      SET last_good_dbcc = (SELECT last_good_dbcc FROM master.dbo.CHECKDB WHERE [database] = 'master')
      WHERE [database] = 'mssqlsystemresource';

      TRUNCATE TABLE #DBInfo;

      FETCH NEXT FROM checkdb_cursor INTO @db
   END

   CLOSE checkdb_cursor
   DEALLOCATE checkdb_cursor
   
   -- Drop whichever temporal table was created
   IF NOT OBJECT_ID('tempdb..#CheckDB_old') IS NULL
   DROP TABLE #CheckDB_old;

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

   IF NOT OBJECT_ID('tempdb..#DBInfo') IS NULL
   DROP TABLE #DBInfo;
END
GO

Creation of the DBCC CheckDB SQL Server Agent Job

Depending on your particular case, you might want to accommodate the schedule of the job to fit your maintenance window.

  • You might create a version for your production environment and another for your non-prod environment and start the deployment from there.
  • The modifications you probably would want to make are the following:
    • This example uses the ‘sa’ account, but you can use a different one.
    • You can change the name of the job.
    • You can change the schedule of the job.
USE [msdb]
GO

/****** Object:  Job [Database Consistency Check]    Script Date: 8/28/2018 5:53:06 PM ******/
BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0
/****** Object:  JobCategory [[Uncategorized (Local)]]    Script Date: 8/28/2018 5:53:06 PM ******/
IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
BEGIN
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

END

DECLARE @jobId BINARY(16)
EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'DBA - Database Consistency Check', 
      @enabled=1, 
      @notify_level_eventlog=0, 
      @notify_level_email=0, 
      @notify_level_netsend=0, 
      @notify_level_page=0, 
      @delete_level=0, 
      @description=N'Runs a DBCC CHECKDB operation against all the databases in the SQL Server instance', 
      @category_name=N'[Uncategorized (Local)]', 
      @owner_login_name=N'sa', @job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object:  Step [CheckDB]    Script Date: 8/28/2018 5:53:07 PM ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @[email protected], @step_name=N'CheckDB', 
      @step_id=1, 
      @cmdexec_success_code=0, 
      @on_success_action=1, 
      @on_success_step_id=0, 
      @on_fail_action=2, 
      @on_fail_step_id=0, 
      @retry_attempts=0, 
      @retry_interval=0, 
      @os_run_priority=0, @subsystem=N'TSQL', 
      @command=N'EXEC Simple_CHECKDB', 
      @database_name=N'master', 
      @flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @[email protected], @name=N'CHECKDB Execution', 
      @enabled=1, 
      @freq_type=8, 
      @freq_interval=64, 
      @freq_subday_type=1, 
      @freq_subday_interval=0, 
      @freq_relative_interval=0, 
      @freq_recurrence_factor=1, 
      @active_start_date=20180822, 
      @active_end_date=99991231, 
      @active_start_time=200000, 
      @active_end_time=235959
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:
GO

SQL Server DBCC CHECKDB Demo

Let me present you a few images of the output stored in the CheckDB table, for possible execution cases.

The execution times for the StackOverflow database are a bit high because I have those test databases in an external mechanical hard drive (5400 RMP), so that kind of slowness is expected.

Normal Execution:

EXEC Simple_CHECKDB
dbcc checkdb output

Modifying the @databaseSizeThreshold parameter:

EXEC Simple_CHECKDB @dbSizeThreshold = 100000

Notice the "PHYSICAL ONLY" result and it took 1,356 sec. less to complete.

dbcc checkdb output

Modifying the @force parameter:

EXEC Simple_CHECKDB @force = 1, @dbSizeThreshold = 5

Even if the database size parameter, to trigger a "PHYSICAL_ONLY" run, matches with the actual sizes of the databases to check, the @force parameter will "force" the CHECKDB to be a full one.

dbcc checkdb output

Bonus Content - PowerShell Version to Collect SQL Server DBCC CHECKDB Results

Attached within this tip you can also find a custom PowerShell that allows you to grab all the CheckDB results from a set of SQL Server instances and store them in a central database that you might have.

  • You will have to modify some parameters, but I’ve put XXX where your values should go to make it work.
$labServer = "XXX"
$inventoryDB = "XXX"


#Fetch all the instances to connect to
$instanceLookupQuery = "SELECT instance from XXX"

$instances = Invoke-Sqlcmd -ServerInstance $labServer -Database $inventoryDB -Query $instanceLookupQuery

if($args[0].Length -gt 0 -and $instances -eq $null){
	Write-Host "Instance doesn't exist"
	break
}

#For each instance, grab the CheckDB results from master.dbo.CheckDB
foreach ($instance in $instances){
	$CheckDBQuery = "SELECT * FROM master.dbo.CheckDB"
	
	Write-Host "Fetching CheckDB results for instance" $instance.instance
	try{$results = Invoke-Sqlcmd -Query $CheckDBQuery -ServerInstance $instance.instance -ErrorAction Stop -querytimeout 30}
         catch{Invoke-Sqlcmd -ServerInstance $labServer -Database $inventoryDB -Query "INSERT INTO XXX VALUES('CheckDB','$($instance.instance)','$($_.Exception.Message)',GETDATE())"}
	
	#Perform the INSERT in the XXX table only if it returns information
   	if($results.Length -ne 0){
    
		#Build the insert statement
		$insert = "INSERT INTO XXX VALUES"
		foreach($result in $results){        
            	$result_text = $result['result'] -replace "'","''"
            
			$insert += "
			(
			'"+$result['instance']+"',
			'"+$result['database']+"',
			"+$result['size']+",
			'"+$result_text+"',
			'"+$result['checkdb_type']+"',
            		'"+$result['data_collection_timestamp']+"',
            		"+$result['completion_time']+",
            		'"+$result['last_good_dbcc']+"'
            		),
			"
	     	}

		#Store the results in the local XXX table in our Lab Server instance
		Invoke-Sqlcmd -Query $insert.Substring(0,$insert.LastIndexOf(',')) -ServerInstance $labServer -Database $inventoryDB
	}	
}

Write-Host "Done!"
Next Steps
  • This code has been tested in the following SQL Server versions: 2005, 2008, 2008R2, 2012, 2014, 2016 and 2017.
  • I’m all ears for any suggestions, improvements, recommendations regarding my code; which, in fact, I’m planning to keep updating to make it even better.
  • I have seen a lot of debate on whether or not you should run a CHECKDB against the tempdb. You should check out this post by Kendra Little.
  • Check out all of the SQL Server DBCC CHECKDB tips.


Last Updated: 2018-09-25


next webcast button


next tip button



About the author
MSSQLTips author Alejandro Cobar Alejandro Cobar is an MCP, SQL Server DBA and Developer with 10+ years of experience working in a variety of environments.

View all my tips
Related Resources




Post a comment or let the author know this tip helped.

All comments are reviewed, so stay on subject or we may delete your comment. Note: your email address is not published. Required fields are marked with an asterisk (*).

*Name    *Email    Email me updates 


Signup for our newsletter
 I agree by submitting my data to receive communications, account updates and/or special offers about SQL Server from MSSQLTips and/or its Sponsors. I have read the privacy statement and understand I may unsubscribe at any time.



    



Tuesday, September 25, 2018 - 11:24:59 AM - Max Back To Top

Alejandro, great script. Very handy. Thanks for sharing!


Learn more about SQL Server tools