Showing posts with label SQL Server 2005. Show all posts
Showing posts with label SQL Server 2005. Show all posts

Thursday, July 23, 2009

SSRS Multi-Select Parameters for Stored Procedures in SQL 2005

[Intro]

Using SSRS multi-value parameters with stored procedures can be a bit of a pain. Multi-Valued parameters are sent to Stored Procedures as a comma-delimited string. What follows is the method I use to write and debug stored procedures for SSRS reports.
The web is full of tutorials on handling multi-valued parameters in stored procedures. What I’m hoping to show is a method to set them up so that you can easily debug the stored procedure later on down the road.

[Background]

I found some code on SQLServerCentral.com that does a good job of splitting a comma-delimited string into a table of individual values. I wish I could credit the person who originally posted this idea, but there are lots of variations of this code floating around, and I modified the version I originally found, so please accept my apologies if this looks familiar.
This function will break down a comma-delimited string into a table of individual values. The one I’m posting here returns a table of ints, but I have others that return a table of other values (i.e. strings):

----------------------------------------------------
-- [dbo].[fnc_split_int]
-- Parse a comma delimited string and insert the values into a table variable.
-- Useful for sending a comma-delimited string parameter to a stored proc that will
-- use it in a where IN clause.
--
-- Ex:
-- select * from customers
-- will not work: Where customer_id in (@parameter)
-- will work : Where customer_id in (select item from fnc_split_string(@parameter,','))
---------------------------------------------------------
ALTER FUNCTION [dbo].[fnc_split_int](
@list varchar(8000),
@Delimiter VARCHAR(10) = ','
)
RETURNS @tablevalues TABLE (
item int
)
AS
BEGIN

DECLARE @P_item varchar(255)

WHILE (DATALENGTH(@list) > 0)
BEGIN
IF CHARINDEX(@Delimiter,@List) > 0
BEGIN
SELECT @p_Item = SUBSTRING(@List,1,(CHARINDEX(@Delimiter,@List)-1))
SELECT @List = SUBSTRING(@List,(CHARINDEX(@Delimiter,@List) + DATALENGTH(@Delimiter)),DATALENGTH(@List))
END
ELSE
BEGIN
SELECT @p_Item = @List
SELECT @List = NULL
END

INSERT INTO @tablevalues
SELECT Item = convert(int, LTRIM(RTRIM(@p_Item)))
END

RETURN

END

Here’s a simple example of a stored procedure using this function:

create proc dbo.salesinfo (
@customers varchar(max)
)
as

Select * from dbo.sales
where customer_id in ( select item from fnc_split_int(@customers, ',') )

[Using Table Variables]


The method above works well, but can get kind of annoying if you ever have to debug the stored procedure. The stored procedure could be huge, and you might not care too much about which customers are displayed. However it now requires a comma-delimited string of customers anytime you want to run some tests.
To keep me from losing my mind, I add a few table variables in my stored procedure that are designed to handle the multi-value SSRS parameters.


This is probably best explained in an example:

create proc rpt.salesinfo (
@customers varchar(max) = null --set to null by default
)
as

--Build local customer table
declare @customerlist table (
customer_id int
)

if @customers is null
begin
--Load all customers (null)
insert into @customerlist
select customer_id from customers
end
else
begin
--Load only those customers chosen in @customers
insert into @customerlist
select item from fnc_split_string(@customers, ',')
end


--report query (using @customerlist)
select * from sales
where customer_id in (select customer_id from @customerlist)


Notes:
  • It might seem a little excessive to build a local table of values when you already have those values in comma-delimited form. But using this method has saved me a lot of headaches when I’m trying to debug something in the stored procedure.

  • I could use a join instead of putting the @customerlist table in the where string. However, I’ve found that performance hasn’t really been an issue for our setup. I also think it’s a little easier to read – a new developer doesn’t have to worry about finding a join that is only used to limit the resultset. From what I remember, the server does a lot to optimize queries anyway, so things like this usually do not matter as much.

[Final Thoughts]

This example shows a method I use to test and debug stored procedures for SSRS reports that use multi-valued parameters. There isn’t any ground breaking functionality introduced, just a bunch of smaller things strung together to hopefully make life easier. I should mention there are other ways of doing this, some might be better than this one. At the time of this writing I was only using SQL Server 2005.

Please feel free to ask questions or suggest improvements.

Thursday, July 3, 2008

Number of Decimal places and the SubString() function

I wanted a function that could give me the number of decimal places used in a given number. I really didn’t find any elaborate solutions on the web, so I decided to write my own by converting the number into a varchar and parsing it appropriately. While building the function, I discovered a neat little feature of the SubString() function.


Normally, SQL seems to work with an index of 1. So I if asked it the position of the “J” in “Jason”, it would return 1 (one). However, I was surprised to learn that the SubString() function accepts a 0 (zero) as a parameter for where to start the sub string.


This is an effective way to trim-off the last character of a string using SubString(@var, 0, len(@var)). For example, if I want to trim off the last character of “0040”, I can use the SubString() command to trim off the last “0”.

Here is an explanation with code:

--SubString(string, start, length)

Declare @var varchar(10);

Set @var = '0040';

Select SubString(@var, 1, len(@var)) --will return ‘.0040’

Select SubString(@var, 0, len(@var)) --will return ‘.004’


Using 0 for the start point with the length of the string has the effect of trimming the last character from the string. I wasn’t expecting this behavior, but it certainly is nice!


Decimal Count Function

Here is the entire function in case you are looking for something similar. I built it for use with SQL Server 2005.


Here is my usual disclaimer:

I’m not sure if this was the best way to go about solving the problem, but it worked. I thought my code and comments might be useful for someone else who has a similar problem. This code is free and comes with no guarantees.


One more thing - apparently I lost my formatting (tabs/spaces/etc) when I pasted this code, and I'm too lazy to fix it. Should only take a couple of minutes to make it look the way you're used to seeing it.



/*

DecimalCount

7/3/2008 JMJ

Count of the number of decimal places used after decimal point.

Params:

@numString varchar(MAX) varchar representing a number, assumes 1 (one) decimal point

Returns:

int Number of places used after decimal point, 0 (zero) if no decimal point

is found in the number. Will not consider zeros on the end of the number

(i.e. .0040 will consider only .004, which will return 3).

*/

CREATE function [dbo].[DecimalCount] (

@numString varchar(MAX)

) returns int

as

begin

declare @decimalPlace bigint;

declare @subStr varchar(max);

set @decimalPlace = 0;

--find index of decimal place

set @decimalPlace = Charindex('.',@numString,0);

if (@decimalPlace > 0)

begin

-- @subStr = string without decimal (or anything left of decimal)

set @subStr = @numString;

set @subStr = Right(@numString, Len(@numString) - @decimalPlace);

-- remove any trailing zeros

while(charindex('0',@subStr,len(@subStr))) = len(@subStr)

begin

-- catch conditions for numbers like '100.' or '100.0'

if ( charindex('0',@subStr,len(@subStr)) = 0

and len(@subStr) = 0 )

begin

break;

end

--using 0 for beginning will trim the last char from string

set @subStr = SubString(@subStr, 0, len(@subStr));

end

if (len(@subStr) is null)

begin

return 0;

end

else

begin

return len(@subStr);

end

end

else

begin

--no decimal place

return 0;

end

return 0;

end