PostgreSQLで始める競馬データ分析入門
PostgreSQLで始める競馬データ分析入門
競馬データベースを構築したら、次はSQLを使ってデータを分析します。この記事では、PostgreSQLで競馬データを分析するための基本的なSQLパターンを紹介します。プログラミング経験がなくても、SQLだけでかなりの分析ができます。
基本: 条件を絞ってデータを取得する
SQLの基本はSELECT文です。WHERE句で条件を絞り込みます。
-- 2025年の東京競馬場のG1レース一覧
SELECT race_code, kyosomei_hondai, kyori, shiba_babajotai_code
FROM race_shosai
WHERE kaisai_nen = '2025'
AND keibajo_code = '05'
AND grade_code = 'A'
ORDER BY kaisai_gappi;
これだけで「去年の東京G1で馬場状態はどうだったか」がわかります。
集計: 勝率・連対率を計算する
GROUP BYとCASE式を組み合わせれば、さまざまな切り口で勝率を出せます。
騎手別の成績集計
SELECT kishumei_ryakusho AS 騎手名,
COUNT(*) AS 出走数,
SUM(CASE WHEN kakutei_chakujun = '01' THEN 1 ELSE 0 END) AS 勝利数,
ROUND(100.0 * SUM(CASE WHEN kakutei_chakujun = '01' THEN 1 ELSE 0 END) / COUNT(*), 1) AS 勝率,
ROUND(100.0 * SUM(CASE WHEN kakutei_chakujun IN ('01','02') THEN 1 ELSE 0 END) / COUNT(*), 1) AS 連対率,
ROUND(100.0 * SUM(CASE WHEN kakutei_chakujun IN ('01','02','03') THEN 1 ELSE 0 END) / COUNT(*), 1) AS 複勝率
FROM umagoto_race_joho
WHERE kakutei_chakujun BETWEEN '01' AND '18'
GROUP BY kishumei_ryakusho
HAVING COUNT(*) >= 100
ORDER BY 勝率 DESC
LIMIT 20;
距離別の成績(特定の馬)
SELECT r.kyori AS 距離,
COUNT(*) AS 出走数,
SUM(CASE WHEN u.kakutei_chakujun IN ('01','02','03') THEN 1 ELSE 0 END) AS 複勝回数,
ROUND(100.0 * SUM(CASE WHEN u.kakutei_chakujun IN ('01','02','03') THEN 1 ELSE 0 END) / COUNT(*), 1) AS 複勝率
FROM umagoto_race_joho u
JOIN race_shosai r USING (race_code)
WHERE u.ketto_toroku_bango = '対象馬のID'
GROUP BY r.kyori
ORDER BY r.kyori;
Window関数: 前走との比較
PostgreSQLのWindow関数を使うと、「前走のタイム」「前走のオッズ」など、時系列での比較が簡単にできます。
-- 各馬の走破タイムと前走タイムの差
SELECT ketto_toroku_bango,
race_code,
soha_time,
LAG(soha_time) OVER (PARTITION BY ketto_toroku_bango ORDER BY race_code) AS 前走タイム,
soha_time - LAG(soha_time) OVER (PARTITION BY ketto_toroku_bango ORDER BY race_code) AS タイム差
FROM umagoto_race_joho
WHERE kakutei_chakujun BETWEEN '01' AND '18';
「前走よりタイムが改善した馬の次走成績」のような分析ができます。
-- 馬体重の推移を見る
SELECT ketto_toroku_bango,
race_code,
bataiju,
bataiju - LAG(bataiju) OVER (PARTITION BY ketto_toroku_bango ORDER BY race_code) AS 体重増減
FROM umagoto_race_joho
WHERE bataiju > 0;
CTE(WITH句): 複雑な分析を整理する
CTEを使うと、複雑なクエリをステップに分けて書けます。
-- 「前走1着→今走の成績」を分析
WITH prev AS (
SELECT ketto_toroku_bango,
race_code,
kakutei_chakujun,
LAG(kakutei_chakujun) OVER (PARTITION BY ketto_toroku_bango ORDER BY race_code) AS 前走着順
FROM umagoto_race_joho
WHERE kakutei_chakujun BETWEEN '01' AND '18'
)
SELECT
COUNT(*) AS 対象数,
ROUND(100.0 * SUM(CASE WHEN kakutei_chakujun = '01' THEN 1 ELSE 0 END) / COUNT(*), 1) AS 勝率,
ROUND(100.0 * SUM(CASE WHEN kakutei_chakujun IN ('01','02','03') THEN 1 ELSE 0 END) / COUNT(*), 1) AS 複勝率
FROM prev
WHERE 前走着順 = '01';
「前走1着馬がどのくらいの確率で連続好走するか」がわかります。
実践: 回収率を計算する
予想の良し悪しは勝率ではなく回収率で測ります。払戻テーブルとオッズを使って算出します。
-- 1番人気の単勝回収率を年別に算出
WITH fav AS (
SELECT u.race_code,
u.umaban,
u.tansho_odds,
u.kakutei_chakujun
FROM umagoto_race_joho u
WHERE u.tansho_odds = (
SELECT MIN(u2.tansho_odds) FROM umagoto_race_joho u2
WHERE u2.race_code = u.race_code AND u2.tansho_odds > 0
)
)
SELECT r.kaisai_nen AS 年,
COUNT(*) AS レース数,
ROUND(100.0 * SUM(CASE WHEN f.kakutei_chakujun = '01' THEN 1 ELSE 0 END) / COUNT(*), 1) AS 勝率,
ROUND(SUM(CASE WHEN f.kakutei_chakujun = '01' THEN f.tansho_odds ELSE 0 END)::numeric / COUNT(*) / 10, 1) AS 回収率
FROM fav f
JOIN race_shosai r USING (race_code)
GROUP BY r.kaisai_nen
ORDER BY r.kaisai_nen;
1番人気の回収率は一般的に75〜80%程度です。これを超える戦略を見つけることが予想モデルのゴールになります。
まとめ
| SQLテクニック | 用途 |
|---|---|
| WHERE + GROUP BY | 条件別の勝率・成績集計 |
| CASE式 | 勝利数・連対数のカウント |
| Window関数(LAG) | 前走との比較 |
| CTE(WITH句) | 複雑な分析の整理 |
| サブクエリ | 人気順の特定 |
SQLだけでもかなりの分析ができます。まずはデータベースにデータを蓄積するところから始めましょう。
JvLink To Importer を使えば、PostgreSQLへのデータ取込がGUI操作だけで完了します。
この記事をシェア