SQL Advanced: Openqueries… and XML

Intro

This is an unorthodox solution. And it came to me two years ago working on a problem that had very particular constraints.

The underlying idea is to use an OPENQUERY to send both a query and some XML to the remote server. This makes for some very fast set-based filtering.

Openquery

Openqueries (OQ) make pulling data from a remote server really fast by delegating processing duties to the remote server. That is the OPENQUERY is executed remotely such that any transformation, filter, or what-have-you is performed only onl the remote server..

If you query a table on a remote server normally, the entire table is sent across before any processing is performed. In other words, cross-server queries take much less time with OPENQUERY!

Filtering using WHERE

For example, I would query my remote server in this way:

SELECT
	*
FROM OPENQUERY([remoteServer],
	'	SELECT 
			FirstName
		,	LastName
		,	BankDetails 
		FROM BankDB.dbo.Customers 
		WHERE accountNo = 123456
	')

Notice how we have a query in single quotes. This is the query that is executed on remoteServer. And because we’re using a WHERE clause to specify that we only want to return a specific tuple, then only one tuple will be returned to the local server.

Set-Based Filtering in Openquery

But what if I have more than one acccountNo that I need to pull from the remote server? What if I have a hundred?

First thing that probably comes to mind is to stick those accounts into a temp table and rewrite the openquery to perform matching the temp table… right?

Right! But that won’t work. Remember - the openquery is executed remotely - far out of scope from where your temp table sits!

Ok - so use a user table - problem solved! But that means the remote server is going to have to query that table on the local server - an openquery within an openquery? This might work.

But for sake of argument, suppose you find yourself in a situation where you can’t use a temp table, user table, nor stick every item into a really long WHERE clause.

Solution: Since the remote server is being sent a query, why not also include some data at the same time? Why not bake this data into our OPENQUERY using XML?

Using XML

Let’s pretend we’ve got a hundred accounts in #accountNo. In the first step, we express #accountNo as an XML string.

DECLARE @XMLStr varchar(max);
SELECT @XMLStr = (
		SELECT 
		accountNo AS a 
		FROM #accountNo AS t	 
		FOR XML AUTO);

Our datatype is varchar (instead of XML) because we will be using dynamic SQL. But on the remote server, we will use a variable of XML datatype so that we can simply use XML methods to read the string.

First there is the nodes() method:

nodes (XQuery) as Table(Column)

So we read the XML like this:

DECLARE @xml xml;
SET @xml = @XMLStr;
SELECT
	Tbl.Col.value('@a','int') AS accountNo
FROM @xml.nodes('//t') AS Tbl(Col)

Next, we use dynamic SQL to pass the contents of @XMLStr to the OPENQUERY.

DECLARE @dSQL varchar(max);

SET @dSQL = '
	SELECT 
		*
	FROM OPENQUERY([remoteServer],''
		DECLARE @xml xml
		SET @xml = ''''' + @XMLStr + ''''';
		WITH [cteXML] AS (
			SELECT
				Tbl.Col.value(''''@a'''',''''int'''') AS accountNo
			FROM @xml.nodes(''''//t'''')Tbl(Col)
			)
		SELECT
			FirstName
		,	LastName
		,	BankDetails
		FROM [BankDB].[dbo].[Customer] AS [c]
		WHERE EXISTS (SELECT 1 FROM [cteXML] WHERE [c].[accountNo] = [accountNo]
		'')
	';
EXEC (@dSQL);

And so without too much verbosity, we are able to filter an OPENQUERY using the contents of a temp table!