sql >> Databáze >  >> RDS >> Mysql

Jak udělat klouzavý součet, každý řádek musí obsahovat součet předchozích řádků

K emulaci analytických funkcí můžete využít uživatelské proměnné MySQL. (Existují také některé další přístupy, jako je použití semi-join nebo použití korelovaného dílčího dotazu. Mohu poskytnout řešení i pro ně, pokud si myslíte, že by mohly být vhodnější.)

Chcete-li emulovat analytickou funkci „součet“, zkuste něco takového:

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

„Trik“ spočívá v použití funkce IF k testování, zda order_number je nulový. Když je null, přidáme do proměnné hodnotu trvání, jinak proměnnou nastavíme na nulu.

Používáme inline zobrazení (jinak d , abyste zajistili, že proměnná @tot_dur bude inicializována na nulu.

POZNÁMKA:Při používání uživatelských proměnných MySQL, jako je tato, buďte opatrní. Ve výše uvedeném příkazu SELECT dochází k přiřazení proměnných v seznamu SELECT po příkazu ORDER BY, takže můžeme získat deterministické chování.

Tento dotaz nezpracovává "přerušení" v user_id. Abychom to získali, budeme potřebovat hodnotu user_id z předchozího řádku. Můžeme to zachovat v jiné uživatelské proměnné. Pořadí operací je deterministické a musíme se postarat o to, abychom provedli akumulaci PŘED přepsáním user_id z předchozího řádku.

Buď musíme změnit pořadí sloupců tak, aby se user_id objevilo za tot_dur (nebo zahrnout druhou kopii sloupce 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

Hodnoty vrácené v user_id a prev_user_id sloupce jsou identické. Tento sloupec „navíc“ lze odstranit nebo lze změnit pořadí sloupců zabalením dotazu (jako vložené zobrazení) do jiného dotazu, i když to stojí za výkon:

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

Tento dotaz ukazuje, že je možné, aby MySQL vracela zadanou sadu výsledků. Ale pro optimální výkon bychom chtěli spustit pouze dotaz ve vloženém zobrazení (s aliasem v ) a zpracovat změnu pořadí sloupců (umístěním sloupce user_id na první místo) na straně klienta, když se načítají řádky.

Další dva běžné přístupy využívají semi-join a korelovaný poddotaz, ačkoli tyto přístupy mohou být náročnější na zdroje při zpracování velkých sad.




  1. MySQL SELECT z více tabulek, více GROUP BY a group_concat?

  2. Je MS-SQL AND/OR podmíněné (provést vyhodnocení zkratu)?

  3. Měl by mít každý cizí klíč SQL Server odpovídající index?

  4. Resetovat pozici kurzoru v PDO