我可以在PDO查询中绑定一个数组到IN()条件吗?

我很好奇是否有可能使用PDO将值数组绑定到占位符。这里的用例试图传递一个值数组,用于IN()条件。

我希望能够这样做:

<?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();
?>

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

239547 次浏览

查看PDO:预定义常量,没有PDO::PARAM_ARRAY,你需要在PDOStatement - > bindParam上列出

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

所以我认为这是不可能实现的。

您必须构造查询字符串。

<?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();
?>

chris(评论)和一些人都有麻烦了,建议foreach-loop…

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


$stmt->execute();

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

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

您使用的是什么数据库?在PostgreSQL中,我喜欢使用ANY(数组)。重复一下你的例子:

<?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();
?>

不幸的是,这是非常不可移植的。

在其他数据库中,您将需要像其他人提到的那样,创建自己的魔法。当然,您需要将该逻辑放入类/函数中,以使其在整个程序中可重用。看看关于PHP的mysql_query页面上的评论。关于这个主题的更多想法和这个场景的示例。

稍微编辑一下施纳勒的代码

<?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

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();

简单来说:

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


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

我也意识到这个线程是旧的,但我有一个独特的问题,在将即将弃用的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 query 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是正确函数。

代码编辑到工作版本。

在经历了同样的问题后,我选择了一个更简单的解决方案(尽管仍然没有PDO::PARAM_ARRAY那么优雅):

给定数组$ids = array(2, 4, 32):

$newparams = array();
foreach ($ids as $n => $val){ $newparams[] = ":id_$n"; }


try {
$stmt = $conn->prepare("DELETE FROM $table WHERE ($table.id IN (" . implode(", ",$newparams). "))");
foreach ($ids as $n => $val){
$stmt->bindParam(":id_$n", intval($val), PDO::PARAM_INT);
}
$stmt->execute();

... 等等

所以如果你正在使用一个混合值数组,你将需要更多的代码来测试你的值,然后再分配类型参数:

// inside second foreach..


$valuevar = (is_float($val) ? floatval($val) : is_int($val) ? intval($val) :  is_string($val) ? strval($val) : $val );
$stmt->bindParam(":id_$n", $valuevar, (is_int($val) ? PDO::PARAM_INT :  is_string($val) ? PDO::PARAM_STR : NULL ));

但是我还没有测试过这个。

这是我的解决方案。我还扩展了PDO类:

class Db extends PDO
{


/**
* SELECT ... WHERE fieldName IN (:paramName) workaround
*
* @param array  $array
* @param string $prefix
*
* @return string
*/
public function CreateArrayBindParamNames(array $array, $prefix = 'id_')
{
$newparams = [];
foreach ($array as $n => $val)
{
$newparams[] = ":".$prefix.$n;
}
return implode(", ", $newparams);
}


/**
* Bind every array element to the proper named parameter
*
* @param PDOStatement $stmt
* @param array        $array
* @param string       $prefix
*/
public function BindArrayParam(PDOStatement &$stmt, array $array, $prefix = 'id_')
{
foreach($array as $n => $val)
{
$val = intval($val);
$stmt -> bindParam(":".$prefix.$n, $val, PDO::PARAM_INT);
}
}
}

下面是上面代码的示例用法:

$idList = [1, 2, 3, 4];
$stmt = $this -> db -> prepare("
SELECT
`Name`
FROM
`User`
WHERE
(`ID` IN (".$this -> db -> CreateArrayBindParamNames($idList)."))");
$this -> db -> BindArrayParam($stmt, $idList);
$stmt -> execute();
foreach($stmt as $row)
{
echo $row['Name'];
}

让我知道你的想法

使用IN语句如此重要吗?尝试使用FIND_IN_SET操作。

例如,在PDO中有这样一个查询

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

然后你只需要绑定一个数组的值,用逗号括起来,就像这个

$ids_string = implode(',', $array_of_smth); // WITHOUT WHITESPACES BEFORE AND AFTER THE COMMA
$stmt->bindParam('array', $ids_string);

做完了。

UPD:正如一些人在评论中指出的,有一些问题需要明确说明。

  1. FIND_IN_SET没有在表中使用索引,它仍然没有实现-请参阅这个记录在MYSQL错误跟踪器。感谢@BillKarwin的通知。
  2. 不能使用内部带有逗号的字符串作为数组的搜索值。由于使用逗号符号作为分隔符,因此在implode之后不可能以正确的方式解析这样的字符串。感谢@VaL的提示。

总之,如果您不严重依赖索引,并且不使用带逗号的字符串进行搜索,那么我的解决方案将比上面列出的解决方案更容易、更简单、更快。

因为我做了很多动态查询,所以这是我做的一个超级简单的helper函数。

public static function bindParamArray($prefix, $values, &$bindArray)
{
$str = "";
foreach($values as $index => $value){
$str .= ":".$prefix.$index.",";
$bindArray[$prefix.$index] = $value;
}
return rtrim($str,",");
}

像这样使用它:

$bindString = helper::bindParamArray("id", $_GET['ids'], $bindArray);
$userConditions .= " AND users.id IN($bindString)";

返回字符串:id1,:id2,:id3,并在运行查询时更新您将需要的$bindArray绑定。简单!

使用postgres数组("{}")是一种非常简洁的方法:

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

据我所知,没有任何可能将数组绑定到PDO语句中。

但有两种常见的解决方案:

  1. 使用位置占位符(?,?,?,?)或命名占位符(:id1,:id2,:id3)

    在美元=内爆(',',array_fill (0, count ($ id ), '?'));

  2. 之前引用数组

    在美元=到(数组($ db,‘引用’),$ id), < / p >

这两个选项都是好的和安全的。 我更喜欢第二个,因为它更短,如果我需要它,我可以var_dump参数。 使用占位符,你必须绑定值,最终你的SQL代码将是相同的

$sql = "SELECT * FROM table WHERE id IN ($whereIn)";

最后一个对我来说很重要的是避免错误“绑定变量的数量与标记的数量不匹配”

它是使用位置占位符的一个很好的例子,只是因为它对传入参数有内部控制。

为了让答案更接近最初使用占位符绑定参数的问题,我做了一些进一步的处理。

这个答案必须在查询中使用的数组中进行两次循环。但它确实解决了为更有选择性的查询使用其他列占位符的问题。

//builds placeholders to insert in IN()
foreach($array as $key=>$value) {
$in_query = $in_query . ' :val_' . $key . ', ';
}


//gets rid of trailing comma and space
$in_query = substr($in_query, 0, -2);


$stmt = $db->prepare(
"SELECT *
WHERE id IN($in_query)";


//pind params for your placeholders.
foreach ($array as $key=>$value) {
$stmt->bindParam(":val_" . $key, $array[$key])
}


$stmt->execute();

以下是我的解决方案:

$total_items = count($array_of_items);
$question_marks = array_fill(0, $total_items, '?');
$sql = 'SELECT * FROM foo WHERE bar IN (' . implode(',', $question_marks ). ')';


$stmt = $dbh->prepare($sql);
$stmt->execute(array_values($array_of_items));

注意array_values的使用。这可以修复键排序问题。

我正在合并id数组,然后删除重复的项。我是这样写的:

$ids = array(0 => 23, 1 => 47, 3 => 17);

那就是失败。

当你有其他参数时,你可以这样做:

$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$query = 'SELECT *
FROM table
WHERE X = :x
AND id IN(';
$comma = '';
for($i=0; $i<count($ids); $i++){
$query .= $comma.':p'.$i;       // :p0, :p1, ...
$comma = ',';
}
$query .= ')';


$stmt = $db->prepare($query);
$stmt->bindValue(':x', 123);  // some value
for($i=0; $i<count($ids); $i++){
$stmt->bindValue(':p'.$i, $ids[$i]);
}
$stmt->execute();

如果列只能包含整数,则可以不使用占位符,直接将id放入查询中。您只需将数组的所有值强制转换为整数。是这样的:

$listOfIds = implode(',',array_map('intval', $ids));
$stmt = $db->prepare(
"SELECT *
FROM table
WHERE id IN($listOfIds)"
);
$stmt->execute();

这应该不会受到任何SQL注入的攻击。

你首先在查询中设置“?”的个数,然后通过一个“for”发送参数 像这样:

require 'dbConnect.php';
$db=new dbConnect();
$array=[];
array_push($array,'value1');
array_push($array,'value2');
$query="SELECT * FROM sites WHERE kind IN (";


foreach ($array as $field){
$query.="?,";
}
$query=substr($query,0,strlen($query)-1);
$query.=")";
$tbl=$db->connection->prepare($query);
for($i=1;$i<=count($array);$i++)
$tbl->bindParam($i,$array[$i-1],PDO::PARAM_STR);
$tbl->execute();
$row=$tbl->fetchAll(PDO::FETCH_OBJ);
var_dump($row);

对我来说,更性感的解决方案是构造一个动态关联数组&使用它

// A dirty array sent by user
$dirtyArray = ['Cecile', 'Gilles', 'Andre', 'Claude'];


// we construct an associative array like this
// [ ':name_0' => 'Cecile', ... , ':name_3' => 'Claude' ]
$params = array_combine(
array_map(
// construct param name according to array index
function ($v) {return ":name_{$v}";},
// get values of users
array_keys($dirtyArray)
),
$dirtyArray
);


// construct the query like `.. WHERE name IN ( :name_1, .. , :name_3 )`
$query = "SELECT * FROM user WHERE name IN( " . implode(",", array_keys($params)) . " )";
// here we go
$stmt  = $db->prepare($query);
$stmt->execute($params);

在PDO中不可能使用这样的数组。

你需要为每个值构建一个带参数的字符串(或使用?),例如:

# EYZ0

这里有一个例子:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = join(
', ',
array_map(
function($index) {
return ":an_array_$index";
},
array_keys($ids)
)
);
$db = new PDO(
'mysql:dbname=mydb;host=localhost',
'user',
'passwd'
);
$stmt = $db->prepare(
'SELECT *
FROM table
WHERE id IN('.$sqlAnArray.')'
);
foreach ($ids as $index => $id) {
$stmt->bindValue("an_array_$index", $id);
}

如果你想继续使用bindParam,你可以这样做:

foreach ($ids as $index => $id) {
$stmt->bindParam("an_array_$index", $ids[$id]);
}

如果你想使用?占位符,你可以这样做:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = '?' . str_repeat(', ?', count($ids)-1);
$db = new PDO(
'mysql:dbname=dbname;host=localhost',
'user',
'passwd'
);
$stmt = $db->prepare(
'SELECT *
FROM phone_number_lookup
WHERE country_code IN('.$sqlAnArray.')'
);
$stmt->execute($ids);

如果你不知道$ids是否为空,你应该测试它并相应地处理这种情况(返回一个空数组,或返回一个空对象,或抛出一个异常……)。

在MySQL和PDO中,我们可以使用JSON数组和JSON_CONTAINS() (https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-contains)进行搜索。

$ids = [123, 234, 345, 456]; // Array of users I search
$ids = json_encode($ids); // JSON conversion


$sql = <<<SQL
SELECT ALL user_id, user_login
FROM users
-- Cast is mandatory beaucause JSON_CONTAINS() waits JSON doc candidate
WHERE JSON_CONTAINS(:ids, CAST(user_id AS JSON))
SQL;


$search = $pdo->prepare($sql);
$search->execute([':ids' => $ids]);
$users = $search->fetchAll();

我们也可以使用JSON_TABLE() (https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table)来进行更复杂的情况和JSON数据探索:

$users = [
['id' => 123, 'bday' => ..., 'address' => ...],
['id' => 234, 'bday' => ..., 'address' => ...],
['id' => 345, 'bday' => ..., 'address' => ...],
]; // I'd like to know their login


$users = json_encode($users);


$sql = <<<SQL
SELECT ALL user_id, user_login
FROM users
WHERE user_id IN (
SELECT ALL user_id
FROM JSON_TABLE(:users, '$[*]' COLUMNS (
-- Data exploration...
-- (if needed I can explore really deeply with NESTED kword)
user_id INT PATH '$.id',
-- I could skip these :
user_bday DATE PATH '$.bday',
user_address TINYTEXT PATH '$.address'
)) AS _
)
SQL;


$search = $pdo->prepare($sql);
$search->execute([':users' => $users]);
...

你可以这样转换:

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

在此:

$stmt = $db->prepare('SELECT * FROM table WHERE id IN(:id1,:id2,:id3,:id7,:id8,:id9)');

然后用这个数组执行它:

< p >美元支撑→执行(数组( : id1 =祝辞1:id2 =祝辞;2:id3 =祝辞;3:id7 =祝辞7:id8 =祝辞8:id9 =比;9 ) < / p >);

因此:

$in = array();
$consultaParam = array();
foreach($ids as $k => $v){
$in[] = ':id'.$v;
$consultaParam[':id'.$v] = $v;
}

最后的代码:

$ids = array(1,2,3,7,8,9);
$db = new PDO(...);


$in = array();
$consultaParam = array();
foreach($ids as $k => $v){
$in[] = ':id'.$v;
$consultaParam[':id'.$v] = $v;
}


$stmt = $db->prepare(
'SELECT *
FROM table
WHERE id IN('.$in.')'
);
$stmt->execute($consultaParam);