我可以将数组绑定到IN()条件吗?

And*_*dru 549 php arrays pdo prepared-statement where-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();
?>
Run Code Online (Sandbox Code Playgroud)

并让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();
?>
Run Code Online (Sandbox Code Playgroud)

这当然能完成这项工作,但只是想知道是否有一个内置的解决方案我缺席了?

ste*_*efs 252

我认为soulmerge是对的.你必须构造查询字符串.

<?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();
?>
Run Code Online (Sandbox Code Playgroud)

修复:丹,你是对的.修复了代码(虽然没有测试)

编辑:克里斯(评论)和somebodyisintrouble建议foreach循环...

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

$stmt->execute();
Run Code Online (Sandbox Code Playgroud)

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

<?php 
  (...)
  $stmt->execute($ids);
?>
Run Code Online (Sandbox Code Playgroud)

(再次,我没有测试它)

  • 老问题,但值得注意的是,我认为`$ foreach`和`bindValue()`不是必需的 - 只需执行数组即可.例如:`$ stmt-> execute($ ids);` (9认同)
  • 对于那些不知道的人来说,只是一个提示,你不能混合命名和未命名的参数.因此,如果在查询中使用命名参数,请将它们切换为?,然后增加bindValue索引偏移量以匹配IN?的位置以及它们相对于另一个的位置?PARAMS. (8认同)
  • 这是一个有趣的解决方案,虽然我更喜欢迭代id并调用PDO :: quote(),我认为'?'的索引 如果任何其他占位符首先出现在查询中的其他位置,那么占位符将会搞砸,对吧? (7认同)
  • 是的,这将是一个问题.但在这种情况下,您可以创建命名参数而不是?. (5认同)
  • 生成占位符应该像这样`str_repeat('?,', count($array) - 1)。'?';` (2认同)

Don*_*ghn 168

对于快速的事情:

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

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

  • 很好,我没有想过以这种方式使用input_parameters参数.对于那些查询的参数多于IN列表的人,可以使用array_unshift和array_push将必要的参数添加到数组的前端和末尾.另外,我更喜欢`$ input_list = substr(str_repeat(',?',count($ ids)),1);` (7认同)
  • 你也可以尝试`str_repeat('?,',count($ ids) - 1).'?'`.少一个函数调用. (7认同)
  • @erfling,这是一个准备好的声明,注入将来自哪里?如果你能用一些实际的证据来支持,我会非常乐意做出任何更正. (5认同)
  • @erfling,是的,这是正确的,绑定params正是我们在这个例子中通过发送`execute`一个id数组来做的 (5认同)
  • 哦,的确.不知何故错过了你传递数组的事实.这确实看起来是安全的,也是一个很好的答案.我很抱歉. (5认同)
  • 我用array_diff来获取一个我想删除的数组,它给了我一个错误,因为第一个数组元素没有索引值0,因此我必须首先使用`array_values`才能重置键. (3认同)
  • 不清楚这个的安全性.它会起作用,但是对我来说,它有点违背准备好的陈述的目的,看起来容易受到注射. (2认同)
  • 准备语句不会阻止注入.你必须绑定params.如果其中一个$ ids指向DROP表用户的索引;它可能很好地执行.最受欢迎的答案通过循环和绑定来防止这种情况. (2认同)
  • @Miguel,这是不正确的,这就是所谓的使用预处理语句来确保类型安全并防止sql注入.看看`PDOStatement :: execute`是如何工作的[这里是PHP文档](http://php.net/manual/de/pdostatement.execute.php) (2认同)

Dmi*_*gov 46

使用IN声明是否如此重要?尝试使用FIND_IN_SETop.

例如,PDO中有一个查询

SELECT * FROM table WHERE FIND_IN_SET(id, :array)
Run Code Online (Sandbox Code Playgroud)

然后你只需要绑定一个值数组,用逗号进行内爆,就像这个一样

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

它已经完成了.

UPD:正如一些人在对这个答案的评论中指出的那样,有一些问题应该明确说明.

  1. FIND_IN_SET不使用表中的索引,它仍然没有实现 - 请参阅MYSQL错误跟踪器中的此记录.感谢@BillKarwin的通知.
  2. 您不能将带有逗号的字符串用作搜索数组的值.implode因为您使用逗号符号作为分隔符,所以无法以正确的方式解析此类字符串.感谢@VaL的笔记.

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

  • IN()可以使用索引,并计为范围扫描.FIND_IN_SET()不能使用索引. (25认同)
  • 是的,但这些天谁拥有一张不那么大的桌子?;-) (11认同)
  • 这种方法的另一个问题是,如果内部会有逗号字符串怎么办?例如`... FIND_IN_SET(描述,'简单,搜索')`将起作用,但`FIND_IN_SET(描述,'第一个值,文本,内部有昏迷)'将失败.因此该函数将搜索"第一个值","文本","内部昏迷",而不是"第一个值","文本,内部昏迷". (3认同)

pro*_*mer 32

由于我做了很多动态查询,这是我做的一个超级简单的辅助函数.

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

像这样使用它:

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

返回一个字符串,:id1,:id2,:id3并且还会更新您$bindArray在运行查询时需要的绑定.简单!

  • 这是一个更好的解决方案,因为它不会破坏参数规则的绑定.这比其他人提出的内联sql安全得多. (6认同)

Ser*_*kin 17

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();
Run Code Online (Sandbox Code Playgroud)


ESC*_*BAR 17

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));
Run Code Online (Sandbox Code Playgroud)


Chr*_*ris 12

我将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;
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

$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();
Run Code Online (Sandbox Code Playgroud)

  • 所有未来读者的注意事项:永远不要使用此解决方案.**断言不适用于生产代码** (4认同)

Pro*_*ock 12

这是我的解决方案:

$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));
Run Code Online (Sandbox Code Playgroud)

注意array_values的使用.这可以解决密钥排序问题.

我正在合并ID数组,然后删除重复的项目.我有类似的东西:

$ids = array(0 => 23, 1 => 47, 3 => 17);
Run Code Online (Sandbox Code Playgroud)

那是失败的.


Dan*_*zil 11

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

$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();
Run Code Online (Sandbox Code Playgroud)


Rou*_*dre 11

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

// 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);
Run Code Online (Sandbox Code Playgroud)


小智 10

查看 PDO:Predefined Constants没有PDO :: PARAM_ARRAY,您需要在PDOStatement-> bindParam上列出

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

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

  • 你是正确的,报价被逃脱,所以赢了;工作.我已删除该代码. (2认同)

Sam*_*aye 9

我也意识到这个线程已经老了,但是我有一个独特的问题,在将即将被弃用的mysql驱动程序转换为PDO驱动程序时,我必须创建一个可以动态构建普通params和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;
}
Run Code Online (Sandbox Code Playgroud)

它仍未经过测试,但逻辑似乎存在.

希望它能帮助处于同一位置的人,

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

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

代码编辑为工作版本.


小智 8

关于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
Run Code Online (Sandbox Code Playgroud)

  • 我必须在 count($ids) 之后删除 -1 才能使其对我有用,否则总会缺少一个占位符。 (2认同)

Rya*_*air 7

你用的是什么数据库?在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();
?>
Run Code Online (Sandbox Code Playgroud)

不幸的是,这非常不便携.

在其他数据库中,你需要像其他人提到的那样构建自己的魔法.您需要将该逻辑放入类/函数中,以使其在整个程序中可重用.请查看mysql_queryPHP.NET页面上的注释,以获取有关该主题的更多想法以及此方案的示例.


Kod*_*son 6

如果该列只能包含整数,您可能可以在没有占位符的情况下执行此操作,只需将 id 直接放入查询中即可。您只需将数组的所有值转换为整数。像这样:

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

这不应该容易受到任何 SQL 注入的影响。


Ole*_*tei 5

据我所知,不可能将数组绑定到 PDO 语句中。

但存在两种常见的解决方案:

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

    $whereIn = implode(',', array_fill(0, count($ids), '?'));

  2. 之前的报价数组

    $whereIn = array_map(array($db, 'quote'), $ids);

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

$sql = "SELECT * FROM table WHERE id IN ($whereIn)";
Run Code Online (Sandbox Code Playgroud)

对我来说最后也是重要的是避免错误“绑定变量的数量与令牌数量不匹配”

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


Ped*_*uto 5

不可能在 PDO 中使用类似的数组。

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

:an_array_0, :an_array_1, :an_array_2, :an_array_3, :an_array_4, :an_array_5

这是一个例子:

<?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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

<?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);
Run Code Online (Sandbox Code Playgroud)

如果您不知道是否$ids为空,则应该对其进行测试并相应地处理这种情况(返回空数组,或返回 Null 对象,或引发异常,...)。