2017-11-10 1 views
0

Dies ist nur ein Teil der Abfrage zählen helfen, aber es scheint der Engpass zu sein:Ich brauche meine SQL-Abfrage Verbesserung für das Ziehen eines aktuelles Dokument

SELECT CAST (CASE WHEN EXISTS 
      (SELECT 1 
       FROM dbo.CBDocument 
       WHERE (FirmId = R.FirmId) AND 
        (ContributionDate > DATEADD(m, -3, GETDATE())) AND 
        ((EntityTypeId = 2600 AND EntityId = P.IProductId) OR 
        (EntityTypeId = 2500 AND EntityId = M.IManagerId))) 
      THEN 1 ELSE 0 END AS BIT) AS HasRecentDocuments 

FROM dbo.CBIProduct P 
    JOIN dbo.CBIManager M ON P.IManagerId = M.IManagerId 
    JOIN dbo.CBIProductRating R ON P.IProductId = R.IProductId 
    JOIN dbo.CBIProductFirmDetail D ON (D.IProductId = P.IProductId) AND 
             (R.FirmId = D.FirmId) 

CROSS APPLY (SELECT TOP 1 RatingDate, IProductRatingId, FirmId 
      FROM dbo.CBIProductRating 
    WHERE (IProductId = P.IProductId) AND (FirmId = R.FirmId) 
    ORDER BY RatingDate DESC) AS RD 

WHERE (R.IProductRatingId = RD.IProductRatingId) AND (R.FirmId = RD.FirmId) 

Es gibt eine Menge von anderen Spalten, die ich in der Regel ziehe zurück, dass die CROSS APPLY und die anderen verbindet. Das Bit, das ich optimieren muss, ist die Unterabfrage in der case-Anweisung. Diese Unterabfrage dauert 3 Minuten, um 119k Datensätze zurückzugeben. Ich weiß genug über SQL, um so weit zu kommen, aber es muss einen Weg geben, dies effizienter zu machen.

Das Kernstück der Abfrage besteht darin, ein Flag zurückzugeben, wenn das zugehörige Produkt Dokumente enthält, die innerhalb der letzten 3 Monate zum System hinzugefügt wurden.

Bearbeiten: Meine DB ist in Azure gehostet und der Datenbank-Tuning-Berater wird keine Verbindung herstellen. In Azure gibt es eine Tuning Advisor-Komponente, die jedoch nichts andeutet. Es muss einen besseren Ansatz für die Abfrage geben.

Edit: In einem Versuch, weiter zu vereinfachen und die Täter zu ermitteln, ich es schnitzte auf diese Abfrage nach unten: (Anstatt festzustellen, ob ein aktuelles Dokument existiert, es zählt nur die letzten docs.)

SELECT D.FirmId, P.IProductId, 
     ,(SELECT COUNT(DocumentId) FROM dbo.CBDocument WHERE 
     (FirmId = D.FirmId) AND 
     (ContributionDate > DATEADD(m, -3, GETDATE())) AND 
     ((EntityTypeId = 2600 AND EntityId = P.IProductId) OR 
     (EntityTypeId = 2500 AND EntityId = M.IManagerId))) AS RecentDocCount 

FROM dbo.CBIProduct P 
FULL JOIN dbo.CBIProductFirmDetail D ON D.IProductId = P.IProductId 
JOIN dbo.CBIManager M ON M.IManagerId = P.IManagerId 

That läuft in 3 Minuten, 53 Sekunden.

Wenn ich eine Variable deklarieren, das Datum zu speichern (DECLARE @Today DATE = GETDATE()) und setze die Variable anstelle von GETDATE() in der Abfrage (DATEADD(m, -3, @Today)), läuft es in 12 Sekunden.

Gibt es ein bekanntes Leistungsproblem mit GETDATE()? Soweit ich weiß, kann ich die Variable nicht in einer Ansichtsdefinition verwenden.

Gibt es ein Licht auf alles, was auf eine Lösung hindeuten könnte? Ich nehme an, ich könnte das Ganze in eine gespeicherte Prozedur umwandeln, aber dann muss ich auch den Anwendungscode anpassen.

Danke.

+0

https://docs.microsoft.com/en-us/sql/tools/dta/tutorial-database-engine-tuning-advisor Machen Sie das und Sie können Glück haben und müssen nur einige neue Indizes hinzufügen – Will

+1

Danke, @Will. Ich bin diesen Weg gegangen. Ich kann den DTA nicht mit meiner Datenbank in Azure verbinden, und Azures Optimierungsratgeber empfiehlt nichts. –

+0

Wenn Sie die Datenbank abfangen und in eine lokale Instanz von SQL Server laden können, können Sie es trotzdem versuchen. Die Indizes würden sich in azurblau nicht unterscheiden, glaube ich. – Will

Antwort

1

auf Material Je kann es schneller sein, eine links zu verwenden, verbinden:

SELECT CAST(CASE when x.FirmId is not null THEN 1 ELSE 0 END AS BIT) AS HasRecentDocuments 

FROM dbo.CBIProduct P 
    JOIN dbo.CBIManager M ON P.IManagerId = M.IManagerId 
    JOIN dbo.CBIProductRating R ON P.IProductId = R.IProductId 
    JOIN dbo.CBIProductFirmDetail D ON (D.IProductId = P.IProductId) AND (R.FirmId = D.FirmId) 


LEFT JOIN dbo.CBDocument x ON x.FirmId = R.FirmId 
          AND x.ContributionDate > DATEADD(m, -3, GETDATE()) 
          AND ( (x.EntityTypeId = 2600 AND x.EntityId = P.IProductId) 
           OR (x.EntityTypeId = 2500 AND x.EntityId = M.IManagerId)) 

CROSS APPLY (SELECT TOP 1 RatingDate, IProductRatingId, FirmId 
      FROM dbo.CBIProductRating 
    WHERE (IProductId = P.IProductId) AND (FirmId = R.FirmId) 
    ORDER BY RatingDate DESC) AS RD 

WHERE (R.IProductRatingId = RD.IProductRatingId) AND (R.FirmId = RD.FirmId) 

es sieht einfacher.

+0

Musste die Syntax ein wenig anpassen, um SQL Server davon abzuhalten, sich zu beschweren, aber das scheint nicht die gleichen Ergebnisse zu liefern ... Es läuft ein bisschen schneller, aber ich muss herausfinden, warum ich einen bekomme unterschiedliche Ergebnismenge. Außerdem scheint der CASE Probleme mit der NULL-Prüfung zu haben. Ich bekomme nur Nullen. –

+0

hmmm, was war die Änderung (sah ich ein 'wenn' etwas anderes?) - Ich denke nicht, dass der Fall Probleme mit dem Null-Check hat - was meinst du? – Hogan

1

Dies ist die Abfrage, die Sie Bedarf Optimierung Anspruch:

SELECT CAST(CASE WHEN EXISTS (SELECT 1 
           FROM dbo.CBDocument d 
           WHERE (d.FirmId = R.FirmId) AND 
            (d.ContributionDate > DATEADD(m, -3, GETDATE())) AND 
            ((d.EntityTypeId = 2600 AND d.EntityId = P.IProductId) OR 
            (d.EntityTypeId = 2500 AND d.EntityId = M.IManagerId) 
            ) 
          ) 
    . . . 

ich Ihr Urteil vertrauen werde. Ich denke, Phrasierung die Abfrage wie folgt man mehrere Pfade zu Optimierung gibt:

SELECT CAST(CASE WHEN EXISTS (SELECT 1 
           FROM dbo.CBDocument d 
           WHERE d.FirmId = R.FirmId AND 
            d.ContributionDate > DATEADD(m, -3, GETDATE()) AND 
            d.EntityTypeId = 2600 AND d.EntityId = P.IProductId 
          ) OR 
         EXISTS (SELECT 1 
           FROM dbo.CBDocument d 
           WHERE d.FirmId = R.FirmId AND 
            d.ContributionDate > DATEADD(m, -3, GETDATE()) AND 
            d.EntityTypeId = 2500 AND d.EntityId = M.IManagerId 
          ) 
    . . . 

Dann möchten Sie einen Index für CBDocument(FirmId, EntityTypeId, EntityId, ContributionDate).

1

Operationen wie correlated subqueries und full outer join sind ziemlich teuer und ich würde vorschlagen, nach Alternativen zu diesen zu suchen. Obwohl ich mit Ihrem Datenmodell oder Ihren Daten nicht vertraut bin, schlage ich vor, die "from table" zu CBIProductFirmDetail zu ändern, und ich habe weiter angenommen, dass ein innerer Join der Produkttabelle und der manager-Tabelle dann intern mit der Produkttabelle verbunden ist. Wenn diese Join-Sequenz korrekt ist, werden die Kosten einiger äußerer Joins reduziert.

Anstelle der korrelierten Unterabfrage, um eine Anzahl zu bestimmen, schlage ich vor, dass Sie das als Unterabfrage behandeln, die links verbunden ist.

SELECT 
     d.FirmId 
    , p.IProductId 
    , COALESCE(Docs.RecentDocCount,0) RecentDocCount 
FROM dbo.CBIProductFirmDetail d 
JOIN dbo.CBIProduct p ON d.IProductId = p.IProductId 
JOIN dbo.CBIManager m ON p.IManagerId = m.IManagerId 
LEFT JOIN (
     SELECT 
      FirmId 
      , EntityId 
      , EntityTypeId 
      , COUNT(DocumentId) recentdoccount 
     FROM dbo.CBDocument 
     WHERE ContributionDate > DATEADD(m, -3, GETDATE()) 
     AND EntityTypeId IN (2500,2600) 
     GROUP BY 
      FirmId 
      , EntityId 
      , EntityTypeId 
) AS docs ON d.FirmId = docs.FirmId 
     AND (
       (docs.EntityTypeId = 2600 AND docs.EntityId = p.IProductId) 
      OR (docs.EntityTypeId = 2500 AND docs.EntityId = m.IManagerId) 
      ) 
; 

Es könnte sein Nutzen in auch, dass die Unterabfrage Dividieren der umständlich zu vermeiden oder dass beitreten, so:

SELECT 
     d.FirmId 
    , p.IProductId 
    , COALESCE(d2500.DocCount,0) + COALESCE(d2600.DocCount,0) RecentDocCount 
FROM dbo.CBIProductFirmDetail d 
JOIN dbo.CBIProduct p ON d.IProductId = p.IProductId 
JOIN dbo.CBIManager m ON p.IManagerId = m.IManagerId 
LEFT JOIN (
     SELECT 
      FirmId 
      , EntityId 
      , COUNT(DocumentId) doccount 
     FROM dbo.CBDocument 
     WHERE ContributionDate > DATEADD(m, -3, GETDATE()) 
     AND EntityTypeId = 2500 
     GROUP BY 
      FirmId 
      , EntityId 
) AS d2500 ON d.FirmId = d2500.FirmId 
     AND m.IManagerId = d2500.EntityId 
LEFT JOIN (
     SELECT 
      FirmId 
      , EntityId 
      , COUNT(DocumentId) doccount 
     FROM dbo.CBDocument 
     WHERE ContributionDate > DATEADD(m, -3, GETDATE()) 
     AND EntityTypeId = 2600 
     GROUP BY 
      FirmId 
      , EntityId 
) AS d2600 ON d.FirmId = d2600.FirmId 
      AND p.IProductId = d2600.EntityId 
; 
+0

Ist Ihre Frage jetzt gelöst? Hast du noch Fragen zu dieser Antwort? Um eine Antwort zu akzeptieren "[** Klicken Sie auf das Häkchen **] (https://ibb.co/ikqyO6)" Weitere Informationen finden Sie unter [help/accepting] (https://stackoverflow.com/help/someone-answers) –

Verwandte Themen