laravelのwhereHasが遅いので別のクエリビエルダで処理したい
ソリューションセクションの由良です
リレーション先のテーブルの条件で検索したいときによくwhereHasを使うのですが、件数が増えるとタイムアウトになり検索結果が見れないといわれたりします。
大抵インデックスを張ればいいんですがそれで対応しけれないときがあったので
今回はこれを改善するためにAIに聞きつつ備忘録としてブログに残します。
基本whereHasを使うと次のようなコードになるのですが
User::whereHas(‘orders’, function ($q) {
$q->where(‘status’, ‘paid’);
})->get();
これをSQLにすると
WHERE EXISTS (
SELECT * FROM orders
WHERE orders.user_id = users.id
AND status = ‘paid’
)
EXISTSサブクエリ+リレーション解決が発生し遅くなります。
件数やwhereHasが増えるほどにパフォーマンスに影響を与えてタイムアウトします。
解決策は以下の3つ
① 最速パターン:JOIN に置き換える
② Eloquent を維持:whereIn + subquery(JOINより安全)
③ Query Builder で exists を直書き(whereHas の軽量版)
$query = $query->whereHas(‘result’, function ($query) use ($key, $value) {
$query->where($key, $value);
});
このクエリビルダを書き換えます
① 最速パターン:JOIN に置き換える
$query->join(‘results’, ‘results.item_id’, ‘=’, ‘items.id’)
->where(“results.$key”, $value)
->select(‘items.*’)
->distinct();
インデックスがそのまま効きEloquentの余計なラッパーを回避します
hasMany の場合は 必ず distinct()(または groupBy(‘items.id’))を使う
② Eloquent を維持:whereIn + subquery(JOINより安全)
$query->whereIn(‘id’, function ($q) use ($key, $value) {
$q->select(‘item_id’)
->from(‘results’)
->where($key, $value);
});
SQL が単純で重複行が出ない
ベースのPK(ここでは id)は実際のものに合わせる
③ Query Builder で exists を直書き(whereHas の軽量版)
$query->whereExists(function ($q) use ($key, $value) {
$q->select(DB::raw(1))
->from(‘results’)
->whereColumn(‘results.item_id’, ‘items.id’)
->where($key, $value);
});
Eloquent のリレーション解決を省ける
コーディングにAIを使うようになってきましたが、まだまだ使うのに慣れていない状況で間違った答えも帰ってきますが
こういった局所の解決にAIを使うのは結構頼りになります。
今後はとりあえずwhereHasやめてJOIN に置き換えようと思います。




