PHP - 将PDO与IN子句数组一起使用

问题描述 投票:50回答:8

我正在使用PDO执行一个带有IN子句的语句,该子句使用数组作为它的值:

$in_array = array(1, 2, 3);
$in_values = implode(',', $in_array);
$my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (".$in_values.")");
$my_result->execute();
$my_results = $my_result->fetchAll();

上面的代码完全正常,但我的问题是为什么这不是:

 $in_array = array(1, 2, 3);
    $in_values = implode(',', $in_array);
    $my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (:in_values)");
    $my_result->execute(array(':in_values' => $in_values));
    $my_results = $my_result->fetchAll();

此代码将返回my_value等于$in_array(1)中第一项的项,但不返回数组中的其余项(2和3)。

php pdo
8个回答
69
投票

PDO对这些东西并不好。您需要动态创建带有问号的字符串并插入查询。

$in  = str_repeat('?,', count($in_array) - 1) . '?';
$sql = "SELECT * FROM my_table WHERE my_value IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($in_array);
$data = $stm->fetchAll();

如果查询中有其他占位符,您可以使用以下方法(代码来自我的PDO tutorial):

您可以使用array_merge()函数将所有变量连接到一个数组中,按照它们在查询中出现的顺序以数组的形式添加其他变量:

$arr = [1,2,3];
$in  = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE foo=? AND column IN ($in) AND bar=? AND baz=?";
$stm = $db->prepare($sql);
$params = array_merge([$foo], $arr, [$bar, $baz]);
$stm->execute($params);
$data = $stm->fetchAll();

如果您使用命名占位符,代码会稍微复杂一些,因为您必须创建一系列命名占位符,例如:id0,:id1,:id2。所以代码是:

// other parameters that are going into query
$params = ["foo" => "foo", "bar" => "bar"];

$ids = [1,2,3];
$in = "";
foreach ($ids as $i => $item)
{
    $key = ":id".$i;
    $in .= "$key,";
    $in_params[$key] = $item; // collecting values into key-value array
}
$in = rtrim($in,","); // :id0,:id1,:id2

$sql = "SELECT * FROM table WHERE foo=:foo AND id IN ($in) AND bar=:bar";
$stm = $db->prepare($sql);
$stm->execute(array_merge($params,$in_params)); // just merge two arrays
$data = $stm->fetchAll();

幸运的是,对于命名占位符,我们不必遵循严格的顺序,因此我们可以按任何顺序合并我们的数组。


12
投票

PDO预处理语句中的变量替换不支持数组。这是一对一的。

您可以通过根据数组的长度生成所需的占位符数来解决该问题。

$variables = array ('1', '2', '3');
$placeholders = str_repeat ('?, ',  count ($variables) - 1) . '?';

$query = $pdo -> prepare ("SELECT * FROM table WHERE column IN($placeholders)");
if ($query -> execute ($variables)) {
    // ...
}

1
投票

由于PDO似乎没有提供一个好的解决方案,你不妨考虑使用DBAL,它主要遵循PDO的API,但也增加了一些有用的功能http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion

$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
    array(array(1, 2, 3, 4, 5, 6)),
    array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
);

可能还有一些其他软件包不会增加复杂性,也不会模糊与数据库的交互(就像大多数ORM一样),但同时使小型典型任务更容易。


1
投票

我能够这样做。这个想法是从搜索表单中获得的一个值。他们正在寻找某些东西,其值可能在以下两个字段之一:thisField,thatField或等于someField。

$randomValue = "whatever";
$query = "SELECT * FROM myTable WHERE ((:randomValue in(thisField) or :randomValue in(thatField)) or (someField = :randomValue));";
                $statement = $this->mySQL_db->prepare($query);
                $statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
                $statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
                $statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);

0
投票

据我所知,这是因为PDO会将$ in_values内容视为单个项目,因此会相应。 PDO会将1,2,3视为单个字符串,因此查询将类似于

SELECT * FROM表WHERE my_value IN(“1,2,3”)

您可能认为更改内爆以使用引号和逗号将修复它,但它不会。 PDO将看到引号并更改引用字符串的方式。

至于为什么你的查询匹配第一个值,我没有解释。


0
投票

我刚刚遇到这个问题并编写了一个小包装器。这不是我确定的最漂亮或最好的代码,但它可能对某人有帮助,所以这里是:

function runQuery(PDO $PDO, string $sql, array $params = [])
{
    if (!count($params)) {
        return $PDO->query($sql);
    }

    foreach ($params as $key => $values) {
        if (is_array($values)) {
            // get placeholder from array, e.g. ids => [7,12,3] would be ':ids'
            $oldPlaceholder  = ':'.$key;
            $newPlaceholders = '';
            $newParams = [];
            // loop through array to create new placeholders & new named parameters
            for($i = 1; $i <= count($values); $i++) {
                // this gives us :ids1, :ids2, :ids3 etc
                $newKey = $oldPlaceholder.$i;
                $newPlaceholders .= $newKey.', ';
                // this builds an associative array of the new named parameters
                $newParams[$newKey] = $values[$i - 1];
            }
            //trim off the trailing comma and space
            $newPlaceholders = rtrim($newPlaceholders, ', ');

            // remove the old parameter
            unset($params[$key]);

            // and replace with the new ones
            $params = array_merge($params, $newParams);

            // amend the query
            $sql = str_replace($oldPlaceholder, $newPlaceholders, $sql);
        }
    }

    $statement = $PDO->prepare($sql);
    $statement->execute($params);
    return $statement;
}

例如,将这些传递给:

SELECT * FROM users WHERE userId IN (:ids)

array(1) {
  ["ids"]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
}

变为:

SELECT * FROM users WHERE userId IN (:ids1, :ids2, :ids3)

array(3) {
  [":ids1"]=>
  int(1)
  [":ids2"]=>
  int(2)
  [":ids3"]=>
  int(3)
}

它不是防弹的,但作为我个人需求的唯一开发者,无论如何它都能完成工作。


0
投票

这是一个未命名的占位符(?)的解决方案。如果传递带有问号的$ sql,例如“A =?AND B IN(?)”和$ args,其中某些元素是像[1,[1,2,3]]这样的数组,它将返回带有适当数字的SQL字符串占位符 - “A =?AND B IN(?,?,?)”。它只需要$ args参数来查找哪个元素是数组以及它需要多少个占位符。您可以使用此方法找到运行查询的小型PDO扩展类:https://github.com/vicF/pdo/blob/master/src/PDO.php

public function replaceArrayPlaceholders($sql, $args)
{
    $num = 0;
    preg_match_all('/\?/', $sql, $matches, PREG_OFFSET_CAPTURE);  // Captures positions of placeholders
    //echo $matches[0][1][1];
    $replacements = [];
    foreach($args as $arg) {
        if(is_array($arg)) {
            $replacements[$matches[0][$num][1]] = implode(',',array_fill(0, count($arg), '?')); // Create placeholders string
        }
        $num++;
    }
    krsort($replacements);
    foreach($replacements as $position => $placeholders) {
        $sql = substr($sql, 0, $position).$placeholders.substr($sql, $position+1); // Replace single placeholder with multiple
    }
    return $sql;
}

0
投票

使用闭包的另一个版本的PHP Delusions(@ your-common-sense):

$filter      = ["min_price" => "1.98"];
$editions    = [1,2,10];

$editions = array_combine(
    array_map(function($i){ return ':id'.$i; }, array_keys($editions)),
    $editions
);
$in_placeholders = implode(',', array_keys($editions));
$sql = "SELECT * FROM books WHERE price >= :min_price AND edition IN ($in_placeholders)";
$stm = $pdo->prepare($sql);
$stm->execute(array_merge($filter,$editions));
$data = $stm->fetchAll();
© www.soinside.com 2019 - 2024. All rights reserved.