Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como fazer uma soma contínua, cada linha precisa incluir a soma das linhas anteriores


Você pode usar variáveis ​​de usuário do MySQL para emular funções analíticas. (Também existem outras abordagens, como usar uma semijunção ou usar uma subconsulta correlacionada. Também posso fornecer soluções para elas, se você achar que elas podem ser mais apropriadas.)

Para emular uma função analítica "total em execução", tente algo assim:
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

O "truque" aqui é usar uma função IF para testar se order_number é nulo. Quando é nulo, adicionamos o valor de duração à variável, caso contrário, definimos a variável como zero.

Usamos uma visualização inline (alias como d , para garantir que a variável @tot_dur seja inicializada com zero.

NOTA:Tome cuidado ao usar variáveis ​​de usuário MySQL como esta. Na instrução SELECT como acima, a atribuição das variáveis ​​na lista SELECT acontece após o ORDER BY, para que possamos obter um comportamento determinístico.

Essa consulta não lida com "quebras" em user_id. Para conseguir isso, vamos precisar do valor de user_id da linha anterior. Podemos preservar isso em outra variável de usuário. A ordem das operações é determinística, e precisamos tomar cuidado para fazer a acumulação ANTES de sobrescrever o user_id da linha anterior.

Precisamos reordenar as colunas para que user_id apareça depois de tot_dur (ou incluir uma segunda cópia da coluna 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

Os valores retornados no user_id e prev_user_id colunas é idêntica. Essa coluna "extra" pode ser removida ou as colunas podem ser reordenadas envolvendo a consulta (como uma visualização inline) em outra consulta, embora isso tenha um custo de desempenho:
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

Essa consulta demonstra que é possível para o MySQL retornar o conjunto de resultados especificado. Mas para um desempenho ideal, gostaríamos de executar apenas a consulta na visualização em linha (alias como v ) e lida com a reordenação das colunas (colocando a coluna user_id primeiro) no lado do cliente, quando as linhas são recuperadas.

As outras duas abordagens comuns são usar uma semijunção e uma subconsulta correlacionada, embora essas abordagens possam consumir mais recursos ao processar grandes conjuntos.