稀にクエリビルダの FROM を詰め替えたい時があります。これは例えば次のようなコードです。
// サブクエリをFROMにします
$fromQuery = \DB::query()
->from('users')
->where('created_at', '>', '2024-06-01');
$query = \DB::query()->fromSub($fromQuery, 'users');
// 後でサブクエリを変更して、それをFROMであると再定義します。
$fromQuery = $fromQuery->orderBy('created_at', 'desc')->limit(10);
$query = $query->fromSub($fromQuery, 'users');
// 詰め替えたクエリを実行します
$users = $query->get();
// SQLSTATE[HY093]: Invalid parameter number とエラーになります
実際のコードですともっと紆余曲折あるものになりやすいのですが概ねこの形です。クエリの高速化などの改修をする際に比較的出てきやすいです。このパラメータ数エラーは主となる$queryにサブクエリを FROM として2回設定した際それぞれの時にパラメータがバインドされているため起きます。そのため次のようにすると解決します。
// サブクエリをFROMにします
$fromQuery = \DB::query()
->from('users')
->where('created_at', '>', '2024-06-01');
$query = \DB::query()->fromSub($fromQuery, 'users');
// 後でサブクエリを変更して、それをFROMであると再定義します。
$fromQuery = $fromQuery->orderBy('created_at', 'desc')->limit(10);
/**
* FROM を再設定する直前に FROM に関するパラメータを空にします
*/
$query->setBindings([], 'from');
$query = $query->fromSub($fromQuery, 'users');
// 詰め替えたクエリを実行します
$users = $query->get();
// エラーになりません
エラーの原因ですが次のようにクエリとバインドされている値を表示するとわかりやすいです。
// クエリの構築を一通りした後にダンプします
dd([
$query->toSql(),
$query->getBindings(),
]);
// エラーが起きる状態ですと次のように ? の数とバインディングの数が合わないです
/*
array:2 [▼ // xxx.php:19
0 => "select * from (select * from `users` where `created_at` > ? order by `created_at` desc limit 10) as `users`"
1 => array:2 [▼
0 => "2024-06-01"
1 => "2024-06-01"
]
]
*/
このようにバインディングの数が合わないため”不正なパラメータ数”というエラーになります。こうなる理由は FROM をセットする度に FROM のサブクエリに含まれるバインディングを引継ぐからです。このため FROM セット前に FROM についてのバインディングを空にすることで問題が解決します。
しばしば JOIN 前に明示的にクエリを一部処理しておくことでクエリ全体の高速化につながる時があります。そういった時に例の様な挙動をする改修を行う時があるのですが、このエラーでちょっと詰まりやすいです。解決の一助となれば幸いです。