最近は生データの分析などで難しめのSQLを書くことが増えてきましたので、SQLの地力を鍛えるために、クックパッド・青木さんが著者の「10年戦えるデータ分析入門」を読みました。
本書は2部構成となっており、第1部ではSQLを使ったデータ分析のテクニック、第2部では分析のためのインフラシステムの構築についてそれぞれ述べられてます。
今回は第1部で知らなかったことや「これは使いでがあるな」と思ったことを、備忘録として整理します。
なお、掲載するSQLの一部は、DVD Rental ERモデルを使ったものとなっています。
PostgreSQL Sample Database
第3章
ランダムサンプリング
random()で0.0以上1未満の値が得られるので、where句で使うとランダムサンプリングができます。
select
*
from
payment
where
random() < 0.001
;
第5章
date_trunc関数
date_trunc('month', request_time)で、月以降を"1日 00:00:00"で切り捨てることができるので、年ごと・月ごと・日ごとなどに集計する場合に便利です。
select
*
from
payment
where
date_trunc('month', payment_date) = timestamp '2007-02-01 00:00:00'
;
第7章
coalesce関数
nullでない最初の値を返す。nullを含むカラムの値に初期値を設定するなど、使い途が多いです。
select
coalesce(staff_id, 0) as staffid
, count(*)
from
payment
group by
staffid
;
併売率
「ある商品を購入する場合、この商品も合わせて購入されている」割合を併売率というそうです。
セルフジョインで併売率を求めることができます。
select
c.item_id
, c.item_id2
, cast(c.order_count as real) / i.order_count as confidence
from
(
select
l.item_id
, r.item_id as item_id2
, count(distinct l.order_id) as order_count
from
order_details as l
inner join order_details as r
on l.order_id = r.order_id
and l.item_id <> r.item_id
where
l.order_time between timestamp '2015-04-01 00:00:00' and timestamp '2015-04-30 23:59:59'
) c
inner join
(
select
item_id
, count(distinct order_id) as order_count
from
order_details
where
order_time between timestamp '2015-04-01 00:00:00' and timestamp '2015-04-30 23:59:59'
) i
on c.item_id = i.item_id
;
第8章
恥ずかしながら、ウィンドウ関数をよく知らなかったので、8章は特に勉強になりました。
スカラーサブクエリ
select節やwhere節に式の一部としてもサブクエリが使える。
select
customer_id
, sum(amount)
, sum(amount) / (select sum(amount) from payment)
from
payment
group by
customer_id
;
sumウィンドウ関数
partition byで指定された値ごとに合計を算出できます。
ウィンドウ関数全体に言えることですが、行を集約されずに(group byと異なる点)、集約関数の結果の値を得られるという利点を持っています。
select
customer_id
, amount
, sum(amount) over (
partition by customer_id
) as customer_total
from
payment
;
rankウィンドウ関数
ウィンドウ関数の計算はwhereやhavingよりも後のため、絞り込む場合はサブクエリ化して、外側のクエリのwhereで絞る必要があります。
select
*
from (
select
customer_id
, payment_date
, amount
, rank() over (
partition by customer_id
order by amount desc
) as customer_rank
from
payment
) tmp
where
customer_rank <= 3
order by
payment_date
;
累積和
sumウィンドウ関数を使って累積和を計算します。
rows between unbounded preceding and current row
とすることで、「過去の全ての行から現在の行まで」と指定するのがミソで、ウィンドウフレームと言うそうです。
n行前ならn preceding
、n行後ならn following
という指定もできます。
select
t.sales_date
, t.staff_id
, t.sales_amount
, sum(sales_amount) over (
partition by staff_id
order by sales_date
rows between unbounded preceding and current row
) as cumulative_sales_amount
from (
select
date_trunc('day', payment_date) sales_date
, staff_id
, sum(amount) sales_amount
from
payment
group by
sales_date
, staff_id
) t
;
デシル分析
デシル分析は、ある値でソート(昇順・降順)し、行を任意のn個に等分割し、上から番号を振っていく分析で、売上額に応じて顧客をランク付けするといったケースに使えます。
select
t.sales_month
, t.customer_id
, t.sales_amount
, ntile(3) over (
partition by sales_month
order by sales_amount desc
) as customer_rank
from (
select
date_trunc('month', payment_date) sales_month
, customer_id
, sum(amount) sales_amount
from
payment
group by
sales_month
, customer_id
) t
;
第9章
generate_seriesテーブル関数(PostgreSQL)
連番のみのテーブルを生成する関数です。
以下の場合では、1から4の値が入った1カラム×4行のテーブルが生成されます。
select * from generate_series(1, 4);
JSONをカラムに展開する(PostgreSQL)
「そんなことできるんだ!」と驚いたSQLの1つですが、JSON文字列が入った1つのカラムを、キー毎に複数のカラムへ展開させています。
以下のSQLではjson_paramsに{"view_seconds":36,"scroll_ratio":0.7,"click_button":"discount_campaign"}
といった文字列が入っており、3つのカラムに展開しています。ポイントは3点です。
- PostgreSQLにはjson型があり、文字列からキャストできる
- json_to_recordテーブル関数で、view_seconds、scroll_ratio、click_buttonの3カラムからなる1行のテーブルを生成できる
- 最後にクロスジョインして、元のaccess_logテーブルにくっつける
select
l.request_time
, v.view_seconds
, v.scroll_ratio
, v.click_button
from
access_log as l
cross join json_to_record(cast(l.json_params as json))
as v (
view_seconds integer
, scroll_ratio real
, click_button text
)
;
第10章
lagウィンドウ関数
前の行の値を取得できる関数です。
前回のアクセスからどのくらい時間が経ったか、などを算出できます。
select
payment_date
, customer_id
, date_trunc('hour',
payment_date - lag(payment_date) over (
partition by customer_id
order by payment_date
)
) as payment_interval
from
payment
;
正規表現とのマッチング
今までlikeくらいしか使ってきませんでしたが、正規表現によるマッチングとCaseを組み合わせると、強力なツールになりそうです。
select
sum(case when name ~ 'o+[a-z]*e+[a-z]*y$' then 1 else 0 end)
from
category
;
まとめ
アクセスログのセッション化やコンバージョンレートの算出など、実際の分析に近いSQLとなっているので、理解が捗りました。
おすすめの1冊です。