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

Somma cumulativa su un insieme di righe in mysql

AGGIORNAMENTO

MySQL 8.0 introduce le "funzioni finestra", funzionalità equivalenti alle "funzioni finestra" di SQL Server (con partizionamento e ordinamento forniti da Transact-SQL OVER sintassi) e le "funzioni analitiche" Oracle.

Manuale di riferimento MySQL 12.21 Funzioni della finestra https://dev.mysql .com/doc/refman/8.0/en/window-functions.html

La risposta fornita qui è un approccio per le versioni di MySQL precedenti alla 8.0.

RISPOSTA ORIGINALE

MySQL non fornisce la funzione di analisi del tipo che useresti per ottenere una "somma cumulativa" in esecuzione, come le funzioni analitiche disponibili in altri DBMS (come Oracle o SQL Server.)

Ma è possibile emulare alcune funzioni analitiche, usando MySQL.

Esistono (almeno) due approcci praticabili:

Uno consiste nell'utilizzare una sottoquery correlata per ottenere il totale parziale. Questo approccio può essere costoso su insiemi di grandi dimensioni e complicato se i predicati sulla query esterna sono complicati. Dipende davvero da quanto sia complicato "unire più su più tabelle". (Sfortunatamente, anche MySQL non supporta i CTE.)

L'altro approccio consiste nell'utilizzare le variabili utente di MySQL, per eseguire un'elaborazione delle interruzioni di controllo. Il "trucco" qui è ordinare i risultati della tua query (usando un ORDER BY) e quindi avvolgere la tua query in un'altra query.

Darò un esempio di quest'ultimo approccio.

A causa dell'ordine in cui MySQL esegue le operazioni, il cumulative_total la colonna deve essere calcolata prima del valore di id e day dalla riga corrente vengono salvati nelle variabili utente. È più facile mettere prima questa colonna.

La vista in linea alias i (nella query seguente) è lì solo per inizializzare le variabili utente, nel caso in cui queste siano già impostate nella sessione. Se a quelli sono già stati assegnati valori, vogliamo ignorare i loro valori correnti e il modo più semplice per farlo è inizializzarli.

La tua query originale viene racchiusa tra parentesi e viene assegnato un alias, c nell'esempio qui sotto. L'unica modifica alla query originale è l'aggiunta di una clausola ORDER BY, quindi possiamo essere sicuri di elaborare le righe della query in sequenza.

La selezione esterna controlla se l'id e day valore dalla riga corrente "corrisponde" alla riga precedente. Se lo fanno, aggiungiamo l'amount dalla riga corrente al totale parziale cumulativo. Se non corrispondono, azzeriamo il totale parziale cumulativo e aggiungiamo l'importo dalla riga corrente (o, più semplicemente, assegniamo l'importo dalla riga corrente).

Dopo aver eseguito il calcolo del totale cumulativo, salviamo l'id e day valori dalla riga corrente in variabili utente, in modo che siano disponibili quando elaboriamo la riga successiva.

Ad esempio:

SELECT IF(@prev_id = c.id AND @prev_day = c.day
         ,@cumtotal := @cumtotal + c.amount
         ,@cumtotal := c.amount) AS cumulative_total
     , @prev_id  := c.id  AS `id`
     , @prev_day := c.day AS `day`
     , c.hr
     , c.amount AS `amount'
  FROM ( SELECT @prev_id  := NULL
              , @prev_day := NULL
              , @subtotal := 0
       ) i
  JOIN (

         select id, day, hr, amount from
         ( //multiple joins on multiple tables)a
         left join
         (//unions on multiple tables)b
         on a.id=b.id

         ORDER BY 1,2,3
       ) c

Se è necessario restituire le colonne in un ordine diverso, con il totale cumulativo come ultima colonna, un'opzione è racchiudere l'intera istruzione in una serie di parentesi e utilizzare quella query come vista in linea:

SELECT d.id
     , d.day
     , d.hr
     , d.amount
     , d.cumulative_total
FROM (
       // query from above
     ) d