Passing a varchar full of comma delimited values to a SQL Server IN function

Viewed 213681

Duplicate of
Dynamic SQL Comma Delimited Value Query
Parameterized Queries with Like and In

I have a SQL Server Stored Procedure where I would like to pass a varchar full of comma delimited values to an IN function. For example:

DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';

SELECT * 
FROM sometable 
WHERE tableid IN (@Ids);

This does not work of course. I get the error:

Conversion failed when converting the varchar value '1,2,3,5,4,6,7,98,234' to data type int.

How can I accomplish this (or something relatively similar) without resorting to building dynamic SQL?

26 Answers

Of course if you're lazy like me, you could just do this:

Declare @Ids varchar(50) Set @Ids = ',1,2,3,5,4,6,7,98,234,'

Select * from sometable
 where Charindex(','+cast(tableid as varchar(8000))+',', @Ids) > 0

Don't use a function that loops to split a string!, my function below will split a string very fast, with no looping!

Before you use my function, you need to set up a "helper" table, you only need to do this one time per database:

CREATE TABLE Numbers
(Number int  NOT NULL,
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
    SET @x=@x+1
    INSERT INTO Numbers VALUES (@x)
END

use this function to split your string, which does not loop and is very fast:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn              char(1)              --REQUIRED, the character to split the @List string on
    ,@List                 varchar(8000)        --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
    ListValue varchar(500)
)
AS
BEGIN

/**
Takes the given @List string and splits it apart based on the given @SplitOn character.
A table is returned, one row per split item, with a column name "ListValue".
This function workes for fixed or variable lenght items.
Empty and null items will not be included in the results set.


Returns a table, one row per item in the list, with a column name "ListValue"

EXAMPLE:
----------
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')

    returns:
        ListValue  
        -----------
        1
        12
        123
        1234
        54321
        6
        A
        *
        |||
        B

        (10 row(s) affected)

**/



----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
INSERT INTO @ParsedList
        (ListValue)
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''



RETURN

END --Function FN_ListToTable

you can use this function as a table in a join:

SELECT
    Col1, COl2, Col3...
    FROM  YourTable
        INNER JOIN FN_ListToTable(',',@YourString) s ON  YourTable.ID = s.ListValue

Here is your example:

Select * from sometable where tableid in(SELECT ListValue FROM dbo.FN_ListToTable(',',@Ids) s)

You can create a function that returns a table.

so your statement would be something like

select * from someable 
 join Splitfunction(@ids) as splits on sometable.id = splits.id

Here is a simular function.

CREATE FUNCTION [dbo].[FUNC_SplitOrderIDs]
(
    @OrderList varchar(500)
)
RETURNS 
@ParsedList table
(
    OrderID int
)
AS
BEGIN
    DECLARE @OrderID varchar(10), @Pos int

    SET @OrderList = LTRIM(RTRIM(@OrderList))+ ','
    SET @Pos = CHARINDEX(',', @OrderList, 1)

    IF REPLACE(@OrderList, ',', '') <> ''
    BEGIN
        WHILE @Pos > 0
        BEGIN
            SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1)))
            IF @OrderID <> ''
            BEGIN
                INSERT INTO @ParsedList (OrderID) 
                VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion
            END
            SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos)
            SET @Pos = CHARINDEX(',', @OrderList, 1)

        END
    END 
    RETURN
END

This works perfectly! The below answers are too complicated. Don't look at this as dynamic. Set up your store procedure as follows:

(@id as varchar(50))
as

Declare @query as nvarchar(max)
set @query ='
select * from table
where id in('+@id+')'
EXECUTE sp_executesql @query

Without using dynamic SQL, you have to take the input variable and use a split function to put the data into a temp table and then join to that.

Thanks, for your function I Used IT........................ This is my EXAMPLE

**UPDATE [RD].[PurchaseOrderHeader]
SET     [DispatchCycleNumber] ='10'
 WHERE  OrderNumber in(select * FROM XA.fn_SplitOrderIDs(@InvoiceNumberList))**


CREATE FUNCTION [XA].[fn_SplitOrderIDs]
(
    @OrderList varchar(500)
)
RETURNS 
@ParsedList table
(
    OrderID int
)
AS
BEGIN
    DECLARE @OrderID varchar(10), @Pos int

    SET @OrderList = LTRIM(RTRIM(@OrderList))+ ','
    SET @Pos = CHARINDEX(',', @OrderList, 1)

    IF REPLACE(@OrderList, ',', '') <> ''
    BEGIN
        WHILE @Pos > 0
        BEGIN
                SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1)))
                IF @OrderID <> ''
                BEGIN
                        INSERT INTO @ParsedList (OrderID) 
                        VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion
                END
                SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos)
                SET @Pos = CHARINDEX(',', @OrderList, 1)

        END
    END 
    RETURN
END

I've written a stored procedure to show how to do this before. You basically have to process the string. I tried to post the code here but the formatting got all screwy.

IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[uspSplitTextList]') AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
   DROP PROCEDURE [dbo].[uspSplitTextList]
GO

SET QUOTED_IDENTIFIER ON 
GO
SET ANSI_NULLS ON 
GO


/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
-- uspSplitTextList
--
-- Description:
--    splits a separated list of text items and returns the text items
--
-- Arguments:
--    @list_text        - list of text items
--    @Delimiter        - delimiter
--
-- Notes:
-- 02/22/2006 - WSR : use DATALENGTH instead of LEN throughout because LEN doesn't count trailing blanks
--
-- History:
-- 02/22/2006 - WSR : revised algorithm to account for items crossing 8000 character boundary
-- 09/18/2006 - WSR : added to this project
--
CREATE PROCEDURE uspSplitTextList
   @list_text           text,
   @Delimiter           varchar(3)
AS

SET NOCOUNT ON

DECLARE @InputLen       integer         -- input text length
DECLARE @TextPos        integer         -- current position within input text
DECLARE @Chunk          varchar(8000)   -- chunk within input text
DECLARE @ChunkPos       integer         -- current position within chunk
DECLARE @DelimPos       integer         -- position of delimiter
DECLARE @ChunkLen       integer         -- chunk length
DECLARE @DelimLen       integer         -- delimiter length
DECLARE @ItemBegPos     integer         -- item starting position in text
DECLARE @ItemOrder      integer         -- item order in list
DECLARE @DelimChar      varchar(1)      -- first character of delimiter (simple delimiter)

-- create table to hold list items
-- actually their positions because we may want to scrub this list eliminating bad entries before substring is applied
CREATE TABLE #list_items ( item_order integer, item_begpos integer, item_endpos integer )

-- process list
IF @list_text IS NOT NULL
   BEGIN

   -- initialize
   SET @InputLen = DATALENGTH(@list_text)
   SET @TextPos = 1
   SET @DelimChar = SUBSTRING(@Delimiter, 1, 1)
   SET @DelimLen = DATALENGTH(@Delimiter)
   SET @ItemBegPos = 1
   SET @ItemOrder = 1
   SET @ChunkLen = 1

   -- cycle through input processing chunks
   WHILE @TextPos <= @InputLen AND @ChunkLen <> 0
      BEGIN

      -- get current chunk
      SET @Chunk = SUBSTRING(@list_text, @TextPos, 8000)

      -- setup initial variable values
      SET @ChunkPos = 1
      SET @ChunkLen = DATALENGTH(@Chunk)
      SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos)

      -- loop over the chunk, until the last delimiter
      WHILE @ChunkPos <= @ChunkLen AND @DelimPos <> 0
         BEGIN

         -- see if this is a full delimiter
         IF SUBSTRING(@list_text, (@TextPos + @DelimPos - 1), @DelimLen) = @Delimiter
            BEGIN

            -- insert position
            INSERT INTO #list_items (item_order, item_begpos, item_endpos)
            VALUES (@ItemOrder, @ItemBegPos, (@TextPos + @DelimPos - 1) - 1)

            -- adjust positions
            SET @ItemOrder = @ItemOrder + 1
            SET @ItemBegPos = (@TextPos + @DelimPos - 1) + @DelimLen
            SET @ChunkPos = @DelimPos + @DelimLen

            END
         ELSE
            BEGIN

            -- adjust positions
            SET @ChunkPos = @DelimPos + 1

            END

         -- find next delimiter      
         SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos)

         END

      -- adjust positions
      SET @TextPos = @TextPos + @ChunkLen

      END

   -- handle last item
   IF @ItemBegPos <= @InputLen
      BEGIN

      -- insert position
      INSERT INTO #list_items (item_order, item_begpos, item_endpos)
      VALUES (@ItemOrder, @ItemBegPos, @InputLen)

      END

   -- delete the bad items
   DELETE FROM #list_items
   WHERE item_endpos < item_begpos

   -- return list items
   SELECT SUBSTRING(@list_text, item_begpos, (item_endpos - item_begpos + 1)) AS item_text, item_order, item_begpos, item_endpos
   FROM #list_items
   ORDER BY item_order

   END

DROP TABLE #list_items

RETURN

/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

GO
SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

Tons of answers here, but to add my two cents I think STRING_SPLIT is a very simple approach to this sort of problem:

DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';

SELECT * 
FROM sometable 
WHERE tableid IN;
(SELECT value FROM STRING_SPLIT(@Ids, ','))

I ran into the same issue, and I don't want to have any footprint on the source database - i.e. no stored procedures or functions. I went about it this way:

declare @IDs table (Value int)

insert into @IDs values(1)
insert into @IDs values(2)
insert into @IDs values(3)
insert into @IDs values(5)
insert into @IDs values(4)
insert into @IDs values(6)
insert into @IDs values(7)
insert into @IDs values(98)
insert into @IDs values(234)


SELECT * 
FROM sometable 
WHERE tableid IN (select Value from @IDs)

The answer by @RBarryYoung (above) worked for me. But when you have spaces in between the comma separated string values, then it will omit IDs with spaces. So I removed the spaces.

Take a look at the code snippet below.

Declare @Ids varchar(50) Set @Ids = '1   ,   2,3'
set @Ids=','+Replace(@Ids,' ', '')+',';

Select * from [tblEmployee]
where Charindex(','+cast(ID as varchar(8000))+',', @Ids) > 0

This came in handy for one of my requirements where I did not want to use CTE and also did not want to go with the inner join.

DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';
    
SELECT
   cn1,cn2,cn3
FROM tableName
WHERE columnName in (select Value from fn_SplitString(@ids, ','))

Function for split string :

CREATE FUNCTION [dbo].[fn_SplitString] ( @stringToSplit VARCHAR(MAX), @seperator Char )  
RETURNS  
 @returnList TABLE ([Value] [nvarchar] (500))  
AS  
BEGIN  
  
 DECLARE @name NVARCHAR(255)  
 DECLARE @pos INT  
  
 WHILE CHARINDEX(@seperator, @stringToSplit) > 0  
 BEGIN  
  SELECT @pos  = CHARINDEX(@seperator, @stringToSplit)    
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)  
  
  INSERT INTO @returnList   
  SELECT @name  
  
  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)  
 END  
  
 INSERT INTO @returnList  
 SELECT @stringToSplit  
  
 RETURN  
END  
Related