0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

G1勝ち馬データベースを作りたいという問題意識

Last updated at Posted at 2025-12-21

はじめに

この記事はばんえい猫のアルルさん主催のMisskey.io競馬部アドカレ Advent Calendar 2025 21日目の記事です.
昨日の記事は谷原さんの "アオラキを3年間見てきての主観など" でした(まだ上がっていないようでしょうか...?).

昨年1を除き, 1本はグレードの話を書いてきたのですが, 今年もまたグレードの話を(また違う観点から)書きます.

G1とは

ここは技術ブログであって競馬ブログではないので, 前提知識としてG1の説明をしておきます.

雑な説明

競馬のトップレースのことです. 現在日本には25あります2.
いろんな馬がこれらレースの勝者になることを目指して日々鍛錬しています.

この記事が出る1週間後ぐらいの12/29に開催される "東京大賞典" などが相当します.

ちゃんとした説明

実際のところ, G1というとかなり多くの意味があるのですが, 少なくとも国際的なコンセンサスを得ることができるであろうG1の定義は以下の通りです3.

国際競馬統括機関連盟(International Federation of Horseracing Authorities, IFHA)が発行するInternational Cataloguing Standards(通称Bluebook)のPart Iに "G1" として掲載されているレース

この記事でも以下, 単に "G1" と言う場合はこの定義に従います.

歴史的問題

ですが, 日本で "G1" という単語はかなり複雑な歴史的経緯を辿っています.

日本で "G1" が辿った歴史

詳細に説明するとなんか本1つ書けそうなので, かいつまんで説明すると

  • 昔は "G1" という言葉は割と自由に振り回していいものだった
  • そのため, 各競馬組織が "G1と称されるレース" を独自に設定していた(たとえ実際にはG1ではなくとも)
  • 2007年に日本競馬が昇格し, その義務として "G1" を国際規格にちゃんと合わせる必要が出てきた
  • そこで, G1と称されていたレースのうち, 期日までに実際にG1になれなかったレースを "JpnI" として隔離した

という経緯を経ています.

問題の発生

以上のような経緯によって, 名実ともにG1であるレースはいいとして,

  • G1と称されていただけでG1ではなかったレース
  • JpnI(隔離枠)

という, "実際にはG1ではないが, 歴史的経緯としてG1ということにしておきたいこともあるレース" が発生し, しかもそれらの中でも扱いに差が生じるという, とても面倒な状況が発生しています.
たとえば,

  • G1のみを "G1" と扱う
  • G1と "G1と称されていただけでG1ではなかったレース" を "G1" と扱う
  • G1と 日本中央競馬会が開催する5 "G1と称されていただけでG1ではなかったレース と JpnI"東京大賞典 を "G1" と扱う

と, サービスによって扱いが全く異なります.

余談ですが, netkeibaのグレード記載は頻繁に変化します.
上述の通り, 現在のnetkeibaで東京大賞典はG1でなかった時代も含め "G1" 表記です.
ですが, ちょっと前までは逆にG1だった時代も "Jpn1" 表記だったりしました.
基本的には参考にしないほうが良いでしょう.

参考にするには, その時点でIFHA BluebookのPart1節を参考にして, そのレースが実際にG1であるかどうかを確認するのが良いでしょう.
ですが, Bluebookは1998年以降しかオンラインで公開されていません.
有識者の方にお聞きしたいのですが, それ以前のBluebookはIFHAに問い合わせたら入手できるのでしょうか...

G1勝ち馬データベース

と, このあまりの扱いの違いにキレた私は考えました.

"G1" と言われたレースの情報, そしてその勝ち馬をありのままに掲載するデータベースを作れば便利なのでは?
集計範囲はユーザーが決定すれば良いでは??

そうと決まれば早速設計しましょう.
データベースということで, いわゆるリレーションを書かねばなりません. 少し考えてみましょう.

第1版

まずレースは以下の要素を持つデータと考えましょう.

  • レース名
  • 開催年
  • 勝ち馬
  • どのようなG1か(Bluebook Part1掲載か? ただG1と称されただけか? JpnIか?など)
  • 勝利騎手
  • 勝利調教師

なんでも, リレーションはDBMLなる言語で書けるらしいので, まともに使ったことありませんが書き起こしてみます.

Table race {
	id integer [pk, increment]
	name varchar(255)
	year integer
	winner_horse varchar(255)
	winner_jockey varchar(255)
	winner_trainer varchar(255)
	// 実際のグレード, Part1基準, G1, G2, G3, L, なしなど
	ifha_grade varchar(50)
	// どのような文脈でG1か? Part1, Part2, JpnI, 八大競走...
	grade_as_g1 varchar(50)
}

最初の Table racerace という要素を定義しています.
それ以降の行で, raceid, name, year とかいろんな情報を持つんだよ, ということを定義しています.

ちなみに id は気にしないでください. データベースを成立させるために技術的に必要なものです.

さて, すでにものすごい量の問題が見えてきました.

同名の騎手とかどうするの?

馬名はまだしも人名は 簡単に同名被りが発生します.
たとえば, "加藤和宏" や "山口勲" という名前の騎手は2人いるようです.
競走馬名についても, 例えば海外は名付けの管轄が日本と違うので, 日本にかつていた競走馬と同じ名前の競争馬を海外で登録し, そこから移籍させる, として人為的に同名を発生させることも可能です7.

そういったときに, 現在のただ名前を記録するだけの設計では, この名前被り時に大事故を起こす可能性が非常に高いです.

これに対して対策するためには, "騎手" "競走馬" "調教師" をそれぞれいくらかの情報を持つ独立した要素として定義し, id を持たせる必要があります.

そもそも, "騎手" とか "競走馬" に付随する情報は名前だけではありません.
例えばフリオーソ号であれば, 彼は2004-05-01生まれの牡馬であり, ハシモトフアーム生産, ダーレー・ジャパン・ファーム所有で千葉の川島厩舎所属で... ものすごい量の情報があります.
独立した要素として定義するならば, それらの情報も持たせることができます.

レース名の変更とかどうするの?

レース名はよく変わります.
このデータベースの対象になりそうなところで最近だと, "ジャパンダートダービー" は2024年に "ジャパンダートクラシック" に変わりました.
過去に遡ると,

  • "チャンピオンズカップ" はかつて "ジャパンカップダート" だった
  • "全日本2歳優駿" はかつて "全日本3歳優駿" だった
  • "JBCスプリント" は1回だけ "JBCマイル" になった
  • "優駿牝馬" はかつて "阪神優駿牝馬" だった

など多数の変化があります.

同一性はだいたい回次("第n回" のあれ)で判断すれば良いとして, どうやらレース名も単なる文字列として扱うことはできないようです.

勝ち馬は1頭だけじゃないときもあるよ

実は競馬では "同着" が起こります.
このデータベースの範囲になりそうなところでは, 2010年の優駿牝馬でアパパネ号とサンテミリオン号が同時に入線, 12分の写真判定の末両馬ともに優勝となった例があります.

これはつまり, "レース" と "勝ち馬" の関係はn:1ではなくn:mであることを意味します.
(ある馬が多数のレースで勝つことがある, 逆にあるレースで多数の馬が勝つこともある)

こういったとき, 一般にデータベースではこの "n:m関係を表すだけの要素" である "連関エンティティ" を定義するのだそうです.
これはつまり "この馬はこのレースを勝ったよ" という結果だけを独立の "結果" 要素として定義するということです.

あとで図にするのでわからなかった方も一旦進んでみてください.

入る値決まってんだから文字列使うなよ

ifha_grade には

  • G1
  • G2
  • G3
  • L
  • なし

という, IFHAのBluebookに掲載があるグレード4種類と, IFHAのBluebookに掲載がない場合の "なし" しか入りません.

同様に grade_as_g1

  • Part1
  • Part2(上での "G1と称されていただけでG1ではなかったレース" のうち, Part1に記載がないだけでBluebookには掲載があるレース)
  • JpnI
  • 八大競走(G1という呼称もなかった時代に別格扱いされていたレース群)
  • ...

といろいろありますが結局決まってはいます.

こういったものは, "マスターテーブル" という "あらかじめ用意したこの値以外使わないでくださいテーブル" を用意して, それらを参照するようにしたほうが良いです.

文字列のままおいておくと, 打ち間違いが怖いですからね.

名前ちゃんと書け

https://qiita.com/genzouw/items/35022fa96c120e67c637

どうやら, テーブル名は複数形にしたほうが良いらしいです.
現在は "要素の種類名" という認識ですが, 最終的にデータベースでは "要素の集合" を表として並べるようになるので, そこにあるものを表すなら複数形のほうが良い, ということですね.

第2版

以上の問題を踏まえて, 第2版を設計してみましょう.

Table race_results {
	id integer [pk, increment]
	race_event_id integer [ref: > race_events.id]
	horse_id integer [ref: > horses.id]
	jockey_id integer [ref: > jockeys.id]
	trainer_id integer [ref: > trainers.id]
}

Table race_events {
	id integer [pk, increment]
	// どのレースシリーズか?
	race_series_id integer [ref: > race_series.id]
	// そのレースシリーズがこのイベントではどのような名称で開催された?
	name varchar(255)
	year integer
	// 実際のグレード, Part1基準, G1, G2, G3, L, なしなど
	ifha_grade_id integer [ref: > ifha_grades.id]
	// どのような文脈でG1か? Part1, Part2, JpnI, 八大競走...
	g1_context_id integer [ref: > g1_contexts.id]
}

Table race_series {
	id integer [pk, increment]
	// レースシリーズ名(例: ジャパンダートダービー)
	name varchar(255)
}

Table horses {
	id integer [pk, increment]
	name varchar(255)
}

Table jockeys {
	id integer [pk, increment]
	name varchar(255)
	// 所属競馬組織
	affiliation varchar(255)
}

Table trainers {
	id integer [pk, increment]
	name varchar(255)
	// 所属競馬組織
	affiliation varchar(255)
}

Table ifha_grades {
	id integer [pk, increment]
	grade_name varchar(50)
}

Table g1_contexts {
	id integer [pk, increment]
	context_name varchar(50)
}

長い. このあと図示するのでちょっとまっていてください.

第2版では, 以下の要素を追加しました.

  • race_series: "ジャパンダートダービー改めジャパンダートクラシック" のような, 通時的なレース名変化などを無視して継続帯として扱う要素
  • race_events: 1回のレース開催を表す要素
  • race_results: 1回のレース開催における勝利馬
  • horses: 競走馬を表す要素
  • jockeys: 騎手を表す要素
  • trainers: 調教師を表す要素
  • ifha_grades: IFHAのPart1に掲載されているグレードを表す要素
  • g1_contexts: G1と称される文脈を表す要素

そして, それらの要素を相互に参照するようにしました.

ref: > horses.id というのは, "この要素は, ここに書かれた id を持つ horses 要素を参照するよ" という意味です.
先程 "データベースを成立させるために技術的に必要なもの" と書いた id ですが, こうやって他の要素から参照されるために存在しているのです.

図にするとこんな感じです.

G1勝ち馬データベースER図

わかりにくそうな race_results 周りだけ, 2010年優駿牝馬の同着例で具体的に説明します.

まず, race_results テーブルです. 優勝馬が2頭いるので, 2行あります.
それぞれアパパネ号(horse_id=0)とサンテミリオン号(horse_id=1)の記録です.
それぞれの騎手/調教師も, 蛯名正義騎手(jockey_id=0), 国枝栄調教師(trainer_id=0), 横山典弘騎手(jockey_id=1), 古賀慎明調教師(trainer_id=1)がそれぞれ別の要素として登録されています.

id race_event_id horse_id jockey_id trainer_id
0 0 0 0 0
1 0 1 1 1

次に, race_events テーブルです. "2010年優駿牝馬" という1開催を表す1行だけが存在します.

id race_series_id name year ifha_grade_id g1_context_id
0 0 優駿牝馬 2010 0 0

race_series テーブルです. "優駿牝馬" というレースシリーズを表す1行が存在します.

id name
0 優駿牝馬

さて, こうして改善されたデータベース構造ですが, まだ問題点は残っています.

同年にレースが2回開催されたらどうする?

race_events.year ですが, もし同じレースシリーズが同じ年に2回開催されたらどうすべきでしょうか.

例えば, race_series に "天皇賞" を入れる場合, 天皇賞は春秋2回開催なので, 同じ年に2回開催されることになります8.
また, 地方競馬全国協会(NAR)傘下の競馬組織は会計年度(4月始まり)で運営されているため, もしかしたら時期変更と組み合わさって同じ会計年度に2回開催されることもあるかもしれません.

そこで, race_events.datetimestamp を持たせて, 開催日の情報も持って置くべきでしょう.

レースに情報が足りなすぎる

race_events にはさすがに以下を追加しておくべきでした.

  • 競馬場
  • 路面
  • 周り方向
  • 距離

距離ですこし悩むのは, 本当にこれが整数値で良いのか, ということです.
日本は基本的に公称距離がメートル単位の整数で表記されますが, イギリスやアメリカはヤード・ポンド法で書かれるときがあります.
ヤードポンド法といえば皆さんおなじみ, メートルとの換算が面倒なことになっている単位系です.

ただ, 競争距離としてIFHA Bluebookの記載を採用する場合, ヤード・ポンド法で書かれていることには変わりありませんが, "Mile"(マイル) や "Furlong"(ハロン) などの単位の換算表があり, かつ整数になりやすいように配慮されているようなので, これを採用するなら整数値として扱っても良いでしょう.

競馬組織もマスター化したほうが良い

trainers.affiliationjockeys.affiliation ですが, これもマスター化したほうが良いでしょう.

association テーブルを作り, そこに各競馬組合等を書いておき, trainers, jockeys から参照するようにすれば良いでしょう.

"人" を独立した要素にしたほうが良いかも?

騎手は一生騎手とは限らず, 途中で調教師になることもあります.
例えば, 2024年には千葉県競馬組合所属の森泰斗元騎手が調教師免許を取得し, 2025年から調教師としてデビューしました9.

更に言うと, 騎手である間でも所属を変更する場合があります. 有名な例が小牧太騎手で, 1985年に兵庫県競馬組合所属の騎手となり, 2004年に日本中央競馬会に移籍, その後2024年に再度兵庫県競馬組合に再移籍しています10.

ここからわかる通り, ある人は "A所属の騎手" や "B所属の騎手" や "C所属の調教師" など, 複数の役割を持つことがあります.
そのため, "人" を独立した要素にし, "騎手" や "調教師" は "この人がやっています" というように人を参照するようにしたほうが良いでしょう.

第3版

以上の問題を踏まえて, 第3版を設計してみましょう.

Table race_results {
	id integer [pk, increment]
	race_event_id integer [ref: > race_events.id]
	horse_id integer [ref: > horses.id]
	jockey_id integer [ref: > jockeys.id]
	trainer_id integer [ref: > trainers.id]
}

Table race_events {
	id integer [pk, increment]
	// どのレースシリーズか?
	race_series_id integer [ref: > race_series.id]
	// そのレースシリーズがこのイベントではどのような名称で開催された?
	name varchar(255)
	date timestamp
	racecourse_id integer [ref: > racecourses.id]
	surface_id integer [ref: > surfaces.id]
	direction_id integer [ref: > directions.id]
	distance_meters integer
	// 実際のグレード, Part1基準, G1, G2, G3, L, なしなど
	ifha_grade_id integer [ref: > ifha_grades.id]
	// どのような文脈でG1か? Part1, Part2, JpnI, 八大競走...
	g1_context_id integer [ref: > g1_contexts.id]
}

Table race_series {
	id integer [pk, increment]
	// レースシリーズ名(例: ジャパンダートダービー)
	name varchar(255)
}

Table horses {
	id integer [pk, increment]
	name varchar(255)
}

Table jockeys {
	id integer [pk, increment]
	person_id integer [ref: > persons.id]
	// 所属競馬組織
	affiliation_id integer [ref: > associations.id]
}

Table trainers {
	id integer [pk, increment]
	person_id integer [ref: > persons.id]
	// 所属競馬組織
	affiliation_id integer [ref: > associations.id]
}

Table persons {
	id integer [pk, increment]
	name varchar(255)
}

Table ifha_grades {
	id integer [pk, increment]
	grade_name varchar(50)
}

Table g1_contexts {
	id integer [pk, increment]
	context_name varchar(50)
}

Table associations {
	id integer [pk, increment]
	name varchar(255)
}

Table racecourses {
	id integer [pk, increment]
	name varchar(255)
}

Table surfaces {
	id integer [pk, increment]
	surface_name varchar(50)
}

Table directions {
	id integer [pk, increment]
	direction_name varchar(50)
}

画像にするとこんな感じです. もうさすがに拡大しないと見えませんね.

G1勝ち馬データベースER図v3

まだ改善したい

これ以上書ききれないのですが, 改善点はまだまだあります.

ドメイン的な(競馬目線の)改善点

  • 名古屋競馬場/盛岡競馬場など, 名前を変えずに移転した場合は違う競馬場として扱うべき?
  • 騎手や調教師の所属は 競馬組織単位ではなくトレーニングセンター単位とすべき?
  • 外回り/内回り/直線の情報が必要
  • コース情報が毎回記入されるので, "コース" という独立要素も必要?
  • 騎手や調教師はJBISコードも持ったほうが良い?
  • Listed Restrictedはどう扱う?

技術的な改善点

  • name という列名があまりに多すぎるので, より名前の差別化が必要かも(せめて racecourse_name ぐらいはやったほうが良いか?)
  • uniquenot null, increment などの制約を扱えていないので, それらを追加すべき
  • マスターテーブルをDBML上で区別したい
  • その他, マスターテーブルの内容などの変化しにくいものもCode化したい
  • レース名など "変わることがあるがめったに変わらない" 情報の管理はもう少しいい方法があるのでは?

おわりに

とりあえずデータベースの構造だけを考えるという, よくわからないことをやってみました.

実際のアプリケーションなどは全く作られていませんが, そのうちOSSとして公開できたらと思います.

明日はしんちゃ。さん"横山武史という騎手について" です.

  1. 競馬の物理モデルの話, 競馬に疲れた話を書いた

  2. IFHA Bluebook, PartI, Japan: https://www.tjcis.com/pdf/icsc25/ICSC-partI_Japan.pdf

  3. なお今回, 平地競走に限定させていただきます. 実際にデータベースを作る場合は障害競走やばんえい競走も含める予定です.

  4. 前述した東京大賞典を開催する大井競馬場など, 南関東圏の地方公共団体系が運営する競馬の通称

  5. 神奈川県川崎競馬組合, 岩手県競馬組合, 特別区競馬組合など, 日本中央競馬会以外が施行するレースは, かつてG1と称されたレース6もJpnIも全部まとめて "JpnI" と記載されています(東京大賞典除く)

  6. 正確には "G1と称され, その事実がIFHA Bluebook Part2に掲載されているレース"

  7. 例えば, "ヒシマサル" という名前は, このようにして馬名再利用のルールを回避したそうです.

  8. この部分は実は大きな問題で, "天皇賞" というくくりで race_series に登録すると, "天皇賞(春)" だけとか "天皇賞(秋)" だけを登録することができなくなります. ですが, 天皇賞の回次は春秋通し番号なので, それを基準とするなら分離して race_series として登録するべきでない, となります. 要検討.

  9. https://www.nikkansports.com/keiba/news/202503180000323.html

  10. https://www.nikkei.com/article/DGXZQOUF1933E0Z10C24A7000000/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?