php - 我可以将数组绑定到一个IN( ) 条件?

  显示原文与译文双语对照的内容

我想知道是否可以使用PDO将一个值数组绑定到一个占位符。 这里的用例试图传递一个值数组,用于 IN() 条件。 我不擅长解释,所以这里有一些psuedocode来演示。。 我希望能够执行以下操作:


<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
 'SELECT *
 FROM table
 WHERE id IN(:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

并具有PDO绑定并引用数组中的所有值。

现在我正在做:


<?php
$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
foreach($ids as &$val)
 $val=$db->quote($val);//iterate through array and quote
$in = implode(',',$ids);//create comma separated list
$stmt = $db->prepare(
 'SELECT *
 FROM table
 WHERE id IN('.$in.')'
);
$stmt->execute();
?>

这当然是做了这个工作,但只是想知道是否有一个内置的解决方案?

cheers!

时间:

我想soulmerge是对 你必须构建 QueryString 。


<?php
$ids = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids), '?'));

$db = new PDO(...);
$stmt = $db->prepare(
 'SELECT *
 FROM table
 WHERE id IN('. $inQuery. ')'
);

//bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
 $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

,你是对的。 修复了代码( 但没有测试它)

同时编辑: 克里斯( 评论) 和somebodyisintrouble设想这些 foreach-loop - -


(...)
//bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
 $stmt->bindValue(($k+1), $id);

$stmt->execute();

可能是多余的,所以 foreach 循环和 $stmt->execute 可以被替换为。just 。


<?php 
 (...)
 $stmt->execute($ids);
?>

( 同样,我没有测试它)

为了快速的:


//$db = new PDO(...);
//$ids = array(...);

$qMarks = str_repeat('?,', count($ids) - 1). '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);

EvilRygy的解决方案不适用于我。 在Postgres中,你可以执行其他操作:



$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
 'SELECT *
 FROM table
 WHERE id = ANY (string_to_array(:an_array, ','))'
);
$stmt->bindParam(':an_array', implode(',', $ids));
$stmt->execute();

我扩展了PDO来做类似于stefs建议的事情,在长期运行中,它更容易:


class Array_Capable_PDO extends PDO {
/**
 * Both prepare a statement and bind array values to it
 * @param string $statement mysql query with colon-prefixed tokens
 * @param array $arrays associatve array with string tokens as keys and integer-indexed data arrays as values 
 * @param array $driver_options see php documention
 * @return PDOStatement with given array values already bound 
 */
 public function prepare_with_arrays($statement, array $arrays, $driver_options = array()) {

 $replace_strings = array();
 $x = 0;
 foreach($arrays as $token => $data) {
//just for testing...
////tokens should be legit
//assert('is_string($token)');
//assert('$token!==""');
////a given token shouldn't appear more than once in the query
//assert('substr_count($statement, $token) === 1');
////there should be an array of values for each token
//assert('is_array($data)');
////empty data arrays aren't okay, they're a SQL syntax error
//assert('count($data)> 0');

//replace array tokens with a list of value tokens
 $replace_string_pieces = array();
 foreach($data as $y => $value) {
////the data arrays have to be integer-indexed
//assert('is_int($y)');
 $replace_string_pieces[] =":{$x}_{$y}";
 }
 $replace_strings[] = '('.implode(', ', $replace_string_pieces).')';
 $x++;
 }
 $statement = str_replace(array_keys($arrays), $replace_strings, $statement);
 $prepared_statement = $this->prepare($statement, $driver_options);

//bind values to the value tokens
 $x = 0;
 foreach($arrays as $token => $data) {
 foreach($data as $y => $value) {
 $prepared_statement->bindValue(":{$x}_{$y}", $value);
 }
 $x++;
 }

 return $prepared_statement;
 }
}

你可以这样使用:


$db_link = new Array_Capable_PDO($dsn, $username, $password);

$query = '
 SELECT *
 FROM test
 WHERE field1 IN :array1
 OR field2 IN :array2
 OR field3 = :value
';

$pdo_query = $db_link->prepare_with_arrays(
 $query,
 array(
 ':array1' => array(1,2,3),
 ':array2' => array(7,8,9)
 )
);

$pdo_query->bindValue(':value', '10');

$pdo_query->execute();

查看 PDO: 预定义的常量,在 PDOStatement-> bindParam中没有需要的PDO::PARAM_ARRAY

bool PDOStatement::bindParam ( 混合 $parameter,混合 & $variable [, int $data_type [, int $length [, mixed $driver_options ] ] ] )

所以我认为这并不意味着。

在语句中使用它是如此重要? 使用FIND_IN_SET而不是。

例如在PDO中,你的查询将被视为


SELECT * FROM table WHERE FIND_IN_SET(id, :an_array)

然后就像这样绑定用逗号分解的值数组


$ids = implode(',', $id_array);//WITHOUT SPACE AFTER COMMA
$stmt->bindParam('an_array', $ids);

等等

更新:就像一些人指出的,这个解决方案有几个问题。

  1. FIND_IN_SET不使用索引在表,它在这里仍然还没有实现- 请参见 MySQL Bug 跟踪器 这份记录的。 感谢 @BillKarwin的通知。
  2. 不能将带有逗号的字符串作为搜索的值。 这是显而易见的通知。 感谢 @VaL.

因此,如果你不依赖于索引和不使用字符串使用的带有逗号定义搜索- 我的解决方案会更顺手过出名者用一个单独的类中。

你使用的数据库是什么? 在PostgreSQL中我喜欢使用 ANY(array) 。 要重用你的示例:


<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
 'SELECT *
 FROM table
 WHERE id = ANY (:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

不幸的是,这是 non-portable 。

在其他的数据库中,你需要自己创建你自己的魔术。 你将希望将该逻辑放入一个类/函数,使它在你的程序中可以重用。 查看 mysql_query 页面上的评论,了解关于这里场景的主题和示例的更多想法。

在相同的参数条件array,我也意识到这个帖子旧了,但我有一个唯一的问题在哪里,而把soon-to-be不推荐使用MySQL驱动程序将它的转换我曾到使一个函数,能编写,动态,既有普通的参数都和PDO驱动程序 ins. 所以我快速构建了:


/**
 * mysql::pdo_query('SELECT * FROM TBL_WHOOP WHERE type_of_whoop IN :param AND siz_of_whoop = :size', array(':param' => array(1,2,3), ':size' => 3))
 *
 * @param $query
 * @param $params
 */
function pdo_query($query, $params = array()){

 if(!$query)
 trigger_error('Could not query nothing');

//Lets get our IN fields first
 $in_fields = array();
 foreach($params as $field => $value){
 if(is_array($value)){
 for($i=0,$size=sizeof($value);$i<$size;$i++)
 $in_array[] = $field.$i;

 $query = str_replace($field,"(".implode(',', $in_array).")", $query);//Lets replace the position in the quiery string with the full version
 $in_fields[$field] = $value;//Lets add this field to an array for use later
 unset($params[$field]);//Lets unset so we don't bind the param later down the line
 }
 }

 $query_obj = $this->pdo_link->prepare($query);
 $query_obj->setFetchMode(PDO::FETCH_ASSOC);

//Now lets bind normal params.
 foreach($params as $field => $value) $query_obj->bindValue($field, $value);

//Now lets bind the IN params
 foreach($in_fields as $field => $value){
 for($i=0,$size=sizeof($value);$i<$size;$i++)
 $query_obj->bindValue($field.$i, $value[$i]);//Both the named param index and this index are based off the array index which has not changed...hopefully
 }

 $query_obj->execute();

 if($query_obj->rowCount() <= 0)
 return null;

 return $query_obj;
}

它仍然没有被测试,但是逻辑似乎在那里。

希望它能帮助同一个位置的人

编辑:经过一些测试之后,我发现:

  • PDO不喜欢在他们的名字中使用'。'( 如果你问我的话,这有点愚蠢)
  • bindParam是错误的函数,bindValue是正确的函数。

编辑到工作版本的代码。

关于Schnalle代码的一点编辑


<?php
$ids = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids)-1, '?'));

$db = new PDO(...);
$stmt = $db->prepare(
 'SELECT *
 FROM table
 WHERE id IN('. $inQuery. ')'
);

foreach ($ids as $k => $id)
 $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

//implode(',', array_fill(0, count($ids)-1), '?'));//'?' this should be inside the array_fill
//$stmt->bindValue(($k+1), $in);//instead of $in, it should be $id

postgres的非常干净的方式使用 postgres-array ("{}"):


$ids = array(1,4,7,9,45);
$param ="{".implode(', ',$ids)."}";
$cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
$result = $cmd->execute(array($param));

...