タイトル: 配列を条件にする方法
SEOタイトル: Laravel Query Builder で配列条件 (whereIn) 完全ガイド
| この記事の要点 |
|
基本: whereIn / whereNotIn
use Illuminate\Support\Facades\DB;
use App\Models\User;
// ID 配列で絞り込み (SQL: WHERE id IN (1, 2, 3))
$ids = [1, 2, 3];
$users = User::whereIn('id', $ids)->get();
$users = DB::table('users')->whereIn('id', $ids)->get();
// 否定 (SQL: WHERE status NOT IN (...))
$active = User::whereNotIn('status', ['deleted', 'banned'])->get();
// 空配列を渡すと: whereIn は「常に false」、whereNotIn は「常に true」
User::whereIn('id', [])->get(); // → 必ず空 (WHERE 0=1)
User::whereNotIn('id', [])->get(); // → 全件 (WHERE 1=1)
// サブクエリ
$activeUserIds = User::where('status', 'active')->pluck('id');
$orders = Order::whereIn('user_id', $activeUserIds)->get();
// サブクエリ式
$orders = Order::whereIn('user_id', function ($q) {
$q->select('id')->from('users')->where('status', 'active');
})->get();
複数カラムの IN (whereIn のタプル)
// 標準 SQL: (user_id, product_id) IN ((1, 100), (2, 200))
// Laravel では whereIn にカラム配列を渡せるバージョンあり (DBドライバ依存)
DB::table('orders')->whereIn(['user_id', 'product_id'], [
[1, 100],
[2, 200],
])->get();
// または whereRaw で書く
DB::table('orders')
->whereRaw('(user_id, product_id) IN ((?, ?), (?, ?))', [1, 100, 2, 200])
->get();
複合条件: クロージャでグルーピング
// (name LIKE ? OR email LIKE ?) AND status = 'active'
$users = User::where(function ($q) use ($keyword) {
$q->where('name', 'LIKE', "%{$keyword}%")
->orWhere('email', 'LIKE', "%{$keyword}%");
})
->where('status', 'active')
->get();
// 動的に条件を組み立てる
$query = User::query();
if ($keyword) {
$query->where(function ($q) use ($keyword) {
$q->where('name', 'LIKE', "%{$keyword}%")
->orWhere('email', 'LIKE', "%{$keyword}%");
});
}
if (!empty($statuses)) {
$query->whereIn('status', $statuses);
}
if ($from) $query->where('created_at', '>=', $from);
if ($to) $query->where('created_at', '<=', $to);
$users = $query->orderBy('id')->paginate(20);
配列 (HashMap 風) を where() で展開
// 配列を渡すと AND で連結
$users = User::where([
'status' => 'active',
'role' => 'admin',
'company_id' => 10,
])->get();
// → WHERE status='active' AND role='admin' AND company_id=10
// 各条件に演算子を指定したい場合
$users = User::where([
['age', '>=', 20],
['status', '=', 'active'],
['name', 'LIKE', 'A%'],
])->get();
JSON カラムの配列条件
MySQL 5.7+ / PostgreSQL の JSON / JSONB カラムは専用メソッドで条件指定できます:
// users.preferences = '{"languages": ["ja", "en"]}'
// JSON 配列に特定の値が含まれる
User::whereJsonContains('preferences->languages', 'ja')->get();
// 複数値すべて含む
User::whereJsonContains('preferences->languages', ['ja', 'en'])->get();
// JSON 配列の要素数
User::whereJsonLength('preferences->languages', '>=', 2)->get();
// JSON オブジェクトのキー値
User::where('preferences->theme', 'dark')->get();
// → WHERE JSON_EXTRACT(preferences, '$.theme') = 'dark'
// JSON カラムをアップデート
DB::table('users')
->where('id', 1)
->update(['preferences->theme' => 'light']);
PostgreSQL 配列型カラム
PostgreSQL は配列型 (text[], int[] 等)をネイティブにサポート。ANY / && / @> 演算子で条件指定:
// products.tags TEXT[] (例: ['ja', 'en', 'sale'])
// 'ja' が含まれる
DB::table('products')
->whereRaw("'ja' = ANY(tags)")
->get();
// 配列同士の重なり (1 つでも一致)
DB::table('products')
->whereRaw("tags && ARRAY['ja', 'en']::text[]")
->get();
// 配列の包含 (tags が指定配列を全て含む)
DB::table('products')
->whereRaw("tags @> ARRAY['ja', 'en']::text[]")
->get();
// バインドパラメータ版
DB::table('products')
->whereRaw('? = ANY(tags)', ['ja'])
->get();
IN サブクエリとの比較
| 方式 | 使う場面 | 性能 |
|---|---|---|
whereIn([...]) | 固定配列・少件数 (~ 数千) | 速い |
whereIn(クロージャ) | サブクエリで動的取得 | オプティマイザ次第 |
whereExists | 存在チェック・大量データ | 速い |
JOIN | 関連データ取得も同時に | 速い |
IN(...) 大量パラメータ | ~ 数万件 | パラメータ上限 (MySQL: 65535、Oracle: 1000) に注意 |
性能上の注意
// ❌ 数万件の配列を whereIn → クエリが巨大化
$ids = range(1, 50000);
User::whereIn('id', $ids)->get(); // SQL が数 MB に
// ✅ 一時テーブルに INSERT してから JOIN
DB::statement('CREATE TEMPORARY TABLE tmp_ids (id BIGINT PRIMARY KEY)');
DB::table('tmp_ids')->insert(array_map(fn($id) => ['id' => $id], $ids));
$users = DB::table('users')
->join('tmp_ids', 'users.id', '=', 'tmp_ids.id')
->get();
// ✅ chunk で分割実行
foreach (array_chunk($ids, 1000) as $chunk) {
User::whereIn('id', $chunk)->each(function ($user) {
// 処理
});
}
// ✅ サブクエリ版 (DB 内で完結)
$users = User::whereIn('id', function ($q) {
$q->select('user_id')->from('orders')->where('status', 'completed');
})->get();
Eloquent の whereHas / whereRelation
// 「アクティブな注文を持つユーザー」
$users = User::whereHas('orders', function ($q) {
$q->where('status', 'active');
})->get();
// Laravel 9+ のショートカット
$users = User::whereRelation('orders', 'status', 'active')->get();
// 注文を 5 件以上持つユーザー
$users = User::has('orders', '>=', 5)->get();
// 注文を持たない
$users = User::doesntHave('orders')->get();
FAQ
Q: 空配列を whereIn に渡すと SQL エラーになる?
A: Laravel は内部で安全に処理し、WHERE 0=1 相当に展開します。何も返らないだけでエラーにはなりません。
Q: whereIn に 1 万件の配列を渡しても大丈夫?
A: MySQL の max_allowed_packet (デフォルト 64MB) を超えるとエラー。Oracle は IN リスト 1000 件上限です。chunk 推奨。
Q: 配列の順序通りに結果を並べたい
A: orderByRaw("FIELD(id, " . implode(',', $ids) . ")") (MySQL)。PostgreSQL は array_position。