Mysql
 sql >> Base de données >  >> RDS >> Mysql

Comment faire une somme glissante, chaque ligne doit inclure la somme des lignes précédentes

Vous pouvez utiliser des variables utilisateur MySQL pour émuler des fonctions analytiques. (Il existe également d'autres approches, comme l'utilisation d'une semi-jointure ou l'utilisation d'une sous-requête corrélée. Je peux également fournir des solutions pour celles-ci, si vous pensez qu'elles peuvent être plus appropriées.)

Pour émuler une fonction analytique "total cumulé", essayez quelque chose comme ceci :

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

Le "truc" ici est d'utiliser une fonction IF pour tester si oui ou non order_number est nul. Lorsqu'elle est nulle, nous ajoutons la valeur de durée à la variable, sinon, nous définissons la variable sur zéro.

Nous utilisons une vue en ligne (aliasée d , pour s'assurer que la variable @tot_dur est initialisée à zéro.

REMARQUE :Faites attention lorsque vous utilisez des variables utilisateur MySQL comme celle-ci. Dans l'instruction SELECT comme ci-dessus, l'affectation des variables dans la liste SELECT se produit après ORDER BY, nous pouvons donc obtenir un comportement déterministe.

Cette requête ne gère pas les "ruptures" dans user_id. Pour obtenir cela, nous allons avoir besoin de la valeur de user_id de la ligne précédente. Nous pouvons conserver cela dans une autre variable utilisateur. L'ordre des opérations est déterministe, et nous devons prendre soin de faire l'accumulation AVANT d'écraser l'user_id de la ligne précédente.

Nous devons soit réorganiser les colonnes afin que user_id apparaisse après tot_dur (ou inclure une deuxième copie de la colonne 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

Les valeurs retournées dans le user_id et prev_user_id colonnes est identique. Cette colonne "supplémentaire" pourrait être supprimée, ou les colonnes pourraient être réorganisées en enveloppant la requête (en tant que vue intégrée) dans une autre requête, bien que cela ait un coût en termes de performances :

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

Cette requête démontre qu'il est possible pour MySQL de renvoyer le jeu de résultats spécifié. Mais pour des performances optimales, nous voudrions exécuter uniquement la requête dans la vue en ligne (alias v ) et gérer la réorganisation des colonnes (en plaçant la colonne user_id en premier) côté client, lorsque les lignes sont récupérées.

Les deux autres approches courantes utilisent une semi-jointure et une sous-requête corrélée, bien que ces approches puissent être plus gourmandes en ressources lors du traitement de grands ensembles.