2016-06-25 9 views
1

In einer MySQL-DB-Tabelle, die Verkaufsaufträge speichert, ich habe eine LastReviewed Spalte, die das letzte Datum und Zeit hält, wenn der Verkauf modifiziert wurde (Typ timestamp, Standardwert CURRENT_TIMESTAMP). Ich möchte die Anzahl der Verkäufe grafisch darstellen, die jeden Tag in den letzten 90 Tagen für einen bestimmten Benutzer geändert wurden.SQL: Reuse Funktion Ergebnis in Abfrage ohne Verwendung von Unter Abfrage

Ich versuche, eine SELECT zu erstellen, die die Anzahl der Tage seit LastReviewed Datum zurückgibt, und wie viele Datensätze in diesem Bereich fallen. Im Folgenden finden Sie meine Frage, die ganz gut funktioniert:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales 
WHERE UserID=123 AND DATEDIFF(CURDATE(),LastReviewed)<=90 
GROUP BY days 
ORDER BY days ASC 

Beachten Sie, dass ich für jeden Datensatz die DATEDIFF() sowie CURDATE() mehrere Male am Berechnung. Dies scheint wirklich ineffektiv zu sein, daher würde ich gerne wissen, wie ich die Ergebnisse der vorherigen Berechnung wiederverwenden kann. Das erste, was ich versuchte, war:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales 
WHERE UserID=123 AND days<=90 
GROUP BY days 
ORDER BY days ASC 

Fehler: Unknown column 'days' in 'where clause'. Also fing ich an, mich im Internet umzusehen. Basierend auf einer andere Diskussion (Can I reuse a calculated field in a SELECT query?) habe ich versucht, neben den folgenden:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales 
WHERE UserID=123 AND (SELECT days)<=90 
GROUP BY days 
ORDER BY days ASC 

Fehler: Unknown column 'days' in 'field list'. Ich habe auch versucht, die folgenden:

SELECT @days := DATEDIFF(CURDATE(), LastReviewed) AS days, 
     COUNT(*) AS number FROM sales 
WHERE UserID=123 AND @days <=90 
GROUP BY days 
ORDER BY days ASC 

Die Abfrage gibt Null-Ergebnis, so @days<=90 scheint false sogar zurück, obwohl, wenn ich es in der SELECT Klausel setzen und entfernen Sie die WHERE Klausel, ich einige Ergebnisse mit @days sehen Werte unter 90

ich habe Dinge bekommen, indem sie eine Unterabfrage zu arbeiten:

SELECT * FROM (
    SELECT DATEDIFF(CURDATE(),LastReviewed) AS sales , 
     COUNT(*) AS number FROM sales 
    WHERE UserID=123 
    GROUP BY days 
) AS t 
WHERE days<=90 
ORDER BY days ASC 

ich odn't jedoch wissen, ob es der effizienteste Weg ist. Ganz zu schweigen davon, dass selbst diese Lösung CURDATE() einmal pro Datensatz berechnet, obwohl ihr Wert vom Anfang bis zum Ende der Abfrage gleich ist. Ist das nicht verschwenderisch? Übertreibe ich das? Hilfe wäre willkommen.

Hinweis: Mods, sollte dies auf CodeReview sein? Ich habe hier gepostet, weil der Code, den ich versuche zu verwenden, nicht wirklich funktioniert

+1

FWIW, Ihre Query + Unterabfrage ist nicht _expensiv_ und Sie können überprüfen, ob 'WHERE Tage <90' in der Query ODER' DATEDIFF (CURDATE(), LastReviewed) 'Ergebnisse schneller gibt. – aneroid

+0

Ich habe es in meinem Beitrag nicht erwähnt (ich füge es in einer Minute hinzu), aber eine andere Sache, die mich nervt, ist, dass ich 'CURDATE()' bei jedem Schritt berechne, obwohl es sich nicht von Anfang an ändert das Ende der Abfrage – BeetleJuice

Antwort

1

Es gibt tatsächlich zwei Probleme mit Ihrer Frage.

Zuerst übersehen Sie die Tatsache, dass WHERESELECT vorausgeht. Wenn der Server auswertet, kennt er den Wert der durchgeführten Berechnungen, um <expression> auszuwerten, und kann diese für SELECT verwenden.

Schlimmer noch als das, sollten Sie fast nie eine Abfrage schreiben, die eine Spalte als Argument für eine Funktion verwendet, da dies normalerweise erfordert den Server den Ausdruck für jede Zeile auszuwerten.

Stattdessen sollten Sie diese verwenden:

WHERE LastReviewed < DATE_SUB(CURDATE(), INTERVAL 90 DAY) 

Der Optimierer wird dies sehen und alle aufgeregt, weil DATE_SUB(CURDATE(), INTERVAL 90 DAY) auf einen konstanten aufgelöst werden können, die auf einer Seite eines < Vergleich verwendet werden können, die bedeutet, dass, wenn ein Index mit LastReviewed als die am weitesten links liegende relevante Spalte existiert, der Server sofort alle Zeilen mit LastReviewed >= diesem konstanten Wert mithilfe des Indexes eliminieren kann.

Dann DATEDIFF(CURDATE(), LastReviewed) AS days (immer noch benötigt für SELECT) wird nur gegen die Zeilen ausgewertet, die wir bereits wissen wir wollen.

Fügen Sie einen einzelnen Index hinzu (UserID, LastReviewed) und der Server wird in der Lage sein, die relevanten Zeilen extrem schnell zu lokalisieren.

+0

Ihr Beitrag war eine wirklich gute Lektion für mich; Sie haben Recht, ich habe nie in Betracht gezogen, dass 'WHERE' zuerst ausgeführt wird oder die Implikation, ein Feld als Argument für eine Funktion zu verwenden. Habe ich richtig verstanden, dass 'CURDATE()' und durch die Erweiterung 'DATE_SUB' nur einmal berechnet werden? Schließlich haben Sie einen Index für 'UserID, LastReviewed' vorgeschlagen. Würde Ihr Rat immer noch gelten, wenn Sie wissen, dass derselbe Benutzer mehrere Datensätze mit demselben 'LastReviewed' haben kann? – BeetleJuice

+0

Wenn dies für Ihre Antwort wichtig ist: Es gibt bereits einen Index der 'UNIQUE UserID, orderID' auf dieser Tabelle. "orderID" selbst ist nicht eindeutig (mehrere Benutzer können dasselbe haben) – BeetleJuice

+0

Ein solcher Index könnte verwendet werden, um die Zeilen auf den übereinstimmenden Benutzer einzugrenzen, aber der Server müsste alle übereinstimmenden Zeilen durchsuchen, um die Daten zu überprüfen. Besser als nichts, aber wie hilfreich es ist, hängt davon ab, wie viele Zeilen pro Benutzer Sie erwarten. –

1

Eingebaute Funktionen sind viel weniger teuer als, sagen wir, Zeilen holen.

Sie könnten viel mehr Leistungssteigerung mit dem folgenden "Verbundindex erhalten:

INDEX(UserID, LastReviewed) 

und Änderung

WHERE UserID=123 
    AND LastReviewed >= CURRENT_DATE() - INTERVAL 90 DAY 

Ihre Formulierung ist LastRevieded in einem Funktionsaufruf‚versteckt‘ und macht es in einem Index unbrauchbar.

Wenn Sie mit dieser Verbesserung immer noch nicht zufrieden sind, dann überlegen Sie sich eine nächtliche Abfrage, die die Statistiken von gestern berechnet und in eine "Zusammenfassungstabelle" bringt. Von dort aus kann der von Ihnen erwähnte SELECT sogar noch schneller laufen.

+0

Derselbe Benutzer kann mehrere Datensätze mit demselben 'LastReviewed' haben, und verschiedene Benutzer können Datensätze mit demselben' LasteReviewed' haben. In der Tabelle gibt es bereits einen Index der eindeutigen Benutzer-ID orderID. Ändert dies Ihre Aussage über das Hinzufügen eines Index 'UserID, LastReviewed'? – BeetleJuice

+0

Meine vorgeschlagene Änderung _plus_ mein vorgeschlagener Index sollte es schneller laufen lassen. 'UNIQUE (UserID, orderID)' könnte verwendet werden, aber es würde nur den 'UserID'-Teil verwenden. Die Verwendung von 'UserID' und' LastReviewed' ist besser. [_Mehr über zusammengesetzte Indizes_] (http://mysql.rjweb.org/doc.php/index1). –