The solution is to create a stored procedure on the local server that includes error handling for queries to remote servers and returns a single dataset. Then to create a view that calls the stored procedure using OPENROWSET.
The stored procedure has to use a table variable for holding the data it gets from each server. It can’t use a UNION between them because that would defeat the purpose of error trapping.
Here is an example of the stored procedure. In this example, I’m querying sys.Databases because that’s a view that everyone has and can easily test:
USE MyDatabase
GO
CREATE PROCEDURE TEST_PROC
AS
SET FMTONLY OFF
SET NOCOUNT ON
DECLARE @OUTPUT TABLE (
Server_Name NVARCHAR(100),
Name NVARCHAR(128),
Database_ID INTEGER,
State INTEGER,
State_Desc NVARCHAR(60) )
BEGIN TRY
INSERT INTO @OUTPUT (Server_Name, Name, Database_ID, State, State_Desc)
SELECT 'SERVER_A', Name, Database_ID, State, State_Desc
FROM SERVER_A.Master.sys.Databases
END TRY
BEGIN CATCH
INSERT INTO @OUTPUT (Server_Name, Name, Database_ID, State, State_Desc)
SELECT 'SERVER_A', 'Server is Unavailable', Null, NULL, ''
END CATCH
BEGIN TRY
INSERT INTO @OUTPUT (Server_Name, Name, Database_ID, State, State_Desc)
SELECT 'SERVER_B', Name, Database_ID, State, State_Desc
FROM SERVER_B.Master.sys.Databases
END TRY
BEGIN CATCH
INSERT INTO @OUTPUT (Server_Name, Name, Database_ID, State, State_Desc)
SELECT 'SERVER_B', 'Server is Unavailable', Null, NULL, ''
END CATCH
BEGIN TRY
INSERT INTO @OUTPUT (Server_Name, Name, Database_ID, State, State_Desc)
SELECT 'SERVER_C', Name, Database_ID, State, State_Desc
FROM SERVER_C.Master.sys.Databases
END TRY
BEGIN CATCH
INSERT INTO @OUTPUT (Server_Name, Name, Database_ID, State, State_Desc)
SELECT 'SERVER_C', 'Server is Unavailable', Null, NULL, ''
END CATCH
GO
You will have to create a TRY/CATCH block for every server you want to query. With a little creativity, I’m guessing that it’s possible to do the above using a cursor loop based on sys.servers to generate and execute the above TRY/CATCH blocks for all linked servers.
Here is the associated view:
CREATE VIEW TEST_VIEW
AS
SELECT SELECT Server_Name, Name, Database_ID, State, State_Desc
FROM OPENROWSET('SQLNCLI11', 'Server=.;Trusted_Connection=yes;', 'EXEC MyDatabase.dbo.Test_Proc')
GO
Note that “Server=.;
” in the connection string points to the local server. My understanding of SQL data sources is a little shaky, so this may not work for everyone. Also, you may have to update the rest of the connection string to match the needs of your environment.
The only downside I’ve encountered in testing is that if one of the servers is offline, it’s got about a 21 second timeout. So it takes at least that long to return. When all servers are up, it runs in a fraction of a second. I’ve tried changing the timeout parameters in the linked servers definitions, but they don’t appear to make a difference. I’ll experiment more and update this solution if I find better results.
Thanks J.D. I wouldn’t have known to go this route without your suggestions.
-Robbie