Ben bir IN () koşulu bir dizi bağlamak miyim?

13 Cevap php

I'm curious to know if it's possible to bind an array of values to a placeholder using PDO. The use case here is attempting to pass an array of values for use with an IN() condition. I'm not very good at explaining, so here's some psuedocode to demonstrate... I'd like to be able to do something like this:

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

Ve PDO bağlama var ve dizideki tüm değerleri alıntı.

Şu anda ben yapıyorum:

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

Hangi kesinlikle iş yapar, ama bir çözüm inşa eksik yaşıyorum varsa sadece merak?

Şerefe!

13 Cevap

i soulmerge doğru olduğunu düşünüyorum. Eğer sorgu dizesi oluşturmak gerekecek.

<?php
$ids     = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids), '?'));

$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(' . $inQuery . ')'
);

// bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

fix: dan, sen haklıydın. kodunu sabit (gerçi bunu test etmedim)

edit: chris (yorum) ve somebodyisintrouble hem de önerdi foreach döngü ...

(...)
// bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();

... Gereksiz olabilir, bu yüzden olabilir foreach döngü ve $stmt->execute sadece yerini olabilir ...

<?php 
  (...)
  $stmt->execute($ids);
?>

(again, i didn't test it)

Hızlı bir şey için:

//$db = new PDO(...);
//$ids = array(...);

$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);

EvilRygy gelen çözüm benim için çalıştı olmadı. Postgres size başka bir çözüm yapabilirsiniz:


$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id = ANY (string_to_array(:an_array, ','))'
);
$stmt->bindParam(':an_array', implode(',', $ids));
$stmt->execute();

Ben Stefs anlaşılacağı ne benzer bir şey yapmak için PDO genişletilmiş ve uzun vadede benim için daha kolay oldu:

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

Bu gibi kullanabilirsiniz:

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

Baktığımızda PDO :Predefined Constants hiçbir PDO yoktur :: PARAM_ARRAY PDOStatement->bindParam listelendiği gibi ihtiyacınız hangi

bool PDOStatement :: bindParam (mixed $ parametre, mixed & $ değişken [, int $data_type [, int $ uzunluk [, mixed $ seçenekler]]])

Yani ulaşılabilir olduğunu sanmıyorum.

Ne veritabanı kullanıyorsunuz? PostgreSQL HERHANGİ (dizi) kullanarak gibi. Yani örnek yeniden:

<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id = ANY (:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

Ne yazık ki bu hoş olmayan-taşınabilir.

Diğer veritabanları üzerinde başkalarının söz edilmiş olarak kendi sihirli makyaj gerekir. Elbette program boyunca yeniden kullanılabilir hale getirmek için bir sınıf / işlevi içine mantığı koymak isteyeceksiniz. Biraz daha konuyla ilgili düşünceleri ve bu senaryonun örnekler için PHP.NET üzerine mysql_query sayfadaki yorumlarına bir göz atın.

Ben de bu konu eski ama dönüştürme sırasında ben, nerede eşsiz bir sorun vardı fark yakında-to-be ben aynı normal params ve INs hem dinamik, inşa edebilecek bir işlevi yapmak zorunda PDO sürücüsü onaylanmaz mysql sürücü param dizi. Bu yüzden hızlı bu yili:

/**
 * mysql::pdo_query('SELECT * FROM TBL_WHOOP WHERE type_of_whoop IN :param AND siz_of_whoop = :size', array(':param' => array(1,2,3), ':size' => 3))
 *
 * @param $query
 * @param $params
 */
function pdo_query($query, $params = array()){

    if(!$query)
        trigger_error('Could not query nothing');

    // Lets get our IN fields first
    $in_fields = array();
    foreach($params as $field => $value){
        if(is_array($value)){
            for($i=0,$size=sizeof($value);$i<$size;$i++)
                $in_array[] = $field.$i;

            $query = str_replace($field, "(".implode(',', $in_array).")", $query); // Lets replace the position in the quiery string with the full version
            $in_fields[$field] = $value; // Lets add this field to an array for use later
            unset($params[$field]); // Lets unset so we don't bind the param later down the line
        }
    }

    $query_obj = $this->pdo_link->prepare($query);
    $query_obj->setFetchMode(PDO::FETCH_ASSOC);

    // Now lets bind normal params.
    foreach($params as $field => $value) $query_obj->bindValue($field, $value);

    // Now lets bind the IN params
    foreach($in_fields as $field => $value){
        for($i=0,$size=sizeof($value);$i<$size;$i++)
            $query_obj->bindValue($field.$i, $value[$i]); // Both the named param index and this index are based off the array index which has not changed...hopefully
    }

    $query_obj->execute();

    if($query_obj->rowCount() <= 0)
        return null;

    return $query_obj;
}

Ancak mantık var gibi görünüyor hala denenmemiş olduğunu.

, Aynı konumda birisi yardımcı olur umarım

Edit: Birkaç denemeden sonra öğrendim:

  • PDO sevmez. ' kendi adlarına (biraz aptalca olduğunu bana sorarsanız)
  • bindParam bindValue doğru fonksiyonu, yanlış işlevidir.

Kod çalışma sürümü düzenlenebilir.

Schnalle kurallar hakkında biraz düzenleme

<?php
$ids     = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids)-1, '?'));

$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(' . $inQuery . ')'
);

foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

//implode(',', array_fill(0, count($ids)-1), '?')); //'?' this should be inside the array_fill
//$stmt->bindValue(($k+1), $in); // instead of $in, it should be $id

postgres'in çok temiz bir şekilde postgres-dizi ("{}") kullanıyor:

$ids = array(1,4,7,9,45);
$param = "{".implode(', ',$ids)."}";
$cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
$result = $cmd->execute(array($param));

Aynı sorun geçiyor sonra, ben daha basit bir çözüm gitti (her ne kadar hala gibi zarif değil PDO::PARAM_ARRAY olurdu):

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

... Ve benzeri

Bir karma değerler dizisi kullanıyorsanız Yani, yazın param atamadan önce değerleri test etmek için daha fazla koda ihtiyacınız olacaktır:

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

Ama bu bir test değil.

O IN deyimini kullanmak için bu kadar önemli mi? Yerine FIND_IN_SET kullanın.

Örneğin, PDO'de sorgu olarak baktı olacak

SELECT * FROM table WHERE FIND_IN_SET(id, :an_array)

Sonra değerler sadece bağlaması dizi bu gibi virgülle imploded

$ids = implode(',', $id_array); // WITHOUT SPACE AFTER COMMA
$stmt->bindParam('an_array', $ids);

ve benzeri

Burada benim çözümdür. Ben de PDO sınıfını artırdık:

class Db extends PDO
{

    /**
     * SELECT ... WHERE fieldName IN (:paramName) workaround
     *
     * @param array  $array
     * @param string $prefix
     *
     * @return string
     */
    public function CreateArrayBindParamNames(array $array, $prefix = 'id_')
    {
        $newparams = [];
        foreach ($array as $n => $val)
        {
            $newparams[] = ":".$prefix.$n;
        }
        return implode(", ", $newparams);
    }

    /**
     * Bind every array element to the proper named parameter
     *
     * @param PDOStatement $stmt
     * @param array        $array
     * @param string       $prefix
     */
    public function BindArrayParam(PDOStatement &$stmt, array $array, $prefix = 'id_')
    {
        foreach($array as $n => $val)
        {
            $val = intval($val);
            $stmt -> bindParam(":".$prefix.$n, $val, PDO::PARAM_INT);
        }
    }
}

İşte yukarıdaki kod için örnek bir kullanım şöyledir:

$idList = [1, 2, 3, 4];
$stmt = $this -> db -> prepare("
  SELECT
    `Name`
  FROM
    `User`
  WHERE
    (`ID` IN (".$this -> db -> CreateArrayBindParamNames($idList)."))");
$this -> db -> BindArrayParam($stmt, $idList);
$stmt -> execute();
foreach($stmt as $row)
{
    echo $row['Name'];
}

Bana ne düşündüğünüzü bildirin

Ben dinamik sorguları bir sürü yapmak beri, bu benim yaptığım bir süper basit bir yardımcı fonksiyonudur.

public static function bindParamArray($prefix, $values, &$bindArray)
{
    $str = "";
    foreach($values as $index => $value){
        $str .= ":".$prefix.$index.",";
        $bindArray[$prefix.$index] = $value;
    }
    return rtrim($str,",");     
}

Bu gibi kullanın:

$bindString = helper::bindParamArray("id", $_GET['ids'], $bindArray);
$userConditions .= " AND users.id IN($bindString)";

Bir dize :id1,:id2,:id3 döndürür ve aynı zamanda $bindArray bu sorguyu çalıştırmak için zaman zaman ihtiyacınız olacak bağları günceller. Kolay!