PHP - 使用带有 IN 子句数组的 PDO

Posted

技术标签:

【中文标题】PHP - 使用带有 IN 子句数组的 PDO【英文标题】:PHP - Using PDO with IN clause array 【发布时间】:2017-04-08 23:16:43 【问题描述】:

我正在使用 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)。

【问题讨论】:

参考this。看看能不能帮上忙。 查看github.com/morris/dop 以获取能够处理数组参数(以及更多,如 NULL 和 SQL 片段)的 API。 【参考方案1】:

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 = "";
$i = 0; // we are using an external counter 
        // because the actual array keys could be dangerous
foreach ($ids as $item)

    $key = ":id".$i++;
    $in .= ($in ? "," : "") . $key; // :id0,:id1,:id2
    $in_params[$key] = $item; // collecting values into a key-value array


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

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

【讨论】:

当然。我有一段时间没有回答这个问题——所以,我不得不自己写。您可以在我的个人资料中找到该链接。随时询问有关使用或任何问题的任何问题。 您能解释一下-1count() 之后的用途吗? @RobertRocha 我认为生成占位符数组的更简洁的方法可能是$in = implode(',', array_fill(0, count($in_array), '?')); - 避免任何奇怪的逐一或字符串连接错误 @YourCommonSense 这是对此类常见问题的最佳答案,谢谢! 没有-1 $in = rtrim(str_repeat('?,', count($array)), ',');【参考方案2】:

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)) 
    // ...

【讨论】:

为什么count ($variables) - 1) . '?'; 为什么不只是count($variable) @RobertRocha 因为你需要的逗号比变量少一个 我尝试使用数组在执行语句中添加另一个变量,但它不起作用。 implode(', ', array_fill(0, count($variables), '?'))怎么样 @AlexeyKosov 这也是一种选择,是的。关键是确保您生成的占位符与输入中的值的数量相匹配。【参考方案3】:

由于 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 一样),但同时使小型典型任务更容易一些。

【讨论】:

【参考方案4】:

使用闭包的 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();

【讨论】:

取决于$editions 的来源,使用真正的数组密钥可能不安全,尽管您的解决方案看起来更优雅【参考方案5】:

我经常使用 FIND_IN_SET 而不是 IN,像这样:

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

这不是最好的解决方案性能,但如果 $in_array 的可能数量选项有限,则通常不是问题。我经常将它用于 my_value 是枚举字段的状态。从来没有任何问题。

【讨论】:

【参考方案6】:

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

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)

它不是万无一失的,但作为满足我需求的唯一开发者,它做得很好,到目前为止。

【讨论】:

【参考方案7】:

这是未命名占位符 (?) 的解决方案。如果您传递带有问号的 $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;

【讨论】:

【参考方案8】:

据我了解,这是因为 PDO 会将 $in_values 内容视为单个项目并相应地对其进行处理。 PDO 会将 1,2,3 视为单个字符串,因此查询看起来像

SELECT * FROM table WHERE my_value IN ("1,2,3")

您可能认为将 implode 更改为带有引号和逗号可以解决问题,但事实并非如此。 PDO 将看到引号并更改它引用字符串的方式。

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

【讨论】:

【参考方案9】:

这是我的完整代码,对于我愚蠢的编码、糟糕的结构和注释行感到抱歉。也许有人重新编写了我的愚蠢代码:)

发送到课堂:

$veri_sinifi = new DB_Connect;
                          $veri_sorgu_degerleri=array(
                            "main_user_id" => (int)$_SESSION['MM_UserId'],
                            "cari_grup_id" => [71,72,73],
                            "cari_grup_adi" => ['fatih','ahmet','ali']                       
                          );                              
                          $siteler =$veri_sinifi->query("Select cari_grup_adi,cari_grup_id FROM m_cari_gruplar WHERE main_user_id= :main_user_id and cari_grup_id in (:cari_grup_id) and cari_grup_adi in (:cari_grup_adi)",$veri_sorgu_degerleri) ; 

类得到这个 sql:

Select cari_grup_adi,cari_grup_id FROM m_cari_gruplar WHERE main_user_id= :main_user_id and cari_grup_id in (:cari_grup_id) and cari_grup_adi in (:cari_grup_adi) 

类将这个sql转换成这个。

Select cari_grup_adi,cari_grup_id FROM m_cari_gruplar WHERE main_user_id= :main_user_id and cari_grup_id in (:cari_grup_id0,:cari_grup_id1,:cari_grup_id2) and cari_grup_adi in (:cari_grup_adi0,:cari_grup_adi1,:cari_grup_adi2)

类绑定参数:

 Select cari_grup_adi,cari_grup_id FROM m_cari_gruplar WHERE main_user_id= 1 and cari_grup_id in (71,72,73) and cari_grup_adi in ('fatih','ahmet','ali')

代码:

class DB_Connect
var $dbh;
function __construct()
    $host = "";
    $db = "";
    $user = "";
    $password = "";
    $this -> dbh = $this -> db_connect($host, $db, $user, $password);

public function getDBConnection()
    return $this -> dbh;

protected function db_connect($host, $db, $user, $password)
    //var_dump($host, $db, $user, $password);exit();
    try 
        $dbh = new PDO("mysql:host=$host;dbname=$db", $user, $password);
    
    catch(PDOException $err) 
        echo "Error: ".$err->getMessage()."<br/>";
        die();
    
    return $dbh;

public function query($statement,$bind_params)
    $keyword = substr(strtoupper($statement), 0, strpos($statement, " ")); // sql in en başındaki kelimeye bakıyor SELECT UPDATE vs gibi ordaki ilk boşluğa kadar olan kelimeyi alıyor.
  //echo $keyword;
    $dbh = $this->getDBConnection();        
    if($dbh)
        try
            $sql = $statement;
            /*GELEN PARAMETRELERE BAKIP İÇİNDE ARRAY VAR İSE SQL STATEMENT KISMINI ONA GÖRE DEĞİŞTİRİYORUZ.
            Alttaki döngünün yaptığı işlem şu. Eğer alttaki gibi bir sorgu değerleri gönderilirse
            $veri_sorgu_degerleri=array(
                                    "main_user_id" => (int)$_SESSION['MM_UserId'],
                                    "cari_grup_id" => [71,72,73],
                                    "cari_grup_adi" => ['fatih','ahmet','ali']                       
                                  );    
            burada main_user_id tek bir değer diğerleri sise array olarak gönderiliyor. Where IN sorgusu birden fazla değer alabileceği için bunları PDO kabul ederken string olarak kabul ediyor yani yukardaki 71,72,73 değerini tırnak içine tek değermiş gib '71,72,73' şeklinde alıyor yapılması gereken sorgunun değiştirilmesi. bu döngü ile 

            Select cari_grup_adi,cari_grup_id FROM m_cari_gruplar WHERE main_user_id= :main_user_id and cari_grup_id in (:cari_grup_id) and cari_grup_adi in (:cari_grup_adi) 

            şeklindeki sorgu in kısımları değiştirilerek

            Select cari_grup_adi,cari_grup_id FROM m_cari_gruplar WHERE main_user_id= :main_user_id and cari_grup_id in (:cari_grup_id0,:cari_grup_id1,:cari_grup_id2) and cari_grup_adi in (:cari_grup_adi0,:cari_grup_adi1,:cari_grup_adi2)

            halini alıyor bir sonraki foreach de ise yine benzer yapı ile arary olarak gelen değerler in için tek tek bind ediliyor, normal gelen değerler ise normal bind yapılıyor.


            */
            foreach($bind_params as $paramkey => $param_value) 
              //echo "Key=" . $paramkey ."-".($param_value)."-, Value=" . $param_value;
              //echo "<br>";                 
              //echo "is_numeric($param_value)>".is_numeric($param_value)."<br>";
              //echo "is_string($param_value)>".is_string($param_value)."<br>";
              //echo "is_array($param_value)>".is_array($param_value)."<br>";
              $in_key="";
              $in_parameters="";
              if (is_array($param_value)) // Gelan parametre array ise yeniden yapılandır.
              
              foreach ($param_value as $i => $item)
                
                    $in_key = ":$paramkey".$i;
                    //echo "<br>$in_key = ".$paramkey.".$i";
                    $in_parameters .= "$in_key,";
                    //echo "<br>$in_parameters = ".$in_key;
                
                $in_parameters = rtrim($in_parameters,","); // :id0,:id1,:id2
                //echo "<br>in_parameters>$in_parameters";
                $sql = str_replace(":".$paramkey, $in_parameters,$sql);
                //echo "<br>oluşan sql>".$sql."<br>"; 
               
            
            $sql = $dbh->prepare($sql);
            foreach($bind_params as $paramkey => $param_value) 
              //echo "Key=" . $paramkey ."-".($param_value)."-, Value=" . $param_value;
              //echo "<br>";                 
              //echo "is_numeric($param_value)>".is_numeric($param_value)."<br>";
              //echo "is_string($param_value)>".is_string($param_value)."<br>";
              //echo "is_array($param_value)>".is_array($param_value)."<br>";
              if (is_numeric($param_value)==1)  // gelen veri  numerik ise
                
                    $param_value = (int)$param_value;
                    $pdo_param_type = PDO::PARAM_INT;
                 
              elseif (is_string($param_value)==1)// gelen veri  string ise
                $pdo_param_type = PDO::PARAM_STR; 
              if (is_array($param_value)) // gelen veri array tipinde ise
              
              foreach ($param_value as $i => $param_array_value) // bu döngünün açıklaması yukardaki döngü için yazılan açıklama içinde mevcut
                
                    $in_key = ":$paramkey".$i;
                                if (is_numeric($param_array_value)==1)  // gelen veri  numerik ise
                                    
                                        $param_array_value = (int)$param_array_value;
                                        $pdo_param_type = PDO::PARAM_INT;
                                     
                                  elseif (is_string($param_array_value)==1)// gelen veri  string ise
                                    $pdo_param_type = PDO::PARAM_STR; 
                                    $sql->bindValue($in_key, $param_array_value, $pdo_param_type );
                    //echo "<br>oldu1";
                    //echo "<br> -$in_key-";
                
                //$sql = str_replace(":".$paramkey, $in_parameters, $sql);
                //echo "oluşan sql>".$sql."<br>"; 
               
              else // array değilse aşağıdaki şekilde bind yap.
                
                  //echo "<br>oldu2";
                  $sql->bindValue(":$paramkey", $param_value, $pdo_param_type ); // bindparam foreach içinde kullanılmaz çünkü execute esnasında bind yaptığı için yani anlık olarak değerleri atamaddığı için for döngüsünde en sonda value değişkeni neyse tüm parametrelere onu atıyor, bu sebeple bindvalue kullanıyoruz.PDO::PARAM_INT
                
             // foreach                
            $exe = $sql->execute();
            $sql->debugDumpParams();
            //echo $exe;

        
        catch(PDOException $err)
            return $err->getMessage();
        

        //BU KISMA AİT AÇIKLAMA AŞAĞIDAIR.
        switch($keyword) // sorgu çalıştıktan sonra sorgu sonucuna göre gerekli işlemler yapılıyor.
            case "SELECT":  // Sorgu select sorgusu ise                
                $result = array(); //sonuçları diziye aktaracak.
                while($row = $sql->fetch(PDO::FETCH_ASSOC))     // sonuç satırlarını tek tek okuyup                   
                    //echo $row;
                    $result[] = $row; // her bir satırı dizinin bir elemanına aktarıyor.bu değer diziden nasıl okunur açıklaması aşağıda                      
                
                return $result; // sorgudan dönen diziyi doğrudan ana programa aktarıyor orada dizi olarak okunabilir.                
                break;
            default: 
                return $exe;
                break;
        
    
    else
        return false;
    

【讨论】:

以上是关于PHP - 使用带有 IN 子句数组的 PDO的主要内容,如果未能解决你的问题,请参考以下文章

PHP - 使用带有 IN 子句数组的 PDO

带有命名占位符的 PDO 准备好的语句 IN 子句无法按预期工作 [重复]

带有条件检查的 PHP MySQL PDO TextArea Where 子句

markdown 将PDO与IN子句一起使用

使用 Oracle,我如何使用带有多个数组的 IN 子句?

如何使用来自 Ajax 变量的 PHP PDO 处理许多 WHERE 子句