希望这能为人们节省时间:在 $stmt->execute() 之后应该使用 $count = $stmt->rowCount() 来真正确定“更新”或“替换”之类的操作是否成功,即是否更改了一些数据。
Jean-Lou Dupont。
(PHP 5 >= 5.1.0, PHP 7, PHP 8, PECL pdo >= 0.1.0)
PDOStatement::execute — 执行预处理语句
执行 预处理语句。 如果预处理语句包含参数标记,则:
必须调用 PDOStatement::bindParam() 和/或 PDOStatement::bindValue() 来将变量或值(分别)绑定到参数标记。 绑定的变量将它们的值作为输入传递,并接收其关联参数标记的输出值(如果有)。
或者必须传递一个仅输入参数值的数组
params
一个包含值的数组,其元素数量与正在执行的 SQL 语句中绑定的参数数量相同。 所有值都被视为 PDO::PARAM_STR
。
不能将多个值绑定到单个参数;例如,不允许将两个值绑定到 IN() 子句中的单个命名参数。
绑定超过指定数量的值是不可能的;如果 params
中存在比在 PDO::prepare() 中指定的 SQL 中更多的键,则语句将失败并发出错误。
如果属性 PDO::ATTR_ERRMODE
设置为 PDO::ERRMODE_WARNING
,则会发出级别为 E_WARNING
的错误。
如果属性 PDO::ATTR_ERRMODE
设置为 PDO::ERRMODE_EXCEPTION
,则会抛出 PDOException。
示例 #1 使用绑定的变量和值执行预处理语句
<?php
/* 通过绑定变量和值来执行预处理语句 */
$calories = 150;
$colour = 'gre';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour LIKE :colour');
$sth->bindParam('calories', $calories, PDO::PARAM_INT);
/* 名称也可以以冒号 ":" 为前缀(可选) */
$sth->bindValue(':colour', "%$colour%");
$sth->execute();
?>
示例 #2 使用命名值的数组执行预处理语句
<?php
/* 通过传递一个插入值的数组来执行预处理语句 */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour');
$sth->execute(array('calories' => $calories, 'colour' => $colour));
/* 数组键也可以以冒号 ":" 为前缀(可选) */
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
?>
示例 #3 使用位置值的数组执行预处理语句
<?php
/* 通过传递一个插入值的数组来执行预处理语句 */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?');
$sth->execute(array($calories, $colour));
?>
示例 #4 使用绑定到位置占位符的变量执行预处理语句
<?php
/* 通过绑定 PHP 变量来执行预处理语句 */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?');
$sth->bindParam(1, $calories, PDO::PARAM_INT);
$sth->bindParam(2, $colour, PDO::PARAM_STR, 12);
$sth->execute();
?>
示例 #5 使用数组为 IN 子句执行预处理语句
<?php
/* 使用 IN 子句的数组值执行预处理语句 */
$params = array(1, 21, 63, 171);
/* 创建一个参数占位符字符串,填充到参数数量 */
$place_holders = implode(',', array_fill(0, count($params), '?'));
/*
这将使用足够数量的未命名占位符来准备语句,以匹配 $params 数组中的每个值。
当语句执行时,$params 数组的值将绑定到预处理语句中的占位符。
这与使用 PDOStatement::bindParam() 不同,因为这需要对变量的引用。 PDOStatement::execute() 仅按值绑定。
*/
$sth = $dbh->prepare("SELECT id, name FROM contacts WHERE id IN ($place_holders)");
$sth->execute($params);
?>
注意:
某些驱动程序需要在执行下一个语句之前关闭游标。
希望这能为人们节省时间:在 $stmt->execute() 之后应该使用 $count = $stmt->rowCount() 来真正确定“更新”或“替换”之类的操作是否成功,即是否更改了一些数据。
Jean-Lou Dupont。
注意,您必须
- 要么将所有要绑定的值传递到 PDOStatement::execute() 的数组中
- 要么在使用 PDOStatement::bindValue() 之前绑定每个值,然后在不带参数的情况下调用 PDOStatement::execute()(即使是“array()”也不行!)。
将数组(空或非空)传递给 execute() 将“擦除”并替换任何先前的绑定(并可能导致,例如使用 MySQL,出现“SQLSTATE[HY000]: General error: 2031” (CR_PARAMS_NOT_BOUND),如果您传递了一个空数组)。
因此,如果准备好的语句之前已“绑定”,则以下函数不正确
<?php
function customExecute(PDOStatement &$sth, $params = NULL) {
return $sth->execute($params);
}
?>
因此应替换为类似以下内容
<?php
function customExecute(PDOStatement &$sth, array $params = array()) {
if (empty($params))
return $sth->execute();
return $sth->execute($params);
}
?>
另请注意,PDOStatement::execute() 不需要 $input_parameters 为数组。
(当然,不要按原样使用它 ^^)。
插入值的数组(命名参数)不需要前缀冒号作为键值对才能工作。
<?php
/* 通过传递插入值的数组来执行预处理语句 */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour');
// 而不是:
// $sth->execute(array(':calories' => $calories, ':colour' => $colour));
// 这个也可以很好地工作:
$sth->execute(array('calories' => $calories, 'colour' => $colour));
?>
这允许使用“常规”的组装哈希表(数组)。
这确实很有道理!
当使用准备好的语句来执行多个插入(例如在循环中)时,在 sqlite 中,通过将循环包装在事务中,性能会大幅提高。
我的应用程序会定期插入 30,000-50,000 条记录。没有事务时,大约需要 150 秒,而使用事务只需 3 秒。
这可能也会影响其他实现,我相信它在某种程度上会影响所有数据库,但我只能使用 PDO sqlite 进行测试。
例如:
<?php
$data = array(
array('name' => 'John', 'age' => '25'),
array('name' => 'Wendy', 'age' => '32')
);
try {
$pdo = new PDO('sqlite:myfile.sqlite');
}
catch(PDOException $e) {
die('Unable to open database connection');
}
$insertStatement = $pdo->prepare('insert into mytable (name, age) values (:name, :age)');
// 开始事务
$pdo->beginTransaction();
foreach($data as &$row) {
$insertStatement->execute($row);
}
// 结束事务
$pdo->commit();
?>
(由 sobak 编辑:Pere 于 2014 年 9 月 12 日 01:07 提交的错别字修复)
“您无法绑定比指定的更多值;如果 input_parameters 中的键比 PDO::prepare() 中指定的 SQL 中的键多,则语句将失败并发出错误。”但是,键较少可能不会导致错误。
只要查询字符串变量中的问号数量与 input_parameters 中的元素数量匹配,查询就会尝试执行。
即使查询字符串变量末尾有额外的信息,也会发生这种情况。分号表示查询字符串的结尾;SQL 引擎将变量的其余部分视为注释,但 PHP 将其视为 input_parameters 的一部分。
看看这两个查询字符串。唯一的区别是第二个字符串中的一个错别字,其中分号意外地替换了逗号。此 UPDATE 查询将运行,将应用于所有行,并将静默地损坏表。
<?php
/**
* 查询旨在根据 WHERE 子句更新行的子集
*/
$sql = "UPDATE my_table SET fname = ?, lname = ? WHERE id = ?";
/**
* 查询更新所有行,忽略分号后的所有内容,包括 WHERE 子句!
*
* 预期(但未收到):
*
*** 警告:
*** PDOStatement::execute():
*** SQLSTATE[HY093]:
*** 无效的参数数量:绑定的变量数量与标记数量不匹配...
*
*/
// 这里有错别字 ------------------------ |
// V
$sql = "UPDATE my_table SET fname = ?; lname = ? WHERE id = ?"; // 实际上只有一个标记
$pdos = $pdo->prepare($sql);
$pdos->execute( [ 'foo', 'bar', 3 ] ); // 三个 input_parameters
?>
PHP 5.4.45,mysqlnd 5.0.10
当传递一个值的数组来执行查询时,您的查询包含问号,请注意,该数组必须从零开始按数字索引。如果不是,请对它运行 array_values() 以强制重新索引数组。
<?php
$anarray = array(42 => "foo", 101 => "bar");
$statement = $dbo->prepare("SELECT * FROM table WHERE col1 = ? AND col2 = ?");
// 这将无法正常工作
$statement->execute($anarray);
// 请执行以下操作以使其正常工作
$statement->execute(array_values($anarray));
?>
简化 $placeholder 形式
<?php
$data = ['a'=>'foo','b'=>'bar'];
$keys = array_keys($data);
$fields = '`'.implode('`, `',$keys).'`';
# 这是我的方法
$placeholder = substr(str_repeat('?,',count($keys)),0,-1);
$pdo->prepare("INSERT INTO `baz`($fields) VALUES($placeholder)")->execute(array_values($data));
如果您的 MySQL 表有 500,000 行以上,并且您的脚本由于达到了 PHP 的内存限制而失败,请设置以下属性。
<?php $this->pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); ?>
这应该使错误消失并使内存使用量恢复正常。
奇怪的是,手册没有提供完整的 SELECT 示例。
<?php
$sql = <<<SQL
SELECT ALL name, calories, colour
FROM fruit
WHERE calories < :calories AND colour = :colour
SQL;
$select = $pdo->prepare($sql);
$select->execute(['calories' => 150, 'colour' => 'red']);
$data = $select->fetchAll();
我发现 PostgreSQL 有非常奇怪的行为。
在事务之外,您可以将布尔值 true/false 作为输入数组的成员传递,并且它似乎可以正常工作。
但是,*在*事务*中*,布尔值 true 可以正常工作,但布尔值 false 则不能。请改为传递类似于整数 0 或字符串“false”的“假值”。
似乎引号行为在不同版本之间有所变化,因为我当前的项目在一个设置上运行良好,但在另一个设置上却抛出错误(这两个设置非常相似)。
设置 1:Ubuntu 6.10,PHP 5.1.6,MySQL 5.0.24a
设置 2:Ubuntu 7.04,PHP 5.2.1,MySQL 5.0.38
导致问题的代码片段(已缩短)
<?php
$stmt = $pdo->prepare("SELECT col1, col2, col3 FROM tablename WHERE col4=? LIMIT ?");
$stmt->execute(array('Foo', 1));
?>
在第一个设置上,这会毫无问题地执行,但在第二个设置上会生成错误。
SQLSTATE[42000]: 语法错误或访问冲突:1064 您的 SQL 语法有错误;请查看与您的 MySQL 服务器版本相对应的参考手册以了解正确的语法。在'1' 附近
问题是,$stmt->execute() 会将传递给第二个占位符的数字加引号(导致:... LIMIT '1'),这在 MySQL 中是不允许的(在两个设置上都测试过)。
为了防止这种情况,您必须使用 bindParam() 或 bindValue() 并指定数据类型。
当您尝试使用日期进行查询时,请使用整个日期,而不仅仅是数字。
如果您像这样尝试,此查询将可以正常工作。
SELECT * FROM table WHERE date = 0
但是,如果您尝试使用预备语句,则必须使用完整的日期格式。
<?php
$sth = $dbh->prepare('SELECT * FROM table WHERE date = :date');
$sth->execute( $arArray );
//--- 错误:
$arArray = array(":date",0);
//--- 正确:
$arArray = array(":date","0000-00-00 00:00:00");
?>
MySQL 驱动程序肯定存在一些问题。
此致
T-Rex
如果缺少一个参数名或参数名拼写错误,则此函数将抛出级别为 E_WARNING 的错误,即使将 PDO::ATTR_ERRMODE 设置为 PDO::ERRMODE_SILENT!
在相同情况下,但当设置 PDO::ERRMODE_WARNING 时,此函数会抛出两个级别为 E_WARNING 的错误!
当设置 PDO::ERRMODE_EXCEPTION 时,此函数不会抛出任何错误,而是会抛出 PDOException。
即使使用 PDOStatement::bindParam() 函数并使用拼写错误的参数名,然后使用 PDOStatement::execute();所有这些都适用。
在以下环境中测试:Windows 10,PHP 5.5.35,mysqlnd 5.0.11,MySQL 5.6.30。
<?php
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE colour = :colour');
/*
请注意参数名称 ':color' 而不是 ':colour'。
当设置 PDO::ERRMODE_SILENT 时,此函数会抛出以下错误:
Warning: PDOStatement::execute(): SQLSTATE[HY093]: 无效的参数编号:参数未在... 中定义
当设置 PDO::ERRMODE_WARNING 时,此函数会抛出以下两个错误:
Warning: PDOStatement::execute(): SQLSTATE[HY093]: 无效的参数编号:参数未在... 中定义
Warning: PDOStatement::execute(): SQLSTATE[HY093]: 无效的参数编号在... 中
*/
$sth->execute(array(':color' => $colour));
?>
我花了好长时间才意识到这一点,而且文档似乎对在 SELECT 语句类型的查询中使用 PDO_Statement::execute() 没有说得太清楚,所以我想在这里指出这一点。在准备 SELECT 查询并使用 PDO_Statement::execute() 执行它时,您可以直接在同一个 PDO_Statement 对象上使用 PDO_Statement::fetch() 或 PDO_Statement::fetchAll()。这与使用 PDO::query() 返回 PDO_Statement 对象,然后在该对象上调用 PDO_Statement::fetch() 没有区别。这是因为 PDO_Statement 对象当然仍然是 PDO_Statement 对象,并且如 PDO::query 文档(https://php.net/manual/en/pdo.query.php)所述,PDO::query 也“[p]reparing and executing an SQL statement”。
<?php
$pdo_statement = $my_pdo_object->prepare( "SELECT * FROM `MyTable` WHERE `Field1` = 'this_string'" );
if ( true === $this->execute_safe_query( $pdo_statement ) ) {
echo $pdo_statement->fetch();
}
?>
注意:参数不适用于名称中带有连字符的参数,例如“:asd-asd”,您可以执行快速 str_replace("-","_",$parameter) 来解决此问题。
如果您在传递要绑定的布尔值时遇到问题,并且正在使用 Postgres 数据库... 但您不想对*每个* *单个* *参数*都使用 bindParam,请尝试传递字符串“t”或“f”而不是布尔值 TRUE 或 FALSE。
如果您要派生 PDOStatement 以扩展 execute() 方法,则必须使用默认 NULL 参数定义签名,而不是空数组。
换句话说
<?php
class MyPDOStatement extends PDOStatement {
// ...
// 不要使用这种形式!
// function execute($input_parameters = array()) {
// 使用这个代替:
function execute($input_parameters = null) {
// ...
return parent::execute($input_parameters);
}
}
?>
顺便说一句,这就是为什么我总是将默认参数设置为 NULL,并在方法或函数体中处理实际的正确默认参数。 因此,当您必须使用所有参数调用函数时,您知道始终将 NULL 传递给默认值。
截至 5.2.6,您仍然无法使用此函数的 $input_parameters 将布尔值传递给 PostgreSQL。 为此,您必须为查询中的每个参数显式调用 bindParam()。
我们知道您无法在数据库解析之前看到最终的原始 SQL,但如果您想模拟最终结果,这可能会有所帮助。
<?php
public function showQuery($query, $params)
{
$keys = array();
$values = array();
# 为每个参数构建正则表达式
foreach ($params as $key=>$value)
{
if (is_string($key))
{
$keys[] = '/:'.$key.'/';
}
else
{
$keys[] = '/[?]/';
}
if(is_numeric($value))
{
$values[] = intval($value);
}
else
{
$values[] = '"'.$value .'"';
}
}
$query = preg_replace($keys, $values, $query, 1, $count);
return $query;
}
?>
正如一些人已经指出的,不要将使用名称作为键的数组传递给问号参数!
<?php
$sth = $dbh->prepare('INSERT INTO fruit (name, colour, colories) VALUES (?, ?, ?)');
// 这是错误的!
// $param = array("name" => "apple", "colour" => "red", "colories" => 150);
// 数组必须使用从零开始的整数作为键
$param = array("apple", "red", 150);
$sth->execute($param);
嗨,
只是一个小小的注意事项,让您在使用引号时能够顺利开始:PDO 不会替换给定的变量,如果它们被包裹在引号中,例如:
<?php
$st = $db->prepare( '
INSERT INTO fruits( name, colour )
VALUES( :name, ":colour" )
';
$st->execute( array( ':name' => 'Apple', ':colour' => 'red' ) );
?>
导致出现一个新的水果,像这样
-> Apple, :colour
而 "red" 没有被替换成 "colour"。 所以让变量**不带**引号 - PDO 会做这件事。
我意识到,当从命令行调试我的 PHP 脚本时,我遇到了严重的问题,尽管我去了 fetchAll 等等,但我总是收到以下错误:
SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.
我意识到我有一个双重初始化命令
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8; SET CHARACTER SET utf8;"
第一个是更好的选择,移除后者,错误就消失了。
我用过它,它返回布尔值=>
$passed = $stmt->execute();
if($passed){
echo "passed";
} else {
echo "failed";
}
如果语句失败,它将打印 failed。 您可能想要使用 errorInfo() 获取更多信息,但对我来说它似乎确实有效。
这个例子展示了如何生成所需数量的问号,这非常浪费
$place_holders = implode(',', array_fill(0, count($params), '?'));
相反,只需这样做
$place_holders = '?'.str_repeat(',?', count($params)-1);
自 simon dot lehmann at gmx dot comment 以来已经过去了 7 年,但今天我发现自己遇到了使用预处理语句的问题,该语句涉及 INSERT、用于 Microsoft Access 的 PDO odbc 驱动程序和 PHP 5.4.7。 预处理语句是使用 prepare + execute 方法完成的,抛出了一个难看的错误
"SQLExecDirect[-3500] at ext\\pdo_odbc\\odbc_driver.c:247" 错误
以及一个
42000 ("Syntax error or access violation") SQLSTATE。
他怀疑问题出在哪里,并指出了一个可能的解决方案:使用 bindParam() 或 bindValue() 并指定数据类型。
好吧,这似乎是正确地识别了问题的来源,但有一个更简单的解决方案对我有用,更简单,并且允许您继续使用 pdo::prepare() 以及 ? 作为参数和 pdo::execute()
您唯一需要做的就是,如果之前没有这样做,在将它们放入传递给 pdo::execute($array) 的数组之前,将绑定参数强制转换为其特定类型(数据库期望的类型)。
以下代码将失败,抛出上述错误
<?php
$name = "John";
$length = "1";
$price = "1.78";
$SQL = "INSERT INTO table (name, length, price) VALUES (?,?,?)";
$arra = array($name, $length, $price);
$sth = $msq->prepare($SQL);
$sth->execute($arra);
?>
这个代码对我来说就像一个魅力一样
<?php
$name = "John";
$length = (int)"1"; // 数据库期望这种类型
$price = (float)"1.78"; // 数据库期望这种类型
$SQL = "INSERT INTO table (name, length, price) VALUES (?,?,?)";
$arra = array($name, $length, $price);
$sth = $msq->prepare($SQL);
$sth->execute($arra);
?>
我在绑定时使用问号方式。 我尝试在执行时使用关联数组
$stmt->execute($values);
以列名作为键。 我认为这会让渲染查询更容易(因为我有可变数量的参数)。
我发现 execute 无法使用关联数组,只能使用数字数组(具有数字索引)。
调试预处理语句有时很痛苦,因为您需要复制查询并在数据库中直接运行它。 以下函数是一个示例,说明如何编译自己的查询(当然,它需要一些调整,并且可能不适用于所有场景)。
<?php
$sql = "
SELECT t1.*
FROM table1 AS t1
INNER JOIN table2 AS t2 ON (
t2.code = t1.code
AND t1.field1 = ?
AND t1.field2 = ?
AND t1.field3 = ?
)
";
$stmt = $pdo->prepare($sql);
$params = [ 'A', 'B', 'C' ];
$stmt->execute($params);
// 输出编译后的查询语句
debug($sql, $params);
function debug($statement, array $params = [])
{
$statement = preg_replace_callback(
'/[?]/',
function ($k) use ($params) {
static $i = 0;
return sprintf("'%s'", $params[$i++]);
},
$statement
);
echo '<pre>Query Debug:<br>', $statement, '</pre>';
}
?>
这将输出类似以下内容
SELECT t1.*
FROM table1 AS t1
INNER JOIN table2 AS t2 ON (
t2.part_code = t1.code
AND t1.field1 = 'A'
AND t1.field2 = 'B'
AND t1.field3 = 'C'
)
对于这样的查询
SELECT
t1.user_id, t1.user_name,
t2.*
FROM table1 t1
LEFT JOIN table2 t2 ON t2.user_id = t1.user_id
WHERE t1.user_id = 2
如果表2中没有 user_id=2 的条目,则结果中的 user_id 将为空。
SELECT
t1.user_id, t1.user_name,
t2.user_pet, t2.user_color, t2.user_sign
FROM table1 t1
LEFT JOIN table2 t2 ON t2.user_id = t1.user_id
WHERE t1.user_id = 2
此查询将返回非空的 user_id。
所以请谨慎使用通配符选择。
您也可以交换 t1 和 t2 的顺序以从 t1 获取 user_id(在 postgresql 上测试)
SELECT
t2.*,
t1.user_id, t1.user_name
FROM table1 t1
LEFT JOIN table2 t2 ON t2.user_id = t1.user_id
WHERE t1.user_id = 2
如果您不想打开异常抛出,请尝试以下操作
//$dbErr = $dbHandler->errorInfo(); 或者
$dbErr = $dbStatement->errorInfo();
if ( $dbErr[0] != '00000' ) {
print_r($dbHandler->errorInfo());
die( "<div class='redbg xlarge'>FAILED: $msg</div><br />".$foot);
// 或者以您自己的方式处理错误...
}
echo "SUCCESS: $msg<br />";
... 如果成功则继续
注意此方法的返回值。
[bool] => true 或 false
因此,当我检查多个查询(如 INSERT、UPDATE、REPLACE 和 DELETE)时,我得到了准确的结果 => bool(true),即查询已成功执行。
$query = "SELECT session_data" FROM sessions WHERE session_id = ?";
$stmt = $pdo->prepare($query);
$stmt->bindValue(1, 'login_user_5121');
if($stmt->execute()) {
print 'Query executed successfully';
}
//输出
Query executed successfully
所以,我认为您可能不需要使用 { $stmt->rowCount() } 方法来确定查询是否已成功执行。
但在每个社区中,我都没有看到任何人指出这一点,所以我可能在这里错了,但您应该尝试使用多个查询。