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

Cómo hacer una suma móvil, cada fila debe incluir la suma de las filas anteriores

Puede utilizar las variables de usuario de MySQL para emular funciones analíticas. (También hay otros enfoques, como usar una combinación parcial o usar una subconsulta correlacionada. También puedo proporcionar soluciones para esos, si cree que pueden ser más apropiados).

Para emular una función analítica de "total acumulado", intente algo como esto:

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

El "truco" aquí es usar una función IF para probar si order_number es nulo. Cuando es nulo, agregamos el valor de duración a la variable; de ​​lo contrario, establecemos la variable en cero.

Usamos una vista en línea (con alias como d , para garantizar que la variable @tot_dur se inicialice a cero.

NOTA:tenga cuidado al usar variables de usuario de MySQL como esta. En la declaración SELECT como la anterior, la asignación de las variables en la lista SELECT ocurre después de ORDER BY, por lo que podemos obtener un comportamiento determinista.

Esa consulta no maneja "descansos" en user_id. Para obtener eso, vamos a necesitar el valor de user_id de la fila anterior. Podemos conservar eso en otra variable de usuario. El orden de las operaciones es determinista y debemos tener cuidado de realizar la acumulación ANTES de sobrescribir el ID_usuario de la fila anterior.

Necesitamos reordenar las columnas para que user_id aparezca después de tot_dur (o incluir una segunda copia de la columna 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

Los valores devueltos en el user_id y prev_user_id columnas es idéntica. Esa columna "adicional" podría eliminarse, o las columnas podrían reordenarse ajustando la consulta (como una vista en línea) en otra consulta, aunque esto tiene un costo de rendimiento:

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

Esa consulta demuestra que es posible que MySQL devuelva el conjunto de resultados especificado. Pero para un rendimiento óptimo, nos gustaría ejecutar solo la consulta en la vista en línea (con alias v ), y manejar el reordenamiento de las columnas (colocando la columna user_id primero) en el lado del cliente, cuando se recuperan las filas.

Los otros dos enfoques comunes son el uso de una semi-unión y el uso de una subconsulta correlacionada, aunque estos enfoques pueden consumir más recursos cuando se procesan conjuntos grandes.