LaravelのEloquentを使ってリレーション先のデータを含めて、複数のテーブルを検索したい場合のメモになります。
やりたいこと
映画をまとめたサイトの検索結果の表示を想定します。
映画タイトル、説明、コメントと投稿者を検索できるようにします。
「映画(Movie)」と「コメント(Comment)」は1:多、「コメント(Comment)」と「ユーザ(User)」は多:1のリレーションでデータベースを構成しています。
検索対象のテーブルは親(Movie)→子(Comment)→孫(User)となりますが、検索結果は親(Movie)テーブルで返します。
テーブル | 検索カラム |
---|---|
Movie | title, content |
Comment | comment |
User | name |
実装方法
Model
「Movie」と「Comment」は1対多なのでhasMany関数を使って配列で取得します。
「Comment」と「User」は多対1なのでbelongsTo関数で1つのデータを取得します。
Models\Movie.php
public function comment()
{
return $this->hasMany(Comment::class);
}
Models\Comment.php
public function movie()
{
return $this->belongsTo(Movie::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
Controller
少し複雑なクエリになってしまうので、クエリ内で利用している関数と句について簡単に捕捉します。
実行構文
Controller\MovieController.php
//$requestから値を取得し、$keywordに格納
$keyword = $request->input('search');
//検索結果を$movieに格納
$movie = Movie::query()
->where(function ($query) use ($keyword) {
$query->withWhereHas('comment', function ($subquery1) use ($keyword) {
$subquery1->where('comment', "LIKE BINARY", "%{$keyword}%")
->orWhere(function($subquery2) use($keyword) {
$subquery2->withWhereHas('user', function($subquery3) use($keyword) {
$subquery3->where('name', 'LIKE BINARY', "%{$keyword}%");
});
});
})
->orWhere('title', 'LIKE BINARY', "%{$keyword}%")
->orWhere('content', 'LIKE BINARY', "%{$keyword}%");
})
利用関数と句の補足
- “withWhereHas関数“でリレーションを紐づけつつ、リレーション先でも検索できるようにしています。
- “where関数“と”orWhere関数“を利用してAND、OR検索をしています。
- MariaDBを利用しているため、”LIKE句“だと濁点の区別がつきません。
なので”LIKE BINARY句“で取得しています。
最後に
クエリ中の構文のネストが気になりますね。
withWhereHas関数は便利な反面、複雑な処理をしているため実行速度が遅いと言われています。
処理を軽くするためにクエリの最適化をしてもいいですが、その代わり構文が複雑になるので一長一短になります。
実行速度と簡潔な構文を加味した最適な方法があれば教えていただけると幸いです。