Variable SQL contenant une liste d'entiers

175

J'essaie de déboguer les rapports SQL de quelqu'un d'autre et j'ai placé la requête de rapports sous-jacente dans une fenêtre de requête de SQL 2012.

L'un des paramètres demandés par le rapport est une liste d'entiers. Ceci est réalisé sur le rapport via une liste déroulante à sélection multiple. La requête sous-jacente du rapport utilise cette liste d'entiers dans la whereclause eg

select *
from TabA
where TabA.ID in (@listOfIDs)

Je ne veux pas modifier la requête que je débogue mais je ne peux pas comprendre comment créer une variable sur le serveur SQL qui peut contenir ce type de données pour le tester.

par exemple

declare @listOfIDs int
set listOfIDs  = 1,2,3,4

Aucun type de données ne peut contenir une liste d'entiers. Comment puis-je exécuter la requête de rapport sur mon serveur SQL avec les mêmes valeurs que le rapport?

ErickTreetops
la source
1
Je sais que j'ai utilisé le paramètre de valeur de table TVP pour insérer des données, mais je sais maintenant s'il peut être utilisé dans un where. Suite?
paparazzo
2
question bien formulée. +1
RayLoveless

Réponses:

224

Variable de table

declare @listOfIDs table (id int);
insert @listOfIDs(id) values(1),(2),(3);    

select *
from TabA
where TabA.ID in (select id from @listOfIDs)

ou

declare @listOfIDs varchar(1000);
SET @listOfIDs = ',1,2,3,'; --in this solution need put coma on begin and end

select *
from TabA
where charindex(',' + CAST(TabA.ID as nvarchar(20)) + ',', @listOfIDs) > 0
slavoo
la source
2
Merci pour cela, mais encore une fois, il me faut réécrire la façon dont la variable est lue dans la requête. Je dois garder la même chose.
ErickTreetops
3
Que faire si vous ne savez pas quels sont les identifiants et que cela provient d'une requête? Exemple: SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234)Cette requête renverra plusieurs ID et j'obtiens une erreur indiquant que la sous-requête a renvoyé plus d'un résultat et que ce n'est pas autorisé. Est-il possible de créer une variable qui stockera un tableau si les ID d'une sous-requête?
Rafael Moreira
1
J'ai essayé la deuxième option et cela fonctionne pour moins d'enregistrements. Lorsque j'augmente le nombre d'identifiants, j'obtiens une erreur TimeOut. Peut-être que le casting nuit à la performance.
Utilisateur M
Ce n'est pas la réponse à la question initiale, mais c'est la réponse à une que je n'ai pas posée, donc je vais bien. Je passe une List <int> comme paramètre, mais je veux créer une variable locale pour tester dans SSMS. C'est tueur.
Wade Hatler
Excellente réponse, m'a fait gagner beaucoup de temps
Alfredo A.
35

En supposant que la variable s'apparente à:

CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)

Et la procédure stockée l'utilise sous cette forme:

ALTER Procedure [dbo].[GetFooByIds]
    @Ids [IntList] ReadOnly
As 

Vous pouvez créer l'IntList et appeler la procédure comme ceci:

Declare @IDs IntList;
Insert Into @IDs Select Id From dbo.{TableThatHasIds}
Where Id In (111, 222, 333, 444)
Exec [dbo].[GetFooByIds] @IDs

Ou si vous fournissez vous-même l'IntList

DECLARE @listOfIDs dbo.IntList
INSERT INTO @listofIDs VALUES (1),(35),(118);
William Mueller
la source
17

Vous avez raison, il n'y a pas de type de données dans SQL-Server qui puisse contenir une liste d'entiers. Mais ce que vous pouvez faire, c'est stocker une liste d'entiers sous forme de chaîne.

DECLARE @listOfIDs varchar(8000);
SET @listOfIDs = '1,2,3,4';

Vous pouvez ensuite diviser la chaîne en valeurs entières séparées et les mettre dans une table. Votre procédure peut déjà le faire.

Vous pouvez également utiliser une requête dynamique pour obtenir le même résultat:

DECLARE @SQL nvarchar(8000);

SET @SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + @listOfIDs + ')';
EXECUTE (@SQL);
Möoz
la source
Merci, mais encore une fois aurait besoin de modifier une requête que je ne suis pas autorisé à faire.
ErickTreetops
2
Si quelqu'un l'utilise, sachez que cela peut être très vulnérable à l'injection SQL si @listOfIDs est un paramètre de chaîne fourni par un utilisateur. En fonction de l'architecture de votre application, cela peut ou non être un problème.
Rogala
@Rogala D'accord, les utilisateurs devront faire leur propre assainissement selon les besoins.
Möoz
1
@ Möoz Je recommande d'ajouter une note à votre réponse pour refléter cela. Tout le monde ne le sait pas et ils copient et collent simplement des solutions sans penser aux conséquences. Le SQL dynamique est TRÈS dangereux, et je l'évite comme ce fléau.
Rogala
@ Möoz De plus, je n'ai pas voté pour votre réponse, mais que pouvez-vous prendre quelques minutes et vérifier ma réponse? La fonction STRING_SPLIT est assez sympa, et je pense que vous en serez TOUT!
Rogala
6

Pour SQL Server 2016+ et Azure SQL Database, la fonction STRING_SPLIT a été ajoutée, ce qui serait une solution parfaite à ce problème. Voici la documentation: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Voici un exemple:

/*List of ids in a comma delimited string
  Note: the ') WAITFOR DELAY ''00:00:02''' is a way to verify that your script 
        doesn't allow for SQL injection*/
DECLARE @listOfIds VARCHAR(MAX) = '1,3,a,10.1,) WAITFOR DELAY ''00:00:02''';

--Make sure the temp table was dropped before trying to create it
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;

--Create example reference table
CREATE TABLE #MyTable
([Id] INT NOT NULL);

--Populate the reference table
DECLARE @i INT = 1;
WHILE(@i <= 10)
BEGIN
    INSERT INTO #MyTable
    SELECT @i;

    SET @i = @i + 1;
END

/*Find all the values
  Note: I silently ignore the values that are not integers*/
SELECT t.[Id]
FROM #MyTable as t
    INNER JOIN 
        (SELECT value as [Id] 
        FROM STRING_SPLIT(@listOfIds, ',')
        WHERE ISNUMERIC(value) = 1 /*Make sure it is numeric*/
            AND ROUND(value,0) = value /*Make sure it is an integer*/) as ids
    ON t.[Id] = ids.[Id];

--Clean-up
DROP TABLE #MyTable;

Le résultat de la requête est 1,3

~ Acclamations

Rogala
la source
4

En fin de compte, je suis arrivé à la conclusion que sans modifier le fonctionnement de la requête, je ne pouvais pas stocker les valeurs dans des variables. J'ai utilisé le profileur SQL pour attraper les valeurs, puis les ai codées en dur dans la requête pour voir comment cela fonctionnait. Il y avait 18 de ces tableaux d'entiers et certains contenaient plus de 30 éléments.

Je pense qu'il est nécessaire que MS / SQL introduise des types de données supplémentaires dans le langage. Les tableaux sont assez courants et je ne vois pas pourquoi vous ne pouvez pas les utiliser dans un processus stocké.

ErickTreetops
la source
6
SQL Server n'a pas besoin de tableaux, lorsqu'il a des paramètres et des variables table.
John Saunders
1
Donc, ce que nous savons, c'est que la requête utilise une liste d'entiers (qui lui sont passés par un tableau?). Ce que je ne comprends pas, c'est comment votre requête les utilisait sans utiliser l'une des méthodes données dans les réponses. Fournissez un peu plus de contexte et nous pouvons vous aider davantage.
Möoz
2

Vous ne pouvez pas le faire comme ça, mais vous pouvez exécuter la requête entière en la stockant dans une variable.

Par exemple:

DECLARE @listOfIDs NVARCHAR(MAX) = 
    '1,2,3'

DECLARE @query NVARCHAR(MAX) = 
    'Select *
     From TabA
     Where TabA.ID in (' + @listOfIDs + ')'

Exec (@query)
thepirat000
la source
2
Comme indiqué dans un commentaire précédent, selon la façon dont vous implémentez ce type de solution, sachez que cela peut être vulnérable à l'injection SQL si @listOfIDs est un paramètre fourni par un utilisateur.
Rogala
2

Il existe une nouvelle fonction dans SQL appelée string_splitsi vous utilisez une liste de chaînes. Lien de référence STRING_SPLIT (Transact-SQL)

DECLARE @tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(@tags, ',')
WHERE RTRIM(value) <> '';

vous pouvez transmettre cette requête incomme suit:

SELECT *
  FROM [dbo].[yourTable]
  WHERE (strval IN (SELECT value FROM STRING_SPLIT(@tags, ',') WHERE RTRIM(value) <> ''))
Ravi Anand
la source
1
Cela semble plausible, mais seulement à partir de SQL Server 2016, malheureusement.
AnotherFineMess
0

J'utilise ceci:

1-Déclarez une variable de table temporaire dans le script de votre bâtiment:

DECLARE @ShiftPeriodList TABLE(id INT NOT NULL);

2-Allouer à la table temporaire:

IF (SOME CONDITION) 
BEGIN 
        INSERT INTO @ShiftPeriodList SELECT ShiftId FROM [hr].[tbl_WorkShift]
END
IF (SOME CONDITION2)
BEGIN
    INSERT INTO @ShiftPeriodList
        SELECT  ws.ShiftId
        FROM [hr].[tbl_WorkShift] ws
        WHERE ws.WorkShift = 'Weekend(VSD)' OR ws.WorkShift = 'Weekend(SDL)'

END

3-Référencez la table lorsque vous en avez besoin dans une instruction WHERE:

INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM @ShiftPeriodList)
Max Alexander Hanna
la source