PreparedStatementでNULLを扱う
挿入・更新
挿入・更新する値がNULLであり得る場合。(決まっているわけではなく、普通の値が入ることもある)
php - Execute PDO with an array containing null values - Stack Overflow
以下のようなSQLを、PDOのプリペアドステートメントにセットしたとして、
UPDATE table SET name=:name, id_extra1=:ex1, id_extra2=:ex2 WHERE id=1;
たとえばid_extra2に入れる値が、NULLであり得るとする。
// $pdo = 接続済みのPDOインスタンス $stmt = $pdo->prepare( 'UPDATE table SET name=:name, id_extra1=:ex1, id_extra2=:ex2 WHERE id=1;'); $stmt->execute([ ':name' => 'hoge', ':ex1' => 1, ':ex2' => null, // ここ ]);
上記のコードでは、id_extra2カラムに入るのはNULLにはならない。id_extra2の型がINTなら'0'、DATEなら'0000-00-00'になってしまう。
これは、execute()
の引数に[placeholder ⇒ value]の配列を与えた場合、その型は全てPDO::PARAM_STR
(文字列型)として処理されてしまうため。なので、そこにPHPとしてのnull
が与えられても、SQL上では空文字列が指定されたことになってしまう。
-- 上記の$stmtの実行時の状態(たぶんこんな感じ) UPDATE table SET name='hoge', id_extra1='1', id_extra2='' WHERE id=1;
SQLとしてのNULLを代入するには、placeholderに値をバインドする際、型にPDO::PARAM_NULL
を指定しなければならない。
しかし、execute()
の引数に渡す方法では、型は指定する方法が無い。
bindValue()
なら、第3引数に型を指定することができるので、それを使う。
// $stmt は上記のものと一緒 $stmt->bindValue(':name', 'hoge'); // INTに関しては、型を省略してもSQLが解釈してくれるため、不要っちゃ不要。明示するという意味はある $stmt->bindValue(':ex1', 1, PDO::PARAM_INT); $stmt->bindValue(':ex2', null, PDO::PARAM_NULL); // ←ここで指定 $stmt->execute();
もし、execute()と似た感じで実行時に配列一発で与えたければ、擬似的な関数を使う方法がある。(ただしこれはこれで、$valueには意図しない配列が入ることは無い、などの前提が必要になるけど)
function executeWithDataTypes(PDOStatement $stmt, array $values) { foreach ($values as $key => $value) { if (is_array($value)) { $stmt->bindValue($key, $value['value'], $value['type']); } else { $stmt->bindValue($key, $value); } } return $stmt->execute(); } // $stmt は上記のものと一緒 executeWithDataTypes($stmt, [ ':name' => 'hoge', ':ex1' => 1, ':ex2' => ['value' => null, 'type' => PDO::PARAM_NULL], ]);
参照
WHERE節の中で、あるカラムの値が、NULLのものを抽出する時もある場合。たとえば以下のSQLで、:id
がNULLである時もあるような場合。
SELECT * FROM table WHERE id=:id; -- 以下の構文は、SQL構文エラーになる。 SELECT * FROM table WHERE id=NULL; -- NULLの場合はISで比較しなければならない。 SELECT * FROM table WHERE id IS NULL;
MySQLの場合
プレースホルダで「= ?」と「IS NULL」に同時に対応する方法 - Qiita
MySQLでは、右辺が、実態のある値でも、NULLでも、同じように比較できる演算子が用意されている。楽ちん。
SELECT * FROM table WHERE id<=>:id;
ただし、':id' placeholderへの関連づけは、挿入・更新時と同様、型を指定する必要があるため、その点は上記参照。
そういう演算子が無い場合
PHPで、SQLを生成するときに場合分けするという愚直な方法が、ちょっと条件分岐は増えるけど、一番わかりやすそう。
$sql = 'SELECT * FROM table '; $query_params = []; if ($id === null) { $sql .= 'WHERE id IS NULL;'; } else { $sql .= 'WHERE id = :id'; $query_params[':id'] = $id; } $stmt = $pdo->prepare($sql); $stmt->execute($query_params);