sql >> Base de Datos >  >> RDS >> Mysql

Suma acumulativa sobre un conjunto de filas en mysql

ACTUALIZAR

MySQL 8.0 introduce "funciones de ventana", una funcionalidad equivalente a las "funciones de ventana" de SQL Server (con partición y orden proporcionados por Transact-SQL OVER sintaxis) y "funciones analíticas" de Oracle.

Manual de referencia de MySQL 12.21 Funciones de ventana https://dev.mysql .com/doc/refman/8.0/en/window-functions.html

La respuesta proporcionada aquí es un enfoque para las versiones de MySQL anteriores a la 8.0.

RESPUESTA ORIGINAL

MySQL no proporciona el tipo de función analítica que usaría para obtener una "suma acumulativa" en ejecución, como las funciones analíticas disponibles en otros DBMS (como Oracle o SQL Server).

Pero es posible emular algunas funciones analíticas usando MySQL.

Hay (al menos) dos enfoques viables:

Una es usar una subconsulta correlacionada para obtener el subtotal. Este enfoque puede ser costoso en conjuntos grandes y complicado si los predicados en la consulta externa son complicados. Realmente depende de cuán complicado sea "múltiples uniones en múltiples tablas". (Desafortunadamente, MySQL tampoco es compatible con CTE).

El otro enfoque es hacer uso de las variables de usuario de MySQL, para controlar el procesamiento de interrupciones. El "truco" aquí es ordenar los resultados de su consulta (usando un ORDEN POR) y luego envolver su consulta en otra consulta.

Daré un ejemplo del último enfoque.

Debido al orden en que MySQL realiza las operaciones, el cumulative_total la columna debe calcularse antes que el valor de id y day de la fila actual se guardan en variables de usuario. Es más fácil poner esta columna primero.

La vista en línea con alias como i (en la consulta a continuación) solo está ahí para inicializar las variables de usuario, en caso de que ya estén configuradas en la sesión. Si ya tienen valores asignados, queremos ignorar sus valores actuales, y la forma más sencilla de hacerlo es inicializándolos.

Su consulta original se envuelve entre paréntesis y se le asigna un alias, c en el ejemplo siguiente. El único cambio a su consulta original es la adición de una cláusula ORDER BY, por lo que podemos estar seguros de que procesamos las filas de la consulta en secuencia.

La selección externa comprueba si el id y day el valor de la fila actual "coincide" con la fila anterior. Si lo hacen, agregamos la amount de la fila actual al subtotal acumulativo. Si no coinciden, restablecemos el subtotal acumulativo a cero y agregamos el monto de la fila actual (o, más simplemente, asignamos el monto de la fila actual).

Después de haber realizado el cálculo del total acumulado, guardamos el id y day valores de la fila actual en variables de usuario, para que estén disponibles cuando procesemos la siguiente fila.

Por ejemplo:

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

Si es necesario devolver las columnas en un orden diferente, con el total acumulativo como la última columna, entonces una opción es envolver toda la declaración en un conjunto de paréntesis y usar esa consulta como una vista en línea:

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