広告配信ログ集計時によく使うMySQLテクニック集

- 広告配信結果のレポート用に、配信ログをSQLで集計することが多くあるのですが、そこでよく使う簡単なテクニックの一部を載せてみます。

## 環境
- MySQL 5.6

## 前提
- 広告（ad_id）・配信日時（delivered_at）ごとの表示回数（imp）・クリック数（click）が保存されている
- 時刻別データ（hourly_statテーブル）と日別データ（daily_statテーブル）がある

<img width="737" alt="_MySQL_5_6_40__localhost_qiita_houly_stat.png" src="https://qiita-image-store.s3.amazonaws.com/0/131942/da3a3dcc-b6a0-c994-1d0d-7aab08b2d8cb.png">

<img width="745" alt="_MySQL_5_6_40__localhost_qiita_daily_stat.png" src="https://qiita-image-store.s3.amazonaws.com/0/131942/0da49ba4-0acc-608b-920b-b2dd2585c6e1.png">


## UTCをJSTに直す
- [convert_tz()](https://dev.mysql.com/doc/refman/5.6/ja/date-and-time-functions.html#function_convert-tz) でもできるが、面倒なのでいつも `+ interval 9 hour` で足し算してしまう。

```sql
select
  id, ad_id,
  delivered_at + interval 9 hour as delivered_at,
  imp, click
from hourly_stat;
```

<img width="486" alt="_MySQL_5_6_40__localhost_qiita_hourly_stat.png" src="https://qiita-image-store.s3.amazonaws.com/0/131942/87b0002b-eaf2-60c9-9e9f-f69a053f8889.png">

## 日別データを月別にまとめる
- date_format と group by を用いる

```sql
select
  ad_id,
  date_format(delivered_at + interval 9 hour, "%Y-%m") as delivered_month,
  sum(imp) as imp,
  sum(click) as click
from daily_stat
group by ad_id, delivered_month
order by ad_id, delivered_month;
```

<img width="503" alt="_MySQL_5_6_40__localhost_qiita_hourly_stat.png" src="https://qiita-image-store.s3.amazonaws.com/0/131942/7159e298-ddeb-9027-86fc-1279f1f9a171.png">

## 特定の条件でグループ化する
- 各レコードを特定の条件で分類して、分類別に合計などを集計したい場合に有効
- CASE文を使って特定の条件に応じて変わるカラムを作り出して、そのカラムをGROUP BY句に指定する
- 下記クエリでは、18:00〜4:59をnight、5:00〜11:59をmorning、12:00〜17:59をafternoonと定義してzoneというカラムを作った後、zoneの値ごとにimpやclickの合計を集計しています。
- group by句をコメントアウトして見比べてみると処理の流れがピンと来るかもしれません。

```sql
select
  case
    when hour(delivered_at + interval 9 hour) >= 18
      or hour(delivered_at + interval 9 hour) <= 4
    then "night"
    when hour(delivered_at + interval 9 hour) >= 5
     and hour(delivered_at + interval 9 hour) <= 11
    then "morning"
    else "afternoon" end as zone,
  sum(imp) as imp,
  sum(click) as click
from hourly_stat
where ad_id = 1
group by zone;
```

<img width="436" alt="_MySQL_5_6_40__localhost_qiita_hourly_stat.png" src="https://qiita-image-store.s3.amazonaws.com/0/131942/d113095f-1cca-33c0-a653-df100ebe22a6.png">

## 特定の条件を満たすレコードだけ合計する
- 特定の条件を満たすレコードだけ合計したいことがあります。Excelで言うとsumifですね。
- 例えば「表示回数（imp）が1時間に10,000回以上のレコードだけ集計した場合と、1時間に15,000回以上のレコードだけ集計した場合でクリック率を比較する」とかでしょうか。
- 複数条件ごとに結果を横に並べることができたり、グループ化して一括で集計できるところが利点です。
- ちなみにsumの中に単純に条件文だけを入れると、trueの時は1、falseの時は0として集計してくれるので、 `条件を満たすレコードの数` を集計できます。

```sql
select
  ad_id,
  sum(imp >= 10000 and imp < 15000)
    as "1時間に10,000〜15,000回表示したレコードの数",
  sum(case when imp >= 10000 and imp < 15000 then imp else 0 end)
    as "1時間に10,000〜15,000回表示した時の合計表示回数",
  sum(case when imp >= 10000 and imp < 15000 then click else 0 end)
    as "1時間に10,000〜15,000回表示した時のクリック数",
  sum(imp >= 15000)
    as "1時間に15,000回以上表示したレコードの数",
  sum(case when imp >= 15000 then imp else 0 end)
    as "1時間に15,000回以上表示した時の合計表示回数",
  sum(case when imp >= 15000 then click else 0 end)
    as "1時間に15,000回以上表示した時のクリック数"
from hourly_stat
group by ad_id;
```

<img width="619" alt="_MySQL_5_6_40__localhost_qiita_hourly_stat.png" src="https://qiita-image-store.s3.amazonaws.com/0/131942/ca0ee200-e6cb-9c30-e3db-61caaea7f198.png">


- まだまだ他にもあるんですが、複雑で説明が難しいものも多いので一旦ここまで。

## サンプルデータ
- もし上記クエリを自分で試したい方は、localhost上で下記コマンドを順番に実行すると上記と同じテーブル・レコードの環境を作成できます。

```sql
create database qiita;
use qiita;
```

```sql
CREATE TABLE `hourly_stat` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `ad_id` bigint(20) NOT NULL,
  `delivered_at` datetime NOT NULL,
  `imp` bigint(20) NOT NULL,
  `click` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
```

```sql
INSERT INTO `hourly_stat` (`id`, `ad_id`, `delivered_at`, `imp`, `click`)
VALUES
	(1,1,'2018-03-31 15:00:00',8000,120),
	(2,2,'2018-03-31 15:00:00',12000,130),
	(3,3,'2018-03-31 15:00:00',9000,110),
	(4,1,'2018-03-31 16:00:00',15000,140),
	(5,2,'2018-03-31 16:00:00',9000,150),
	(6,3,'2018-03-31 16:00:00',8000,130),
	(7,1,'2018-03-31 17:00:00',12000,120),
	(8,2,'2018-03-31 17:00:00',9000,130),
	(9,3,'2018-03-31 17:00:00',15000,110),
	(10,1,'2018-03-31 18:00:00',9000,140),
	(11,2,'2018-03-31 18:00:00',10000,150),
	(12,3,'2018-03-31 18:00:00',12000,130),
	(13,1,'2018-03-31 19:00:00',8000,120),
	(14,1,'2018-03-31 20:00:00',12000,130),
	(15,1,'2018-03-31 21:00:00',9000,110),
	(16,1,'2018-03-31 22:00:00',15000,140),
	(17,1,'2018-03-31 23:00:00',9000,150),
	(18,1,'2018-04-01 00:00:00',8000,130),
	(19,1,'2018-04-01 01:00:00',12000,120),
	(20,1,'2018-04-01 02:00:00',9000,130),
	(21,1,'2018-04-01 03:00:00',15000,110),
	(22,1,'2018-04-01 04:00:00',9000,140),
	(23,1,'2018-04-01 05:00:00',10000,150),
	(24,1,'2018-04-01 06:00:00',12000,130),
	(25,1,'2018-04-01 07:00:00',8000,120),
	(26,1,'2018-04-01 08:00:00',12000,130),
	(27,1,'2018-04-01 09:00:00',9000,110),
	(28,1,'2018-04-01 10:00:00',15000,140),
	(29,1,'2018-04-01 11:00:00',9000,150),
	(30,1,'2018-04-01 12:00:00',8000,130),
	(31,1,'2018-04-01 13:00:00',12000,120),
	(32,1,'2018-04-01 14:00:00',9000,130),
	(33,1,'2018-04-01 15:00:00',15000,110),
	(34,1,'2018-04-01 16:00:00',9000,140),
	(35,1,'2018-04-01 17:00:00',10000,150),
	(36,1,'2018-04-01 18:00:00',12000,130);
```

```sql
CREATE TABLE `daily_stat` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `ad_id` bigint(20) NOT NULL,
  `delivered_at` datetime NOT NULL,
  `imp` bigint(20) NOT NULL,
  `click` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
```

```sql
INSERT INTO `daily_stat` (`id`, `ad_id`, `delivered_at`, `imp`, `click`)
VALUES
	(1,1,'2018-03-31 15:00:00',8000,120),
	(2,2,'2018-03-31 15:00:00',12000,130),
	(3,3,'2018-03-31 15:00:00',9000,110),
	(4,1,'2018-04-01 15:00:00',15000,140),
	(5,2,'2018-04-01 15:00:00',9000,150),
	(6,3,'2018-04-01 15:00:00',8000,130),
	(7,1,'2018-04-02 15:00:00',12000,120),
	(8,2,'2018-04-02 15:00:00',9000,130),
	(9,3,'2018-04-02 15:00:00',15000,110),
	(10,1,'2018-04-03 15:00:00',9000,140),
	(11,2,'2018-04-03 15:00:00',10000,150),
	(12,3,'2018-04-03 15:00:00',12000,130);
```