😺
SQLをかいてみた:MAUの日次推移
がく@ちゅらデータです。
「MAUの日次推移を出したいんですが、SQLでどうかいたらいいかわからないの、教えてドラ○もん!」
みたいな相談を受けたので、考えていましたので、メモとして残しておきます。
確認環境:Snowflake ( date_trunc が他DBMSでは動かないかもなので、その場合は適宜書き換えてください)
こんなテーブルがあったとします。日毎のログインユーザを保持しています。
user_id | login_date | user_id | login_date | |
---|---|---|---|---|
user_a | 2021-06-01 | user_e | 2021-06-04 | |
user_b | 2021-06-01 | user_f | 2021-06-04 | |
user_a | 2021-06-01 | user_g | 2021-06-04 | |
user_b | 2021-06-01 | user_h | 2021-06-04 | |
user_c | 2021-06-01 | user_i | 2021-06-04 | |
user_a | 2021-06-02 | user_j | 2021-06-04 | |
user_b | 2021-06-02 | user_c | 2021-07-01 | |
user_a | 2021-06-03 | user_a | 2021-07-02 | |
user_b | 2021-06-03 | user_b | 2021-07-02 | |
user_d | 2021-06-03 | user_a | 2021-07-03 | |
user_b | 2021-07-03 |
みたいなデータがあったとして、ここから欲しいデータとしては、月初からのユニーク user_id 数 を日毎に出したい
login_date | count | (unique user_id) | user_id |
---|---|---|---|
2021-06-01 | 3 | user_a, user_b, user_c | user_a, user_b, user_a, user_b, user_c |
2021-06-02 | 3 | user_a, user_b, user_c | user_a, user_b |
2021-06-03 | 4 | user_a, user_b, user_c, user_d | user_a, user_b, user_d |
2021-06-04 | 10 | user_a, user_b, user_c, user_d, user_e, user_f, user_g, user_h, user_i, user_j | user_e, user_f, user_g, user_h, user_i, user_j |
2021-07-01 | 1 | user_c | user_c |
2021-07-02 | 3 | user_a, user_b, user_c | user_a, user_b |
2021-07-03 | 3 | user_a, user_b, user_c | user_a, user_b |
with hoge as (
select
login_date
, user_id
from
user_login
group by
login_date
, user_id
)
, hoge2 as (
select distinct
h1.login_date as login_date,
h2.user_id as user_id
from
hoge h1
left outer join hoge h2
on h1.login_date >= h2.login_date
and date_trunc('month', h1.login_date) = date_trunc('month', h2.login_date)
)
select
login_date
, count( user_id )
from
hoge2
group by
login_date
order by
login_date
;
- 自己結合 ( hoge h1 left outer join hoge h2 )
- onに、h1.login_date >= h2.login_date
- 複数が紐づく形になる
- 月ごとにやりたいので、h1、h2は、同じ月にしてる
ウィンドウ関数でできないかなぁ・・・・と思ったのですが、いい方法が思いつかなかった><
※ with句に「hoge」はよくないので、ちゃんと使うときはしっかり意味のわかる文字列にしましょう!
累計を出すだけなら、自己結合は必要ないかなぁとも思ったのですが、月初からその日までのCOUNT DISTINCT だったので、ちょっとパフォーマンスに難がありそうなSQLになってしまいました
もし、もっといい解決方法があるよ!という方がいたらぜひ教えてください!
Discussion
記事ありがとうございます!
user_id
について、月ごとの最初のログイン日min(login_date)
を計算するuser_id
の数をcount()
する (= 各日に新しく追加されたuser_id
数)user_id
数について、各月内でlogin_date
順にsum
ウィンドウ関数で累積和を計算するという手順にすることで、範囲自己結合のような結合爆発が起こり得る結合を取り除くことができそうです。
下記の方法でも、結果に「新規ユーザが一人もいない日」を含めるための外部自己結合が含まれていますが、これは必ず 1:1 なので結合爆発が起こることがありません。
ページを更新したらベストなコメントがついていたので、補足します。(最初にコメントしたかった...)
Window関数でシンプルに書こうとすると、以下のクエリになりますが、エラーになります。( count句の後の
DISTINCT
を抜くと重複込みでカウントしてくれます。)count(distinct)
とover(order by)
の併用はできないみたいです。上記エラーを回避するため、あらかじめ月ごとにユーザーが最初にログインした日を求めて、重複を排除すると、Window関数でうまく求められるようになります。
ただ、このクエリでは「新規ユーザが一人もいない日」の行が無いため、 indigo13love さんのコメントのようなクエリを実行する必要があります。
@bgcanary さんへ
コメントありがとうございます!
一日コメントが遅れてしまい、ほんと申し訳ないです…
distinctの検証などほんとありがとうございます!そこは気にかけておりませんでした
SQLって、ほんと奥が深いと
うまくいかーん、けど結果だけは出た、もっといいのがあるよなぁと恥を忍んで記事化してよかったです!ありがとうございました!