This is a similar problem to what I recently faced. I needed to select from a table those rows that intersected a large list of primary keys. The question was how to efficently send the large list of keys to the SQL server in a form that is fast and efficent to use.
What works really well for this type of situation is the XML data type. If you create a stored procedure that takes one parameter of type XML, you can pre-format the input into a XML fragment. As an example, let's say the XML fragment will look like this:
<a>
<b>1</b>
<b>3</b>
<b>7</b>
<b>14</b>
<b>147</b>
</a>
I gave the elements short names ("a" and "b") because a longer name would mean more bytes to transmit from the client to the SQL server. Here is how you would select all the contents of the "b" elements as a record set:
declare @x xml
set @x = '<a><b>1</b><b>3</b><b>7</b><b>14</b><b>147</b></a>'
select t.item.value('.', 'int') from @x.nodes('//a/b') as t(item)
Although the syntax is cryptic, the XML type can be queried just like a table. Now you should see where this is going. If we can query the XML type like a table, we can intersect that with another table:
select * from MyTable where ID in
(select t.item.value('.', 'int') from @x.nodes('//a/b') as t(item))
or using a join
;with cte as
(select ID = t.item.value('.', 'int') from @x.nodes('//a/b') as t(item))
select * from MyTable inner join cte on MyTable.ID = cte.ID
You need to run both versions to see which will be faster for your data. I find the JOIN works faster with my data. Here is a stored procedure that takes the XML type as input and spits back our selected rows:
create procedure MyProc @x xml as
begin
set nocount on
;with cte as
(select ID = t.item.value('.', 'int') from @x.nodes('//a/b') as t(item))
select * from MyTable inner join cte on Table.ID = cte.ID
end
A sample call of the new stored procedure:
exec MyProc '<a><b>1</b><b>3</b><b>7</b><b>14</b><b>147</b></a>'
I also found that adding a XML schema for the input fragment helped to speed up the stored procedure slightly. I won't go into the details of XML schemas here, but the idea is to tell SQL beforehand what the XML fragment will look like. Here's how we would input our schema:
create xml schema collection MyInputSchema as
'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="a">
<xsd:complexType>
<xsd:complexContent>
<xsd:restriction base="xsd:anyType">
<xsd:sequence>
<xsd:element name="b" type="xsd:integer" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>'
Now we can associate this schema with the input to our stored procedure like this:
create procedure MyProc @x xml(MyInputSchema) as
begin
set nocount on
;with cte as
(select ID = t.item.value('.', 'int') from @x.nodes('//a/b') as t(item))
select * from MyTable inner join cte on Table.ID = cte.ID
end
With all this in place, I was able to send a XML fragment of 43,016 characters from my client machine to the SQL server and get back a result set very quickly. I made a test of 1,000 requests on 10 threads for a total of 10,000 requests. The result was 72 requests processed per second. Of course your millage will vary depending on your hardware and software.
NOTE: This code works on SQL 2005 and should also work on 2008.