2008-10-23 6 views
3

Je travaille sur une procédure stockée avec plusieurs paramètres facultatifs. Certains de ces paramètres sont des valeurs uniques et il est assez facile d'utiliser une clause WHERE comme:Utilisation des paramètres de procédure stockée en option

WHERE (@parameter IS NULL OR column = @parameter) 

Cependant, dans certains cas, la condition WHERE est plus compliquée:

WHERE (@NewGroupId IS NULL OR si.SiteId IN (SELECT gs.SiteId 
FROM [UtilityWeb].[dbo].[GroupSites] AS gs 
WHERE gs.GroupId = @NewGroupId)) 

Quand je décommentez ces complexes WHERE clauses, le temps d'exécution de la requête double et le plan d'exécution devient remarquablement plus compliqué. Bien que le plan d'exécution ne me dérange pas, doubler le temps d'exécution d'une requête est un problème certain.

Existe-t-il une meilleure pratique ou un modèle que d'autres ont trouvé pour travailler avec des paramètres facultatifs dans leurs procédures stockées?

Est-ce une de ces instances où SQL dynamique serait une meilleure solution?

+0

Le plan d'exécution devrait vous déranger, car il vous indique exactement ce que l'exécution de la requête est en train de faire. –

+0

La complexité ne me dérange pas tout à fait parce que j'ai des requêtes très complexes qui s'exécutent rapidement. Celui-ci ne le fait malheureusement pas. –

+0

Voir aussi: http://stackoverflow.com/questions/532468/ignoring-a-null-parameter-in-t-sql/532510#532510 –

Répondre

4

Le problème principal est susceptible d'être parameter sniffing, et des plans d'exécution optimale très différents en fonction de vos paramètres sont NULL. Essayez d'exécuter le proc stocké avec RECOMPILE.

Contrairement à certaines croyances, Sql Server doesdo des évaluations de court-circuit - bien que (comme avec toutes les optimisations de la requête), il peut ne pas être exactement ce que vous vouliez.

BTW - je ne serais probablement réécrire cette partie de la requête comme une table dérivée JOINTES:

SELECT * 
FROM Table as si 
JOIN (
    SELECT SiteId 
    FROM [UtilityWeb].[dbo].[GroupSites] 
    WHERE GroupId = ISNULL(@NewGroupId, GroupId) 
    /* --Or, if all SiteIds aren't in GroupSites, or GroupSites is unusually large 
    --this might work better 
    SELECT @newGroupId 
    UNION ALL 
    SELECT SiteId FROM [UtilityWeb].[dbo].[GroupSites] 
    WHERE GroupId = @NewGroupId 
    */ 
) as gs ON 
    si.SiteId = gs.SiteId 

Il peut ou ne peut pas influer sur le plan de requête, mais il est un peu plus propre pour moi.

5

Je créerais des requêtes séparées pour que le paramètre soit disponible ou non.

Cela va créer un SQL plus simple, et l'optimiseur fera un meilleur travail.

Comme ceci:

if (@parameter IS NULL) then begin 
    select * from foo 
end 
else begin 
    select * from foo where value = @parameter 
end 

En vous de nombreux paramètres à redessiner comme celui-ci, et vous allez pour la solution SQL dynamique, puis utilisez également toujours les paramètres, vous risquez d'être mordu par le bogue SQL-Injection.

Une combinaison est également possible. Les requêtes/requêtes les plus probables sont codées en entier et précompilées. Toutes les autres combinaisons sont créées dynamiquement.

+0

C'est probablement la meilleure approche, oui, plutôt que le SQL dynamique ou en utilisant le Fonction ISNULL, car ISNULL ne considère pas les chaînes de longueur nulle comme nulles. Ceci est particulièrement un problème en fonction de la façon dont votre procédure est appelée (à partir de quelle application, etc.). En outre, l'exécution est susceptible d'être plus rapide, en utilisant une structure if. Juste à noter: la structure if ci-dessus devrait également tester la longueur de données (@parameter)> 0, afin d'éviter les problèmes de chaîne de longueur nulle. –

2

SQL dynamique est probablement une meilleure solution dans ce cas, en particulier si la procédure stockée n'emballe que cette requête. Une chose à garder à l'esprit est que SQL Server ne court-circuite pas les expressions booléennes à l'intérieur d'une seule requête. Dans beaucoup de langues, "(a) || (b)" ne fera pas évaluer b si a est vrai. De même, "(a) & & (b)" ne fera pas évaluer b si a est faux. Dans SQL Server, ce n'est pas le cas. Ainsi, dans l'exemple que vous donnez, la requête à l'extrémité "ou" sera évaluée même si @NewGroupId n'est pas null.

+0

Sql Server effectue une évaluation de court-circuit. –

+0

Je suis tout à fait sûr qu'il n'a pas à un moment donné dans le passé - au moins pour les déclarations "si" - mais je concède que je n'ai pas testé cela depuis probablement SQL Server 7. –

2

Pour un petit nombre de paramètres optionnels, le choix conditionnel d'une des requêtes statiques suggérées par GvS est OK. Cependant, cela devient compliqué s'il y a plusieurs paramètres, puisque vous devez gérer toutes les permutations - avec 5 paramètres c'est 32 requêtes statiques! En utilisant le SQL dynamique, vous pouvez construire la requête exacte qui correspond le mieux aux paramètres donnés. Assurez-vous d'utiliser des variables de liaison bien!

+0

Je viens d'ajouter cette à ma réponse aussi +1 – GvS

4

instructions CASE sont vos amis ...

Plutôt que:

if (@parameter IS NULL) then begin 
    select * from foo 
end 
else begin 
    select * from foo where value = @parameter 
end 

Vous pouvez utiliser:

SELECT * FROM foo 
WHERE value = CASE WHEN @parameter IS NULL THEN value ELSE @parameter END 

Ou

SELECT * FROM foo 
WHERE value = ISNULL(@parameter,value) 

J'ont tendance à utiliser CASE déclarations plus parce que mon optiona l paramètres peuvent utiliser certaines valeurs au lieu de NULL ...

+0

Je voudrais fortement mettre en garde contre cette approche. Si vous voulez extraire tous les résultats lorsque votre paramètre est nul, mais que votre champ est nullable, alors vous aurez une situation "WHERE NULL = NULL" et ceux-ci seront toujours faux, ainsi vous filtrerez les résultats où les valeurs des champs contient null. Mordez la balle et utilisez un SQL dynamique paramétré. – MikeTeeVee

+0

@MikeTeeVee, bon point. Cependant, nos filtres ne sont pas utilisés sur les champs à valeurs nulles, donc nous ne nous en préoccupons pas. –

1

À mon humble avis, le problème de reniflage de paramètre peut être résolu en copiant tous les paramètres dans des variables; évitez ensuite d'utiliser les paramètres directement à tout prix, utilisez plutôt les variables. Exemple:


create proc ManyParams 
(
    @pcol1 int, 
    @pcol2 int, 
    @pcol3 int 
) 
as 
declare 
    @col1 int, 
    @col2 int, 
    @col3 int 

select 
    @col1 = @pcol1, 
    @col2 = @pcol2, 
    @col3 = @pcol3 

select 
    col1, 
    col2, 
    col3 
from 
    tbl 
where 
    1 = case when @col1 is null then 1 else case when col1 = @col1 then 1 else 0 end end 
and 1 = case when @col2 is null then 1 else case when col2 = @col2 then 1 else 0 end end 
and 1 = case when @col3 is null then 1 else case when col3 = @col3 then 1 else 0 end end 
+0

Oui, ma clause WHERE peut certainement être simplifiée comme: \t col1 = isnull (@ col1, col1) et \t col2 = isnull (@ col2, col2) et \t col3 = isnull (@ col3, col3) –

+0

Non , le moyen le plus sûr est: (@ col1 est nul ou col1 = @ col1) et (@ col2 est nul ou col2 = @ col2) et (@ col3 est nul ou col3 = @ col3) De 3415582/how-can-i- use-optional-parameters-in-at-sql-stocké procédure. –

Questions connexes