【Laravel】Eloquentの orOn を丁寧に理解する。結合条件を柔軟にコントロールするために。

Laravel orOn PHP
この記事は約6分で読めます。

この記事の最終更新日: 2025年4月27日

Laravel orOn

はじめに

Laravel の Eloquent クエリビルダは、複雑な SQL をシンプルに記述できる点が大きな魅力です。特にテーブル結合においては、onorOn を組み合わせることで AND 条件・OR 条件の両方を直感的に表現できます。本記事では、orOn を中心に機能概要から内部動作、具体例、パフォーマンスへの影響、実践的な改善パターンまでを丁寧に解説します。


1. on と orOn の役割を整理する

1.1 on の基本

  • 用途:複数の結合条件を AND で結びたいとき

書き方

$query->join('staffs', function ($join) { $join->on('staff_list.staff_id', '=', 'staffs.id') ->on('latest_histories.staff_id', '=', 'staffs.id'); });

生成 SQL

INNER JOIN staffs ON staff_list.staff_id = staffs.id AND latest_histories.staff_id = staffs.id

この場合は「staff_list と latest_histories の両方で staff_id が一致するレコードのみを結合する」ので、いわゆる『かつ条件』の結合となります。

1.2 orOn の基本

  • 用途:結合条件を OR で追加したいとき
  • 書き方
$query->leftJoin('staffs', function ($join) { $join->on('staff_list.staff_id', '=', 'staffs.id') ->orOn('latest_histories.staff_id', '=', 'staffs.id'); });
  • 生成 SQL
LEFT JOIN staffs ON (staff_list.staff_id = staffs.id OR latest_histories.staff_id = staffs.id)

OR 条件を利用することで、『登録されている staff_list か、過去に履歴がある latest_histories のいずれかとマッチすれば結合する』という柔軟な結合が可能です。


2. orOn を用いた具体的なユースケース

2.1 要件定義

  • プロジェクトに紐づくスタッフ情報を取得したい
  • スタッフは「現在プロジェクトに割り当てられている staff_list」か「過去に割り当てられていた latest_histories」に存在していれば対象とする

2.2 コード例

$results = Project::from('project_staff_list as staff_list')
    // プロジェクト ID で履歴テーブルを結合
    ->leftJoin('latest_histories', 'staff_list.project_id', '=', 'latest_histories.project_id')
    // staff_list または latest_histories のいずれかでマッチする staffs テーブルを結合
    ->leftJoin('staffs', function ($join) {
        $join->on('staff_list.staff_id', '=', 'staffs.id')
             ->orOn('latest_histories.staff_id', '=', 'staffs.id');
    })
    ->select([
        'staff_list.project_id',
        'staffs.name as staff_name',
        'latest_histories.status',
    ])
    ->get();
  • 実行結果としては、両方のテーブルに存在するスタッフは重複せず1行で取得され、どちらか一方に存在するスタッフも漏れなく取得されます。
  • 実際には、結合先テーブル(staffs)は一度だけ結合されるため、同じ id のレコードが重複して返されることはありません。

3. パフォーマンスとインデックスの観点

3.1 OR 条件とインデックス

  • データベースは通常、インデックスを使って高速に検索を行います。
  • OR 条件を含むと、複数のカラムをまたいでのマッチ判定になるため、単一インデックスの効果が薄れ、結果的にテーブルスキャンに近い動作になることがある。

3.2 対策方法

  1. 事前フィルタリング:全件取得前に where で絞り込み、対象レコードを減らす。
  2. UNION ALL 分割:OR ではなく、複数のシンプルなクエリを実行して結果を結合。
  3. 適切なインデックス設計:複合インデックスを検討する場合もあるが、OR 条件では効果が限定的。

4. 失敗例と改善例

4.1 失敗例:安易な orOn の多用

$query->leftJoin('staffs', function ($join) {
    $join->on('staff_list.staff_id', '=', 'staffs.id')
         ->orOn('latest_histories.staff_id', '=', 'staffs.id')
         ->orOn('archived_staffs.staff_id', '=', 'staffs.id');
});
  • 問題点
    • OR 条件が増えるほど SQL が複雑化し、可読性が低下
    • OR 判定範囲が広がり、意図しないレコードが結合されるリスク
    • データ量が増えるとパフォーマンス悪化が顕著に

4.2 改善例:UNION ALL によるスライス&ダイス

// 現在所属中のスタッフ
$q1 = Project::select('project_id', 'staffs.name')
    ->from('project_staff_list as staff_list')
    ->join('staffs', 'staff_list.staff_id', '=', 'staffs.id');

// 過去に所属していたスタッフ
$q2 = Project::select('project_id', 'staffs.name')
    ->from('latest_histories')
    ->join('staffs', 'latest_histories.staff_id', '=', 'staffs.id');

// アーカイブされたスタッフ
$q3 = Project::select('project_id', 'staffs.name')
    ->from('archived_staffs')
    ->join('staffs', 'archived_staffs.staff_id', '=', 'staffs.id');

// UNION ALL で結合
$results = $q1->unionAll($q2)
              ->unionAll($q3)
              ->get();
  • 改善ポイント
    • 各結合がシンプルで可読性が高い
    • クエリプランが単純化し、インデックスをフル活用できる
    • 大量データでもスケーラブルに

5. まとめ

  • on は AND 条件、orOn は OR 条件で結合
  • OR 条件は柔軟だが、インデックス効率や可読性に注意
  • 大規模データ複雑要件 では、UNION ALL やサブクエリへの切り出しも検討
  • クエリプランを確認しながら最適化を進めることが、ベストプラクティスです

以上を参考に、orOn を適切に使い分け、可読性とパフォーマンスを両立させたクエリ設計を心がけましょう。

クリックしたら、
楽天モバイル大盤振る舞いのキャンペーン中らしいです (本ブログ管理人は楽天モバイル2台持ちです) 楽天モバイル
PHP
daigoroをフォローする

コメント

タイトルとURLをコピーしました