メインコンテンツへスキップ
見出し画像

【全29テーブルの全体配線図】工場の制御盤屋が組んだ、競馬ブックKD3とSQLiteの「複合主キー」設計思想!Alma Core Pro開発記 #4

    どうも、アルマコアです!

    本業は工場の自動化機械やシーケンサー(PLC・制御盤)の設計に向き合っている、ただの現役機械エンジニアです。

    前回の第3回にも、たくさんの「スキ」やフォローをいただき本当にありがとうございます!

    「東京11Rを引いたはずが新潟が飛び出してくる」という、競馬ブック独自コードの洗礼に共感の声をいただき感無量です(笑)。

    さて、固定長バイナリの切り出し(第2回)に成功し、マスタ統合(第3回)によって「00=京都」「-0051=GⅠ」といった暗号も美しい日本語として意味が通るようになりました。

    「よっしゃ! これで部品の加工と規格変換はぜんぶ終わったで!」

    ……と、深夜に達成感に浸りながらSQLiteの中身を眺めていた私ですが、ふと冷静になって手が止まりました。

    目の前にあるのは、

    • 出馬表(kol_den1・kol_den2)

    • 競走成績(kol_sei1・kol_sei2・kol_sei3)

    • トラックマンのコメント・次走メモ(kol_com1)

    • 確定・予想オッズ(kol_kod・kol_ods・kol_ods_detail 等)

    • 競走馬マスタ(kol_uma)や血統マスタ(kol_ket) ……などなど、合計29個ものテーブルがただバラバラに転がっているだけの光景だったのです。

    機械設計の世界で例えるなら、「公差±0で精密加工した29個のギアやプーリー、ベアリングが工場の床一面に転がっているだけで、まだ1本のシャフト(回転軸)も通っていない状態」です。

    部品単体がいくらピカピカに仕上がっていても、軸を通してガチッと噛み合わせなければ、機械は1ミリも動きません!

    今回は、このバラバラな29個のテーブルに「共通のシャフト(複合主キーと結合キー)」を貫通させ、コンマ数秒で連動する「1つの生き物(リレーショナルデータベース)」へと組み上げた、エンジニア魂のリレーション設計をお届けします!

    1. 工場の床に転がる「全29テーブル」の正体

    まず、「なんで競馬のデータごときでテーブルが29個にも分かれとんねん!」と思われるかもしれません(笑)。

    でも、競馬ブック(KD3)の生データを実用レベルで動かすために正規化していくと、必然的にこの数に達するのです。

    実際に私が『Alma Core Pro』のSQLite内に削り出した全29テーブルの内訳がこちらです。

    ① レース・出馬表・成績・コメント系(8テーブル)

    • kol_den1:出馬表(レース条件・コース・展開予想などレース単位の親情報)

    • kol_den2:出馬表(枠番・馬番・負担重量・調教タイム・短評・SP指数など出走馬単位の子情報)

    • kol_sei1:競走成績(天候・馬場・ラップタイム lap_time_01〜18・払戻金などレース単位の親情報)

    • kol_sei2:競走成績(確定着順・走破タイム・前半3F first_3f_time・上り3F last_3f_time・通過順など馬単位の子情報)

    • kol_sei3:競走成績(制裁内容 sanction_details などレース付帯情報)

    • kol_tok1:特別登録(レース条件などの親情報)

    • kol_tok2:特別登録(登録馬ごとの子情報)

    • kol_com1:コメント(騎手・厩舎談話 jockey_stable_comment、次走へのメモ memo_next_race 等)

    ② オッズ親テーブル&明細テーブル(10テーブル)

    • kol_kod / kol_kod2 / kol_kod3:確定オッズ親テーブル(単複枠・馬連ワイド馬単・3連複3連単)

    • kol_ods / kol_ods2:予想オッズ親テーブル

    • kol_kod_detail / kol_kod2_detail / kol_kod3_detail:確定オッズ明細(組番ごとのオッズ実数値)

    • kol_ods_detail / kol_ods2_detail:予想オッズ明細(組番ごとの予想オッズ実数値)

    ③ 競走馬・人物・血統マスタ系(5テーブル)

    • kol_uma:競走馬マスタ(血統登録番号、父母名、芝・ダート・距離別の通算着度数など)

    • kol_kis:騎手マスタ(騎手コード、所属、通算・本年成績など)

    • kol_kyu:厩舎(調教師)マスタ(厩舎コード、所属、成績など)

    • kol_syu:種牡馬マスタ(種牡馬コード、距離適性、重馬場適性など)

    • kol_ket / kol_ket5:3代・5代血統マスタ

    ④ コード変換マスタ&拡張指数・管理系(6〜7テーブル)

    • 7大コード表マスタ:mst_venue(場所) / mst_condition(条件) / mst_condition_addition(条件付加) / mst_horse_mark(馬記号) / mst_coat_color(毛色) / mst_breed(血種) / mst_birthplace(産地)※さらに予想ペース mst_expected_pace、脚質 mst_running_style 等

    • kol_stride:ストライド指数テーブル(先行力 senko、追走力 tsuiso、持続力 jizoku、瞬発力 shunpatsu、SAV、STなど)

    • dl_history:インポート履歴管理テーブル

    ……どうですか、この圧倒的な物量(笑)!

    これだけの精密部品が、最初は全部バラバラに存在しているわけです。

    2. 壁:何を「軸(シャフト)」にして29個を串刺しにするか?

    私が自作GUI画面(『Alma MDI Pro』)で絶対に実現したかったのは、こういうことでした。

    「出馬表に並んでいるこの馬の、過去走の走破時計と、今回の調教タイムと、トラックマンの次走メモと、リアルタイムの予想オッズと、父馬の重馬場適性を、1画面に0.03秒で同時に呼び出したい!」

    これを実現するには、バラバラのテーブル同士を寸分の狂いもなく繋ぎ止める「共通のシャフト(主キー / 結合キー)」を設計しなければなりません。

    「1つのテーブルの中で、何を主キー(PRIMARY KEY)に据えればデータが重複せず、かつ他のテーブルと一瞬で結合できるのか?」

    仕様書と睨めっこしながら導き出した結論は、すべてのデータを「3本のメインシャフト(主軸)」に分類して串刺しにする設計でした。

    ① 第1の軸:「レース単位」で貫く5連複合キー

    まず、レース全体の条件を持つ親テーブル(kol_den1、kol_sei1、kol_sei3、kol_tok1、kol_ods 等)です。

    これらを一意(ユニーク)に特定するため、私は以下の5つのカラムを組み合わせた「5連複合主キー」を定義しました。

    SQL

    PRIMARY KEY (year, venue_code, times, day, race_number)
    
    • year:開催年(例:2026)

    • venue_code:場所コード(例:04=東京)

    • times:回次(第何回開催か)

    • day:日次(何日目か)

    • race_number:レース番号(例:11=11R)

    「開催日(race_date)だけじゃアカンの?」と思うかもしれませんが、同日に東京・京都・新潟の3場開催があるため日付だけでは一意になりません。

    さらにKD3の仕様上、年・場所・回・日・Rの5点セットこそが、すべてのテーブルに共通して刻まれている「絶対座標(位置決めピン)」だったのです!

    ② 第2の軸:「レース×馬番」で貫く6連複合キー

    次に、1つのレースに最大18頭(データ仕様上は最大28頭枠)が出走する子テーブル(kol_den2、kol_sei2、kol_com1)です。

    ここは先ほどの5連キーの末尾に、horse_number(馬番)を1本追加した「6連複合主キー」でガチッと固定しました。

    SQL

    -- kol_den2(出馬表・馬単位)、kol_sei2(成績・馬単位)、kol_com1(コメント)の主キー
    PRIMARY KEY (year, venue_code, times, day, race_number, horse_number)
    

    さらに、オッズ明細テーブル(kol_ods_detail や kol_kod_detail)では、同じレースの中に単勝・複勝・馬連などの券種と組番がぶら下がるため、

    SQL

    PRIMARY KEY (year, venue_code, times, day, race_number, odds_type, combination)
    

    という「7連複合主キー」を採用しました(※特別登録 kol_tok2 は枠順確定前で馬番がないため、horse_code を使った6連キーにしています)。

    これで、当日の出馬表(kol_den2)に単勝予想オッズ(kol_ods_detail)を横付けするときも、5連キー+馬番(combination)を噛み合わせるだけで、1ミリの狂いもなくピタリと同期します!

    ③ 第3の軸:「時間軸(過去〜現在)」を貫く個体識別シャフト

    そして、一番重要なのが「今走っているこの馬の、過去の成績やコメントをどうやって引っ張ってくるか?」です。

    過去のレースは当然、開催年(year)も場所(venue_code)も馬番(horse_number)も毎回バラバラです。

    ここで時間軸を超えて1頭の競走馬を縦串で貫く絶対のシャフトが、horse_code(血統登録番号)です!

    競走馬マスタ(kol_uma)や血統マスタ(kol_ket / kol_ket5)は、この horse_code 単体を PRIMARY KEY として設計しました。人物系・種牡馬系も同様に単一主キーで整理しています。

    • 競走馬マスタ(kol_uma):PRIMARY KEY (horse_code)

    • 騎手マスタ(kol_kis):PRIMARY KEY (jockey_code)

    • 厩舎マスタ(kol_kyu):PRIMARY KEY (stable_code)

    • 種牡馬マスタ(kol_syu):PRIMARY KEY (sire_code)

    3. 機械エンジニア流「総組立図(ER図)」と魂のJOINクエリ

    部品の規格とシャフトの径が決まれば、あとは工場の制御盤の「全体配線図」や機械の「総組立図(アセンブリ図)」を描くように、29テーブルの結合ルート(ER図)とインデックス(CREATE INDEX)を張り巡らせていきます。

    • 縦の配線(当日のレース分析):kol_den1(親) ⇔ kol_den2(出走馬) ⇔ kol_ods_detail(予想オッズ) ⇔ mst_venue / mst_condition(日本語変換マスタ)を、レース複合キーで直結!

    • 横の配線(過去走・適性の深掘り):kol_den2 の horse_code から ⇔ kol_sei2(過去成績) ⇔ kol_com1(過去の次走メモ・厩舎コメント) ⇔ kol_uma(通算成績・馬場適性)を、horse_code インデックス(idx_kol_sei2_horse 等)で超高速結合!

    この総組立図通りにインデックスを張り、記念すべき最初の結合テスト(JOIN クエリ)を走らせたときのコードがこちらです。

    SQL

    SELECT 
        v.venue_name AS "競馬場",
        d1.race_number AS "R",
        d1.special_race_name_7 AS "レース名",
        d2.bracket_number AS "枠",
        d2.horse_number AS "馬番",
        d2.horse_name AS "馬名",
        d2.jockey_name_short AS "騎手",
        o.odds_value / 10.0 AS "予想単勝オッズ",
        d2.training_short_comment AS "調教短評",
        d2.speed_index_1_race_ago AS "前走SP指数",
        u.dirt_heavy_aptitude AS "ダ重適性",
        c.memo_next_race AS "前走時・次走メモ"
    FROM kol_den2 d2
    -- ① レース親情報(kol_den1)を5連複合キーで結合
    INNER JOIN kol_den1 d1 
        ON d2.year = d1.year 
       AND d2.venue_code = d1.venue_code 
       AND d2.times = d1.times 
       AND d2.day = d1.day 
       AND d2.race_number = d1.race_number
    -- ② 場所マスタ(mst_venue)でコードを日本語に変換
    LEFT JOIN mst_venue v 
        ON d2.venue_code = v.venue_code
    -- ③ 予想オッズ明細(kol_ods_detail)を6連キー+券種で結合
    LEFT JOIN kol_ods_detail o 
        ON d2.year = o.year 
       AND d2.venue_code = o.venue_code 
       AND d2.times = o.times 
       AND d2.day = o.day 
       AND d2.race_number = o.race_number 
       AND d2.horse_number = o.combination
       AND o.odds_type = '01'
    -- ④ 競走馬マスタ(kol_uma)をhorse_codeで結合
    LEFT JOIN kol_uma u 
        ON d2.horse_code = u.horse_code
    -- ⑤ 前走のコメントテーブル(kol_com1)から最新メモを取得
    LEFT JOIN kol_com1 c 
        ON d2.horse_code = c.horse_code
       AND c.race_date = (
           SELECT MAX(c2.race_date) FROM kol_com1 c2 
           WHERE c2.horse_code = d2.horse_code AND c2.race_date < d2.race_date
       )
    WHERE d2.race_date = '20261011' 
      AND d2.venue_code = '04' 
      AND d2.race_number = '11'
    ORDER BY CAST(d2.horse_number AS INTEGER);
    

    エンターキーを叩いた瞬間でした。

    コンマ数秒(0.03秒の世界)で、画面にズラッと18頭の「馬名」「予想オッズ」「調教短評」「前走SP指数」「重馬場適性」「トラックマンの次走メモ」が、1行にピシッと整列して飛び出してきたのです!

    「よっしゃーーーッ……!! 完全に繋がりよったわ……!!」

    バラバラだった29個の金属パーツが、1台の精密機械として完全に噛み合って動き出した瞬間です!

    工場の現場で、何百本もの配線を終えた制御盤のメインブレーカーを上げ、すべてのサーボモーターとセンサーが設計図通りに一斉に同期運転を始めた瞬間とまったく同じです。

    あの背筋がゾクッとするような、エンジニア冥利に尽きる感動が深夜の自室に蘇りました。

    4. ExcelのVLOOKUPでは絶対不可能。『Alma Core Pro』が提供する真の価値

    少し想像してみてください。

    もし、この競馬ブック(KD3)の生データを個人で手に入れて、Excelの VLOOKUP や XLOOKUP で繋ごうとしたらどうなるでしょうか?

    • 数百万行ある過去成績(kol_sei2)や93万件を超えるコメント(kol_com1)を開いた瞬間、Excelはメモリ不足でフリーズして強制終了します(笑)。

    • そして何より、29種類もあるファイルの中から「どのファイルとどのファイルを、5連キーで繋ぐのか? 6連キーなのか? horse_code なのか?」という複雑怪奇なリレーション構造を自力で解読できなければ、データの海で100%遭難します。

    第2回の「固定長バイナリの切り出し」、第3回の「独自コードのマスタ変換」、そして今回の「全29テーブルの複合キー&リレーション設計」。

    この3つの巨大な壁があるからこそ、競馬ブックの濃密な生データを個人でデータベース化できる人間は世の中にほとんど存在しないのです。

    だからこそ、私が心血を注いで開発したデータベース構築アプリ『Alma Core Pro』には、他にはない圧倒的な価値があります!

    『Alma Core Pro』を導入すれば、

    • 全29テーブルの生成(DDL)、主キー設定、7大マスタの初期登録、そして高速検索用インデックス(idx_kol_sei2_horse 等)の構築まで、初回起動時に全自動で完了します。

    • ユーザーは「どのキーとどのキーを繋ぐか」といった小難しい配線図を一切意識する必要はありません。

    • すでにすべてのシャフトが通り、完璧に配線された状態のSQLiteデータベース(keiba.db)が手に入るため、最初から「勝つためのデータ抽出・分析」だけに100%集中できるのです!

    5. 次回予告:毎週落ちてくる「大量の圧縮ファイル」をどうやって流し込む?

    さあ、これで全29テーブルの「強固な器」と「完璧な配線(リレーション)」が仕上がりました!

    どんな複雑な条件検索にもコンマ数秒で応える、最強の心臓部の完成です。

    ……しかし、ここで次の現実的な問題が襲いかかります。

    競馬のデータは、1回入れて終わりではありません。

    毎週・毎開催ごとに、競馬道OnLineから大量の「出馬表」「成績」「オッズ」「マスタ更新分」のファイルが落ちてきます。

    しかもそのファイル形式は、「.zip を解凍したら中から昭和の圧縮形式 .lzh が出てきて、さらにそれを剥いたら .kd3 が出てくる」という、あの悪魔のマトリョーシカ構造です(笑)!

    何十個、何百個もあるこの二重圧縮ファイルを、まさか毎週手動で解凍する?

    29個のテーブルへ1つずつ手作業で振り分けてインポートするの……?

    「うん! そうそう!」

    「こうやって、1つずつ皮剥いて!」

    「電子レンジで、チ〜ンて!」

    「アホかーーーッッ!!!」

    「そんなん毎週やっとったら死んでまうし! 予想する時間なくなるわ!」

    「ほな君、機械屋やろ! やったらここも完全自動化せんかい!」

    深夜2時、ハイテンションの私は一人で激しい乗りツッコミをしていました(笑)。

    次回、

    【Alma Core Pro開発記 NO.5】昭和の圧縮ファイル「.lzh」を全自動で剥く!大量ファイル処理と29テーブル自動仕分けラインの構築劇。

    二重圧縮ファイルをメモリ・一時領域で全自動解体し、ファイル名から対象テーブルを自動判別して29テーブルへ高速仕分けする「自動搬送ライン」の構築劇をお届けします!

    どうぞお楽しみに!

    ☕ 最後まで読んでいただき、ホンマにありがとうございました!

    「29テーブルの設計図、マニアックすぎて最高!」「複合キーとJOINの威力、めちゃくちゃ勉強になった!」と思っていただけたら、ぜひ画面下の「スキ(ハートマーク)」や「フォロー」をポチッと押していただけると、開発と執筆を続ける最高のガソリンになります!

    ◀️ 前の記事はこちら

    👉 【第3回:東京11Rが新潟に化けた!?競馬ブックKD3の独自コード地獄をSQLiteマスタ統合でねじ伏せた執念】

    📚 【全話イッキ読み】自作競馬ソフト開発記マガジン

    👉 [Alma Core Pro / MDI Pro 開発マガジンを開く]

    💡 「なぜExcelでもサーバー型DBでもなくSQLiteなのか?」原点の第1回はこちら

    👉 [開発記 第1回を読む]

    ⏩ 次のお話はこちら

    👉 【第5回:昭和の圧縮ファイル「.lzh」を全自動で剥く!自動仕分けラインの構築劇】

    🤖 工場のPLC屋が本気で書く!痛快&重厚な自作Web小説も連載中

    👉 [『転生したらイーロン・マスクが大統領だった件(笑)』第1話へ]

    👉 [本格クライムSFサスペンス『アブダクション・プロトコル』第1話へ]


     
     
    競馬のビッグデータと闘う機械エンジニア。Python×SQLiteでDBアプリ『Alma Core Pro』&解析アプリ『Alma MDI Pro』を個人開発中。指数解析、回収率に直結するロジカルな競馬予想と開発裏話を気ままに発信します。

    あなたへのおすすめ