1

I am trying to use a multi-valued parameter in SSRS within a dynamic SQL query. In a static query I would use

SELECT myField
FROM myTable 
WHERE myField IN (@myParameter)

Using answers to this question (TSQL Passing MultiValued Reporting Services Parameter into Dynamic SQL) I have tried

-- SSRS requires the output field names to be static
CREATE TABLE #temp
(
    myField VARCHAR(100)
)

DECLARE @myQuery VARCHAR(5000) = 'SELECT myField
INTO #temp
FROM myTable
WHERE CHARINDEX('','' + myField + '','', '',''+''' + @myParameter + '''+'','') > 0'

EXEC (@myQuery)

This approach should work if the query understood @myParameter to be a string in a CSV format, but it doesn't seem to (as suggested by the link above). For example

SELECT @myParameter

won't work if there is more than one value selected.

I've also tried moving the parameter into a temporary table:

SELECT myField 
INTO #tempParameter
FROM @myParameter

-- SSRS requires the output field names to be static
CREATE TABLE #temp
(
    myField VARCHAR(100)
)

DECLARE @myQuery VARCHAR(5000) = 'SELECT myField
INTO #temp
FROM myTable
WHERE myField IN (SELECT myField FROM #tempParameter)'

EXEC (@myQuery)

I have SSRS 2012 and SQL Server 2012. NB: I need to use dynamic SQL for other reasons.

J.Warren
  • 728
  • 1
  • 4
  • 14
  • 1
    Use a [table-values parameter](https://learn.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine?view=sql-server-2017) or a [custom splitter function](https://www.sqlservercentral.com/articles/tally-oh-an-improved-sql-8k-%E2%80%9Ccsv-splitter%E2%80%9D-function) to split that list into a table, and JOIN to it. If you are on SQL Server 2016 onward, you can use [STRING_SPLIT](https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-2017) . – S3S Aug 12 '19 at 13:56
  • 1
    [Here is a similar question](https://stackoverflow.com/questions/878833/passing-a-varchar-full-of-comma-delimited-values-to-a-sql-server-in-function) – S3S Aug 12 '19 at 13:56
  • 1
    @scsimon Do the SSRS parameter get returned as a table-valued parameter? Is it possible to cast them into one? I can't find any examples of them online except in a 'WHERE x IN (@parameter)' statement – J.Warren Aug 12 '19 at 14:21
  • 1
    Every time I have created a multi select parameter in SSRS it gets returned as a comma separated string. Thus you need to split it on the DB side. I added the TVP as a FYSA but you can probably ignore it here. – S3S Aug 12 '19 at 14:25
  • 1
    How have you seen that? I would have assumed that if it was a comma separated string then my above attempt would have worked. Particularly ```SELECT @myParamter``` should return a single valued table with that string. It will work if there is only one value selected in the list of options, but errors if I select more than one. – J.Warren Aug 12 '19 at 14:38
  • 1
    Well, because I'm passing the parameter list to a proc, which errors out. You can't pass a comma list like that, mainly because with `IN` you need EACH of your values to be quoted. Your parameter list will not be quoted. So, `@var` looks like this... `where my field in ('a,b,c')` instead of `where my field in ('a','b','c')`. This is why it only works with ` values, because that gets returned as `a` or whatever the single value is, thus `where my field in ('a')` which will work – S3S Aug 12 '19 at 15:15

3 Answers3

2

You don't need dynamic SQL for this. SSRS will (much to my dislike) inject multi value parameters when using a hard coded SQL statement in the report. Therefore you can just do something like the following:

SELECT *
FROM MyTable
WHERE MyColumn IN (@MyParameter)
  AND OtherCol > 0;

Before running the query, SSRS will remove @MyParameter and inject a delimited list of parameters.

The best guess, if you need to use dynamic SQL, is to use a string splitter and an SP (I use DelimitedSplit8K_LEAD here). SSRS will then pass the value of the parameter (@MultiParam) as a delimited string, and you can then split that in the dynamic statement:

CREATE PROC dbo.YourProc @MultiParam varchar(8000), @TableName sysname AS
BEGIN

    DECLARE @SQL nvarchar(MAX);
    SET @SQL = N'SELECT * FROM dbo.' + QUOTENAME(@TableName) + N' MT CROSS APPLY dbo.DelimitedSplit8K_LEAD (@MultiParam,'','') DS WHERE MT.MyColumn = DS.item;';

    EXEC sp_executesql @SQL, N'@MultiParam varchar(8000)', @MultiParam;
END;
GO
Thom A
  • 88,727
  • 11
  • 45
  • 75
  • 1
    I'm using dynamic SQL for another reason – J.Warren Aug 12 '19 at 14:09
  • 1
    Then you need to give a representative example @J.Warren. There's no reason for your statement to be dynamic in the above; there's no dynamic objects in it. – Thom A Aug 12 '19 at 14:11
  • 1
    I know that. I felt that my question re-created the issues I am having as well as my attempted solutions in a reasonable and concise way. The issue isn't with the dynamic objects, it's injecting the multi-valued parameters into the dynamic SQL – J.Warren Aug 12 '19 at 14:16
  • 1
    well, without knowing what your dynamic statement looks like, I can't answer. my only guess is, use a table value parameter, and parametrise your dynamic statement, or a string splitter. – Thom A Aug 12 '19 at 14:18
  • 1
    Ok here is an example: ```CREATE TABLE #temp ( myField VARCHAR(100) ); DECLARE @selectOption VARCHAR(500) = CASE WHEN @timeOfDay = 'Morning' THEN 'morningField' ELSE 'afternoonField' END; DECLARE @myQuery VARCHAR(5000) = 'SELECT '+@selectOption +' INTO #temp FROM myTable WHERE myField IN (@myParameter)'; EXEC (@myQuery)``` where @timeOfDay is another SSRS parameter – J.Warren Aug 12 '19 at 14:28
  • 1
    Comments aren't the place for examples @J.Warren – Thom A Aug 12 '19 at 14:38
  • 1
    I realise that @Larnu, but given that I was simply aiming to demonstrate to you that I have a legitimate reason for writing this as dynamic SQL without over complicating my question, this was the best place for it. As yet, you haven't actually answered my question which is how to use multi-valued SSRS parameters within a dynamic SQL query. – J.Warren Aug 12 '19 at 14:43
  • 1
    I've made my (new) best guess, @J.Warren – Thom A Aug 12 '19 at 14:49
1

As I mentioned in the comments, your parameter is coming from SSRS as a single comma separated string as such:

@myParameter = 'FirstValue, Second Value Selected, Third Val'

When you try to use the parameter in the IN clause, it is read as such:

select *
from my table
where my column in ('FirstValue, Second Value Selected, Third Val')

This is invalid. The correct syntax would be like below, with quotes around each value.

select *
from my table
where my column in ('FirstValue', 'Second Value Selected', 'Third Val') 

So, you need to find a way to quote each value, which is hard because you don't know how many values there will be. So, the best thing to do is split that parameter into a table, and JOIN to it. Since we use a table-valued function in this example, we use CROSS APPLY.

First, create the function that Jeff Moden made, and so many people use. Or, use STRING_SPLIT if you are on 2016 onward, or make your own. However, anything that uses a recursive CTE, WHILE loop, cursor, etc will be far slower than the one below.

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;

Then, you simply call that with your function like so:

DB FIDDLE DEMO

create table mytable (Names varchar(64))

insert into mytable values ('Bob'),('Mary'),('Tom'),('Frank')

--this is your parameter from SSRS    
declare @var varchar(4000) = 'Bob,Mary,Janice,Scarlett'

select distinct mytable.*
from mytable
cross apply dbo.DelimitedSplit8K(@var,',') spt
where spt.Item = mytable.Names
S3S
  • 24,809
  • 5
  • 26
  • 45
  • 1
    SSRS doesn't seem to be returning a single comma separated string. I've set up a report with a multi-valued parameter called myParameter with the options 'a', 'b' and 'c' (both label and value), and a dataset with the query `Select SQL_VARIANT_PROPERTY(@myParameter, 'BaseType')`. I then added a table to display the dataset. When I select just one value from the parameter dropdown list, the report returns the a single-valued table with the value `nvarchar` in it. If I select more than one value for the parameter, it gives the generic error: `Query execution failed for dataset 'DataSet1'.` – J.Warren Aug 13 '19 at 07:56
  • 1
    Yes @J.Warren for the reason I described above. If you’d just make your parameter nvarchar and do it like I showed it’ll work. I’ve done it dozens of times. – S3S Aug 13 '19 at 10:47
  • 1
    I can see that your above query will work for declaring it as a single comma separated string. But I'm not sure that SSRS is returning a multi-value parameter as a single comma separated string. If it were then why would `SELECT @myParameter` work where one option is selected, but not if there are multiple values selected? – J.Warren Aug 13 '19 at 11:39
  • 1
    Ok you can do this... create a procedure that inserts your parameter value into a nvarchar(max) column in a dummy table. Then you’ll see it’s true value. I don’t know what else to suggest. When I set up multi valued parameters and pass that to a proc it’s always a comma separated string. – S3S Aug 13 '19 at 11:42
0

The split string solution didn't work for me either, and I tried to figure out what type the Multi parameter variable in SSRS actually is so I somehow could work with it. The multi parameter variable was @productIds and the type of the data field was UNIQUEIDENTIFIER. So I wrote the type to a log table

INSERT INTO DebugLog 
SELECT CAST(SQL_VARIANT_PROPERTY(@productIds,'BaseType') AS VARCHAR(MAX))

When I selected one value the type was NVARCHAR, however when I selected two or more values I got an error

System.Data.SqlClient.SqlException: The sql_variant_property function requires 2 argument(s).

So I stopped trying to figure out what a multi parameter variable actually was, I already spent to much time.

Since I had the values in a table I could select the values from that table into a temp table and then join on that in the dynamic built query

SELECT p.Id
INTO #tempProdIds
FROM Products p WHERE p.Id IN @productIds

SET @query +='
....
JOIN #tempProdIds tpi on tl.Product = tpi.Id
....
'
EXEC(@query)
kristoffer_o
  • 590
  • 5
  • 7