Upload
ken-morishita
View
826
Download
3
Embed Size (px)
Citation preview
BigQuery勉強会~Standard SQL Dialect~
2016/8/23森下健 @ Sprocket
1
今回の内容• Standard SQLの基本
• Standard SQLの便利機能を紹介
• (user, action, time) というイベントデータの処理例• 間隔ベースのセッションIDを付ける• ユーザやセッションの属性とイベントデータを⼀つのテーブルにする• あるAction Xが発⽣した後、時刻T以内にAction Zが発⽣する割合の集計
2
Standard SQLの基本
3
Standard SQL Dialect• 2016年6⽉にBeta公開された書き⽅
• ⼤規模かつ複雑な処理の記述が可能• (以前のをよく知らないので差分はわからないんですが)
• BQは処理が複雑でも料⾦同じなのでかなり使いみちがある
• なぜ「Standard」なのかと⾔われてもわかりません...
https://cloud.google.com/blog/big-data/2016/06/bigquery-111-now-with-standard-sql-iam-and-partitioned-tables4
Standard SQLモードに切り替えて使う
②チェックを外す①Show Optionsで開いて
5
Time Partitioning• _PARTITIONTIME というのがあるらしいが、よく知らない…• https://cloud.google.com/bigquery/docs/partitioned-tables
• 今の旧来のテーブルならば
という形で取れます
SELECT ...FROM `my_dataset.table_name_*` WHERE _TABLE_SUFFIX BETWEEN '20160720' AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
①
②
6
細かいこと• JSON_EXTRACT 関数が消えた
• DISTINCT がまともに動く感じになった
• /* ... */ でコメントになる
• 最後の結果セットをOrder Byできるサイズの上限は結構低い• 数千万レコードでもNGだったりする• ※ 完全ではないが対処法はある
• 昔はTipsとしてよく⾔われていた EACH とかもう不要
7
Standard SQLの便利機能 3つWITHAnalytic FunctionNest
8
WITH構⽂
SELECT ...FROM ( SELECT ... FROM (SELECT ...FROM original_data
) as A) as B
WITH A as (SELECT ...FROM original_data
)
, B as (SELECT ...FROM A
)
SELECT ...FROM B
■ 従来記法 ■ WITH記法
⼊れ⼦地獄から解放され、上から下に処理を書けるようになった 9
Analytic Function• 以前の Window Function (今回呼び名変わった?)• ※新しい機能ではないが紹介
• 普通のSQL(例えばMySQLの)ではできない、レコード間の演算ができる
10
11
user action time1 A 11 B 11 X 51 B 81 Z 92 A 22 B 52 D 10
例えば、ユーザ毎にアクセス順序でのSequence IDを付けたい
user action time seq1 A 1 11 B 1 21 X 5 31 B 8 41 Z 9 52 A 2 12 B 5 22 D 10 3
SELECT *, RANK() OVER (PARTITION BY user ORDER BY time) as seqFROM dataORDER BY user, seq
RANK():1から順に連番をふる
12
user action time1 A 11 B 11 X 51 B 81 Z 92 A 22 B 52 D 10
例えば、ユーザ毎に前回アクセスからの経過時刻をとりたい
WITH add_last_time as (SELECT *,
LAG(time) OVER (PARTITION BY user ORDER BY time) as last_timeFROM data
), add_span as (SELECT *,
time - last_time as spanFROM add_last_time
)select * from add_span ORDER BY user, time
user action time last_time span1 A 1 null null1 B 1 1 01 X 5 1 41 B 8 5 31 Z 9 8 12 A 2 null null2 B 5 2 32 D 10 5 5
LAG(X):⼀つ前のXの値を取得する
13
user action time1 A 11 B 11 X 51 B 81 Z 92 A 22 B 52 D 10
例えば、ユーザ毎に最初の2つのデータを残したい
WITH add_seq as (SELECT *, RANK() OVER (PARTITION BY user ORDER BY time) as seqFROM data
), omit_records as (SELECT * FROM add_seq WHERE seq <= 2
)SELECT * FROM omit_recordsORDER BY user, seq
user action time seq1 A 1 11 B 1 22 A 2 12 B 5 2
Nest (⼊れ⼦)• これがかなり便利
• 普通のSQLには無い感覚なので慣れは必要だが慣れるとデータのこねくり回しが捗ります
• 保存・Scanデータ量の削減になる• user_idのような⽂字列などの場合特に
14
15
例えば、Actionを⾏ったユーザをAction毎にまとめる(ユーザの重複OKの場合)
user action time1 A 11 B 31 X 51 B 201 Z 222 A 22 B 302 X 603 X 44 B 24 A 4
Row action users1 A 1
24
2 B 1124
3 X 123
4 Z 1
重複しているよ?
WITH action_users as (SELECT action, ARRAY_AGG(user) as usersFROM dataGROUP by action
)SELECT * FROM action_usersORDER BY action
ARRAY_AGG():GROUP BYと共に使い、値をARRAY型の1レコードにまとめる
16
例えば、Actionを⾏ったユーザをAction毎にまとめる(ユニークユーザの場合)
user action time1 A 11 B 31 X 51 B 201 Z 222 A 22 B 302 X 603 X 44 B 24 A 4
WITH action_users as (SELECT action, ARRAY_AGG(user) as usersFROM dataGROUP by action
), unique_action_users as (
SELECT action,ARRAY(
SELECT DISTINCT user FROM UNNEST(users) as user) as users
FROM action_users)SELECT * FROM unique_action_users ORDER BY action
Row action users1 A 1
24
2 B 124
3 X 123
4 Z 1
ARRAY():Sub Queryが単⼀列の複数(0~N)レコードを返す場合に使う.
UNNEST():ARRAY型を展開する。JOIN的になる場合とグループ単位で処理したい場合で少し意味合いが違う感じ.
Row action users1 A 1
24
2 B 1124
3 X 123
4 Z 1
■Group By する必要がある場合: ARRAY_AGG(STRUCT(...)) ... GROUP BY
SELECT ...,ARRAY_AGG(STRUCT(col1, col2, ...))
FROM ...GROUP BY X
複数列をArray型に⼊れる⽅法
■Group By する必要がない場合 or 何か計算処理する場合: ARRAY(SELECT STRUCT(...))
SELECT ...,ARRAY(
SELECT STRUCT(col1 * 2, SUM(col2) OVER ... , ...) FROM ...)
FROM ... STRUCT():レコード型を作る。レコード型という単⼀列になる。
(user, action, time) というイベントデータの処理例
18
19
間隔ベースのセッションIDを付ける
user action time1 A 11 B 31 X 51 B 201 Z 222 A 22 B 302 X 35
やりたいこと: ユーザ毎にアクセス間隔が10以上なら別のSessionIDを付けるようにしたい
1
212
user action time last_time new_session session_seq1 A 1 null 0 11 B 3 1 0 11 X 5 3 0 11 B 20 5 1 21 Z 22 20 0 22 A 2 null 0 12 B 30 2 1 22 X 35 30 0 2
WITH add_last_time as (SELECT *,
LAG(time) OVER (PARTITION BY user ORDER BY time) as last_timeFROM data
), add_span as (
SELECT *, IF(time - last_time >= 10, 1, 0) as new_sessionFROM add_last_time
)SELECT *, 1+SUM(new_session) OVER (PARTITION BY user ORDER BY time) as session_seqFROM add_span ORDER BY user, time
user session_seq session_start_time session_end_time session_time_span actions.action actions.time1 1 1 5 4 A 1
B 3X 5
1 2 20 22 2 B 20Z 22
2 1 2 2 0 A 22 2 30 35 5 B 30
20
ユーザやセッションの属性とイベントデータを⼀つのテーブルにする: Step1
user action time1 A 11 B 31 X 51 B 201 Z 222 A 22 B 302 X 35
やりたいこと:ユーザやセッションの属性とイベントデータを⼀つのテーブルにする
WITH ... 略 ..., add_session_seq as (
SELECT *, 1+SUM(new_session) OVER (PARTITION BY user ORDER BY time) as session_seqFROM add_span
)SELECT user, session_seq,
MIN(time) as session_start_time,MAX(time) as session_end_time,MAX(time) - MIN(time) as session_time_span,ARRAY_AGG(STRUCT(action, time)) as actions
FROM add_session_seqGROUP BY user, session_seq
user session_seq ... actions.action actions.time1 1 A 1
B 3X 5
1 2 B 20Z 22
2 1 A 22 2 B 30
21
ユーザやセッションの属性とイベントデータを⼀つのテーブルにする: Step2
やりたいこと:ユーザやセッションの属性とイベントデータを⼀つのテーブルにする
WITH ... 略 ..., group_by_user as (
SELECT user,MAX(session_seq) as session_num,ARRAY_AGG(STRUCT(
session_start_time, session_end_time, session_time_span, actions)) as sessions
FROM group_by_session GROUP BY user)SELECT * FROM group_by_userORDER BY user
user session_num sessions.session_start_time sessions.session_end_time sessions.session_time_span sessions.actions.action sessions.actions.time1 2 1 5 4 A 1
B 3X 5
20 22 2 B 20Z 22
2 2 2 2 0 A 230 35 5 B 30
X 35
22
ユーザ毎に, あるAction Xが発⽣した後、時刻T以内にAction Zが発⽣する割合の集計
やりたいこと: Xの後に時刻10以内にZが発⽣する割合を求める
user action time1 A 11 X 51 B 91 Z 112 A 22 X 152 Z 403 Z 33 X 64 X 44 X 54 Z 7
1
0 (時刻10以内でない)
0 (ZがXの後で発⽣していない)
1 (最初のXに対してのみ計算するとする)
X=4, Z=2 → 50% とカウントしたい
23
ユーザ毎に, あるAction Xが発⽣した後、時刻T以内にAction Zが発⽣する割合の集計
user action time1 A 11 X 51 B 91 Z 112 A 22 X 152 Z 403 Z 33 X 64 X 44 X 54 Z 7
user first_x_time action_times.action action_times.time1 5 X 5
Z 112 15 X 15
Z 403 6 Z 3
X 64 4 X 4
X 5Z 7
WITH group_by_user as (SELECT user, ARRAY_AGG(STRUCT(action, time)) as action_timesFROM data WHERE action in ('X', 'Z')
GROUP BY user), pickup_first_x_timeas (
SELECT user, action_times,(
SELECT MIN(time)FROM UNNEST(action_times) WHERE action='Xʼ
) as first_x_timeFROM group_by_user
)SELECT * FROM pickup_first_x_time ORDER BY user
24
ユーザ毎に, あるAction Xが発⽣した後、時刻T以内にAction Zが発⽣する割合の集計
user first_x_time action_times.action action_times.time1 5 X 5
Z 112 15 X 15
Z 403 6 Z 3
X 64 4 X 4
X 5Z 7
WITH ..., count_z as (
SELECT user, first_x_time,(
SELECT COUNT(*)FROM UNNEST(action_times) as aWHERE a.action = 'ZʼAND a.time BETWEEN first_x_time AND first_x_time + 10
) as z_count,action_times
FROM pickup_first_x_time)SELECT * FROM count_z ORDER BY user
user first_x_time z_count action_times.action action_times.time1 5 1 X 5
Z 112 15 0 X 15
Z 403 6 0 Z 3
X 64 4 1 X 4
X 5Z 7
25
WITH ...
SELECT COUNT(*) as x_cnt,COUNTIF(z_count > 0) as z_cnt
FROM count_z
user first_x_time z_count action_times.action action_times.time1 5 1 X 5
Z 112 15 0 X 15
Z 403 6 0 Z 3
X 64 4 1 X 4
X 5Z 7
ユーザ毎に, あるAction Xが発⽣した後、時刻T以内にAction Zが発⽣する割合の集計
x_cnt z_cnt4 2
まとめ• Standard SQLは結構強い
• まだβ版なので仕様が変わる可能性もあるが、考え⽅などは変わらないので慣れておくのは良いこと
• 是⾮活⽤していきましょう!
26