我很好奇是否有可能使用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();
?>

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


当前回答

以下是我的解决方案:

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

那就是失败。

其他回答

使用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:正如一些人在评论中指出的,有一些问题需要明确说明。

FIND_IN_SET没有在表中使用索引,而且它还没有实现——请参阅MYSQL错误跟踪器中的这条记录。感谢@BillKarwin的通知。 不能使用内部带有逗号的字符串作为数组的搜索值。这是不可能在内爆后以正确的方式解析这样的字符串,因为你使用逗号符号作为分隔符。感谢@VaL的提示。

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

我扩展了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();

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

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

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

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

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

使用位置占位符(?,?,?,?)或命名占位符(:id1,:id2,:id3) 在美元=内爆(',',array_fill (0, count ($ id), ' ? ')); 之前引用数组 $where = array_map(数组($db, 'quote'), $ids);

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

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

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

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

在经历了同样的问题之后,我采用了一个更简单的解决方案(尽管仍然没有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 ));

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