• subscribe
December 20, 2005 12:00 AM

Reporting Services and Multivalued Parameters

A few tricks coax your stored procedures into accepting a delimited list
SQL Server Pro
InstantDoc ID #48596
Downloads
48596.zip

If we pass multiple ProductSubCategoryID values to the stored procedure, Reporting Services converts the multivalued parameter into a string of values and we get the error message Msg 8114, Level 16, State 1, Procedure Test_MVP, Line 0 Error converting data type varchar to int. This error indicates that the parameter in the stored procedure is expecting an integer value for ProductSub-Category. Because we passed in a string— '2,3,4,5'—the execution fails.

To allow a string value for a multivalued parameter list coming from a Reporting Services report—in this case, the original Product Line Sales report, we need to alter the stored procedure so that the data type of @ProductSubCategory is variable-length character, or varchar, because we don't know precisely how long the string will be. We'll set a limit of 100 total characters, varchar(100), which will be an ample length for testing the @ProductSubCategory parameter. To alter the stored procedure, we modify the first and third lines of the code in Listing 1 so that the first five lines of code appear as follows:

ALTER PROCEDURE [DBO].[Test_MVP] 
@ProductCategory Int , 
@ProductSubCategory varchar(100), 
@StartDate datetime, @EndDate datetime 

When I executed the altered stored procedure to test it, I received a new error message. Predictably, previewing the Product Line Sales SP report displayed the same error, which Figure 5 shows. It was about this time that I almost gave up on multivalued parameters, but I know that there's typically a way around a stubborn child. I did some research and learned that I could create a user-defined function (UDF) to parse the comma-delimited list of values returned from the original Product Line Sales report and introduce them to the Product Line Sales SP report.

A UDF Does the Trick
You can successfully introduce a multivalued parameter list into a stored procedure in several ways. For example, you can store the SQL statement that drives a report in a variable, concatenate the parameter list to the variable, then execute the constructed statement through the built-in procedure sp_executesql. Another possibility is to load the parsed values of the comma-delimited list of individual parameters into a temporary table or table variable, then join the original query to this temporary table by using a statement such as

INNER JOIN #mytempPSC ON
  ProductSubCategoryID =
  #mytempPSC.ProductSubCategoryID 

The temporary table would contain only the user-selected list of values from the report and would therefore return the desired results.

Because I tend to shy away from anything that resembles dynamic SQL (as the first example does) and because I'd most likely need to put temporary table logic to parse the delimited string directly in the stored procedure, I chose to incorporate a UDF to store and execute the parsing logic. Using a UDF also lets me call the parsing logic from any stored procedure that requires string parsing.The type of UDF I chose to create is a table-valued function, which like a temporary table, stores values as rows in a table, essentially turning the string into the required array. Listing 2 shows the code for the UDF that I used, called fn_MVParam.

The function uses several of its own parameters to store and parse multivalued strings coming from the original Product Line Sales report's parameter.The function accepts two input values, a string value and a delimiter character, which by default is a comma (,). The delimiter parameter lets us extend the versatility of the function by giving us the ability to pass in any delimiter that we need.

The fn_MVParam function uses the CHARINDEX function to search for the existence of the delimiter. If it finds the delimiter, fn_MVParam knows that the string has multiple values. It then uses the LEFT function to find and store the first parameter value from the string. The fn_MVParam function gets all the characters up to but not including the delimiter and inserts this value into a table that has one column called Param.

Next, fn_MVParam uses the RIGHT function to remove the already extracted parameter in the string. For example, if we have a string value of '1,2,3,4,5', the first iteration of the WHILE loop extracts the 1, then sets the value of the string to '2,3,4,5'. The fn_MVParam function procedes until the LEN function determines that the length of the string is 0.

The end result is a row list of multiple parameter values that we can use in the WHERE clause of our stored procedure. We can test the execution of the fn_MVParam function in Management Studio by passing in a comma-delimited list, as Figure 6 shows.

Finally, we must alter the stored procedure that drives the Product Line Sales SP report one more time so that it uses the new fn_MVParam function.We use the function in the WHERE clause as a subquery to be evaluated for the ProductSubCategory field, as follows:

WHERE (PC.ProductCategoryID = 
  @ProductCategory) AND
 (PS.ProductSubCategoryID IN
 (SELECT Param FROM fn_MVParam
 (@ProductSubCategory,','))) 
AND (SOH.OrderDate > @StartDate) 
  AND (SOH.OrderDate < @EndDate) 

Web Listing 1 (http://www.sqlmag.com, InstantDoc ID 47788) shows the final version of theTest_MVP stored procedure with the two changes we've made.

Figure 7 shows the Product Line Sales SP report successfully run by using the Test_MVP stored procedure and its fn_MVParam UDF. It's worth mentioning that the Product Line Sales and Product Line Sales SP reports use two Visual Basic .NET functions—Join() and Split()—to work with the multivalued parameter as an array.You can see the Join() function at work in Figure 7. It creates the report header information for product categories and subcategories based on the parameter selection.

Adding multivalued parameters as a Reporting Services feature was absolutely a great idea. Many report designers—those who didn't want to write their own parameter selection front end—were formerly relegated to an all-or-nothing approach when setting criteria for a report by using parameters. Multivalued parameters let us set exactly the criteria we want. For those of us who used stored procedures to develop the bulk of reports in SQL Server 2000 Reporting Services, string-parsing workarounds are available that let us migrate our reports to make use of multivalued parameters. Thankfully, SQL Server has UDFs that we can use to create a solution, not just a workaround, to using arrays in T-SQL.

Rodney Landrum (rodneyl@healthware.com) is director of research and development for Pro Business Systems, a software development company in Pensacola, Florida that produces a SQL Server?based application for the healthcare industry. He is an MCSE and a Citrix Certified Administrator.



ARTICLE TOOLS

Comments
  • Tim
    7 years ago
    Dec 23, 2005

    Another scenario I forgot to mention is when the parameter being passed is a GUID. Since guids are by definition a fixed length when part of a string consisting of an array of guids, there is an even more efficient parsing method credited by Sommarskog to Ken Henderson. Here is my implementation of the guid parser using nvarchar(MAX) to allow a basically unlimited number of GUID's:

    CREATE FUNCTION dbo.fnParseGUIDs(@str nvarchar(MAX))
    RETURNS @ParsedValues TABLE (pk int, GUID uniqueidentifier)
    AS

    BEGIN
    INSERT INTO @ParsedValues (pk, GUID)
    SELECT Number
    , CAST(LEFT(SUBSTRING(@str, 37 * (Number - 1) + 1, 37),36) AS uniqueidentifier)
    FROM Numbers
    WHERE Number <= DATALENGTH(@str) / (37 * 2)
    + CASE DATALENGTH(@str) % 37 WHEN 0 THEN 0 ELSE 1 END
    RETURN
    END

    Happy Holidays,

    Tim

  • Tim
    7 years ago
    Dec 23, 2005

    We have definitely run into this issue on our current project. However, I don't believe the UDF presented is particularly efficient as it basically uses a WHILE loop to do cursor-like row-based processing of the array. Here is an alternative I borrowed from Erland Sommarskog's excellent article 'Arrays and Lists in SQL Server' (http://www.sommarskog.se/arrays-in-sql.html#overview). It uses a prepopulated Numbers table which is simply an integer column with values from 1-8000. In order to get a key for ordering the list, I originally added an IDENTITY column, which I replaced with the SQL Server 2005 ROW_NUMBER() OVER syntax.

    CREATE FUNCTION dbo.fnParseDelimitedList (@list varchar(7998), @delimiter char(1))
    RETURNS @ParsedValues TABLE (pk int, Value varchar(7997))
    AS

    BEGIN
    INSERT INTO @ParsedValues (pk, Value)
    SELECT ROW_NUMBER() OVER(ORDER BY Number), SUBSTRING(@delimiter + @list + @delimiter, Number + 1,
    CHARINDEX(@delimiter, @delimiter + @list + @delimiter, Number + 1) - Number - 1)
    AS Value
    FROM dbo.Numbers
    WHERE Number <= LEN(@delimiter + @list + @delimiter) - 1
    AND SUBSTRING(@delimiter + @list + @delimiter, Number, 1) = @delimiter
    RETURN
    END

    Tim Overlund, MCDBA, MCSD.NET
    Database Practice
    Avanade, Inc

You must log on before posting a comment.

Are you a new visitor? Register Here