第3回:GROUP BY と集計関数(データをグループ化して平均・合計・件数を瞬時に計算する技術)
これまでは「1頭ずつ、1行ずつの個別データ」を取り出して見ていました。
しかし、競馬分析では**「馬番ごとの平均勝率は?」「競馬場ごとの平均先行力は?」「種牡馬ごとの産駒数は?」**のように、データをまとまり(グループ)にして計算したい場面が数多くあります。
これを一瞬で解決するのが GROUP BY(グループ・バイ)です。Excelでいう「ピボットテーブル」と同じ作業を、SQLが裏で瞬時に計算してくれます。
生徒40人全員の点数を1人ずつ並べるのではなく、「1組」「2組」「3組」というクラスごとにまとめて、各クラスの【平均点】や【最高点】を出すようなものです。
「GROUP BY horse_number(馬番ごとに分けて)、AVG(senko)(それぞれの先行力の平均)を計算して見せて!」
グループ分けしたあと、何を計算させたいかを SELECT の中に書きます。
| 関数名 | やりたい計算 | 書き方の例 | 出力されるもの |
|---|---|---|---|
| COUNT( ) | 件数を数える | COUNT(*) AS 出走頭数 |
そのグループにあるデータの「総数」 |
| AVG( ) | 平均を出す | ROUND(AVG(st), 1) AS 平均ST |
そのグループの「平均値」(ROUNDで小数第1位に丸める) |
| MAX( ) | 一番大きい値を出す | MAX(senko) AS 最高先行力 |
そのグループの中での「最大値(トップ)」 |
| MIN( ) | 一番小さい値を出す | MIN(finish_time) AS 最速タイム |
そのグループの中での「最小値(底値・最速)」 |
| SUM( ) | 全部足し合わせる | SUM(prize_money_1st) AS 総賞金 |
そのグループの数値を合計した「総額」 |
AVG(st) で平均を計算すると、58.33333333... のように小数が長く続いてしまいます。
そこで ROUND(AVG(st), 1) と書くと、**「小数第1位(例: 58.3)」**に綺麗に四捨五入してくれます。
ストライド指数テーブル(kol_stride)から、競馬場ごとの登録頭数と平均ST指数を算出する基本構文です。
SELECT
venue_code AS 場コード,
COUNT(*) AS 総頭数,
ROUND(AVG(st), 1) AS 平均ST指数
FROM kol_stride
WHERE race_date LIKE '202608%'
GROUP BY venue_code
ORDER BY venue_code ASC;
| 書いたコード | 店員(データベース)への命令内容 |
|---|---|
FROM kol_stride |
「ストライド指数の本棚を開いて!」 |
WHERE race_date LIKE '202608%' |
「まず、2026年8月のデータだけに絞り込んで!」(事前フィルター) |
GROUP BY venue_code |
「残ったデータを、**場コード(競馬場)ごとに山分け**して!」 |
SELECT venue_code, COUNT(*), AVG(st) |
「各競馬場の『場コード』『集まった頭数』『ST指数の平均』を並べて見せて!」 |
ORDER BY venue_code ASC |
「場コードの番号が若い順(昇順)に並べて出力して!」 |
| 場コード | 総頭数 | 平均ST指数 |
|---|---|---|
| 03 (小倉) | 288 | 54.2 |
| 07 (新潟) | 312 | 55.8 |
| 08 (札幌) | 264 | 56.1 |
初心者が最もつまずきやすいのが、「条件の絞り込みで WHERE と HAVING のどちらを使うか」です。
違いはたった1つ、**「計算する前に絞るか(WHERE)」「計算したあとに絞るか(HAVING)」**です。
| キーワード | 動くタイミング | 使い分けのルール |
|---|---|---|
| WHERE | グループ化して計算する前 | WHERE race_date >= '20260101' のように、テーブルに最初から入っている元の行を絞り込むとき。 |
| HAVING | グループ化して計算したあと | HAVING COUNT(*) >= 20 のように、**計算し終わった集計結果(件数や平均など)**に対して絞り込むとき。 |
「馬番ごとの平均ST指数を出したいが、出走数が少ない(10頭未満の)馬番は除外したい」という場合のクエリです。
SELECT
horse_number AS 馬番,
COUNT(*) AS 出走回数,
ROUND(AVG(st), 1) AS 平均ST指数
FROM kol_stride
WHERE race_date LIKE '2026%'
GROUP BY horse_number
HAVING COUNT(*) >= 10
ORDER BY CAST(horse_number AS INTEGER) ASC;
| 馬番 | 出走回数 | 平均ST指数 |
|---|---|---|
| 01 | 142 | 54.8 |
| 02 | 138 | 55.1 |
| ... | ... | ... |
| 16 | 24 | 56.3 |
「種牡馬ごとに、産駒の【牡馬の数】と【牝馬の数】を横並びで別々に数えたい」という上級分析も、CASE WHEN を組み合わせれば簡単に実現できます。
COUNT(CASE WHEN sex_code = '1' THEN 1 END) ➔ 「もし性別コードが 1(牡馬)なら、1頭としてカウントする」という命令になります。
SELECT
sire_horse_name AS 種牡馬名,
COUNT(*) AS 総産駒数,
COUNT(CASE WHEN sex_code = '1' THEN 1 END) AS 牡馬数,
COUNT(CASE WHEN sex_code = '2' THEN 1 END) AS 牝馬数
FROM kol_uma
WHERE sire_horse_name IN ('キタサンブラック', 'ロードカナロア', 'エピファネイア')
GROUP BY sire_horse_name;
| 種牡馬名 | 総産駒数 | 牡馬数 | 牝馬数 |
|---|---|---|---|
| エピファネイア | 520 | 268 | 252 |
| キタサンブラック | 310 | 162 | 148 |
| ロードカナロア | 780 | 410 | 370 |
以下の枠内のSQLをコピーし、Alma Core Pro の入力欄に貼り付けて ▶ SQL実行 ➔ 📊 Excel用コピー (TSV) をお試しください。
レシピ 1 馬番別の平均能力値分析(内枠・外枠の傾向調査)
馬番ごとの出走頭数と、先行力・追走力・瞬発力の平均値を一括集計し、枠ごとの有利不利を分析します。
SELECT
horse_number AS 馬番,
COUNT(*) AS 出走頭数,
ROUND(AVG(st), 1) AS 平均ST指数,
ROUND(AVG(senko), 1) AS 平均先行力,
ROUND(AVG(tsuiso), 1) AS 平均追走力,
ROUND(AVG(shunpatsu), 1) AS 平均瞬発力
FROM kol_stride
WHERE race_date LIKE '2026%'
GROUP BY horse_number
HAVING COUNT(*) >= 10
ORDER BY CAST(horse_number AS INTEGER) ASC;
馬番ごとの平均能力値が綺麗に集計され、Excelでの枠順バイアス分析が即座に可能になります
レシピ 2 競馬場別の指数分布サマリー(レベル感と最高値の把握)
各開催場ごとの登録馬数と平均先行力・最高ST指数を瞬時に集計します。
SELECT
venue_code AS 場コード,
COUNT(*) AS 総登録頭数,
ROUND(AVG(st), 1) AS 平均ST指数,
MAX(st) AS 最高ST指数,
ROUND(AVG(senko), 1) AS 平均先行力,
MAX(senko) AS 最高先行力
FROM kol_stride
WHERE race_date LIKE '2026%'
GROUP BY venue_code
ORDER BY venue_code ASC;
競馬場ごとのメンバーレベルや先行馬の割合の違いが一目で数値化されます
レシピ 3 競走馬マスタ(kol_uma)から主要種牡馬の産駒数ランキング
登録頭数が15頭以上の主要種牡馬を対象に、牡馬・牝馬の頭数バランスと合計登録数をランキング化します。
SELECT
sire_horse_name AS 種牡馬名,
COUNT(*) AS 登録頭数,
COUNT(CASE WHEN sex_code = '1' THEN 1 END) AS 牡馬数,
COUNT(CASE WHEN sex_code = '2' THEN 1 END) AS 牝馬数
FROM kol_uma
WHERE sire_horse_name IS NOT NULL AND sire_horse_name != ''
GROUP BY sire_horse_name
HAVING COUNT(*) >= 15
ORDER BY COUNT(*) DESC
LIMIT 30;
主要種牡馬の勢力図と性別バランスがExcel上で一発で集計できます
| よくあるミス | 間違ったコード例 | 原因と解決策 |
|---|---|---|
| ① グループ化していない個別項目を書く | SELECT venue_code, horse_number, AVG(st) FROM kol_stride GROUP BY venue_code; |
GROUP BY venue_code(競馬場ごと)にまとめているのに、horse_number(個別の馬番)を書くと、どの馬番を出せばいいか分からずエラーや意図しないデータになります。SELECTには「GROUP BYに書いた項目」か「集計関数(AVG等)」だけを書きましょう。 |
| ② WHERE に集計関数を書いてしまう | WHERE AVG(st) >= 60 |
WHERE は計算前の行を見る場所なので、平均値(AVG)は書けません。集計後の条件は HAVING AVG(st) >= 60 に書きましょう。 |