Définir la variable à utiliser avec l'opérateur IN (T-SQL)

138

J'ai une requête Transact-SQL qui utilise l'opérateur IN. Quelque chose comme ça:

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

Existe-t-il un moyen de définir une variable pour contenir la liste entière "(1,2,3,4)"? Comment devrais-je le définir?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList
Marcos Crispino
la source
7
Cette question n'est pas la même que la question "Paramétrer une clause SQL IN". Cette question fait référence au T-SQL natif, l'autre question se réfère au C #.
Slogmeister Extraordinaire

Réponses:

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
la source
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
la source
T-SQL dit[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Cees Timmerman
1
Corrigé pour vous @Paul
Stefan Z Camilleri
5
Pouvez-vous simplement utiliser (VALUES (1),(2),(3),(4),(5))directement?
toddmo
C'était la meilleure solution pour mes besoins. J'avais besoin d'une variable comme liste d'identifiants que j'obtenais d'un Select afin que les valeurs ne soient pas prédéterminées. Cela a accompli exactement ce dont j'avais besoin. Merci!
Lexi847942
12

Il existe deux façons de traiter les listes csv dynamiques pour les requêtes TSQL:

1) En utilisant une sélection intérieure

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

2) Utilisation de TSQL concaténé dynamiquement

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) Une troisième option possible est les variables de table. Si vous disposez de SQl Server 2005, vous pouvez utiliser une variable de table. Si vous êtes sur Sql Server 2008, vous pouvez même passer des variables de table entières en tant que paramètre aux procédures stockées et l'utiliser dans une jointure ou comme sous-sélection dans la clause 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)
houx
la source
5
@ badbod99 - C'est une généralisation et toutes les généralisations sont fausses :) J'ai proposé des alternatives
hollystyles
1
@Vilx - voulez-vous dire pour définir la variable @list? si c'est le cas, set est correct mais ne définit qu'une seule variable, avec select, vous pouvez remplir plusieurs variables dans une instruction. Comme il n'y a pas grand-chose entre eux, j'ai l'habitude de toujours utiliser SELECT.
hollystyles
1
Vrai ... très général. Votre alternative est meilleure. Je veux vraiment dire que la génération de SQL à partir d'un script SQL entraîne généralement un code non maintenable, un risque d'attaques par injection et une série d'autres méchancetés.
badbod99
9

Utilisez une fonction comme celle-ci:

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;

Au lieu d'utiliser like, vous créez une jointure interne avec la table retournée par la fonction:

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

devient

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

Dans une table d'enregistrement 1M non indexée, la deuxième version a pris environ la moitié du temps ...

à votre santé

allaphore
la source
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)

Veuillez noter que pour les systèmes de longue liste ou de production, il n'est pas recommandé d'utiliser cette méthode car elle peut être beaucoup plus lente qu'un INopérateur simple comme someColumnName in (1,2,3,4)(testé avec une liste de 8000+ éléments)

Vova
la source
4

Non, ce type n'existe pas. Mais il y a quelques choix:

  • Requêtes générées dynamiquement (sp_executesql)
  • Tables temporaires
  • Variables de type table (chose la plus proche d'une liste)
  • Créez une chaîne XML puis convertissez-la en table avec les fonctions XML (vraiment gênant et détourné, sauf si vous avez un XML pour commencer)

Aucun de ceux-ci n'est vraiment élégant, mais c'est le meilleur qui soit.

Vilx-
la source
4

légère amélioration sur @LukeH, il n'est pas nécessaire de répéter le "INSERT INTO": et la réponse de @ realPT - pas besoin d'avoir le SELECT:

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

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

la source
4

Je sais que c'est vieux maintenant mais TSQL => 2016, vous pouvez utiliser 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
la source
4

À partir de SQL2017, vous pouvez utiliser STRING_SPLIT et faire ceci:

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

Si vous voulez faire cela sans utiliser une deuxième table, vous pouvez faire une comparaison LIKE avec un CAST:

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

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

Si le champ que vous comparez est déjà une chaîne, vous n'aurez pas besoin de CAST.

Entourer à la fois la correspondance de colonne et chaque valeur unique entre des virgules garantira une correspondance exacte. Sinon, une valeur de 1 serait trouvée dans une liste contenant ', 4,2,15,'

Michael Reyes
la source
1

Comme personne ne l'a mentionné auparavant, à partir de Sql Server 2016, vous pouvez également utiliser des tableaux json et 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)

Vous pouvez le tester dans sql fiddle demo

Vous pouvez également couvrir des cas plus complexes avec json plus facilement - voir Rechercher une liste de valeurs et une plage en SQL à l'aide de la clause WHERE IN avec une variable SQL?

Roman Pekar
la source
1

Celui-ci utilise PATINDEX pour faire correspondre les identifiants d'une table à une liste d'entiers non délimités par des chiffres.

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

Remarques:

  • lors de la conversion en varchar et sans spécifier la taille d'octet entre parenthèses, la longueur par défaut est de 30
  • % (caractère générique) correspondra à toute chaîne de zéro caractère ou plus
  • ^ (caractère générique) ne doit pas correspondre
  • [^ 0-9] correspondra à n'importe quel caractère non numérique
  • PATINDEX est une fonction standard SQL qui renvoie la position d'un motif dans une chaîne
Marne
la source
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
la source
5
ce sera une meilleure réponse si vous décrivez votre code là-bas!
Deep Kakkar
0

Je pense que vous devrez déclarer une chaîne, puis exécuter cette chaîne SQL.

Jetez un œil à sp_executeSQL

BIDeveloper
la source