Different ways to sort multi-server SQL Server query results using Central Management Server

By:   |   Comments   |   Related: More > Central Management Servers


Problem

Central Management Server (CMS) is a SQL Server feature that allows you to manage multiple SQL Servers at the same time. You can run T-SQL scripts or execute Policy-Based Management policies on multiple servers. You can also group SQL Servers and run the scripts on a specific group of SQL Servers.

How else can CMS be used? How can we overcome some of the limitations when we run queries against CMS Server Groups?

Solution

In this tip we will show you a couple of CMS tricks. In our examples below we will show how you can sort the CMS query results for different scenarios. We will also provide a couple of examples of alternative uses of CMS.

Note, that the same techniques could be applied to SQL Server Management Studio (SSMS) Registered Servers.

Sorting a Query's Results in the CMS

One of the limitations of Multi-Server Queries is that results cannot really be sorted the same way as for a single server query. The results are sorted inside each server's query and then grouped by server:

CMS Query results example

Note, that the first column in our result is not sorted by the SQL Server name.

The entire result set is tricky to sort, but we will show you how to get sorted results in specific scenarios.

Note, that results in our examples below are sorted, but may not be always sorted as expected (depending on how fast/slow SQL Server is, etc.). See examples and explanations below.

Sorting the CMS Query Results by SQL Server Version

In this example we will sort our query results by SQL Server Version.

USE [master]
GO

DECLARE @delay VARCHAR(12)

SELECT  @delay = ISNULL('00:00:' + RIGHT('00' +
                 CAST(CAST(CAST(ISNULL(SERVERPROPERTY('ProductMajorVersion'),
                 REPLACE(LEFT(CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(200)), 2), '.', ''))
                 AS VARCHAR(2)) AS INT) - 8 AS VARCHAR(2)), 2) + '.' +
                 LEFT(CAST(ISNULL(SERVERPROPERTY('ProductBuild'), '000') AS VARCHAR(4))
                 , 3), '00:00:00.000')

/* -- or if you don't have in your environment SQL Server 2005 and lower (or other versions 
    -- that don't support "ProductBuild" Server Property) replace the code above with this:
SELECT  @delay = '00:00:' + RIGHT('00' + CAST(CAST(CAST(SERVERPROPERTY('ProductMajorVersion')
                 AS VARCHAR(2)) AS INT) - 8 AS VARCHAR(2)), 2) + '.' + 
                 LEFT(CAST(SERVERPROPERTY('ProductBuild') AS VARCHAR(4)), 3)
*/

WAITFOR DELAY @delay

SELECT COUNT([name]) number_of_dbs, 
   SERVERPROPERTY('ProductVersion') product_version, 
   @@VERSION full_version 
FROM sys.databases
GO
CMS Query results sorted by SQL Server version

We use SQL Server Major Version and subtract 8 from it to generate seconds for a delay in our query.

The WAITFOR statement that we use in the query above accepts arguments that have a datetime data type.

Fractional precision of the datetime data type has 3 digits (for example, "2018-01-01 23:59:59.997"). Note, that SQL Server's Build number that we use in our query has 4 characters (12.0.5553.0), but in our query we only use the 3 left characters ("555"). There is a chance that you are going to have, for example, version 12.0.5553.0 and 12.0.5557.0. So, you should be aware that the query above doesn't guarantee sorted results for the cases like this.

For example, in our demo above we have SQL2014_TEMP and SQL2014_TEMP_2 SQL Servers with the following versions and delay calculated based on the version:

SQL Server Name SQL Server Version Delay calculated
SQL2014_TEMP 12.0.5553.0 00:00:04.555
SQL2014_TEMP_2 12.0.5557.0 00:00:04.555

Here is another example. Let's assume we have the following servers (the versions are made up for this example):

SQL Server Name SQL Server Version Delay calculated
SQL2014_TEMP_3 12.0.5563.0 00:00:04.556
SQL2014_TEMP_4 12.0.5577.0 00:00:04.557

Because the difference in the delay is only 1 millisecond, the results might be sorted differently each time you run the query. You may get "SQL2014_TEMP_3" above "SQL2014_TEMP_4" one time and another way the next time.

There are a couple of reasons for that:

  1. The servers may have a different response time depending on a speed of a network connection, how busy the server is etc.
  2. If you check Microsoft's documentation for the datetime data type you will notice that milliseconds are rounded to increments of .000, .003, or .007 seconds. Here is a query that demonstrates this rounding:
USE [master]
GO

SELECT '00:00:04.556' AS char_delay,
CAST('00:00:04.556' AS DATETIME) AS delay_datetime, 
DATEADD(MILLISECOND , 1, CAST('00:00:04.556' AS DATETIME)) AS delay_time_added
UNION
SELECT '00:00:04.557',
CAST('00:00:04.557' AS DATETIME), 
DATEADD(MILLISECOND , 1, CAST('00:00:04.557' AS DATETIME))
UNION
SELECT '00:00:04.558',
CAST('00:00:04.558' AS DATETIME), 
DATEADD(MILLISECOND , 1, CAST('00:00:04.558' AS DATETIME))
UNION
SELECT '00:00:04.559',
CAST('00:00:04.559' AS DATETIME), 
DATEADD(MILLISECOND , 1, CAST('00:00:04.559' AS DATETIME))
UNION
SELECT '00:00:04.560',
CAST('00:00:04.560' AS DATETIME), 
DATEADD(MILLISECOND , 1, CAST('00:00:04.560' AS DATETIME))
UNION
SELECT '00:00:04.561',
CAST('00:00:04.561' AS DATETIME), 
DATEADD(MILLISECOND , 1, CAST('00:00:04.561' AS DATETIME))
GO 
Datetime conversion/rounding

Note, that the delay value we generate will be rounded further in some cases and this may affect the sorting. Sorting only by SQL Server Major Version though should work fine (unless server's response is really slow, slower than 1 sec).

Please note that we don't have in our example SQL Server 2008. We assume that an environment has either SQL Servers 2008 or SQL Servers 2008 R2. If your environment has both - SQL Server 2008 and SQL Server 2008 R2 you may need to tweak the initial query.

Sorting the CMS Query Results by an Environment (Last 3 Characters of the SQL Server Name)

If you use SQL Server naming conventions to include your SQL Server Environment in some kind of suffix in server's name then probably it's a good idea to have these suffixes in increments that could be sorted.

In our example below we have the following suffixes for the naming conventions:

  • "5nn" - Production Environment
  • "6nn" - Test Environment
  • "7nn" - Development Environment.
USE [master]
GO
DECLARE  @delay VARCHAR(12), @server_name SYSNAME 

SELECT @server_name = CASE WHEN @@SERVERNAME LIKE '%\%' 
            THEN LEFT(@@SERVERNAME, CHARINDEX('\', @@SERVERNAME) -1) 
            ELSE @@SERVERNAME END

SELECT @delay = '00:00:0' + LEFT(RIGHT(@server_name, 3), 1) + '.' 
         + CAST(CAST(RIGHT(@server_name, 2 ) AS SMALLINT)*2 AS VARCHAR(12)) + '00'

WAITFOR DELAY @delay

SELECT @delay AS delay_set, COUNT([name]) number_of_dbs
 FROM sys.databases
GO

Depending on your naming conventions you may need to modify the query above.

Sorting the CMS Query Results by an Environment

Note, that for another execution we have a slightly different result as DEVSQL701\INST1 was responding slower this time and the difference in delay is only 10 milliseconds:

Sorting the CMS Query Results by an Environment  - nonsorted

Sorting the CMS Query Results by the Environment (First 3 Characters of the SQL Server Name)

This is similar to the previous example, but SQL Servers in this example have a prefix that identifies the environment:

  • "DEV" for the Development Environment
  • "TST" for the Test Environment
  • "PRD" for the Production.

We will use the first 3 characters for the sorting in our script below:

USE [master]
GO 
DECLARE  @delay VARCHAR(12)

SELECT @delay = '00:00:0' + CASEWHEN LEFT(@@servername,3 ) = 'DEV' THEN '1.000'
               WHEN LEFT(@@servername,3 ) = 'TST' THEN '2.000'
               WHEN LEFT(@@servername,3 ) = 'PRD' THEN '3.000' END

WAITFOR DELAY @delay

SELECT COUNT([name]) number_of_dbs FROM sys.databases
GO	
CMS Results Sorted by SQL Server environment (using server name prefix)

Sorting the CMS Query Results by SQL Server Started Date/Time

In this example we would like to have query results ordered by SQL Server last start time.

We will use sys.dm_os_sys_info Dynamic Management View (DMV) to get the start date and time.

Please note that this works only on currently supported versions of SQL Server (SQL Server 2008 and higher versions). The sys.dm_os_sys_info DMV in SQL Server 2005 did not have the sqlserver_start_time column that we use in our query. Also, we use the TIME data type that was introduced with other new data types in SQL Server 2008.

Here is a query we used to get steps for generating the "WAITFOR" delay:

USE [master]
GO
 
DECLARE  @delay VARCHAR(12)

SELECT  @delay = CAST(DATEADD(ss, DATEDIFF(dd, sqlserver_start_time, GETDATE()),
                 CAST('00:00:00.' + 
                 LEFT(RIGHT('00' + CAST(24 - DATEPART(hh, sqlserver_start_time) AS VARCHAR(2)), 2) +
                 CAST(60 - DATEPART(mi, sqlserver_start_time) AS VARCHAR(2)), 3) AS TIME)) 
                 AS VARCHAR(12))
FROM sys.dm_os_sys_info   

WAITFOR DELAY @delay

SELECT sqlserver_start_time,
   CAST(sqlserver_start_time AS DATE) AS date_started ,
   DATEDIFF(dd, sqlserver_start_time, GETDATE()) AS N_days_before,
   CAST(sqlserver_start_time AS TIME) AS time_started,
   DATEPART(hh, sqlserver_start_time) AS hour_started,
   24-DATEPART(hh, sqlserver_start_time) AS hour_started_desc,
   DATEADD(ss,  DATEDIFF(dd, sqlserver_start_time, GETDATE()), 
      CAST('00:00:00' AS TIME)) AS generate_waitfor_days_only,
      '.' + LEFT(RIGHT('00' + CAST(24 - DATEPART(hh, sqlserver_start_time) AS VARCHAR(2)), 2) + 
      CAST(60 - DATEPART(mi, sqlserver_start_time) AS VARCHAR(2)), 3) 
      AS generate_milliseconds,
   CAST(DATEADD(ss, DATEDIFF(dd, sqlserver_start_time, GETDATE()), CAST('00:00:00.' + 
      LEFT(RIGHT('00' + CAST(24 - DATEPART(hh, sqlserver_start_time) AS VARCHAR(2)), 2) +
      CAST(60 - DATEPART(mi, sqlserver_start_time) AS VARCHAR(2)), 3) AS TIME)) 
      AS VARCHAR(12)) AS generate_waitfor_desc,
   CAST(DATEADD(ss, 31 - DATEDIFF(dd, sqlserver_start_time, GETDATE()),CAST('00:00:00.' + 
      LEFT(RIGHT('00' + CAST( DATEPART(hh, sqlserver_start_time) AS VARCHAR(2)), 2) +
      CAST(DATEPART(mi, sqlserver_start_time) AS VARCHAR(2)), 3) AS TIME)) 
      AS VARCHAR(12))  generate_waitfor_asc
FROM sys.dm_os_sys_info
GO
Sorting the CMS Query Results by SQL Server Started Date/Time (Prep)

Seconds are "N_days_before" column for descending order or “31 - DATEDIFF(dd, sqlserver_start_time, GETDATE())” for ascending order.

Milliseconds generated from an hour started (or “24 - DATEPART(hh, sqlserver_start_time)” ) and tens of minutes.

Here is the final query to sort the results in descending order by SQL Server start time:

USE [master]
GO 
DECLARE  @delay VARCHAR(12)

SELECT @delay = CAST(DATEADD(ss, DATEDIFF(dd, sqlserver_start_time, GETDATE()),
                CAST('00:00:00.' + 
                LEFT(RIGHT('00' + CAST(24 - DATEPART(hh, sqlserver_start_time) AS VARCHAR(2)), 2) +
                CAST(60 - DATEPART(mi, sqlserver_start_time) AS VARCHAR(2)), 3) AS TIME)) 
                AS VARCHAR(12)) 
FROM sys.dm_os_sys_info   

WAITFOR DELAY @delay

SELECT @delay delay_gen, CAST(@delay AS DATETIME) AS delay_datetime, sqlserver_start_time 
FROM sys.dm_os_sys_info
GO	
Sorting the CMS Query Results by SQL Server Started Date/Time (DESC)

Here is the final query to sort the results in ascending order by SQL Server start time:

USE [master]
GO 
DECLARE @delay VARCHAR(12)

SELECT @delay = CAST(DATEADD(ss, 31 - DATEDIFF(dd, sqlserver_start_time, GETDATE()),
                CAST('00:00:00.' + LEFT(RIGHT('00' + 
                CAST( DATEPART(hh, sqlserver_start_time) AS VARCHAR(2)), 2) +
                CAST(DATEPART(mi, sqlserver_start_time) AS VARCHAR(2)), 3) AS TIME)) 
                AS VARCHAR(12)) 
FROM sys.dm_os_sys_info  
 
WAITFOR DELAY @delay

SELECT @delay delay_gen, CAST(@delay AS DATETIME) AS delay_datetime, sqlserver_start_time 
FROM sys.dm_os_sys_info
GO	
Sorting the CMS Query Results by SQL Server Started Date/Time (ASC)

The result might not be sorted perfectly by time (depending on a server response as we noted above), but this should work fine for sorting results just by date. If your servers restarted on the same day then you can slightly modify the queries above and sort by time only.

If you need to run a query with sorted results for SQL Server 2005 or earlier you can use another method to find SQL Server start time from this article.

Other Uses of the CMS or Registered Servers

Using Registered Servers to Mask SQL Servers Names in the CMS

Sometimes it's hard to find a perfect set of SQL Servers for your demos. Especially, if you don't want to expose real SQL Servers names. You can use the CMS Registered Server name as server's alias for these purposes.

Here is an example of a SQL Server registered in CMS under a different name:

Register Server in CMS

Note, that the actual SQL Server name is REALSQL1 and the registered server name is DEMOSRV1.

Below is an example of the CMS Server Group with multiple SQL Servers registered under different names:

Demo Servers group in CMS

Double click on DEMOSRV1 server or right click and select "Object Explorer":

Connect to the Demo Server from CMS

Note, that when you connect to one of these "masked" SQL Servers from the CMS the SQL Server's name displayed in the SSMS will match the registered name in the CMS server's name. But, when you run the following query for the "Demo Servers" group you are still going to see the real SQL Server names:

USE [master]
GO

SELECT @@SERVERNAME AS [@@servername]
GO	
CMS Server Name vs. Actual SQL Server Name

Existing Tip to Run T-SQL Scripts on Different Databases

In this tip you can find out how to run a query against multiple databases (not necessarily servers) with the same database structure using CMS or Registered Servers.

Use the CSM System View and PowerShell to Sort Results by the Registered SQL Server Name

As you could see the examples there are still some limitations.

Another way to run multi-server queries is with PowerShell that can use a sorted SQL Servers list from CMS sysmanagement_shared_registered_servers view as a starting point:


$CentralManagementServer = "SQLCMS"

# get all registered SQL Server names from CMS
$Result =  Invoke-Sqlcmd -ServerInstance $CentralManagementServer -Database msdb -Query "SELECT DISTINCT server_name FROM msdb.dbo.sysmanagement_shared_registered_servers ORDER BY server_name" -ErrorAction Stop

foreach($item in $Result)
{$Inst=$item.server_name
Invoke-Sqlcmd -ServerInstance  ${Inst} -Database master -Query "SELECT @@SERVERNAME AS srv_name, COUNT([name]) AS num_of_dbs FROM sys.sysdatabases" -ErrorAction SilentlyContinue
  }

Conclusion

We have provided a couple of examples to getting sorted results using CMS queries.

Note, that a query with a WAITFOR for a high number of servers may run quite long. Also, the results might not be sorted in every case (see the explanations of limitations above).

There are limitations, but this technique is good enough to produce quick results that are easier to read for multi-server queries and can help you with some of the DBAs checks. You can use it as a starting point for other multi-server queries that need to be sorted.

Next Steps


sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Svetlana Golovko Svetlana Golovko is a DBA with 13 years of the IT experience (including SQL Server and Oracle) with main focus on performance.

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips



Comments For This Article

















get free sql tips
agree to terms