Passing variable numbers of parameters to sprocs using XML

Published 12 February 07 09:28 AM | James World 

Passing a variable number of parameters to a stored procedure is a problem that’s been around and solved for a while – in fact there’s a good article on several approaches for passing parameters in a comma-delimited string here:

 

http://www.sommarskog.se/arrays-in-sql.html

 

Today I’d like to talk about an alternative approach made possible in SQL Server 2005 – passing parameters with the XML data type.

 

Let’s imagine that we have a table of employees with an EmployeeID. We want to select some arbitrary subset of this table by passing in a list of the EmployeeIDs of the desired employees.

 

The XML we are going to pass will look something like this:

 

<employees><employee id=”1” /><employee id=”3” />…..</employees>

 

This can be built up quite simply in .NET using an XmlTextWriter as the following code snippet shows:

 

StringBuilder xmlEmployeeListBuilder = new StringBuilder();

StringWriter stringWriter = new StringWriter(xmlEmployeeListBuilder);

XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);

xmlWriter.WriteStartDocument();

xmlWriter.WriteStartElement("employees");

 

… other code

 

// add an employee id to xml parameter string

// repeat this section as necessary

xmlWriter.WriteStartElement("employee");

xmlWriter.WriteAttributeString("id", <variable containing id>);

xmlWriter.WriteEndElement();

 

… other code

 

xmlWriter.WriteEndElement();

xmlWriter.WriteEndDocument();

xmlWriter.Close();

string xmlEmployeeList = xmlEmployeeListBuilder.ToString();

 

Now let’s look at the stored procedure in full and then break it down:

 

SET QUOTED_IDENTIFIER ON

GO

SET ANSI_NULLS ON

GO

 

CREATE PROCEDURE SelectSpecificEmployees

      @EmployeeList xml

AS

SET NOCOUNT ON;

 

CREATE TABLE #EmployeeList

(

      EmployeeId int

);

 

INSERT #EmployeeList

SELECT employee.value('.', 'int')

FROM @EmployeeList.nodes('/employees/employee/@id') T(employee);

 

SELECT Employees.EmployeeId, FirstName, LastName

FROM Employees INNER JOIN #EmployeeList ON Employees.EmployeeId = #EmployeeList.EmployeeId;

 

DROP TABLE #EmployeeList;

 

GO

 

The first thing to note is the two SET statements outside the stored procedure declaration. These SET options are different than all other set options in that whenever a stored procedure executes, it uses the settings for QUOTED_IDENTIFIER and ANSI_NULLS that were in place at the time the stored procedure was created!

 

Changing these settings inside a stored procedure has no effect and produces no errors, and neither does the setting at the database level have any effect on the stored procedure.

 

If you don’t have these options set as shown, then when you try and run this stored procedure you’ll get an error like:

 

“INSERT failed because the following SET options have incorrect settings: 'QUOTED_IDENTIFIER'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or query notifications and/or xml data type methods.”

 

You have been warned!

 

The next step is to create a temporary table to hold the employee ids parsed from the XML.

 

Now we insert the employee ids into the temporary table. To do this, we use the nodes() method of an XML data type. This executes an XQuery expression that returns a nodeset against the xml instance and places the results into a table. The table is scoped to the SQL statement in which it is declared.

 

In the example above, we declare a table called T with a column of employee, and insert the rows from table T into our temporary #EmployeeList table.

 

That’s all the hard work done – now we just join our #EmployeeList to the actual Employees table and return the matching employees.

 

Comparing this approach to the amount of code required to deal with comma-delimited strings, I think this is a neater, shorter solution to the problem. How about you?

Comments

# MPoole said on February 23, 2007 7:09 AM:

James,

I do prefer this approach and would like to apply it in our system. As part of the ASP.NET applications we run there is a configurable input form where a variable number of inputs can be displayed to the user and ultimately stored on our SQL Server 2000 DB. All data is stored in one generic table while validation is done on an input-by-input basis in server side C#.

Currently, after all data has passed validation we loop thru each input in C# and repeatedly call the Stored Proc to simply insert/update/delete the data point on the database. Some forms run into 30-40 fields. It may be inefficient but the configurable nature of the application takes precedence.

To improve efficiency, rather than many calls on the DB to submit data could I transform the data to XML using your suggested method using SQL Server 2000? (We are looking to move to 2005 this year)

Following on from this, and In respect of either 2000 or 2005 versions, is there a neat way of submitting many datapoints in XML format into 1 table or are we constrained to the looping around a temporary table?

cheers,

Mike

# James World said on March 19, 2007 7:52 PM:

SQL Server 2000 is not so easy because it doesn't have the XML data-type. You might want to look at the OPENXML function which allows you to crack open passed in XML - it's not as memory or time efficient as the XML data-type queries in SQL 2005, but it might work for you here.

With regards to avoiding the use of a temporary table - this is something of a false econonmy since SQL Server cracks XML types into temporary tables in order to query them anyway. Small temporary tables that exist for a short duration are likely to only ever exist in memory and in scenarios like this are not likely to be your biggest cause for concern performance wise.

James.

Anonymous comments are disabled

Search

This Blog

Syndication

Page view tracker