こんにちは、Art Directorの奥田です。
夏季休暇は妻の実家に帰省し、リフレッシュできるかと思いきや甥たちの強襲に会い、更に疲れが増してしまいました(笑)
しかし帰省はやはりいいものですね。もう一つ田舎が出来たようで毎年帰るのが楽しみです。
さて今回はLaravelでさまざまな条件でデータを絞り込む方法をご説明いたします。
Table of contents
通常のQueryの使い方
まずは通常の絞り込み方法です。
Controller側で使用するModelをuseで記述し、queryを呼び出し、最後にget()することで条件の行を取得することが出来ます。
use App\Post; class PostsController extends Controller { public function index(){ $query = Post::query(); $query->where('user_id',1); // user_id が 1 のものだけを取得する $posts = $query->get(); } }
例えば条件に合ったものの1行目だけを取得したいときはfirst()とするだけでOKです。
$query = Post::query(); $query->where('status',1); // status が 1 のものだけを取得する $first_post = $query->first(); // 1行目だけを取得
他にもプライマリーキーが存在するテーブルであればfind($id)とするだけで取得することが可能です。
$post = Post::find(1);// id が 1 のものだけを取得する
find()は配列で指定すれば複数取得することも可能です。
$posts = Post::find([1,2,3]);// id が 1,2,3 のものを取得する
記事の編集ページでURL内に指定したidの記事を取得して編集したい場合があると思います。
そんな場合はまずRoute内に{}で取得したいキーを指定し、同じキー名でコントローラー側で値を受け取ることで記事を取得できます。
Route::get('/posts/edit/{id}','PostsController@edit');
public function edit($id){ $post = Post::findOrFail($id); // findOrFailはその行が存在しない場合に404を返します。
複数条件での絞り込み
AND検索
複数条件での絞り込みについてはAND検索であれば簡単です。
whereを追加してあげればそれだけでAND検索になります。
$query = Post::query(); $query->where('user_id',1); $query->where('status',1); $posts = $query->get();// user_id が 1 のもの且つstatus が 1 のものだけを取得する
同じフィールドの複数条件の指定はwhereIn()を使います。
$query->whereIn('user_id',[1,2,3]); // user_id が 1,2,3 のものを取得する
OR検索
OR検索はorWhereを使うのですがANDとは少し違い、クロージャーで指定します。
$query->where(function($query){ $query->where('id', 1) ->orWhere('status', 1); // idが1またはstatusが1のものを取得する });
キーワード検索などで複数のフィールドのOR検索がしたい場合などは以下のようにします。
$q = \Request::get('q'); // キーワードの値をGETで取得。 $query = Post::query(); if ($q) { $query->where(function($query) use ($q){ $query->where('title', 'LIKE', "%$q%") ->orWhere('content', 'LIKE', "%$q%"); //titleもしくはcontentフィールドのキーワード検索 }); }
カラム数の違う別テーブルの複数条件での絞り込み
さて、ここまではググれば出てくるような簡単な内容をご紹介しました。
今回、カラム数の違うテーブルの絞り込みを実装したい時にかなり苦戦しましたのでその方法をご紹介いたします。
例として、postsテーブルとcommentsテーブルの複数カラムからキーワード検索をしたい場合でご説明いたします。
※必須条件としては取得した後の値は同じフィールドがどちらのテーブルにも存在している必要があります。
はじめは単純に条件結果を結合すればいいと思い、調べてみるとunion()というメソッドがあったので以下のようにしました。
$q = \Request::get('q'); $posts = Post::query(); if ($q) { $posts->where(function($posts) use ($q){ $s->where('title', 'LIKE', "%$q%") ->orWhere('content', 'LIKE', "%$q%"); }); } $comments = Comment::query(); if ($q) { $comments->where(function($comments) use ($q){ $comments->where('title', 'LIKE', "%$q%") ->orWhere('comment_body', 'LIKE', "%$q%"); }); } $results = $posts->union($comments)->get();
すると「The used SELECT statements have a different number of columns」というエラーが出て結合に失敗しました。
調べてみると2つのテーブルのカラム数が一致しない場合に起こるエラーでした。
このエラーの回避方法は以下のように取得するフィールドを同じにしてしまえば良いようです。
$q = \Request::get('q'); $posts = Post::query(); $posts->select(['id','title','updated_at']); // SELECTするフィールドを合わせる if ($q) { $posts->where(function($posts) use ($q){ $s->where('title', 'LIKE', "%$q%") ->orWhere('content', 'LIKE', "%$q%"); }); } $comments = Comment::query(); $comments->select(['id','title','updated_at']); // SELECTするフィールドを合わせる if ($q) { $comments->where(function($comments) use ($q){ $comments->where('title', 'LIKE', "%$q%") ->orWhere('comment_body', 'LIKE', "%$q%"); }); } $results = $posts->union($comments)->get();
結合した結果のページネーションを機能させる方法
先程のように強引にデータを抽出した際にページネーションが機能しなくなってしまいます。
そんなときは通常のPaginatorを拡張してしまいます。
以下のメソッドを追加します。
function custom_paginate($items, $perPage) { $pageStart = request('page', 1); $offSet = ($pageStart * $perPage) - $perPage; $itemsForCurrentPage = $items->slice($offSet, $perPage); return new \Illuminate\Pagination\LengthAwarePaginator( $itemsForCurrentPage, $items->count(), $perPage, \Illuminate\Pagination\Paginator::resolveCurrentPage(), ['path' => \Illuminate\Pagination\Paginator::resolveCurrentPath()] ); }
次に結合した結果を引数に入れて実行することで通常通りのページネーションが実現します。
$results = $this->custom_paginate($results, 20); // 第二引数は1ページあたりの件数