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

はじめに
Laravel の Eloquent クエリビルダは、複雑な SQL をシンプルに記述できる点が大きな魅力です。特にテーブル結合においては、on
と orOn
を組み合わせることで 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 対策方法
- 事前フィルタリング:全件取得前に
where
で絞り込み、対象レコードを減らす。 - UNION ALL 分割:OR ではなく、複数のシンプルなクエリを実行して結果を結合。
- 適切なインデックス設計:複合インデックスを検討する場合もあるが、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
を適切に使い分け、可読性とパフォーマンスを両立させたクエリ設計を心がけましょう。

大阪のエンジニアが書いているブログ。
コメント