179

I have a Transact-SQL query that uses the IN operator. Something like this:

select * from myTable where myColumn in (1,2,3,4)

Is there a way to define a variable to hold the entire list "(1,2,3,4)"? How should I define it?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList
2

16 Answers 16

141
DECLARE @MyList TABLE (Value INT)
INSERT INTO @MyList VALUES (1)
INSERT INTO @MyList VALUES (2)
INSERT INTO @MyList VALUES (3)
INSERT INTO @MyList VALUES (4)

SELECT *
FROM MyTable
WHERE MyColumn IN (SELECT Value FROM @MyList)
62
DECLARE @mylist TABLE (Id int)
INSERT INTO @mylist
SELECT id FROM (VALUES (1),(2),(3),(4),(5)) AS tbl(id)

SELECT * FROM Mytable WHERE theColumn IN (select id from @mylist)
2
  • 12
    Can you just use (VALUES (1),(2),(3),(4),(5)) directly?
    – toddmo
    Oct 8, 2016 at 19:50
  • This was the best solution for my needs. I needed a variable as a list of Ids that I was getting from a Select so the values were not predetermined. This accomplished exactly what I needed. Thanks!
    – Lexi847942
    Sep 7, 2017 at 18:11
13

There are two ways to tackle dynamic csv lists for TSQL queries:

1) Using an inner select

SELECT * FROM myTable WHERE myColumn in (SELECT id FROM myIdTable WHERE id > 10)

2) Using dynamically concatenated TSQL

DECLARE @sql varchar(max)  
declare @list varchar(256)  
select @list = '1,2,3'  
SELECT @sql = 'SELECT * FROM myTable WHERE myColumn in (' + @list + ')'

exec sp_executeSQL @sql

3) A possible third option is table variables. If you have SQl Server 2005 you can use a table variable. If your on Sql Server 2008 you can even pass whole table variables in as a parameter to stored procedures and use it in a join or as a subselect in the IN clause.

DECLARE @list TABLE (Id INT)

INSERT INTO @list(Id)
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4


SELECT
    * 
FROM 
    myTable
    JOIN @list l ON myTable.myColumn = l.Id

SELECT
    * 
FROM 
    myTable
WHERE
    myColumn IN (SELECT Id FROM @list)
3
  • 5
    @badbod99 - Thats a generalisation and all generalisations are wrong :) I have offered alternatives Nov 10, 2009 at 11:49
  • 1
    @Vilx - do you mean for setting the variable @list? if so set is fine but only sets one variable, with select you can populate several variables in one statement. As there's not much between them I'm in the habit of always using SELECT. Nov 10, 2009 at 11:51
  • 1
    True... very general. Your alternative is better. I really mean generating SQL from within an SQL script usually causes unmaintainable code, risk of injection attackes and a raft of other nastiness.
    – badbod99
    Nov 10, 2009 at 11:53
11

I know this is old now but TSQL => 2016, you can use STRING_SPLIT:

DECLARE @InList varchar(255) = 'This;Is;My;List';

WITH InList (Item) AS (
    SELECT value FROM STRING_SPLIT(@InList, ';')
)

SELECT * 
FROM [Table]
WHERE [Item] IN (SELECT Tag FROM InList)
11

Use a function like this:

CREATE function [dbo].[list_to_table] (@list varchar(4000))
returns @tab table (item varchar(100))
begin

if CHARINDEX(',',@list) = 0 or CHARINDEX(',',@list) is null
begin
    insert into @tab (item) values (@list);
    return;
end


declare @c_pos int;
declare @n_pos int;
declare @l_pos int;

set @c_pos = 0;
set @n_pos = CHARINDEX(',',@list,@c_pos);

while @n_pos > 0
begin
    insert into @tab (item) values (SUBSTRING(@list,@c_pos+1,@n_pos - @c_pos-1));
    set @c_pos = @n_pos;
    set @l_pos = @n_pos;
    set @n_pos = CHARINDEX(',',@list,@c_pos+1);
end;

insert into @tab (item) values (SUBSTRING(@list,@l_pos+1,4000));

return;
end;

Instead of using like, you make an inner join with the table returned by the function:

select * from table_1 where id in ('a','b','c')

becomes

select * from table_1 a inner join [dbo].[list_to_table] ('a,b,c') b on (a.id = b.item)

In an unindexed 1M record table the second version took about half the time...

10

Starting with SQL2017 you can use STRING_SPLIT and do this:

declare @myList nvarchar(MAX)
set @myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(@myList,','))
2
  • 1
    This seems to repeat the solution by Nathan Evans a month prior.
    – TylerH
    Dec 9, 2021 at 19:36
  • 1
    @TylerH Agree, evidently I didn't notice it. Dec 11, 2021 at 9:07
6
DECLARE @myList TABLE (Id BIGINT) INSERT INTO @myList(Id) VALUES (1),(2),(3),(4);
select * from myTable where myColumn in(select Id from @myList)

Please note that for long list or production systems it's not recommended to use this way as it may be much more slower than simple INoperator like someColumnName in (1,2,3,4) (tested using 8000+ items list)

5

slight improvement on @LukeH, there is no need to repeat the "INSERT INTO": and @realPT's answer - no need to have the SELECT:

DECLARE @MyList TABLE (Value INT) 
INSERT INTO @MyList VALUES (1),(2),(3),(4)

SELECT * FROM MyTable
WHERE MyColumn IN (SELECT Value FROM @MyList)
4

No, there is no such type. But there are some choices:

  • Dynamically generated queries (sp_executesql)
  • Temporary tables
  • Table-type variables (closest thing that there is to a list)
  • Create an XML string and then convert it to a table with the XML functions (really awkward and roundabout, unless you have an XML to start with)

None of these are really elegant, but that's the best there is.

4

If you want to do this without using a second table, you can do a LIKE comparison with a CAST:

DECLARE @myList varchar(15)
SET @myList = ',1,2,3,4,'

SELECT *
FROM myTable
WHERE @myList LIKE '%,' + CAST(myColumn AS varchar(15)) + ',%'

If the field you're comparing is already a string then you won't need to CAST.

Surrounding both the column match and each unique value in commas will ensure an exact match. Otherwise, a value of 1 would be found in a list containing ',4,2,15,'

2

As no one mentioned it before, starting from Sql Server 2016 you can also use json arrays and OPENJSON (Transact-SQL):

declare @filter nvarchar(max) = '[1,2]'

select *
from dbo.Test as t
where
    exists (select * from openjson(@filter) as tt where tt.[value] = t.id)

You can test it in sql fiddle demo

You can also cover more complicated cases with json easier - see Search list of values and range in SQL using WHERE IN clause with SQL variable?

1

This one uses PATINDEX to match ids from a table to a non-digit delimited integer list.

-- Given a string @myList containing character delimited integers 
-- (supports any non digit delimiter)
DECLARE @myList VARCHAR(MAX) = '1,2,3,4,42'

SELECT * FROM [MyTable]
    WHERE 
        -- When the Id is at the leftmost position 
        -- (nothing to its left and anything to its right after a non digit char) 
        PATINDEX(CAST([Id] AS VARCHAR)+'[^0-9]%', @myList)>0 
        OR
        -- When the Id is at the rightmost position
        -- (anything to its left before a non digit char and nothing to its right) 
        PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR), @myList)>0
        OR
        -- When the Id is between two delimiters 
        -- (anything to its left and right after two non digit chars)
        PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR)+'[^0-9]%', @myList)>0
        OR
        -- When the Id is equal to the list
        -- (if there is only one Id in the list)
        CAST([Id] AS VARCHAR)=@myList

Notes:

  • when casting as varchar and not specifying byte size in parentheses the default length is 30
  • % (wildcard) will match any string of zero or more characters
  • ^ (wildcard) not to match
  • [^0-9] will match any non digit character
  • PATINDEX is an SQL standard function that returns the position of a pattern in a string
1

My experience with a commonly proposed technique offered here,

SELECT * FROM Mytable WHERE myColumn IN (select id from @mylist)

is that it induces a major performance degradation if the primary data table (Mytable) includes a very large number of records. Presumably, that is because the IN operator’s list-subquery is re-executed for every record in the data table. I’m not seeing any offered solution here that provides the same functional result by avoiding the IN operator entirely. The general problem isn’t a need for a parameterized IN operation, it’s a need for a parameterized inclusion constraint. My favored technique for that is to implement it using an (inner) join:

DECLARE @myList varchar(50) /* BEWARE: if too small, no error, just missing data! */
SET @myList = '1,2,3,4'

SELECT *
FROM myTable
JOIN STRING_SPLIT(@myList,',') MyList_Tbl
  ON myColumn = MyList_Tbl.Value

It is so much faster because the generation of the constraint-list table (MyList_Tbl) is executed only once for the entire query execution. Typically, for large data sets, this technique executes at least five times faster than the functionally equivalent parameterized IN operator solutions, like those offered here.

0
DECLARE @StatusList varchar(MAX);
SET @StatusList='1,2,3,4';
DECLARE @Status SYS_INTEGERS;
INSERT INTO  @Status 
SELECT Value 
FROM dbo.SYS_SPLITTOINTEGERS_FN(@StatusList, ',');
SELECT Value From @Status;
1
  • 7
    it will be a better answer if you describe your code there! Mar 20, 2017 at 10:45
0

Most of these seem to focus on separating-out each INT into its own parenthetical, for example:

  • (1),(2),(3), and so on...

That isn't always convenient. Especially since, many times, you already start with a comma-separated list, for example:

  • (1,2,3,...) and so on...

In these situations, you may care to do something more like this:

DECLARE @ListOfIds TABLE (DocumentId INT);

INSERT INTO @ListOfIds
SELECT Id FROM [dbo].[Document] WHERE Id IN (206,235,255,257,267,365)

SELECT * FROM @ListOfIds

I like this method because, more often than not, I am trying to work with IDs that should already exist in a table.

-1

I think you'll have to declare a string and then execute that SQL string.

Have a look at sp_executeSQL

Not the answer you're looking for? Browse other questions tagged or ask your own question.