集計する
読了目安 約3分
COUNT などの集計関数と、GROUP BY、HAVING によるグループごとの集計を学びます。
- たくさんの行 元データ
- GROUP BY 同じ値でまとめる
- COUNT グループごとに数える
この章の目次
集計関数
投稿は全部で何件あるか。 数えるのは COUNT です。
SELECT COUNT(*) FROM posts;+----------+
| COUNT(*) |
+----------+
| 6 |
+----------+COUNT のように、行の集まりを 1 つの値にまとめる関数を集計関数と呼びます。 主なものは 5 つです。
- COUNT(*):行数。
- SUM(列):合計。
- AVG(列):平均。
- MAX(列):最大値。
- MIN(列):最小値。
WHERE と組み合わせれば、数える対象を絞れます。
SELECT COUNT(*) FROM posts WHERE user_id = 1;+----------+
| COUNT(*) |
+----------+
| 3 |
+----------+GROUP BY でグループごとに集計する
「ユーザーごとの投稿数」を上のクエリで求めると、user_id を変えて人数ぶん打つことになります。 GROUP BY を使うと、行をグループに分けてからグループごとに集計関数を適用する処理が 1 回のクエリで書けます。
SELECT user_id, COUNT(*) AS post_count
FROM posts
GROUP BY user_id;+---------+------------+
| user_id | post_count |
+---------+------------+
| 1 | 3 |
| 2 | 2 |
| 3 | 1 |
+---------+------------+GROUP BY user_id が「user_id の値が同じ行を 1 グループにまとめよ」、SELECT 側の COUNT(*) が「各グループの行数を出せ」です。
AS post_count は結果の列に付ける名前で、次章の Go から読むときに効きます。
前章の JOIN と組み合わせれば、番号ではなく名前で出せます。
SELECT users.name, COUNT(*) AS post_count
FROM posts
JOIN users ON posts.user_id = users.id
GROUP BY users.id;+-------+------------+
| name | post_count |
+-------+------------+
| alice | 3 |
| bob | 2 |
| carol | 1 |
+-------+------------+AS で付けた名前は ORDER BY でも使え、ORDER BY post_count DESC LIMIT 3 を足せば投稿数ランキングが 1 クエリで書けます。
COUNT には
COUNT(列名)という形もあり、こちらはその列が NULL の行を数えません。
HAVING は集計後の絞り込み
「2 件以上投稿しているユーザーだけ」を WHERE COUNT(*) >= 2 と書くと、エラーになります。
WHERE は行をグループに分ける前に 1 行ずつ絞り込む句で、その時点では COUNT(*) の値がまだ存在しないからです。
集計した後の値で絞り込むには HAVING を使います。
SELECT user_id, COUNT(*) AS post_count
FROM posts
GROUP BY user_id
HAVING COUNT(*) >= 2;+---------+------------+
| user_id | post_count |
+---------+------------+
| 1 | 3 |
| 2 | 2 |
+---------+------------+WHERE と HAVING は併用できます(期間で絞ってから件数で絞る、など)。
SQLの句は実行順が決まっていて、コードも実行順に書かないとエラーになります。但し、SELECT文であることを特徴づけるSELECT句だけ実行順を飛ばして最初に書くことになっています。これまでのコードも全てこの順番で書かれていたはずです。
| 実行順 | SQLの句 | 役割 |
|---|---|---|
| 1 | FROM | 参照するテーブルを決定する |
| 2 | JOIN | テーブル同士を結合する(ON の条件を評価) |
| 3 | WHERE | 行を絞り込む(グループ化前) |
| 4 | GROUP BY | 行をグループ化する |
| 5 | HAVING | グループを絞り込む |
| 6 | SELECT | 出力する列・式を計算する |
| 7 | ORDER BY | 結果を並べ替える |
| 8 | LIMIT | 取得する行数を制限する |
アプリ側で数えてはいけない場面
全 posts を取ってきて Go のループで map に数え上げても、結果は GROUP BY と同じです。 しかし投稿が 100 万件なら、数個の数字のために 100 万行がネットワークを渡ります。 数える作業はデータを手元に持つ DB に任せ、結果の数字だけ受け取ります。
「アプリのループを GROUP BY 1 発に置き換える」は ISUCON で頻出の改善パターンで、/isucon/03-nplus1/ でまとめて扱います。