Laravel以前は、ほぼコードにSQL文を埋め込んでいたので、Eloquentよりクエリビルダーの方が馴染みあります。特に複数のDBテーブルをjoinした検索などには。

しかし、各Modelにおいてリレーションを定義していると、それを使用しないのがもったいないように思えてきます。

クエリビルダーでできることをEloquentではどうやるのか、興味ありありになってきました。

前回と同じ検索、親子関係のテーブルをリレーションを使ってできるか考えてみましょう。

前回と同様に、商品productと商品画像product_imageの親子関係、つまり、1対多の関係があるとします。
その関係をモデルで定義するには、以下。一応名前は、product_imagesと複数形にしてありますが、関数名のコンフリクトがないなら単数形でもOKです(要はプログラム内でどちらかに統一すること)。

namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
...
    public function product_images()
    {
        return $this->hasMany('App\ProductImage');
    }
}

Laravelのマニュアルによると、hasが親子のテーブルをjoinしてくれるようです。


$products = Product::has('product_images')->get();

この実行は以下のようなSQL文となります。


select * from `product` where exists (select * from `product_image` where `product_image`.`product_id` = `product`.`product_id`)

ちょっと通常のjoinとは違いますね。

しかし、商品画像のレコードを1つでも持つ商品は、これ使えそうですね。そうなら、検索値で絞るとすると、


$input = [
    'name' => '商品名',
    'mime' => 'image/gif'
];

$products = Product::has('product_images')
    ->where('name', $input['name'])
  ->where('mime', $input['mime'])
  ->get();

しかし、これを実行するとエラーとなります。なぜなら、whereは両方とも、Productに対しての条件となり、mimeの項目名が、productに存在しないというエラーとなります。

正しくやるには、前回のクエリビルダーで使用したwhereInのようなものが必要です。


$input = [
    'name' => '商品名',
    'mime' => 'image/gif'
];

$products = Product::where('name', $input['name'])
    ->whereHas('product_images', function($query) use($input) {
	$query->where('mime', $input['mime']);
})->get();

今回は、クエリビルダーと違って、whereInを使うではなく、whereHasとなります。また、whereHasゆえに、Product::hasは要らなくなります。ちょっと慣れが必要ですね。

これを実行すると、SQL文は以下のようになります。

select * from `product` where `name` = 'Produt' and exists (select * from `product_image` where `product_image`.`product_id` = `product`.`product_id` and `mime` = 'image/gif')

By khino