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


80

我正在使用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)中的其余项目。


请参考。看看是否有帮助。
Bhavik Shah 2013年

请访问github.com/morris/dop,以获取能够处理数组参数(以及更多信息,例如NULL和SQL片段)的API。
morris4

Answers:


108

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教程):

您可以使用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 .= "$key,";
    $in_params[$key] = $item; // collecting values into a 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();

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


1
当然。我暂时没有这个问题的答案-因此,我必须自己写它。您可以在我的个人资料中找到链接。随时问有关使用或任何问题的任何问题。
您的常识

2
您能解释一下-1之后的作用count()吗?
罗伯特·罗查

2
@RobertRocha我认为生成占位符数组的一种更干净的方法可能是$in = implode(',', array_fill(0, count($in_array), '?'));-避免任何奇怪的逐一或字符串连接错误
barryp

1
@YourCommonSense,这是对此类常见问题的最佳答案,谢谢!
digitai

1
没有-1$in = rtrim(str_repeat('?,', count($array)), ',');
老板

15

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)
Robert Rocha

2
@RobertRocha因为您所需要的逗号比变量少
GordonM '16

我试图在使用数组的execute语句中添加另一个变量,但是它不起作用。
Usama Ahmed

4

由于PDO似乎无法提供良好的解决方案,因此您不妨考虑使用DBAL,它主要遵循PDO的API,但是还添加了一些有用的功能http://docs.doctrine-project.org/projects/doctrine-dbal/ zh_CN / 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一样),但是同时使小型的典型任务更容易些。


2

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

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

这是我的完整代码,对不起我的愚蠢代码,错误的结构和注释行。也许有人重新编码了我的愚蠢代码:)

发送给班级:

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

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.