JvLink To Importer

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操作だけで完了します。

この記事をシェア

Post