2009-11-06 6 views
3

Je travaille actuellement sur un cas d'utilisation particulièrement complexe. Simplification ci-dessous :)Performance des sous-requêtes Sql fonctions

Tout d'abord, un enregistrement client a une relation plusieurs-à-un avec une collection de services, c'est-à-dire qu'un seul client peut avoir plusieurs services associés.

Dans mon déclencheur, j'écris une requête qui renvoie l'identifiant d'un client en fonction de certains critères. Les critères sont les suivants,

  1. Si au moins un service est de type B, et aucun service de type A existent, id retour
  2. Si au moins un service est de type C, et aucun service de type B ou a existent, id retour
  3. Si au moins un service est de type D, et aucun service de type C ou B ou a existent, id retour

et mon approche actuelle est de former une requête similaire à celui ci-dessous

SELECT c.ClientId 
FROM 
    Clients AS c 
    -- actually INNER JOIN is superfluous in this sample, but required for 
    -- other auxilliary criteria i have left out. illustrates relationship 
    -- between Clients and Services table 
    INNER JOIN Services AS s ON c.ClientId = s.ClientId 
WHERE 
-- has at least one service of type B, no A 
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) OR 

-- has at least one service of type C, no B, no A 
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) OR 

-- has at least one service of type D, no C, no B, no A 
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'D')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) 

[dbo].[Get_ServicesByClientIdAndType] est une fonction qui renvoie des services associés pour l'ID client et le type de service spécifiés. Similaire à

-- this query is actually significantly more complex than shown 
-- below, but this illustrates use of parameters client id and 
-- service type 
SELECT s.ServiceType 
FROM 
    Services AS s 
WHERE 
    s.ClientId = @clientId AND 
    s.ServiceType = @serviceType 

supposer que ce soit un moyen optimal d'exprimer ce cas d'utilisation, fonctionneraient [dbo].[Get_ServicesByClientIdAndType] sous-requête mise en cache ou ne changeant le paramètre de service nécessite une nouvelle évaluation chaque appel? [J'invoque cette chose comme 9 fois !!! Sql Server 2005]

Je sais que Sql Server 2005 prend en charge certaines optimisations de sous-requêtes, comme les résultats de mise en cache, mais je ne sais pas avec certitude dans quelles circonstances ou comment former mes sous-requêtes [ou fonctions] telles que Je profite au maximum des capacités de Sql Server.


EDIT: examiné mes critères ci-dessus, et ne pouvait pas laisser aller d'une chose lancinante sentiment était éteint. J'ai joué avec une certaine logique dans ma tête, et est venu avec cette [plus simple] formulation

SELECT c.ClientId 
FROM 
    Clients AS c 
    INNER JOIN Services AS s ON c.ClientId = s.ClientId 
WHERE 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')) AND 
    (EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) OR 
    EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) OR 
    EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'D'))) 

essentiellement, il existe pas de scénario impliquant B qui conduirait à un rejet, de même pour C et D, de sorte que toute configuration est acceptable. nous nous soucions seulement que A n'est présent dans aucune sélection. Arg! Charlie Brown!


laissant les deux expressions pour examen, et je reste très bien apprécier les réponses concernant les performances utilisateur WRT SQL Server fonctions définies.

Répondre

3

je en train d'écrire une réponse à votre question et en attendant, vous avez changé vos besoins, mais vous ne devriez pas avoir de problèmes pour convertir ma solution à vos besoins spécifiques ..

Mais permettez-moi de commencer par le début. Je suis assez sûr que SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A') n'est pas mis en cache de toute façon par le serveur. Ce n'est pas si intelligent;) Donc, il est calculé plusieurs fois dans votre requête principale.

Votre première optimisation devrait donc aller dans cette direction. Vous devez réduire le nombre de fois où Get_ServicesByClientIdAndType est appelée. Vous pouvez le faire de plusieurs façons. Mais la règle générale est que vous devez calculer tous les résultats possibles de cette fonction pour tous vos clients. Ces résultats devraient être mis dans une certaine table temporelle ou ils seront placés dans une table virtuelle qui est faite par SQL Server lui-même.

Lorsque vous avez obtenu tous les résultats possibles, il vous suffit de les joindre à la table de vos clients. Mais vous les rejoignez seulement UNE FOIS.

Bien sûr, beaucoup de choses et d'astuces d'optimisation dépendent de votre exemple réel. Dans l'exemple que vous avez donné, il n'est même pas nécessaire d'utiliser Get_ServicesByClientIdAndType. Pourquoi ne pas simplement joindre ces deux tables et effectuer quelques calculs sur eux?

Jetez un oeil à cette requête:

SELECT A.* FROM 
(
SELECT C.ClientID, 
    SUM(CASE(S.ServiceType) WHEN 'A' THEN 1 ELSE 0 END) AS ServiceA, 
    SUM(CASE(S.ServiceType) WHEN 'B' THEN 1 ELSE 0 END) AS ServiceB, 
    SUM(CASE(S.ServiceType) WHEN 'C' THEN 1 ELSE 0 END) AS ServiceC, 
    SUM(CASE(S.ServiceType) WHEN 'D' THEN 1 ELSE 0 END) AS ServiceD 
FROM Clients AS C 
INNER JOIN Services AS s ON c.ClientId = s.ClientId 
GROUP BY C.ClientID 
) A 
WHERE ((A.ServiceB > 0) AND (A.ServiceA = 0)) 
OR ((A.ServiceC > 0) AND (A.ServiceA = 0) AND (A.ServiceB = 0)) 
OR ((A.ServiceD > 0) AND (A.ServiceA = 0) AND (A.ServiceB = 0) AND (A.ServiceC = 0)) 

Dans la requête interne nous rejoignons les tables. Nous jetons la fonction puisque nous n'en avons pas besoin. Au lieu de cela, nous calculons le nombre de services différents pour chaque client. Ensuite, sur les résultats de la requête interne, nous implémentons vos conditions. Nous vérifions simplement la présence de services donnés dans un ensemble particulier.

Le résultat est comme ceci:

ClientID ServiceA ServiceB ServiceC ServiceD 
-------- -------- -------- -------- -------- 
26915  0  4  2  2 
26917  0  0  1  1 
26921  0  3  2  3 
26927  0  4  2  4 

Bien sûr, vous pouvez enlever le résultat final des colonnes de service. Je les ai inclus parce que je l'aime de cette façon ;-) Et cela permet de vérifier si la requête fonctionne correctement. Vous pouvez même écrire une requête qui ne calculera pas le nombre de type de service donné pour un client donné. Cela fonctionnera encore plus vite et vous donnera les bons résultats.

Aussi si vous avez vraiment besoin de votre fonction, pourquoi ne pas changer son implémentation de telle sorte que la fonction revienne et ID après la première jointure réussie? Cela vous fera gagner beaucoup de temps.

Mais que vous connaissez la plus grande image si j'écrit ici peut-être ;-) ordures

Quoi qu'il en soit, je l'espère, je vous ai aidé d'une certaine façon.

+0

Wow, très cool. Cela a du sens, en tabulant essentiellement les occurrences dans une seule sous-requête, puis en sélectionnant celles-ci. J'ai seulement reformulé mes critères dans une expression plus simple mais équivalente - le seul effet sur votre solution est une clause WHERE externe plus simple 'WHERE A.ServiceA = 0 ET (A.ServiceB> 0 OU A.ServiceC> 0 OU A.ServiceD> 0) ' –

1

Je suppose que le serveur SQL appelle votre fonction Get_ServicesByClientIdAndType une fois pour chaque combinaison de valeurs de paramètre, mais pour chaque ligne de la table Clients. Vous avez trois combinaisons de valeurs, donc pour 100 lignes dans la table Client, vous pouvez voir 300 appels de la fonction. Mais pour être confiant, lancez la requête dans sql server management studio et activez l'option "show execution plan". De cette façon, vous pouvez facilement détecter quelle partie de votre requête consomme le plus de ressources et vous concentrer sur l'optimisation de cette partie.

0

Une chose à garder à l'esprit est d'éviter "NON" si possible. "NOT" est non-sargable, il ne pourra pas tirer pleinement parti de l'indexation. À première vue, je ne vois pas un moyen de le réécrire pour éviter les expressions NOT cependant. FWIW, YMMV. :-)