July 25, 2004
Passing a variable number of parameters to a stored procedure
I came up against this problem any times. Say you want to retrieve the details of a few rows from a table by passing each of those variables into a stored procedure.
Traditionally you would have to have an array of ID's (or whatever you are basing your SELECT on) and you would loop through each value in your code, calling the stored procedure each time with all the rigmarole of opening a connection, reading the data, closing the connection, doing stuff with the data, then going back through the loop again.
Its a bit monolithic. No finesse.
It would be much better if you could just pass the stored procedure a list of ID's and have it return a recordset as normal.
Lets have an example table first:
Table name: AddressBook
ID Name Telephone Email
1 John Boy 123456 jb@mail.com
2 Micky 98792837 mc@home.nl
: : : :
etc....
you get the idea
Here's how I managed it:
CREATE PROCEDURE DatabaseName.ProcName
@Ids VARCHAR(500);
AS
CREATE TABLE #IDs ( ID INT );
DECLARE @Comma INT;
DECLARE @ID INT;
BEGIN
/* now process the @Ids variable */
SELECT @Comma = CHARINDEX( ',' , @Ids , 0 );
IF @Comma = 0 BEGIN
SELECT @ID = (@Ids);
END
IF @Comma <> 0 BEGIN
SELECT @ID = LEFT( @Ids, @Comma - 1 );
SELECT @Ids = RIGHT( @Ids, LEN(@Ids) - @Comma );
END
WHILE LEN(@Ids) > 0 BEGIN
INSERT INTO #ConfigsID (@ID) VALUES (ConfigID);
/* now process the @Ids variable */
SELECT @Comma = CHARINDEX( ',' , @Ids , 0 );
IF @Comma = 0 BEGIN
SELECT @ID = (@Ids);
END
IF @Comma <> 0 BEGIN
SELECT @ID = LEFT( @Ids, @Comma - 1 );
SELECT @Ids = RIGHT( @Ids, LEN(@Ids) - @Comma );
END
END
SELECT DISTINCT * FROM AddresBook WHERE ID IN (#IDs) ORDER BY Name;
END
GO
But even that was a bit messy - the block that breaks off the next ID is used twice. We can modularise that by creating a user defined function and readjusting the code, so:
CREATE FUNCTION splitString ( @String NVARCHAR(500) )
RETURNS @Return TABLE ( ID INT, List NVARCHAR(500))
AS
BEGIN
DECLARE @Comma INT;
DECLARE @ID INT;
SELECT @Comma = CHARINDEX( ',' , @String , 0 );
IF @Comma = 0 BEGIN
SELECT @ID = (@String);
SELECT @String = (NULL);
END
IF @Comma <> 0 BEGIN
SELECT @ID = LEFT( @String, @Comma - 1 );
SELECT @String = RIGHT( @String, LEN(@String) - @Comma );
END
INSERT @Return
SELECT @ID, @String
RETURN
END
CREATE FUNCTION splitList ( @List NVARCHAR(500) )
RETURNS @Return TABLE ( ID INT )
AS
BEGIN
DECLARE @ID INT;
DECLARE @Temp TABLE ( ID INT );
SELECT @ID = ( SELECT ID FROM splitString( @List ) );
SELECT @List = ( SELECT List FROM splitString( @List ) );
WHILE LEN(@List) > 0 BEGIN
INSERT @Temp SELECT ( @ID );
SELECT @ID = ( SELECT ID FROM splitString( @List ) );
SELECT @List = ( SELECT List FROM splitString( @List ) );
END
INSERT @Temp SELECT ( @ID );
INSERT @Return
SELECT * FROM @Temp
RETURN
END
CREATE PROCEDURE Database.FunctionName
@IDs VARCHAR(500)
AS
CREATE TABLE #IDs ( ID INT )
BEGIN
INSERT #IDs SELECT * FROM splitList( @IDs );
SELECT DISTINCT * FROM AddressBook WHERE ID IN (SELECT * FROM #IDs) ORDER BY Name;
END
GO
Your code is cleaner as you can use your stored procedure like a normal stored procedure and just pass it a comma delimeted list of ID's to retrieve a recordset. Cant say it will do too much for your SQL server efficiency though - I have never had to deal with huge overheads so I've no idea what effect this will have on your performance.
The code side of things should be much more efficient as the connection needs only be made once. The SQL server is less efficient though with all that string parsing. There should be a sweet spot though. I leave it up to better men than I to find out where it is :)
Posted by dottie at 1:47 AM