Определите переменную для использования с оператором IN (T-SQL)

138

У меня есть запрос Transact-SQL, который использует оператор IN. Что-то вроде этого:

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

Есть ли способ определить переменную для хранения всего списка "(1,2,3,4)"? Как мне это определить?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList
Маркос Криспино
источник
7
Этот вопрос отличается от вопроса «Параметризация предложения SQL IN». Этот вопрос относится к нативному T-SQL, другой вопрос относится к C #.
Слогмейстер Extraordinaire

Ответы:

113
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)
LukeH
источник
47
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)
realPT
источник
T-SQL говорит[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Сис Тиммерман,
1
Исправлено для вас @Paul
Stefan Z Camilleri
5
Вы можете просто использовать (VALUES (1),(2),(3),(4),(5))напрямую?
Toddmo
Это было лучшее решение для моих нужд. Мне нужна была переменная в виде списка идентификаторов, которые я получал из Select, чтобы значения не были предопределены. Это сделано именно то, что мне нужно. Спасибо!
Lexi847942
12

Существует два способа решения динамических списков csv для запросов TSQL:

1) Использование внутреннего выбора

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

2) Использование динамически сцепленного 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) Возможный третий вариант - это переменные таблицы. Если у вас SQl Server 2005, вы можете использовать табличную переменную. Если вы используете Sql Server 2008, вы можете даже передавать переменные всей таблицы в качестве параметра хранимым процедурам и использовать его в объединении или в качестве подвыбора в предложении IN.

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)
hollystyles
источник
5
@ badbod99 - Это обобщение, и все обобщения неверны :) Я предложил альтернативы
hollystyles
1
@Vilx - вы имеете в виду установку переменной @list? Если это так, то это нормально, но устанавливает только одну переменную, с помощью select вы можете заполнить несколько переменных одним оператором. Поскольку между ними не так много, я всегда использую SELECT.
Холлистайл
1
Правда ... очень общий. Ваша альтернатива лучше. Я действительно имею в виду, что генерация SQL из скрипта SQL обычно приводит к неуправляемому коду, риску внедрения атак и множеству других мерзостей.
badbod99
9

Используйте такую ​​функцию:

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;

Вместо использования like вы создаете внутреннее соединение с таблицей, возвращаемой функцией:

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

становится

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

В неиндексированной таблице записей 1М вторая версия заняла примерно половину времени ...

ура

allaphor
источник
5
DECLARE @myList TABLE (Id BIGINT) INSERT INTO @myList(Id) VALUES (1),(2),(3),(4);
select * from myTable where myColumn in(select Id from @myList)

Обратите внимание, что для длинных списков или производственных систем не рекомендуется использовать этот способ, так как он может быть намного медленнее, чем простой INоператор someColumnName in (1,2,3,4)(тестируется с использованием списка элементов более 8000)

Vova
источник
4

Нет, такого типа нет. Но есть несколько вариантов:

  • Динамически генерируемые запросы (sp_executesql)
  • Временные столы
  • Переменные табличного типа (самое близкое к списку)
  • Создайте строку XML, а затем преобразуйте ее в таблицу с функциями XML (очень неудобно и обходно, если у вас нет XML для начала)

Ни один из них не является действительно изящным, но это лучшее, что есть.

Vilx-
источник
4

небольшое улучшение в @LukeH, нет необходимости повторять «INSERT INTO»: и ответ @ realPT - нет необходимости иметь 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

Я знаю, что это старый, но TSQL => 2016, вы можете использовать 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)
Натан Эванс
источник
4

Начиная с SQL2017, вы можете использовать STRING_SPLIT и сделать это:

declare @myList nvarchar(MAX)
set @myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(@myList,','))
Макс Фавилли
источник
2

Если вы хотите сделать это без использования второй таблицы, вы можете сделать LIKE сравнение с CAST:

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

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

Если сравниваемое поле уже является строкой, вам не нужно делать CAST.

Окружение совпадения столбцов и каждого уникального значения в запятых обеспечит точное совпадение. В противном случае значение 1 будет найдено в списке, содержащем ', 4,2,15,'

Майкл Рейес
источник
1

Как никто не упоминал ранее, начиная с Sql Server 2016, вы также можете использовать массивы json и 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)

Вы можете проверить это в sql fiddle demo

Вы также можете проще охватить более сложные случаи с помощью json - см. Поиск списка значений и диапазона в SQL с использованием предложения WHERE IN с переменной SQL?

Роман Пекар
источник
1

Он использует PATINDEX для сопоставления идентификаторов из таблицы с целочисленным списком без разделителей.

-- 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

Ноты:

  • при приведении к типу varchar без указания размера байта в скобках длина по умолчанию составляет 30
  • % (подстановочный знак) будет соответствовать любой строке от нуля или более символов
  • ^ (подстановочный знак) не соответствует
  • [^ 0-9] будет соответствовать любому нецифровому символу
  • PATINDEX - это стандартная функция SQL, которая возвращает позицию шаблона в строке
Марн
источник
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;
Мухаммед Фатих Йылдыз
источник
5
это будет лучший ответ, если вы опишите свой код там!
Deep Kakkar
0

Я думаю, вам придется объявить строку, а затем выполнить эту строку SQL.

Посмотрите на sp_executeSQL

BIDeveloper
источник