简体   繁体   English

PHP - 使用 PDO 和 IN 子句数组

[英]PHP - Using PDO with IN clause array

I'm using PDO to execute a statement with an IN clause that uses an array for its values:我正在使用 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();

The above code works perfectly fine, but my question is why this doesn't: 上面的代码工作得很好,但我的问题是为什么不这样做:
 $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();

This code will return the item whose my_value equals the first item in the $in_array (1), but not the remaining items in the array (2, and 3).此代码将返回其my_value等于$in_array (1) 中的第一项的项,但不返回数组中的其余项(2 和 3)。

PDO is not good with such things. PDO 不适用于此类事情。 You need to create a string with placeholders dynamically and insert it into the query, while binding array values the usual way.您需要动态创建一个带有占位符的字符串并将其插入到查询中,同时以通常的方式绑定数组值。 With positional placeholders it would be like this:使用位置占位符将是这样的:

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

In case there are other placeholders in the query, you could use the following approach (the code is taken from my PDO tutorial ):如果查询中有其他占位符,您可以使用以下方法(代码来自我的PDO 教程):

You could use array_merge() function to join all the variables into a single array, adding your other variables in the form of arrays, in the order they appear in your query:您可以使用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();

In case you are using named placeholders, the code would be a little more complex, as you have to create a sequence of the named placeholders, eg :id0,:id1,:id2 .如果您使用命名占位符,代码会稍微复杂一些,因为您必须创建命名占位符的序列,例如:id0,:id1,:id2 So the code would be:所以代码是:

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

Luckily, for the named placeholders we don't have to follow the strict order, so we can merge our arrays in any order.幸运的是,对于命名占位符,我们不必遵循严格的顺序,因此我们可以按任何顺序合并我们的数组。

Variable substitution in PDO prepared statements doesn't support arrays. PDO 准备语句中的变量替换不支持数组。 It's one for one.这是一对一的。

You can get around that problem by generating the number of placeholders you need based on the length of the array.您可以通过根据数组的长度生成所需的占位符数量来解决该问题。

$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)) {
    // ...
}

As PDO doesn't seem to provide a good solution, you might as well consider using DBAL, which mostly follows PDO's API, but also adds some useful features http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion由于 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)
);

There are probably some other packages out there that don't add complexity and don't obscure the interaction with the database (like most ORM do), but at the same time make small typical tasks bit easier.可能还有一些其他的包不会增加复杂性并且不会模糊与数据库的交互(就像大多数 ORM 所做的那样),但同时使小型典型任务更容易一些。

An alternative version of PHP Delusions (@your-common-sense) using closures:使用闭包的 PHP 妄想(@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();

I often use FIND_IN_SET instead of IN, like this:我经常使用 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();

It is not the best solution performance wise, but if the possible number options of the $in_array are limited than it is usually not an issue.这不是最好的解决方案性能,但如果 $in_array 的可能数量选项有限,则通常不是问题。 I use it often for statuses where my_value is an enum field.我经常将它用于 my_value 是枚举字段的状态。 Never had any issue with it.从来没有任何问题。

I was able to do it like this. 我能够做到这一点。 The idea is one value is coming in from a search form. 这个想法是一个价值来自搜索表单。 They are looking for something and the value might be in one of the following two fields: thisField, thatField, or equal to someField. 他们正在寻找东西,值可能在以下两个字段之一中: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);

I've just come up against this problem and coded a small wrapper.我刚刚遇到了这个问题并编写了一个小包装器。 It's not the prettiest or best code I'm sure, but it might help somebody so here it is:我敢肯定,这不是最漂亮或最好的代码,但它可能对某人有所帮助,所以这里是:

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

Eg, passing these in:例如,将这些传入:

SELECT * FROM users WHERE userId IN (:ids)

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

Becomes:变成:

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

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

It's not bulletproof, but as a sole dev for my needs it does the job fine, so far anyway.它不是万无一失的,但作为满足我需求的唯一开发人员,它可以很好地完成工作,无论如何。

Here is a solution for unnamed placeholders (?).这是未命名占位符(?)的解决方案。 If you pass $sql with question mark like "A=? AND B IN(?) " and $args where some of the elements are arrays like [1, [1,2,3]] it will return SQL string with appropriate number of placeholders - "A=? AND B IN(?,?,?)" .如果你传递带有问号的 $sql 像"A=? AND B IN(?) "和 $args 其中一些元素是像 [1, [1,2,3]] 这样的数组,它将返回带有适当数字的 SQL 字符串占位符 - "A=? AND B IN(?,?,?)" It needs $args parameter only to find which element is array and how many placeholders it needs.它只需要 $args 参数来查找哪个元素是数组以及它需要多少个占位符。 You can find the small PDO extension class with this method that will run your query: https://github.com/vicF/pdo/blob/master/src/PDO.php您可以使用此方法找到将运行您的查询的小型 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;
}

As I understand it it is because PDO will treat the $in_values contents as a single item and will quite it accordingly.据我了解,这是因为 PDO 会将 $in_values 内容视为单个项目并相应地进行处理。 PDO will see 1,2,3 as a single string so the query will look something like PDO 会将 1,2,3 视为单个字符串,因此查询看起来像

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

You may think that changing the implode to have quotes and commas will fix it, but it will not.您可能认为将 implode 更改为带有引号和逗号会修复它,但事实并非如此。 PDO will see the quotes and change how it quotes the string. PDO 将看到引号并更改它引用字符串的方式。

As to why your query matches the first value, I have no explanation.至于为什么您的查询与第一个值匹配,我没有解释。

I use OR instead of IN() with PDO like this: 我使用PDO使用OR而不是IN() ,如下所示:

For a select like this SELECT * FROM table WHERE id IN(1,2,3); 对于这样的SELECT * FROM table WHERE id IN(1,2,3);

You can do SELECT * FROM table WHERE id = 1 OR id = 3 OR id = 3; 您可以在SELECT * FROM table WHERE id = 1 OR id = 3 OR id = 3;执行SELECT * FROM table WHERE id = 1 OR id = 3 OR id = 3;

So the code for this is : 因此,此代码为:

$in_array  = array(1, 2, 3);  
$where     = '';
$pdo_param = [];

// looping through array 
foreach ($in_array as $ikey => $ival) {
    // building SQL query where
    $where .= 'id = :p_'.$ikey.' OR ' ;
    // filling param array
    $pdo_param_array[':p_'.$ikey] = $ival ;
} // end foreach 
// remove trailing OR
$where = rtrim($where, ' OR ');

$my_result = $wbdb->prepare("SELECT * FROM my_table WHERE ".$where);
$my_result->execute($pdo_param_array);
$my_results = $my_result->fetchAll();

Here is my full code, sorry for my stupid coding, bad structure and comment lines.这是我的完整代码,对于我愚蠢的编码、糟糕的结构和注释行感到抱歉。 Maybe someone recode my stupid code :)也许有人重新编码了我的愚蠢代码:)

sending to class:发送到课堂:

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

class get this sql :类得到这个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) 

class convert this sql to this.类将此 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)

class binding params:类绑定参数:

 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')

code:代码:

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

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM