過去の記事でも紹介されていますが、親子(or 1対多)関係にあるModelにおいて、「1つ以上子を持つ親」などの条件で絞り込む際にhas()
は便利です。
しかし、has()
を使わずともjoinSub()
でサブクエリを指定して同じ条件で絞り込む事もできそうです。
どちらを使うのがベターなのか?気になったので調べてみました。
has()とjoinSub()の違い
例えば、店(Shop)と商品(Product)というModelが有った場合、「1つ以上商品を持つ店」はそれぞれ以下で絞り込むことができます。
// has()で絞り込み $shops = Shop::has('product')->get(); // joinSub()で絞り込み $sub = Product::distinct()->select('shop_id'); $shops = Shop::joinSub($sub, 'sub', function ($join) { $join->on('sub.shop_id', '=', 'shop.shop_id'); })->get();
見ての通り、joinSub()
の方が冗長ですね。
発行されたsqlはそれぞれ以下になります。
# has()のsql select * from `shop` where exists (select * from `product` where `shop`.`shop_id` = `product`.`shop_id`); # joinSub()のsql select * from `shop` inner join (select distinct `shop_id` from `product`) as `sub` on `sub`.`shop_id` = `shop`.`shop_id`;
has()ではEXISTS句で相関サブクエリを使用して絞り込んでいます。
相関サブクエリとは
mysqlの公式ドキュメントからの引用です。
相関サブクエリは、外部クエリにも現れるテーブルへの参照を含むサブクエリです。
前述のhas()
のsqlでは、外部クエリのFROM
に指定しているshopをサブクエリ内で参照している為、相関サブクエリと言えます。
相関サブクエリの注意点
レコード数が多いテーブルに紐づいたModelにてhas()
使う際は注意が必要です。
相関サブクエリでは外部クエリの結果行数分、比較処理が行われる可能性があり、結果行数が多ければ遅くなる為です。
例えば前述のhas()
のsqlでは、shopのレコード数 x productのレコード数分の比較処理が走ることになります。
(EXISTS句では条件にヒットする行が見つかればその時点でtrueを返し、次のレコードの比較処理に移るため、あくまでも全件ヒットしなかった場合)
とはいえ、それをjoinSub()
に置き換えた場合もGROUP BY
やDISTINCT
で重複を削除する処理のコストが有るため、一概にどちらが速いと言い切れません。
実際に実装する環境でテストしてみるのが良いでしょう。
今回の実装環境ではhas()
でもjoinSub()
でも速度に大きな違いはありませんでした。
であればよりシンプルに記述できるhas()
がベターと考えてそちらを採用することにしました。