Визначення змінної для використання з оператором 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 #.
Slogmeister 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)

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)

T-SQL каже[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Cees Timmerman

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)

5
@ badbod99 - Це узагальнення, і всі узагальнення неправильні :) Я запропонував альтернативи
hollystyles

1
@Vilx - ти маєш на увазі встановлення змінної @list? якщо так встановлено, добре, але встановлюється лише одна змінна, за допомогою select ви можете заповнити кілька змінних в одному операторі. Оскільки між ними не так багато, я звик завжди використовувати SELECT.
hollystyles

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)

У недекларованій таблиці запису 1M друга версія зайняла приблизно половину часу ...

ура


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+ списку елементів)


4

Ні, такого типу немає. Але є кілька варіантів:

  • Динамічно генеровані запити (sp_executesql)
  • Тимчасові столи
  • Змінні типу таблиці (найближче до списку)
  • Створіть рядок XML, а потім перетворіть його в таблицю з функціями XML (дійсно незручно і навколо, якщо ви не маєте XML для початку)

Жодне з них не є справді елегантним, але це найкраще.


4

незначне поліпшення @LukeH, немає необхідності повторювати "ВСТАВИТЬ В ІНТЕ": і відповідь @ realPT - не потрібно мати ВИБІР:

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
  • % (wildcard) буде відповідати будь-якому рядку з нуля або більше символів
  • ^ (підстановка) не збігається
  • [^ 0-9] відповідатиме будь-яким нецифровим символам
  • PATINDEX - це стандартна функція SQL, яка повертає положення шаблону в рядку


0

Я думаю, вам доведеться оголосити рядок, а потім виконати цей рядок SQL.

Погляньте на sp_executeSQL

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.