ER 図

推移的な関係を直結しない

推移的関係をショートカットすると、親を飛ばして子が存在できる矛盾設計になる。

推移的な関係を直結しない diagram

短絡線が生む矛盾

「ユーザーは注文を持ち、注文は明細を持つ」という関係は、本来 2 本の線で段階的に描くべきものです。

users 1──N orders 1──N order_items

ここに「ユーザー ── 明細」の線を直接引いてしまうと、「注文を介さずに明細が存在できる」モデルになります。

  • どの注文にも紐付かない明細レコードが存在し得る
  • 注文が削除されても明細が宙に残る(別経路で参照され続ける)
  • 「合計金額 = 明細の総和」といった業務ルールが、注文を無視して成立してしまう

ER 図の線は「存在の依存関係」そのものです。短絡線を引くと、依存関係が複線化され、どちらの経路が正なのか分からなくなります。

実例 — 会社・部署・社員、ブログの記事とコメント

推移的関係の短絡は、実務で頻繁に出る 2 つの形で覚えておくと気付きやすくなります。

  • 会社 → 部署 → 社員: 会社 1:N 部署、部署 1:N 社員。ここに employees.company_id を追加で生やすと、「部署無しで会社に所属する社員」や「会社と部署で所属先が矛盾する社員」が作れてしまう。素直に employees.department_id のみにして、会社は JOIN departments で辿る
  • 記事 → コメント → 返信: コメントは記事に、返信はコメントに属するのが自然。ここに replies.article_id を冗長に持たせると「削除された記事の返信が残る」「別記事のコメントへの返信ができる」等の抜け道ができる

「存在依存を飛び越える線が引かれていないか?」をレビューで必ず確認します。短絡線の誘惑はほぼ全て「クエリを 1 回少なくしたい」という実装都合から来ますが、それは後述の冗長 FKとして明示的に扱うべき設計判断で、こっそり線を引くべきではありません。

※ 逆に「短絡線に見えて実は独立した関係」のケースもあります。例えば「社員 ── メンター社員」の自己参照関係は、階層構造の上下関係とは別軸の情報なので、推移関係ではありません。

JOIN で辿れる関係は線を引かない

実用的な経験則として、「既存の線を辿って JOIN で到達できる関係は、新しい線を引かない」を守ると短絡は起きません。

「ユーザーが持つ明細一覧」は users JOIN orders JOIN order_items で取れるので、ER 図には書かなくてよい情報です。「そこに線が無くても、データモデルとしては到達可能」である点が重要です。

※ これは 推移従属の除去第 3 正規形)と同じ発想。同じ事実を 2 箇所に持たない、関係も 2 経路に持たない、という原則が通底しています。

集約ルートの考え方

「明細は注文を介してしか操作しない」というルールを、集約ルート(aggregate root) と呼びます。DDD の用語ですが、純粋な DB 設計でも役立つ発想です。

  • 集約ルート: orders(外部から直接触れるエンティティ)
  • 集約内部: order_items(必ず orders を経由する)

集約の外側から order_items に直接線を引く = 集約の境界を破る、と捉えると、「短絡線を引くべきか」が見分けやすくなります。迷ったら「このテーブルを単独で削除できるか?」を問うのが有効です。単独で意味を持たないテーブルは、外から直接線を引くべきではありません。

例外: パフォーマンス目的の冗長 FK

短絡が意図的に許容されるケースもあります。order_itemsuser_id 列を冗長に持たせ、「ユーザーごとの明細合計」を 1 回の JOIN で出したい、といった場面です。

  • 用途はパーティショニング・検索高速化・集計用の限定的な目的に
  • 正規化に反する冗長列であることを コメントや命名で明示denorm_user_id 等)
  • 親(orders.user_id)と子(order_items.user_id)の同期をトリガーやアプリで保証(差異が出るのが最大の罠)

冗長 FK は「短絡が許される例外」であって、最初から引くものではありません。計測してホットスポットと判断してから導入するのが鉄則です。