sql >> Databasteknik >  >> RDS >> Mysql

Hur man gör en rullande summa, varje rad måste inkludera summan av tidigare rader

Du kan använda MySQL användarvariabler för att emulera analytiska funktioner. (Det finns några andra tillvägagångssätt också, som att använda en semi-join eller att använda en korrelerad underfråga. Jag kan tillhandahålla lösningar för dessa också, om du känner att de kan vara mer lämpliga.)

För att emulera en "running total" analytisk funktion, prova något i stil med detta:

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

"Knepet" här är att använda en IF-funktion för att testa om order_number är inget. När den är null lägger vi till varaktighetsvärdet till variabeln, annars sätter vi variabeln till noll.

Vi använder en inline-vy (alias d , för att säkerställa att @tot_dur-variabeln initieras till noll.

OBS:Var försiktig med att använda MySQL-användarvariabler som denna. I SELECT-satsen enligt ovan sker tilldelningen av variablerna i SELECT-listan efter ORDER BY, så att vi kan få deterministiskt beteende.

Den frågan hanterar inte "pauser" i user_id. För att få det kommer vi att behöva värdet för user_id från föregående rad. Vi kan bevara det i en annan användarvariabel. Ordningen på operationerna är deterministisk och vi måste vara noga med att göra ackumuleringen INNAN vi skriver över user_id från föregående rad.

Vi måste antingen ordna om kolumnerna så att user_id visas efter tot_dur (eller inkludera en andra kopia av user_id-kolumnen)

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

Värdena som returneras i user_id och prev_user_id kolumner är identiska. Den "extra" kolumnen kan tas bort, eller så kan kolumnerna ordnas om genom att radera frågan (som en inline-vy) i en annan fråga, även om detta kostar en prestanda:

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

Den frågan visar att det är möjligt för MySQL att returnera den angivna resultatuppsättningen. Men för optimal prestanda skulle vi bara vilja köra frågan i inlinevyn (alias v ), och hantera omordningen av kolumnerna (sätta user_id-kolumnen först) på klientsidan, när raderna hämtas.

De andra två vanliga metoderna är att använda en semi-join och att använda en korrelerad underfråga, även om dessa tillvägagångssätt kan vara mer resurskrävande när man bearbetar stora uppsättningar.




  1. Mysqldump' känns inte igen som ett internt eller externt kommandoprogram eller batchfil

  2. Hur man uppdaterar en MySQL-kolumn baserat på en kryssruteinmatning

  3. Effektiv övervakning av MySQL med SCUMM Dashboards:Del ett

  4. TransactSQL för att köra ett annat TransactSQL-skript