Definire la variabile da utilizzare con l'operatore IN (T-SQL)


138

Ho una query Transact-SQL che utilizza l'operatore IN. Qualcosa come questo:

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

C'è un modo per definire una variabile per contenere l'intero elenco "(1,2,3,4)"? Come dovrei definirlo?

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

7
Questa domanda non è la stessa della domanda "Parametrizza una clausola SQL IN". Questa domanda si riferisce a T-SQL nativo, l'altra domanda si riferisce a C #.
Slogmeister Extraordinaire,

Risposte:


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 dice[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Cees Timmerman,

1
Risolto il problema per te @Paul
Stefan Z Camilleri,

5
Puoi semplicemente usare (VALUES (1),(2),(3),(4),(5))direttamente?
toddmo

Questa è stata la soluzione migliore per le mie esigenze. Avevo bisogno di una variabile come un elenco di ID che stavo ottenendo da un Select, quindi i valori non erano predeterminati. Ciò ha realizzato esattamente ciò di cui avevo bisogno. Grazie!
Lexi847942

12

Esistono due modi per affrontare gli elenchi CSV dinamici per le query TSQL:

1) Utilizzando una selezione interna

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

2) Utilizzo di TSQL concatenato dinamicamente

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) Una terza opzione possibile sono le variabili di tabella. Se si dispone di SQl Server 2005 è possibile utilizzare una variabile di tabella. Se su Sql Server 2008 è anche possibile passare intere variabili della tabella come parametro alle stored procedure e utilizzarlo in un join o come sottoselezione nella clausola 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 - Questa è una generalizzazione e tutte le generalizzazioni sono sbagliate :) Ho offerto alternative
hollystyles

1
@Vilx - intendi per impostare la variabile @list? in tal caso set va bene ma imposta solo una variabile, con select puoi popolare diverse variabili in una sola istruzione. Dato che non c'è molto tra loro, ho l'abitudine di usare sempre SELECT.
hollystyles,

1
Vero ... molto generale. La tua alternativa è migliore. Intendo dire che generare SQL dall'interno di uno script SQL di solito provoca codice non mantenibile, rischio di attacchi di iniezione e una serie di altre cattiverie.
badbod99,

9

Utilizzare una funzione come questa:

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;

Invece di usare like, si crea un join interno con la tabella restituita dalla funzione:

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

diventa

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

In una tabella di record 1M non indicizzata la seconda versione ha impiegato circa la metà del tempo ...

Saluti


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)

Si prega di notare che per un elenco lungo o sistemi di produzione non è consigliabile utilizzare in questo modo in quanto potrebbe essere molto più lento di un semplice INoperatore someColumnName in (1,2,3,4)(testato utilizzando oltre 8000 articoli elenco)


4

No, non esiste tale tipo. Ma ci sono alcune scelte:

  • Query generate dinamicamente (sp_executesql)
  • Tabelle temporanee
  • Variabili di tipo tabella (cosa più vicina a un elenco)
  • Crea una stringa XML e poi convertila in una tabella con le funzioni XML (davvero scomode e rotonde, a meno che tu non abbia un XML con cui iniziare)

Nessuno di questi è davvero elegante, ma è il migliore che esista.


4

leggero miglioramento su @LukeH, non è necessario ripetere "INSERT INTO": e la risposta di @ realPT - non è necessario avere 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

So che ora è vecchio ma TSQL => 2016, puoi utilizzare 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

A partire da SQL2017 è possibile utilizzare STRING_SPLIT e procedere come segue:

declare @myList nvarchar(MAX)
set @myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(@myList,','))

2

Se vuoi farlo senza usare una seconda tabella, puoi fare un confronto LIKE con un CAST:

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

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

Se il campo che stai confrontando è già una stringa, non avrai bisogno di CAST.

Circondare sia la corrispondenza della colonna che ciascun valore univoco tra virgole garantirà una corrispondenza esatta. Altrimenti, un valore di 1 verrebbe trovato in un elenco contenente ", 4,2,15,"


1

Come nessuno ha mai detto prima, a partire da SQL Server 2016 è possibile utilizzare anche array JSON e 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)

Puoi provarlo sql fiddle demo

Puoi anche semplificare la gestione di casi più complicati con json: vedi l' elenco di valori e intervallo di ricerca in SQL utilizzando la clausola WHERE IN con variabile SQL?


1

Questo utilizza PATINDEX per far corrispondere gli id ​​da una tabella a un elenco intero delimitato da cifre.

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

Appunti:

  • quando si esegue il cast come varchar e non si specifica la dimensione in byte tra parentesi la lunghezza predefinita è 30
  • % (carattere jolly) corrisponderà a qualsiasi stringa di zero o più caratteri
  • ^ (carattere jolly) non corrispondente
  • [^ 0-9] corrisponderà a qualsiasi carattere non numerico
  • PATINDEX è una funzione standard SQL che restituisce la posizione di un modello in una stringa

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
sarà una risposta migliore se descrivi il tuo codice lì!
Deep Kakkar,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.