2010-01-29 11 views
5

Je me demande s'il existe un moyen judicieux de réécrire la requête suivante afin que les index sur les colonnes soient utilisés par l'optimiseur?Comment optimiser l'utilisation de la clause "OU" lorsqu'elle est utilisée avec des paramètres (SQL Server 2008)

Create Procedure select_Proc1 
    @Key1 int=0, 
    @Key2 int=0 
As 
BEGIN 
    Select key3 
    From Or_Table 
    Where (@key1 =0 OR Key1 [email protected]) AND 
      (@key2 =0 OR Key2 [email protected]) 
END 
GO 

Même si des colonnes dans les clauses WHERE sont couvertes par des index, SQL Server ne peut pas utiliser ces indices. Cela soulève la question de savoir si quelque chose «bloque» l'utilisation des index. La réponse à cette question est oui - les coupables sont les paramètres et la condition "OU". Les paramètres ne sont pas couverts par des index, ce qui signifie que SQL Server ne peut utiliser aucun des index pour évaluer "@ key1 = 0" (condition qui s'applique également à @ key2 = 0). Effectivement, cela signifie que SQL Server ne peut pas utiliser les index pour évaluer la clause "@ key1 = 0 OU Key1 = @ key1" (comme la clause "OR" est l'union des lignes couvertes par les deux conditions). Le même principe s'applique également à l'autre clause (note 2). Cela conduit SQL Server à conclure qu'aucun index ne peut être utilisé pour extraire les lignes, laissant SQL Server utiliser la meilleure approche suivante: un index indexé

Comme vous le voyez, l'optimiseur SQL n'utilisera pas d'index sur les colonnes si les prédicats sont "OU" dans la clause WHERE. Une solution à ce problème consiste à séparer les requêtes avec la clause IF pour toutes les combinaisons possibles de paramètres.

S'il vous plaît lire cet article court pour obtenir une meilleure vue du problème: http://www.sql-server-performance.com/articles/per/optimize_or_clause_p1.aspx

Maintenant, ma question est, que devons-nous faire si les combinaisons possibles sont plus que seulement trois ou quatre? Ecrire une requête séparée pour chaque combinaison ne semble pas une solution rationnelle. Y a-t-il une autre solution de contournement pour ce problème?

Répondre

11

SQL Server n'est pas très bon dans l'optimisation des prédicats OR.

Utilisez ceci:

SELECT key3 
FROM or_table 
WHERE @key1 = 0 
     AND @key2 = 0 
UNION ALL 
SELECT key3 
FROM or_table 
WHERE @key1 = 0 
     AND @key2 <> 0 
     AND key2 = @key2 
UNION ALL 
SELECT key3 
FROM or_table 
WHERE @key2 = 0 
     AND @key1 <> 0 
     AND key1 = @key1 
UNION ALL 
SELECT key3 
FROM or_table 
WHERE @key1 <> 0 
     AND @key2 <> 0 
     AND key1 = @key1 
     AND key2 = @key2 

SQL Server se tourneront vers les valeurs des variables avant d'exécuter les requêtes et optimisera les requêtes redondantes sur.

Cela signifie qu'une seule requête de quatre sera réellement exécutée.

+0

Approche intéressante, je vais devoir rappeler que. Je suppose que cela s'appliquerait aussi à d'autres SGBDR comme Oracle, où 'OR' est terriblement inefficace. – Lucero

+1

Est-ce le seul moyen? Que faire si la requête est plus compliquée (plus de clauses OR impliquées) – Meysam

3

MSSQL 2008 a la syntaxe d'optimisation de la simplification de la condition, ici il est

Where (@key1 =0 OR Key1 [email protected]) AND 
     (@key2 =0 OR Key2 [email protected]) option(recompile) 

Cela permettra d'optimiser l'utilisation des constantes

+0

Je ne pense pas que cela aide. Cela ne fonctionnerait que lorsque nous avons affaire à un problème de reniflage de paramètres. – Meysam

2

Avez-vous une tente fonction table?

CREATE FUNCTION select_func1 ( 
    @Key1 int=0, 
    @Key2 int=0 
) 
RETURNS TABLE 
AS RETURN (
    Select key3 
    From Or_Table 
    Where (@key1 =0 OR Key1 [email protected]) AND 
      (@key2 =0 OR Key2 [email protected]) 
) 


select * from select_func1(1,2) 
+0

OMG! Comme je peux le voir dans le plan d'exécution, les index sont utilisés lors de l'utilisation de la fonction table! Quelle est la différence?! Pourrais-tu l'expliquer ?! – Meysam

+0

Une fonction de valeur table est évaluée chaque fois qu'elle est utilisée (comme une vue). Cela signifie que l'optimiseur sera en mesure d'évaluer les paramètres avant de créer le plan de requête. Vous pouvez voir les fonctions de table valorisées comme des macros de texte avancées dans la requête externe. – adrianm

+0

Oups, Il y a toujours un problème avec votre approche. Si vous remplacez les paramètres constants dans l'appel de fonction par des paramètres variables, les index ne sont pas utilisés. quelque chose comme ceci: select * from select_func1 (@ key1, @ key2) – Meysam

0

Oui - utilisation prudente de sql dynamique résoudra ce problème. Il y a deux façons de le faire:

a. Si vous êtes un "puriste" à propos des procs stockés, alors composez une chaîne de requête personnalisée à l'intérieur d'un proc stocké et exécutez la chaîne. La requête spécifique peut ensuite être écrite dynamiquement par exécution pour inclure uniquement les critères pertinents.

b. Si vous êtes flexible quant à l'emplacement de ce SQL, vous pouvez (encore une fois SOIGNEUSEMENT) composer la chaîne de requête dans votre application et la transmettre au serveur.

Le danger, bien sûr, est autour de l'injection SQL. Vous devez donc faire très attention à la façon dont les données sont transmises du client dans l'instruction SQL dynamique.

Des informations complètes de Erland Sommarskog: http://www.sommarskog.se/dynamic_sql.html et http://www.sommarskog.se/dyn-search.html