Zdefiniuj zmienną do użycia z operatorem IN (T-SQL)

138

Mam zapytanie Transact-SQL, które używa operatora IN. Coś takiego:

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

Czy istnieje sposób na zdefiniowanie zmiennej do przechowywania całej listy „(1,2,3,4)”? Jak mam to zdefiniować?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList
Marcos Crispino
źródło
7
To pytanie nie jest tym samym, co pytanie „Sparametryzuj klauzulę SQL IN”. To pytanie odnosi się do natywnego T-SQL, drugie pytanie odnosi się do C #.
Slogmeister Extraordinaire

Odpowiedzi:

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
źródło
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
źródło
T-SQL mówi[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Cees Timmerman,
1
Naprawiliśmy to dla ciebie @Paul
Stefan Z Camilleri,
5
Czy możesz po prostu użyć (VALUES (1),(2),(3),(4),(5))bezpośrednio?
toddmo
To było najlepsze rozwiązanie dla moich potrzeb. Potrzebowałem zmiennej jako listy identyfikatorów, które otrzymywałem z Select, więc wartości nie były z góry określone. To osiągnęło dokładnie to, czego potrzebowałem. Dzięki!
Lexi847942,
12

Istnieją dwa sposoby radzenia sobie z dynamicznymi listami csv dla zapytań TSQL:

1) Korzystanie z wewnętrznego wyboru

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

2) Korzystanie z dynamicznie konkatenowanego języka 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) Trzecią możliwą opcją są zmienne tabelaryczne. Jeśli masz SQl Server 2005, możesz użyć zmiennej tabeli. Jeśli korzystasz z Sql Server 2008, możesz nawet przekazać zmienne całej tabeli jako parametr do procedur składowanych i użyć ich w złączeniu lub jako podselekcji w klauzuli 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)
styl holenderski
źródło
5
@ badbod99 - To uogólnienie i wszystkie uogólnienia są błędne :) Zaproponowałem alternatywy
hollystyles
1
@Vilx - czy masz na myśli ustawienie zmiennej @list? jeśli tak, set jest w porządku, ale ustawia tylko jedną zmienną, za pomocą select można wypełnić kilka zmiennych w jednej instrukcji. Ponieważ nie ma między nimi zbyt wiele, mam zwyczaj zawsze używać SELECT.
hollystyles
1
Prawda ... bardzo ogólna. Twoja alternatywa jest lepsza. Naprawdę mam na myśli, że generowanie SQL z poziomu skryptu SQL zwykle powoduje nieobsługiwany kod, ryzyko ataku typu injection i mnóstwo innych nieprzyjemności.
badbod99
9

Użyj takiej funkcji:

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;

Zamiast używać like, tworzysz sprzężenie wewnętrzne z tabelą zwracaną przez funkcję:

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

staje się

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

W niezindeksowanej tabeli rekordów 1M druga wersja zajęła mniej więcej połowę czasu ...

Twoje zdrowie

alafora
źródło
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)

Należy pamiętać, że w przypadku długich list lub systemów produkcyjnych nie zaleca się używania tego sposobu, ponieważ może być znacznie wolniejszy niż prosty INoperator someColumnName in (1,2,3,4)(testowany przy użyciu listy 8000+ pozycji)

Vova
źródło
4

Nie, nie ma takiego typu. Ale jest kilka możliwości:

  • Zapytania generowane dynamicznie (sp_executesql)
  • Tabele tymczasowe
  • Zmienne tablicowe (najbliższe liście)
  • Utwórz ciąg XML, a następnie przekonwertuj go na tabelę z funkcjami XML (naprawdę niezręczne i okrężne, chyba że masz XML, od którego można zacząć)

Żaden z nich nie jest naprawdę elegancki, ale to najlepsze, jakie istnieje.

Vilx-
źródło
4

niewielka poprawa w stosunku do @LukeH, nie ma potrzeby powtarzania "INSERT INTO": i odpowiedzi @ realPT - nie ma potrzeby posiadania SELECT:

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

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

źródło
4

Wiem, że to jest teraz stare, ale TSQL => 2016, możesz użyć 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)
Nathan Evans
źródło
4

Począwszy od SQL2017 możesz użyć STRING_SPLIT i zrobić to:

declare @myList nvarchar(MAX)
set @myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(@myList,','))
Max Favilli
źródło
2

Jeśli chcesz to zrobić bez użycia drugiej tabeli, możesz wykonać LIKE porównanie z CAST:

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

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

Jeśli pole, które porównujesz, jest już ciągiem znaków, nie musisz wykonywać RZUTU.

Ujęcie zarówno dopasowania kolumny, jak i każdej unikalnej wartości przecinkami zapewni dokładne dopasowanie. W przeciwnym razie wartość 1 zostałaby znaleziona na liście zawierającej „, 4,2,15,”

Michael Reyes
źródło
1

Jak nikt wcześniej o tym nie wspominał, począwszy od Sql Server 2016 można również używać tablic json i 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)

Możesz to przetestować w sql fiddle demo

Możesz również łatwiej opisać bardziej skomplikowane przypadki za pomocą json - zobacz Przeszukiwanie listy wartości i zakresu w SQL za pomocą klauzuli WHERE IN ze zmienną SQL?

Roman Pekar
źródło
1

Ten używa PATINDEX do dopasowania identyfikatorów z tabeli do niecyfrowej listy liczb całkowitych.

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

Uwagi:

  • przy rzutowaniu jako varchar i nie określaniu rozmiaru bajtu w nawiasach domyślna długość wynosi 30
  • % (symbol wieloznaczny) dopasuje dowolny ciąg zawierający zero lub więcej znaków
  • ^ (symbol wieloznaczny) nie pasuje
  • [^ 0-9] dopasuje dowolny znak niebędący cyfrą
  • PATINDEX to standardowa funkcja SQL, która zwraca pozycję wzorca w ciągu
Marne
źródło
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;
Muhammed Fatih Yıldız
źródło
5
będzie lepszą odpowiedzią, jeśli opiszesz tam swój kod!
Deep Kakkar
0

Myślę, że będziesz musiał zadeklarować ciąg, a następnie wykonać ten ciąg SQL.

Spójrz na sp_executeSQL

BIDeveloper
źródło