Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Um eine fortlaufende Summe zu erstellen, muss jede Zeile die Summe der vorherigen Zeilen enthalten

Sie können MySQL-Benutzervariablen verwenden, um Analysefunktionen zu emulieren. (Es gibt auch einige andere Ansätze, wie die Verwendung eines Semi-Joins oder die Verwendung einer korrelierten Unterabfrage. Ich kann auch Lösungen dafür bereitstellen, wenn Sie der Meinung sind, dass sie angemessener sind.)

Versuchen Sie zum Emulieren einer "laufenden Summe"-Analysefunktion Folgendes:

SELECT t.user_id
     , t.starttime
     , t.order_number
     , IF(t.order_number IS NOT NULL,
         @tot_dur := 0,
         @tot_dur := @tot_dur + t.visit_duration_seconds) AS tot_dur
  FROM visit t
  JOIN (SELECT @tot_dur := 0) d
 ORDER BY t.user_id, t.start_time

Der "Trick" hier ist, eine IF-Funktion zu verwenden, um zu testen, ob order_number ist Null. Wenn es null ist, fügen wir den Dauerwert zur Variablen hinzu, andernfalls setzen wir die Variable auf null.

Wir verwenden eine Inline-Ansicht (aliased als d , um sicherzustellen, dass die Variable @tot_dur auf Null initialisiert wird.

HINWEIS:Seien Sie vorsichtig bei der Verwendung solcher MySQL-Benutzervariablen. In der obigen SELECT-Anweisung erfolgt die Zuweisung der Variablen in der SELECT-Liste nach dem ORDER BY, sodass wir deterministisches Verhalten erhalten können.

Diese Abfrage behandelt keine "Unterbrechungen" in user_id. Dazu benötigen wir den Wert von user_id aus der vorherigen Zeile. Wir können das in einer anderen Benutzervariablen speichern. Die Reihenfolge der Operationen ist deterministisch, und wir müssen darauf achten, die Akkumulation durchzuführen, BEVOR wir die user_id aus der vorherigen Zeile überschreiben.

Entweder müssen wir die Spalten neu anordnen, sodass user_id nach tot_dur erscheint (oder wir müssen eine zweite Kopie der user_id-Spalte einfügen)

SELECT t.user_id
     , t.starttime
     , t.order_number
     , IF(t.order_number IS NULL,
         @tot_dur := IF(@prev_user_id = t.user_id,@tot_dur,0) + t.visit_duration_seconds,
         @tot_dur := 0
       ) AS tot_dur
     , @prev_user_id := t.user_id AS prev_user_id
  FROM visit t
  JOIN (SELECT @tot_dur := 0, @prev_user_id := NULL) d
 ORDER BY t.user_id, t.start_time

Die in user_id zurückgegebenen Werte und prev_user_id Spalten ist identisch. Diese "zusätzliche" Spalte könnte entfernt werden, oder die Spalten könnten neu angeordnet werden, indem die Abfrage (als Inline-Ansicht) in eine andere Abfrage eingeschlossen wird, obwohl dies zu Leistungseinbußen führt:

SELECT v.user_id
     , v.starttime
     , v.order_number
     , v.tot_dur
  FROM (SELECT t.starttime
             , t.order_number
             , IF(t.order_number IS NULL,
                 @tot_dur := IF(@prev_user_id = t.user_id,@tot_dur,0) + t.visit_duration_seconds,
                 @tot_dur := 0
               ) AS tot_dur
             , @prev_user_id := t.user_id AS user_id
          FROM visit t
          JOIN (SELECT @tot_dur := 0, @prev_user_id := NULL) d
         ORDER BY t.user_id, t.start_time
       ) v

Diese Abfrage zeigt, dass MySQL die angegebene Ergebnismenge zurückgeben kann. Für eine optimale Leistung möchten wir jedoch nur die Abfrage in der Inline-Ansicht ausführen (mit dem Alias ​​v ) und handhabt die Neuordnung der Spalten (wobei die user_id-Spalte an erster Stelle steht) auf der Clientseite, wenn die Zeilen abgerufen werden.

Die anderen beiden gängigen Ansätze verwenden einen Semi-Join und eine korrelierte Unterabfrage, obwohl diese Ansätze bei der Verarbeitung großer Mengen ressourcenintensiver sein können.