Могу ли я связать массив с условием IN ()? - PullRequest
536 голосов
/ 28 мая 2009

Мне любопытно узнать, возможно ли привязать массив значений к заполнителю с помощью PDO. Вариант использования здесь пытается передать массив значений для использования с условием IN().

Я бы хотел сделать что-то вроде этого:

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

И PDO должен связывать и заключать в кавычки все значения в массиве.

На данный момент я делаю:

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

Что, безусловно, делает работу, но просто интересно, есть ли встроенное решение, которое мне не хватает?

Ответы [ 21 ]

10 голосов
/ 13 сентября 2017

Для меня более сексуальное решение - создать динамический ассоциативный массив и использовать его

// A dirty array sent by user
$dirtyArray = ['Cecile', 'Gilles', 'Andre', 'Claude'];

// we construct an associative array like this
// [ ':name_0' => 'Cecile', ... , ':name_3' => 'Claude' ]
$params = array_combine(
    array_map(
        // construct param name according to array index
        function ($v) {return ":name_{$v}";},
        // get values of users
        array_keys($dirtyArray)
    ),
    $dirtyArray
);

// construct the query like `.. WHERE name IN ( :name_1, .. , :name_3 )`
$query = "SELECT * FROM user WHERE name IN( " . implode(",", array_keys($params)) . " )";
// here we go
$stmt  = $db->prepare($query);
$stmt->execute($params);
9 голосов
/ 12 июля 2012

Я также понимаю, что этот поток старый, но у меня возникла уникальная проблема, когда при преобразовании устаревшего драйвера mysql в драйвер PDO мне пришлось создать функцию, которая могла бы динамически создавать как обычные параметры, так и IN из того же массива параметров. Поэтому я быстро построил это:

/**
 * 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 query 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;
}

Это еще не проверено, однако логика, кажется, там.

Надеюсь, это поможет кому-то в том же положении,

Редактировать: После некоторого тестирования я узнал:

  • PDO не любит '.' в их именах (что довольно глупо, если вы спросите меня)
  • bindParam - неправильная функция, bindValue - правильная функция.

Код отредактирован до рабочей версии.

8 голосов
/ 18 февраля 2010

Небольшое редактирование о коде Schnalle

<?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
7 голосов
/ 28 мая 2009

Какую базу данных вы используете? В PostgreSQL мне нравится использовать ЛЮБОЙ (массив). Итак, для повторного использования вашего примера:

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

К сожалению, это довольно непереносимо.

В других базах данных вам нужно создать свою магию, как уже упоминали другие. Вы захотите поместить эту логику в класс / функцию, чтобы сделать ее многократно используемой в вашей программе. Взгляните на комментарии на странице mysql_query в PHP.NET, чтобы узнать больше об этой теме и примерах этого сценария.

4 голосов
/ 14 марта 2017

Если столбец может содержать только целые числа, вы, вероятно, можете сделать это без заполнителей и просто вставить идентификаторы в запрос напрямую. Вам просто нужно привести все значения массива к целым числам. Как это:

$listOfIds = implode(',',array_map('intval', $ids));
$stmt = $db->prepare(
    "SELECT *
     FROM table
     WHERE id IN($listOfIds)"
);
$stmt->execute();

Это не должно быть уязвимо для любых SQL-инъекций.

4 голосов
/ 20 июня 2014

Как я знаю, нет никакой возможности связать массив в оператор PDO.

Но существует 2 общих решения:

  1. Использовать позиционные заполнители (?,?,?,?) Или именованные заполнители (: id1,: id2,: id3)

    $ whereIn = implode (',', array_fill (0, count ($ ids), '?'));

  2. Массив цитат ранее

    $ whereIn = array_map (array ($ db, 'quote'), $ ids);

Оба варианта хороши и безопасны. Я предпочитаю второй, потому что он короче, и я могу изменить параметры var_dump, если мне это нужно. Используя заполнители, вы должны связать значения, и в конце ваш код SQL будет таким же.

$sql = "SELECT * FROM table WHERE id IN ($whereIn)";

И последнее и важное для меня - избежать ошибки "количество связанных переменных не соответствует количеству токенов"

Doctrine - отличный пример использования позиционных заполнителей только потому, что он имеет внутренний контроль над входящими параметрами.

4 голосов
/ 05 апреля 2013

Пройдя через ту же проблему, я пришел к более простому решению (хотя и не так элегантно, как было бы PDO::PARAM_ARRAY):

с учетом массива $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();

... и т. Д.

Поэтому, если вы используете массив со смешанными значениями, вам потребуется больше кода для проверки ваших значений перед назначением параметра типа:

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

Но я не проверял это.

3 голосов
/ 30 мая 2013

вот мое решение. Я также расширил класс PDO:

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

Вот пример использования вышеуказанного кода:

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

Дайте мне знать, что вы думаете

1 голос
/ 22 марта 2018

Невозможно использовать такой массив в PDO.

Вам необходимо построить строку с параметром (или использовать?) Для каждого значения, например:

:an_array_0, :an_array_1, :an_array_2, :an_array_3, :an_array_4, :an_array_5

Вот пример:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = join(
    ', ',
    array_map(
        function($index) {
            return ":an_array_$index";
        },
        array_keys($ids)
    )
);
$db = new PDO(
    'mysql:dbname=mydb;host=localhost',
    'user',
    'passwd'
);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN('.$sqlAnArray.')'
);
foreach ($ids as $index => $id) {
    $stmt->bindValue("an_array_$index", $id);
}

Если вы хотите продолжать использовать bindParam, вы можете сделать это вместо этого:

foreach ($ids as $index => $id) {
    $stmt->bindParam("an_array_$index", $ids[$id]);
}

Если вы хотите использовать ? заполнители, вы можете сделать это следующим образом:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = '?' . str_repeat(', ?', count($ids)-1);
$db = new PDO(
    'mysql:dbname=dbname;host=localhost',
    'user',
    'passwd'
);
$stmt = $db->prepare(
    'SELECT *
     FROM phone_number_lookup
     WHERE country_code IN('.$sqlAnArray.')'
);
$stmt->execute($ids);

Если вы не знаете, является ли $ids пустым, вы должны проверить его и соответственно обработать этот случай (вернуть пустой массив, либо вернуть нулевой объект, либо выдать исключение ...).

0 голосов
/ 24 августа 2017

вы сначала установили номер "?" в запросе, а затем с помощью параметра «для» отправки как это:

require 'dbConnect.php';
$db=new dbConnect();
$array=[];
array_push($array,'value1');
array_push($array,'value2');
$query="SELECT * FROM sites WHERE kind IN (";

foreach ($array as $field){
    $query.="?,";
}
$query=substr($query,0,strlen($query)-1);
$query.=")";
$tbl=$db->connection->prepare($query);
for($i=1;$i<=count($array);$i++)
    $tbl->bindParam($i,$array[$i-1],PDO::PARAM_STR);
$tbl->execute();
$row=$tbl->fetchAll(PDO::FETCH_OBJ);
var_dump($row);
...