Mysql
 sql >> Database >  >> RDS >> Mysql

Come fare una somma mobile, ogni riga deve includere la somma delle righe precedenti

È possibile utilizzare le variabili utente di MySQL per emulare funzioni analitiche. (Esistono anche altri approcci, come l'utilizzo di un semi-join o di una sottoquery correlata. Posso fornire soluzioni anche per questi, se ritieni che possano essere più appropriati.)

Per emulare una funzione analitica "totale parziale", prova qualcosa del genere:

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

Il "trucco" qui è usare una funzione SE per verificare se order_number o meno è zero. Quando è nullo, aggiungiamo il valore della durata alla variabile, altrimenti impostiamo la variabile a zero.

Usiamo una vista in linea (alias d , per garantire che la variabile @tot_dur sia inizializzata su zero.

NOTA:fare attenzione con l'utilizzo di variabili utente MySQL come questa. Nell'istruzione SELECT come sopra, l'assegnazione delle variabili nell'elenco SELECT avviene dopo ORDER BY, quindi possiamo ottenere un comportamento deterministico.

Quella query non gestisce le "interruzioni" in user_id. Per ottenerlo, avremo bisogno del valore di user_id dalla riga precedente. Possiamo conservarlo in un'altra variabile utente. L'ordine delle operazioni è deterministico e dobbiamo fare attenzione ad accumulare PRIMA di sovrascrivere user_id dalla riga precedente.

Dobbiamo riordinare le colonne in modo che user_id appaia dopo tot_dur (o includere una seconda copia della colonna user_id)

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

I valori restituiti in user_id e prev_user_id le colonne sono identiche. Quella colonna "extra" potrebbe essere rimossa o le colonne potrebbero essere riordinate racchiudendo la query (come vista in linea) in un'altra query, anche se ciò comporta un costo in termini di prestazioni:

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

Tale query dimostra che è possibile per MySQL restituire il set di risultati specificato. Ma per prestazioni ottimali, vorremmo eseguire solo la query nella vista in linea (alias v ) e gestire il riordino delle colonne (mettendo prima la colonna user_id) sul lato client, quando le righe vengono recuperate.

Gli altri due approcci comuni utilizzano un semi-join e una sottoquery correlata, sebbene questi approcci possano richiedere più risorse durante l'elaborazione di insiemi di grandi dimensioni.