2016-12-19 1 views
5

J'ai la requête suivante et il est la preuve très coûteuse et prend 6-8 secondes pour exécuter. En regardant le plan d'exécution, le coût est de 79% sur une opération SORT. Puis-je obtenir une amélioration ici?Bottle neck sur le fonctionnement SORT

IMG

SELECT 
     A.StageName, 
     C.Month, 
     C.MonthName as Label, 
     C.Year, 
     isnull(A.Average,0) as Data 
    FROM 
    ( 
     SELECT 
      S.StageName, 
      MONTH(TimeIn) as MonthNumber, 
      DATENAME(MONTH,TimeIn) as Month, 
      YEAR(TimeIn) as Year, 
      ISNULL(AVG(DATEDIFF(mi,TimeIn,isnull(TimeOut,@TodayDate))),0) as Average 
     FROM 
      VisitMovement VM 
     INNER JOIN Stage S on 
      VM.StageID = S.StageID 
     WHERE 
      (VM.TimeIn >= @StartDate AND 
      VM.TimeIn < DATEADD (d,1,@EndDate)) AND 
      (VM.TimeOut < DATEADD (d,1,@EndDate) OR VM.TimeOut IS NULL) 
     GROUP BY 
      S.StageNumber, 
      S.StageName, 
      MONTH(TimeIn), 
      DATENAME(MONTH,TimeIn), 
      YEAR(TimeIn) 
    ) A 
    RIGHT JOIN (select distinct Month,MonthName,Year from Calendar WHERE DATE >= @StartDate AND DATE < DATEADD (d,1,@EndDate)) C on 
     A.MonthNumber = C.Month and 
     A.Month = C.MonthName and 
     A.Year = C.Year 
    GROUP BY 
     A.StageName, 
     C.Month, 
     C.MonthName, 
     C.Year, 
     A.Average 
    ORDER BY 
     CASE WHEN @Ordering = 'asc' THEN C.Year   END ASC, 
     CASE WHEN @Ordering = 'asc' THEN C.Month   END ASC, 
     CASE WHEN @Ordering = 'asc' THEN A.StageName  END ASC, 
     CASE WHEN @Ordering = 'desc' THEN C.Year   END DESC, 
     CASE WHEN @Ordering = 'desc' THEN C.Month   END DESC, 
     CASE WHEN @Ordering = 'desc' THEN A.StageName  END DESC 
+0

Le genre le plus cher dans votre plan se passe à cause de la 'flux Aggregate' iterator comme ce iterator attend les entrées triées, et le' flux Aggregate' iterator est là à cause de votre 'groupe intérieur by' clause. Essayez d'obtenir les lignes uniques sans utiliser la clause 'Group by', cela supprimera' Stream Aggregate' et le tri disparaîtra aussi. –

+0

Merci @ M.Ali - c'est impossible, malheureusement, comme je l'ai besoin de regrouper tous les enregistrements de la table afin de calculer la moyenne globale. – Philip

Répondre

0

Même si je comprends que vous ne pouvez pas vous débarrasser de la GROUP BY sur les différentes colonnes de la sous-requête, vous pouvez le rendre plus facile pour le système.

Actuellement, vous avez

S.StageNumber, 
S.StageName, 
MONTH(TimeIn), 
DATENAME(MONTH,TimeIn), 
YEAR(TimeIn) 

qui je pense est tout à fait beaucoup de données à passer. Permettez-moi de faire quelques suppositions:

S.StageNumber, -- int, 4 bytes 
S.StageName, -- string, 20 bytes 
MONTH(TimeIn), -- int, 4 bytes 
DATENAME(MONTH,TimeIn), -- string 5 bytes 
YEAR(TimeIn) -- int, 4 byte 

Maintenant, il y a quelques dépendances:

  • Si vous connaissez le MOIS (nombre), alors vous aussi maintenant le nom de celui-ci
  • Je suis deviner que StageName + StageNumber est unique et directement lié à StageID. Si ce n'est pas le cas, vous devrez peut-être GROUP BY une fois de plus dans la couche externe.

Cela nous amènerait à

S.StageID, -- int, 4 bytes 
MONTH(TimeIn), -- int, 4 bytes 
YEAR(TimeIn) -- int, 4 byte 

Cela signifie que le tri pour le GROUP BY doit parcourir seulement 12 octets par enregistrement au lieu des 37 octets par enregistrement qu'il était avant, et les numéros de tri tout à fait un peu plus vite que les chaînes de toute façon (par exemple, en raison de majuscules/minuscules, accents, etc ..)

J'ai essayé de réécrire la requête en conséquence (non testé!). J'ai également déplacé la récupération de Month-information dans une table temporaire distincte, ce qui devrait aider un peu l'Optimiseur de requêtes.

SELECT DISTINCT Month,MonthName,Year 
    INTO #dates 
    FROM Calendar 
WHERE DATE >= @StartDate AND DATE < DATEADD (d,1,@EndDate) 

CREATE UNIQUE CLUSTERED INDEX uq0_#dates ON #dates (Month,Year) 

SELECT 
     A.StageName, 
     C.Month, 
     C.MonthName as Label, 
     C.Year, 
     isnull(A.Average,0) as Data 
    FROM 
    ( 
     SELECT S.StageName, 
       MonthNumber, 
       Year, 
       Average 
      FROM ( 
        SELECT VM.StageID, 
         MONTH(TimeIn) as MonthNumber, 
         YEAR(TimeIn) as Year, 
         ISNULL(AVG(DATEDIFF(mi,TimeIn,isnull(TimeOut,@TodayDate))),0) as Average 
        FROM 
         VisitMovement VM 
        WHERE 
         (VM.TimeIn >= @StartDate AND 
         VM.TimeIn < DATEADD (d,1,@EndDate)) AND 
         (VM.TimeOut < DATEADD (d,1,@EndDate) OR VM.TimeOut IS NULL) 
        GROUP BY 
         VM.StageID, 
         MONTH(TimeIn), 
         YEAR(TimeIn) 
       ) grp 
      JOIN Stage S 
      ON S.StageID = grp.StageID 
     ) 
    ) A 
    RIGHT OUTER JOIN #dates C 
    on 
     A.MonthNumber = C.Month and 
     -- A.Month = C.MonthName and 
     A.Year = C.Year 
    ORDER BY 
     CASE WHEN @Ordering = 'asc' THEN C.Year   END ASC, 
     CASE WHEN @Ordering = 'asc' THEN C.Month   END ASC, 
     CASE WHEN @Ordering = 'asc' THEN A.StageName  END ASC, 
     CASE WHEN @Ordering = 'desc' THEN C.Year   END DESC, 
     CASE WHEN @Ordering = 'desc' THEN C.Month   END DESC, 
     CASE WHEN @Ordering = 'desc' THEN A.StageName  END DESC 

Espérons que cela aide.

+0

Beaucoup d'excuses pour la réponse retardée @deroby.J'étais parti au cours de la période de Noël/Nouvel An. Merci pour votre solution et bonne année :) – Philip

0

Parce que le order by doit évaluer chaque ligne, je ne pense pas qu'il peut faire une utilisation optimale des indices. Remplacement de la order by avec un row_number() pour le tri par défaut et seulement inverser l'ordre une fois devrait au moins éviter que plusieurs évaluations de @Ordering pour une seule ligne.

Dans le code pseudo ci-dessous, la requête initiale est placée dans un CTE. Row_number détermine le tri ascendant et en dessous du cte l'ordre est inversé en cas de besoin:

;with cte as 
(
    SELECT 
     A.StageName, 
     C.Month, 
     ..., 
     row_number() over (order by C.Year,C.Month,A.StageName) sortOrder 
    FROM 
     ...rest of the query, excluding the order by 
) 
select * --or list the columns without the sortOrder 
from cte 
order by sortOrder * case @Ordering when 'desc' then -1 else 1 end 
+0

Merci pour ce @ Me.Name - apprécier le conseil. L'utilisation de l'approche cte rend cependant la requête encore plus lente malheureusement. Avec l'approche CTE, elle place également une marque d'explication jaune au-dessus de l'opération de tri. – Philip