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

30

79

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?

Negotiant answered 18/5, 2009 at 17:22 Comment(6)
Is there any particular reason why you're against dynamic SQL?Muslin
Dynamic SQL makes you more vulnarable to SQL injection attacks.Perkins
HAve you considered using a table valued parameter?Mannerless
@Mannerless - Table valued parameters are only available on sql server 2008 and higher (which was only released few months before this question was asked), however it does seem like it's the best approach to accomplish this.Phrenic
simplest but might be a performance hit - select * from sometable where CONVERT(varchar, tableid) in (@Ids)Favin
consider the SQL injection risk, It's not very safe using Dynamic SQL.Woofer
M
46

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)
Misdeem answered 18/5, 2009 at 17:49 Comment(6)
What do you thik the Query proceser is doing, when you execute your Select statement? - generating all the rows instantaneously using trans-temporal quantumn physics? It's also looping... You are just changing from a loop you explicitly control, to one the SQL Server Query processer controls...Nymphalid
@Charles Bretana, Ha! You can write code 10 different ways, and each will perform differently (speed wise). The goal is to write it the way that will run the fastest. Just try it out, run this split method against the stored procedure looping method listed in another question. Run each 100 times, and see how long they take. ----- FYI, I'm sure the SQL Server internal looping is MUCH faster and better optimized than a user created stored procedure, with local variables and a WHILE loop!Misdeem
Do you have a solution for more than 8000 characters? A few of the places I've needed this have hit the 8000 character limitation so I wrote the implementation I linked above.Revalue
@Will Rickards, if you need to handle strings >8k, you could make your loop faster by using a CLR (sommarskog.se/arrays-in-sql.html) or change your loop to process chunks of 8k (make sure you break on commas), but pass those chunks into a function like mine.Misdeem
Charles and KM. There is some merit in each of your comments. Yes, the SQL engine will, at some point, loop through the individual numbers. But the engine's loop will likely run much faster than a user written loop. The real solution, to avoid looping in the first place is to redesign the schema to comply with first normal form. The CSV field looks like 1NF, but it isn't really 1NF. That's the real problem.Cavalier
Brilliant! Thank-you so much for this. I've been battling with an efficient way to do this for ages! Well done!Singsong
T
69

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
Taillight answered 19/5, 2009 at 3:16 Comment(6)
I used this approach and it worked fine until I deployed to our live server which has 4.5 million rows at which point it was far too slow. Always consider scalability!Suspire
@Suspire Already considered. Note the word "lazy", when I care about performance, scalability, maintenance or supportability, I do it similar to KM.'s answer. I.E., the right way.Taillight
@Taillight That's a nice creative solution, I did thumbed it up. Althought I never like seeing CharIndex(..)>0, the most semantic and readable alternative I can come up with would be using LIKE to know whether it contains the string =) Cheers!Stereoscopy
The reason is that using a function in a where statement will make the statement non-sargable meaning that it will result in a scan.Voluntarism
@Voluntarism That's not entirely true, there are some (few) functions and cases that are sargable (LEFT(..) on an indexed [N]VARCHAR column being the most common example). However, most are not, and it is true that this one certainly is not.Taillight
This poor-man's way of doing this is exactly what I was looking for. I didn't want to create a custom function (because reasons) and I'm only dealing with generating an in-memory set of days in a year (365-366 records in memory) to populate a configuration table once a year. This is perfect! (Yes, I know this is a very old answer but still, thanks!)Paratroops
M
46

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)
Misdeem answered 18/5, 2009 at 17:49 Comment(6)
What do you thik the Query proceser is doing, when you execute your Select statement? - generating all the rows instantaneously using trans-temporal quantumn physics? It's also looping... You are just changing from a loop you explicitly control, to one the SQL Server Query processer controls...Nymphalid
@Charles Bretana, Ha! You can write code 10 different ways, and each will perform differently (speed wise). The goal is to write it the way that will run the fastest. Just try it out, run this split method against the stored procedure looping method listed in another question. Run each 100 times, and see how long they take. ----- FYI, I'm sure the SQL Server internal looping is MUCH faster and better optimized than a user created stored procedure, with local variables and a WHILE loop!Misdeem
Do you have a solution for more than 8000 characters? A few of the places I've needed this have hit the 8000 character limitation so I wrote the implementation I linked above.Revalue
@Will Rickards, if you need to handle strings >8k, you could make your loop faster by using a CLR (sommarskog.se/arrays-in-sql.html) or change your loop to process chunks of 8k (make sure you break on commas), but pass those chunks into a function like mine.Misdeem
Charles and KM. There is some merit in each of your comments. Yes, the SQL engine will, at some point, loop through the individual numbers. But the engine's loop will likely run much faster than a user written loop. The real solution, to avoid looping in the first place is to redesign the schema to comply with first normal form. The CSV field looks like 1NF, but it isn't really 1NF. That's the real problem.Cavalier
Brilliant! Thank-you so much for this. I've been battling with an efficient way to do this for ages! Well done!Singsong
S
27

No Table No Function No Loop

Building on the idea of parsing your list into a table our DBA suggested using XML.

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

DECLARE @XML XML
SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)

SELECT * 
FROM
    SomeTable 
    INNER JOIN @XML.nodes('i') x(i) 
        ON  SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)')

These seems to have the same performance as @KM's answer but, I think, a lot simpler.

Suspire answered 5/6, 2013 at 9:48 Comment(9)
this is what other people have told me to use.. can you please explain the INNER JOIN @XML.nodes('i') x(i) ON SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)') part to me? sorry im very new to this.Chorister
Is there a way to just split the @xml without joining to the other table? e.g. select @xml.nodes(i) and it will return rows for each of 1,2,3,5,4 etcAsbestos
@PeterPitLock - Yes, See my answer below. You can just use xml as if it were any other tableChivalrous
Does not work for me. Tried it with Northwind's Categories table using CategoryID and I got was the error: Error 493: The column 'i' that was returned from the nodes() method cannot be used directly. It can only be used with one of the four XML data type methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT NULL checks.Salerno
This is a very succinct and performant way of doing it. This is my preferred answer .Tabbi
@Salerno I got that too. Try replacing SELECT * with SELECT SomeTable.* and it should work.Salerno
@Salerno - I tried that, but then I am getting a different error: Error 207: Invalid column name 'Id'.Salerno
This worked perfectly for me: I could query the original list with: SELECT x.i.value('.', 'varchar(max)') FROM @XML.nodes x('i')Tenebrae
this worked great for me. Normally i use a #tempTable but since I was working on a function, this was a great substitute.Wellpreserved
M
11

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
Mimosa answered 18/5, 2009 at 17:29 Comment(2)
This looping will be slow, you do not need to loop to split a string in SQL, see my answer for an example of how...Misdeem
That could be one of the reasons you would love RDBMS with first class array support fxjr.blogspot.com/2009/05/… Integrating CLR to MSSQL to implement multiple values for IN, vendor lock-in: sommarskog.se/arrays-in-sql-2005.htmlEstrogen
L
9

It's a very common question. Canned answer, several nice techniques:

http://www.sommarskog.se/arrays-in-sql-2005.html

Ludovico answered 18/5, 2009 at 19:52 Comment(1)
The linked page really has some great info, especially if you want to down the CLR route.Carnay
I
8

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
Indetermination answered 18/5, 2009 at 17:32 Comment(3)
Not wise.... try this: SET @id = '0); SELECT ''Hi, I just hosed your server...''--'Dewain
ahh, injection. But this usually only applies when a user is allowed to input.Indetermination
Security aside, use of concatenated literals is also not a great idea from a performance standpoint: the concatenated literals will create duplicate query plans in the query plan cache each time the SQL statement is executed with a different value in @id. If this is a busy server, say 'hola' to query plan cache bloat (ref. mssqltips.com/sqlservertip/2681/…)Eastward
U
8

I think a very simple solution could be following:

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

SELECT * 
FROM sometable 
WHERE ','+@Ids+',' LIKE '%,'+CONVERT(VARCHAR(50),tableid)+',%';
Unfetter answered 31/5, 2017 at 6:7 Comment(3)
could you explain a little?Conservative
It's clear that like operator is used to filter records I generally use this in such scenario for a long time. It's really simple & easy to understand.Unfetter
This is great for me because I've got a scenario where I don't want to add any new functions to the database and I'm working on an older version that doesn't support STRING_SPLIT.Thickleaf
N
7

I can suggest using WITH like this:

DECLARE @Delim char(1) = ',';
SET @Ids = @Ids + @Delim;

WITH CTE(i, ls, id) AS (
    SELECT 1, CHARINDEX(@Delim, @Ids, 1), SUBSTRING(@Ids, 1, CHARINDEX(@Delim, @Ids, 1) - 1)
    UNION ALL
    SELECT i + 1, CHARINDEX(@Delim, @Ids, ls + 1), SUBSTRING(@Ids, ls + 1, CHARINDEX(@Delim, @Ids, ls + 1) - CHARINDEX(@Delim, @Ids, ls) - 1)
    FROM CTE
    WHERE  CHARINDEX(@Delim, @Ids, ls + 1) > 1
)
SELECT t.*
FROM yourTable t
    INNER JOIN
    CTE c
    ON t.id = c.id;
Nygaard answered 26/9, 2015 at 5:18 Comment(1)
Brilliant. I added a cast to int on the CTE id for joining to my table's unique identifier.Apodictic
M
6

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.

Mannerless answered 18/5, 2009 at 17:26 Comment(0)
M
4

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, ','))
Millimeter answered 10/6, 2021 at 4:22 Comment(2)
Supported in SQL Server 2016 and above.Quorum
I like this answer but it seems a little slowAudwen
S
3

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
Surfboarding answered 5/3, 2010 at 6:37 Comment(0)
P
3

If you use SQL Server 2008 or higher, use table valued parameters; for example:

CREATE PROCEDURE [dbo].[GetAccounts](@accountIds nvarchar)
AS
BEGIN
    SELECT * 
    FROM accountsTable 
    WHERE accountId IN (select * from @accountIds)
END

CREATE TYPE intListTableType AS TABLE (n int NOT NULL)

DECLARE @tvp intListTableType 

-- inserts each id to one row in the tvp table    
INSERT @tvp(n) VALUES (16509),(16685),(46173),(42925),(46167),(5511)

EXEC GetAccounts @tvp
Phrenic answered 6/10, 2014 at 8:17 Comment(0)
C
3

Its been a while but I have done this in the past using XML as a interim.

I can't take any credit for this, but I'm afraid I no longer know where I got this idea from:

-- declare the variables needed
DECLARE @xml as xml,@str as varchar(100),@delimiter as varchar(10)

-- The string you want to split
SET @str='A,B,C,D,E,Bert,Ernie,1,2,3,4,5'

-- What you want to split on. Can be a single character or a string
SET @delimiter =','

-- Convert it to an XML document
SET @xml = cast(('<X>'+replace(@str,@delimiter ,'</X><X>')+'</X>') as xml)

-- Select back from the XML
SELECT N.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as T(N)
Chivalrous answered 23/2, 2016 at 11:17 Comment(1)
I guess I was a little thick looking at the answer you commented on, but had trouble turning that into an IN clause. Using this example helped. Thanks!Ennis
C
3

Create a table function like below which parse comma separated varchar and returns a table that can be inner joined with other tables.

CREATE FUNCTION [dbo].[fn_SplitList]
(
  @inString     varchar(MAX)  = '',
  @inDelimiter  char(1)       = ',' -- Keep the delimiter to 100 chars or less.  Generally a delimiter will be 1-2 chars only.
)
RETURNS @tbl_Return  table
(
  Unit  varchar(1000) COLLATE Latin1_General_BIN
)
AS
BEGIN 
    INSERT INTO @tbl_Return
    SELECT DISTINCT
      LTRIM(RTRIM(piece.value('./text()[1]', 'varchar(1000)'))) COLLATE DATABASE_DEFAULT AS Unit
    FROM
    (
      --
      --  Replace any delimiters in the string with the "X" tag.
      --
      SELECT
        CAST(('<X>' + REPLACE(s0.prsString, s0.prsSplitDelimit, '</X><X>') + '</X>') AS xml).query('.') AS units
      FROM
      (
        --
        --  Convert the string and delimiter into XML.
        --
        SELECT
          (SELECT @inString FOR XML PATH('')) AS prsString,
          (SELECT @inDelimiter FOR XML PATH('')) AS prsSplitDelimit
      ) AS s0
    ) AS s1
    CROSS APPLY units.nodes('X') x(piece)
  RETURN
END

================================================= Now consume above created table function in your code,creation of function is one time activity in your database that can be used across databases as well on same server.

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

SELECT
     *
FROM sometable AS st
INNER JOIN fn_SplitList(@ids, ',') AS sl
     ON sl.unit = st.tableid
Cornellcornelle answered 7/3, 2017 at 11:26 Comment(0)
R
2

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
Revalue answered 18/5, 2009 at 17:29 Comment(2)
Your answer have some broken links... can you check them out?Semipalmate
added code as requested though I'm not sure I use this algorithm anymore. I switched to passing xml and then using sql's xml support some time ago.Revalue
L
2

I have same idea with user KM. but do not need extra table Number. Just this function only.

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
    DECLARE @number int = 0
    DECLARE @childString varchar(502) = ''
    DECLARE @lengthChildString int = 0
    DECLARE @processString varchar(502) = @SplitOn + @List + @SplitOn

    WHILE @number < LEN(@processString)
    BEGIN
        SET @number = @number + 1
        SET @lengthChildString = CHARINDEX(@SplitOn, @processString, @number + 1) - @number - 1
        IF @lengthChildString > 0
        BEGIN
            SET @childString = LTRIM(RTRIM(SUBSTRING(@processString, @number + 1, @lengthChildString)))

            IF @childString IS NOT NULL AND @childString != ''
            BEGIN
                INSERT INTO @ParsedList(ListValue) VALUES (@childString)
                SET @number = @number + @lengthChildString - 1
            END
        END
    END

RETURN

END

And here is the test:

SELECT ListValue FROM dbo.FN_ListToTable('/','a/////bb/c')

Result:

   ListValue
______________________
   a
   bb
   c
Lir answered 29/11, 2016 at 2:14 Comment(0)
D
1
-- select * from dbo.Split_ID('77,106')  

    ALTER FUNCTION dbo.Split_ID(@String varchar(8000))     
    returns @temptable TABLE (ID varchar(8000))     
    as     
    begin     
        declare @idx int     
        declare @slice varchar(8000)     
        declare @Delimiter char(1)
         set @Delimiter =','

        select @idx = 1     
            if len(@String)<1 or @String is null  return     

        while @idx!= 0     
        begin     
            set @idx = charindex(@Delimiter,@String)     
            if @idx!=0     
                set @slice = left(@String,@idx - 1)     
            else     
                set @slice = @String     

            if(len(@slice)>0)
                insert into @temptable(ID) values(@slice)     

            set @String = right(@String,len(@String) - @idx)     
            if len(@String) = 0 break     
        end 
    return     
    end
Destination answered 31/7, 2012 at 11:20 Comment(0)
M
1

You could do it like this:

create or replace 
PROCEDURE UDP_SETBOOKMARK 
(
  P_USERID IN VARCHAR2  
, P_BOOKMARK IN VARCHAR2  
) AS 
BEGIN

UPDATE T_ER_Bewertung
SET LESEZEICHEN = P_BOOKMARK
WHERE STAMM_ID in( select regexp_substr(P_USERID,'[^,]+', 1, level) from dual
                   connect by regexp_substr(P_USERID, '[^,]+', 1, level) is not null )
and ER_ID = (select max(ER_ID) from T_ER_Bewertung_Kopie);

commit;

END UDP_SETBOOKMARK;

Then try it with

Begin
UDP_SETBOOKMARK ('1,2,3,4,5', 'Test');
End;

You can use this IN-Clause with regexp_substr in other situations too, just try it.

Misguide answered 22/12, 2014 at 12:58 Comment(0)
C
1
Error 493: The column 'i' that was returned from the nodes() method cannot be 
   used directly. It can only be used with one of the four XML data type 
   methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT 
   NULL checks.

The above errorr was fixed in SQL Server 2014 by using following snippet

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

DECLARE @XML XML
SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)

SELECT SomeTable.* 
FROM
    SomeTable 
    cross apply @XML.nodes('i') x(i) 
        where SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)')
Ceruse answered 21/7, 2017 at 17:51 Comment(0)
I
1

Try this:

SELECT ProductId, Name, Tags  
FROM Product  
WHERE '1,2,3,' LIKE '%' + CAST(ProductId AS VARCHAR(20)) + ',%'; 

As said on the last example of this link

Ilowell answered 20/8, 2017 at 3:31 Comment(1)
it is not work when id > 10, for example DECLARE @Ids NVARCHAR(1000) = '3,4,5,6,7,8,9,10,11,12,'. it get all 1,2 & 11, 12Religionism
A
0

Best and simple approach.

DECLARE @AccumulateKeywordCopy NVARCHAR(2000),@IDDupCopy NVARCHAR(50);
SET @AccumulateKeywordCopy ='';
SET @IDDupCopy ='';
SET @IDDup = (SELECT CONVERT(VARCHAR(MAX), <columnName>) FROM <tableName> WHERE <clause>)

SET @AccumulateKeywordCopy = ','+@AccumulateKeyword+',';
SET @IDDupCopy = ','+@IDDup +',';
SET @IDDupCheck = CHARINDEX(@IDDupCopy,@AccumulateKeywordCopy)
Amann answered 6/11, 2014 at 10:36 Comment(0)
C
0
CREATE TABLE t 
  ( 
     id   INT, 
     col1 VARCHAR(50) 
  ) 

INSERT INTO t 
VALUES     (1, 
            'param1') 

INSERT INTO t 
VALUES     (2, 
            'param2') 

INSERT INTO t 
VALUES     (3, 
            'param3') 

INSERT INTO t 
VALUES     (4, 
            'param4') 

INSERT INTO t 
VALUES     (5, 
            'param5') 

DECLARE @params VARCHAR(100) 

SET @params = ',param1,param2,param3,' 

SELECT * 
FROM   t 
WHERE  Charindex(',' + Cast(col1 AS VARCHAR(8000)) + ',', @params) > 0 

working fiddle find here Fiddle

Cordiecordier answered 21/2, 2018 at 8:42 Comment(1)
This is the same approach posted in 2009 here.Tenebrae
P
0

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)
Produce answered 28/11, 2020 at 22:5 Comment(0)
C
0

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
Centenarian answered 11/5, 2021 at 12:16 Comment(0)
I
0
WHERE someId IN (SELECT convert(int, value) FROM string_split(@stringOfCommaDelimitedIds, ','))
Id answered 26/10, 2022 at 19:11 Comment(0)
T
0

As told by Matt above done it like below(Rectified little bit) and it's working:

 DECLARE @XML XML
 Declare @Ids VARCHAR(MAX)='1001,1002,1003,1004'
SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)
--SELECT @XML

SELECT x.i.value('.', 'VARCHAR(MAX)') Code,FOOD_PRODUCT FROM DIET_CAT_FOOD_ITEMS_MST 
    INNER JOIN @XML.nodes('i') x(i) 
        ON  DIET_CAT_FOOD_ITEMS_MST.CODE = x.i.value('.', 'VARCHAR(MAX)')
Tobin answered 22/3, 2023 at 7:5 Comment(0)
C
0
DECLARE @Ids varchar(8000);
SET @Ids = '3,5,4,6';
SELECT convert(int, value) FROM string_split(@Ids, ',')
Clockwise answered 13/6, 2023 at 10:49 Comment(0)
H
0

This simple answer is for SQL 2016 standard

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

SELECT * 
FROM sometable 
WHERE tableid IN (SELECT ss.value from string_split(@Ids, ',') ss)
Herdic answered 10/1 at 18:38 Comment(1)
Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?Toilette
S
-1

The simplest way i found was to use FIND_IN_SET

FIND_IN_SET(column_name, values)

values=(1,2,3)

SELECT name WHERE FIND_IN_SET(id, values)
Stringhalt answered 30/3, 2017 at 4:54 Comment(1)
MS SQL (T-SQL) doesn't have FIND_IN_SET()Personal
M
-1

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  
Maintain answered 21/5, 2020 at 9:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.