2015-03-25 1 views
0

J'ai une table dbo.participation:Problème avec DateCreated

ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED, 
User VARCHAR(MAX) NOT NULL, 
ParticipationLevel TINYINT NOT NULL, 
Selector VARCHAR(MAX) NOT NULL, 
DateCreated DATETIME NOT NULL 

J'ai créé le code ci-dessous, mais malheureusement, il fait preuve de mauvaise performance pour @DateStart et @DateStop

SELECT 
    dateadd(month, datediff(month, 0, DateCreated), 0) AS MDate 
    ,COUNT(CASE WHEN ParticipationLevel >= 10 THEN Selector ELSE NULL END) AS ParticipationLevel1 
    ,COUNT(CASE WHEN ParticipationLevel >= 30 THEN Selector ELSE NULL END) AS ParticipationLevel2 
FROM 
    Participation 
WHERE 
    (@DateStart IS NULL OR (@DateStart IS NOT NULL 
          AND DateCreated >= @DateStart)) 
    AND (@DateEnd IS NULL OR (@DateEnd IS NOT NULL 
           AND DateCreate < @DateEnd)) 
GROUP BY 
    Dateadd(month, datediff(month, 0, DateCreate), 0) 

Est-ce que vous arrive d'avoir des idées comment améliorer mon code ou alternativement comment modifier la table pour améliorer les performances?

+1

Vous pouvez simplifier les choses un peu en fixant DateStart à min-date si null, et DateEnd à max-date avant de les utiliser dans la requête ... en éliminant deux clauses OR qui sont toujours mauvaises pour la performance. Assurez-vous également que votre colonne DateCreated a un index approprié, avec ParticipationLevel en tant que colonne include. Je ne vois pas de définition pour "Suivi" ... – pmbAustin

+1

Avez-vous des index sur votre table? Vous pouvez utiliser un index avec une clé sur 'DateCreated' et vous pouvez inclure' ParticipationLevel'. Avez-vous vraiment besoin de compter 'Tracking' ?, si cette colonne ne peut pas avoir' NULL', vous pouvez simplement utiliser 'COUNT (1)' à la place – Lamak

+0

Les deux bons commentaires ci-dessus. Un autre élément d'information? Combien de lignes dans la table? Combien de mois distincts contient-il? –

Répondre

1

Vous avez besoin d'un index le long des lignes suivantes

CREATE INDEX ix 
    ON dbo.Participation(DateCreated) 
    INCLUDE (ParticipationLevel); 

Et vous devez réécrire la requête pour se débarrasser de la OR et d'éviter l'arbitre inutile nce à une colonne définie comme NOT NULL.

(Remarque simple COUNT(Selector) ne regarderait pas la valeur que SQL Server reconnaît qu'il ne peut pas être NULL, mais l'emballage dans une expression va à l'encontre de cette logique)

SELECT DATEADD(month, DATEDIFF(month, 0, DateCreated), 0) AS MDate, 
     COUNT(CASE 
       WHEN ParticipationLevel >= 10 THEN 1 
      END)           AS ParticipationLevel1, 
     COUNT(CASE 
       WHEN ParticipationLevel >= 30 THEN 1 
      END)           AS ParticipationLevel2 
FROM Participation 
WHERE DateCreated >= ISNULL(@DateStart, '17530101') 
     AND DateCreated <= ISNULL(@DateEnd, '99991231') 
GROUP BY DATEDIFF(month, 0, DateCreated) 

Cela peut donner un plan avec une recherche en ci-dessous

enter image description here

Notez qu'il serait possible de se débarrasser de la sorte par le traitement des morceaux de l'indice d'un mois au moment (peut-être dans un CTE récursive), mais cela peut être exagéré.

code pour cela pourrait ressembler à quelque chose comme

/*Cheap to find out from the index*/ 

IF @DateStart IS NULL 
    SELECT @DateStart = MIN(DateCreated) 
    FROM dbo.Participation 

IF @DateStart IS NULL 
    SELECT @DateEnd = MAX(DateCreated) 
    FROM dbo.Participation 

/*Adjust to start of month*/ 
SELECT @DateStart = DATEADD(month, DATEDIFF(month, 0, @DateStart), 0), 
     @DateEnd = DATEADD(month, 1 + DATEDIFF(month, 0, @DateEnd), 0); 


WITH Dates 
    AS (SELECT @DateStart AS MDate 
     UNION ALL 
     SELECT dateadd(MONTH, 1, MDate) AS MDate 
     FROM Dates 
     WHERE dateadd (MONTH, 1, MDate) <= @DateEnd) 
SELECT D.MDate, 
     CA.ParticipationLevel1, 
     CA.ParticipationLevel2 
FROM Dates D 
     CROSS APPLY (SELECT COUNT(CASE 
            WHEN ParticipationLevel >= 10 
            THEN 1 
           END) AS ParticipationLevel1, 
          COUNT(CASE 
            WHEN ParticipationLevel >= 30 
            THEN 1 
           END) AS ParticipationLevel2 
        FROM Participation P WITH (INDEX = ix) 
        WHERE P.DateCreated >= D.MDate 
          AND P.DateCreated < DATEADD(MONTH, 1, D.MDate) 
        GROUP BY() /* So no grouping row returned for empty months */ 
      ) CA(ParticipationLevel1, ParticipationLevel2) 
OPTION (MAXRECURSION 0); 

Ce qui donne un plan avec répété cherche et sans sortes

enter image description here

0

Ci-dessous deux chèques ne sont pas nécessaires dans votre clause
@DateStart IS NOT NULL ET
@DateEnd IS NOT NULL ET

SELECT dateadd(month, datediff(month, 0, DateCreated), 0) AS MDate 
       ,COUNT(CASE WHEN ParticipationLevel >= 10 THEN Tracking ELSE NULL END) AS ParticipationLevel1 
       ,COUNT(CASE WHEN ParticipationLevel >= 30 THEN Tracking ELSE NULL END) AS ParticipationLevel2 
FROM Participation 
WHERE (@DateStart IS NULL OR DateCreated >= @DateStart) AND (@DateEnd IS NULL OR DateCreate < @DateEnd) 
GROUP BY Dateadd(month, datediff(month, 0, DateCreate), 0) 
+0

C'est une condition très superflu. Cela ne devrait pas avoir d'impact sur les performances (bien que ce soient des conditions inutiles) – Lamak

+0

Désolé, je l'ai vu dans l'OP au second coup d'œil et j'ai retiré mon commentaire. –