希望這能為大家節省時間:在 $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 = '?' . str_repeat(', ?', count($params) - 1);
/*
這會準備語句,其中包含足夠的未命名佔位符來對應 $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() 繫結每個值,然後呼叫沒有參數(甚至不是 "array()"!)的 PDOStatement::execute()。
將陣列(無論是否為空)傳遞到 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 提交的錯字修正]
奇怪的是,手冊沒有提供完整的 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();
當傳遞一個數值陣列給 execute 時,如果您的查詢包含問號,請注意該陣列的索引必須從零開始的數字鍵。如果不是,請對其執行 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));
?>
「您不能綁定比指定的更多的值;如果 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]:
*** 無效的參數編號:綁定變數的數量與 token 的數量不符...
*
*/
// 這裡有錯字 ------------------------ |
// V
$sql = "UPDATE my_table SET fname = ?; lname = ? WHERE id = ?"; // 一個有效的 token
$pdos = $pdo->prepare($sql);
$pdos->execute( [ 'foo', 'bar', 3 ] ); // 三個 input_parameters
?>
PHP 5.4.45,mysqlnd 5.0.10
簡化的 $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); ?>
這應該會使錯誤再次消失,並將記憶體使用量恢復正常。
我發現 PostgreSQL 有非常奇怪的行為
在交易之外,您可以將布林值 true/false 作為輸入陣列的成員傳遞,它似乎可以正常工作。
但是,在交易*內部*,布林值 true 可以正常工作,但布林值 false 不行。相反,傳遞一些「falsey」的值,例如整數 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 行附近使用 ''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
如果遺失或拼錯一個參數名稱,即使 PDO::ATTR_ERRMODE 設定為 PDO::ERRMODE_SILENT,此函數也會拋出 E_WARNING 層級的錯誤!
在相同的情況下,但當 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]: Invalid parameter number: parameter was not defined in...
當 PDO::ERRMODE_WARNING 設定時,此函數會拋出以下兩個錯誤:
Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: parameter was not defined in...
Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number in...
*/
$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.dev.org.tw/manual/en/pdo.query.php)所說,PDO::query 也「[p]repares and executes 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;
}
?>
嗨,
只是一個快速的注意事項,讓您在使用引號時能順利開始:如果 PDO 給定的變數包在引號中,則不會替換這些變數,例如
<?php
$st = $db->prepare( '
INSERT INTO fruits( name, colour )
VALUES( :name, ":colour" )
';
$st->execute( array( ':name' => 'Apple', ':colour' => 'red' ) );
?>
會產生一個像這樣的新水果
-> Apple, :colour
而沒有將顏色替換為「red」。因此請將變數保留在沒有引號的情況下 - PDO 會處理。
正如一些人已經指出的那樣,不要將使用名稱鍵控的陣列傳遞給問號參數!
<?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);
我發現當我從命令列偵錯我的 PHP 指令碼時遇到了嚴重的麻煩,並且儘管我使用了 fetchAll 等功能,但我總是收到錯誤
SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.
我發現我有一個重複的 init 命令
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 ("語法錯誤或存取違規") 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 無法使用關聯陣列,只能使用數字陣列(帶有數字索引)。