Procédure stockée T-SQL qui accepte plusieurs valeurs d'ID

145

Existe-t-il un moyen élégant de gérer le passage d'une liste d'ID en tant que paramètre à une procédure stockée?

Par exemple, je veux que les départements 1, 2, 5, 7, 20 soient renvoyés par ma procédure stockée. Dans le passé, j'ai passé une liste d'ID délimités par des virgules, comme le code ci-dessous, mais je me sens vraiment sale de le faire.

Je pense que SQL Server 2005 est ma seule limitation applicable.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)
JasonS
la source
Voici une variante de la méthode XML que je viens de trouver.
JasonS
5
Si vous utilisez SQL Server 2008, vous pouvez utiliser un paramètre table. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Réponses:

237

Erland Sommarskog a maintenu la réponse faisant autorité à cette question pendant les 16 dernières années: tableaux et listes dans SQL Server .

Il existe au moins une douzaine de façons de transmettre un tableau ou une liste à une requête; chacun a ses propres avantages et inconvénients.

  • Paramètres de table . SQL Server 2008 et versions ultérieures uniquement, et probablement la plus proche d'une «meilleure» approche universelle.
  • La méthode itérative . Passez une chaîne délimitée et parcourez-la en boucle.
  • Utilisation du CLR . SQL Server 2005 et supérieur à partir des langages .NET uniquement.
  • XML . Très bon pour insérer de nombreuses lignes; peut être excessif pour les SELECT.
  • Table des nombres . Performances / complexité supérieures à la méthode itérative simple.
  • Éléments de longueur fixe . La longueur fixe améliore la vitesse sur la chaîne délimitée
  • Fonction des nombres . Variations de la table des nombres et de longueur fixe où le nombre est généré dans une fonction plutôt que tiré d'un tableau.
  • Expression de table commune récursive (CTE). SQL Server 2005 et supérieur, toujours pas trop complexe et plus performant que la méthode itérative.
  • SQL dynamique . Peut être lent et a des implications sur la sécurité.
  • Passer la liste en tant que paramètres . Fastidieux et sujet aux erreurs, mais simple.
  • Méthodes vraiment lentes . Méthodes qui utilisent charindex, patindex ou LIKE.

Je ne saurais vraiment recommander assez de lire l'article pour en savoir plus sur les compromis entre toutes ces options.

Portman
la source
11

Oui, votre solution actuelle est sujette aux attaques par injection SQL.

La meilleure solution que j'ai trouvée est d'utiliser une fonction qui divise le texte en mots (il y en a quelques-uns postés ici, ou vous pouvez utiliser celle-ci de mon blog ), puis de la joindre à votre table. Quelque chose comme:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Matt Hamilton
la source
14
Je ne suis pas sûr qu'il soit "sujet aux attaques par injection SQL" à moins que le proc stocké ne puisse être appelé directement à partir de clients non approuvés, auquel cas vous avez de plus gros problèmes. Le code de la couche de service doit générer la chaîne @DepartmentIds à partir de données fortement typées (par exemple int [] departmentIds), auquel cas tout ira bien.
Anthony
Solution géniale, @Matt Hamilton. Je ne sais pas si cela aidera quelqu'un, mais j'ai obtenu des résultats plus précis sur SQL Server 2008r lorsque je recherchais des champs de texte en utilisant "join dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki
3

Une méthode que vous voudrez peut-être envisager si vous allez beaucoup travailler avec les valeurs consiste à les écrire d'abord dans une table temporaire. Ensuite, vous rejoignez simplement comme d'habitude.

De cette façon, vous ne analysez qu'une seule fois.

Il est plus facile d'utiliser l'un des UDF `` fractionnés '', mais tellement de gens en ont publié des exemples, j'ai pensé que je choisirais une voie différente;)

Cet exemple créera une table temporaire que vous pourrez rejoindre (#tmpDept) et la remplira avec les identifiants de service que vous avez transmis. Je suppose que vous les séparez par des virgules, mais vous pouvez - bien sûr - changer à ce que vous voulez.

IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
    DROP TABLE #tmpDept
END

SET @DepartmentIDs=REPLACE(@DepartmentIDs,' ','')

CREATE TABLE #tmpDept (DeptID INT)
DECLARE @DeptID INT
IF IsNumeric(@DepartmentIDs)=1
BEGIN
    SET @DeptID=@DepartmentIDs
    INSERT INTO #tmpDept (DeptID) SELECT @DeptID
END
ELSE
BEGIN
        WHILE CHARINDEX(',',@DepartmentIDs)>0
        BEGIN
            SET @DeptID=LEFT(@DepartmentIDs,CHARINDEX(',',@DepartmentIDs)-1)
            SET @DepartmentIDs=RIGHT(@DepartmentIDs,LEN(@DepartmentIDs)-CHARINDEX(',',@DepartmentIDs))
            INSERT INTO #tmpDept (DeptID) SELECT @DeptID
        END
END

Cela vous permettra de passer un identifiant de service, plusieurs identifiants avec des virgules entre eux, ou même plusieurs identifiants avec des virgules et des espaces entre eux.

Donc, si vous avez fait quelque chose comme:

SELECT Dept.Name 
FROM Departments 
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name

Vous verriez les noms de tous les ID de service que vous avez transmis ...

Encore une fois, cela peut être simplifié en utilisant une fonction pour peupler la table temporaire ... Je l'ai principalement fait sans une juste pour tuer l'ennui :-P

- Kevin Fairchild

Kevin Fairchild
la source
3

Vous pouvez utiliser XML.

Par exemple

declare @xmlstring as  varchar(100) 
set @xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>' 

declare @docid int 

exec sp_xml_preparedocument @docid output, @xmlstring

select  [id],parentid,nodetype,localname,[text]
from    openxml(@docid, '/args', 1) 

La commande sp_xml_preparedocument est intégrée.

Cela produirait la sortie:

id  parentid    nodetype    localname   text
0   NULL        1           args        NULL
2   0           1           arg         NULL
3   2           2           value       NULL
5   3           3           #text       42
4   0           1           arg2        NULL
6   4           3           #text       -1

qui a tout (plus?) de ce dont vous avez besoin.

Non tranché
la source
2

Une méthode XML ultra-rapide, si vous souhaitez utiliser une procédure stockée et transmettre la liste des ID de service séparés par des virgules:

Declare @XMLList xml
SET @XMLList=cast('<i>'+replace(@DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i))

Tout le mérite revient au blog de Guru Brad Schulz

Nishant
la source
-3

Essaye celui-là:

@list_of_params varchar(20) -- value 1, 2, 5, 7, 20 

SELECT d.[Name]
FROM Department d
where @list_of_params like ('%'+ CONVERT(VARCHAR(10),d.Id)  +'%')

très simple.

user1006743
la source
1
très simple - et très faux. Mais même si vous résolviez le problème dans votre code, ce serait très lent. Voir le lien «Méthodes vraiment lentes» dans la réponse acceptée pour plus de détails.
Sebastian Meine