PHPからPDOのPrepared Statementを使ってMariaDBを操作してると、たまに遭遇するのが表題のエラー。
何がどうあかんのか具体的に教えてくれないので、意外とハマりがちでして。
以下、筆者が経験的にマスターしたエラーの原因あるあるを。
1. パラメータ名が間違っている
今一度、落ち着いて確認しましょう
2. (SQL実行を連続で記述しているとき)直前のprepareが抜けている
同上。
3. SQLの指定が間違っている
これ、意外と分かりにくいんですよね。
MariaDBは、下記の方法で再起動することなくクエリーログを取得できます。
ここで実際にPrepareされたSQLの内容が分かるので、実際の値を当てはめて指定内容が正しいか確かめるとよいでしょう。
①Mariadbにrootでログインして、以下のコマンドを実行します。
SET GLOBAL general_log = 'ON'
該当のMariaDBエンジンを使ってる全データベースでクエリーログを取得する(=オーバーヘッドが増える)ので、テストサーバーなどを用い、ほかのプログラムに影響を与えないよう配慮の上実行します。
②次のコマンドを実行して、状態やログファイル名を確認します。
SHOW VARIABLES LIKE 'general_log%'
::: note info
当該ログは一般に、CentOS7では/var/lib/mysql/配下に、Windowsであればmysql\data\配下に、ホスト名.logのファイル名で出力されます。
:::
③デバッグが完了したら、必ず次のコマンドを実行してログ出力を止めます。
SET GLOBAL general_log = 'off'
4. PDO接続時のATTR_EMULATE_PREPARESオプション:Falseのとき、文字通り実際に実行されるSQLと割り当ててるパラメータの数が異なる
以下に2パターン挙げましたが、いずれもATTR_EMULATE_PREPARESパラメータ:Trueの時はパスしました(PDOのデフォルトはTrue)。
不足はまだしも、パラメータを過剰に与えてもあかんようでして、開発途中で上記オプションを変えるときは十分ご注意を。
①よくあるパターン1
$sql = "SELECT * FROM tbl1 ";
if (条件) {
$sql .= "WHERE a = :item1 ";
} else {
$sql .= "WHERE b = :item2 ";
}
$smt->prepare($sql);
$smt->bindvalue("item1", $item1, PDO::PARAM_STR);
$smt->bindvalue("item2", $item2, PDO::PARAM_STR);
・・・
ってやりがちなんですが、面倒でも
$smt->prepare($sql);
if (条件) {
$smt->bindvalue("item1", $item1, PDO::PARAM_STR);
} else {
$smt->bindvalue("item2", $item2, PDO::PARAM_STR);
}
などとせねばならないようです。
②よくあるパターン2
$sql = "SELECT * FROM tbl1 ";
$sql .= "WHERE id = :id ";
$sql .= "UNION ";
$sql .= "SELECT * FROM tbl2 ";
$sql .= "WHERE id = :id ";
$smt = $mdb=>prepare($sql);
$smt->bindValue("id", 3, PDO::PARAM_INT);
・・・
も、面倒&ブサイクですが
$sql = "SELECT * FROM tbl1 ";
$sql .= "WHERE id = :id1 ";
$sql .= "UNION ";
$sql .= "SELECT * FROM tbl2 ";
$sql .= "WHERE id = :id2 ";
$smt = $mdb=>prepare($sql);
$smt->bindValue("id1", 3, PDO::PARAM_INT);
$smt->bindValue("id2", 3, PDO::PARAM_INT);
としたら解決しました。