Могу ли я связать массив с условием 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 ]

247 голосов
/ 28 мая 2009

Я думаю, что Соулмердж прав. вам нужно будет создать строку запроса.

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

исправить: Дэн, ты был прав. исправил код (хотя не проверял)

edit: и Крис (комментарии), и кто-то из них предположили, что цикл foreach ...

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

$stmt->execute();

... может быть избыточным, поэтому цикл foreach и $stmt->execute можно заменить просто ...

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

(опять же, я не проверял)

166 голосов
/ 23 мая 2012

Для чего-то быстрого:

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

$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);
45 голосов
/ 03 октября 2013

Так ли важно использовать оператор IN? Попробуйте использовать FIND_IN_SET op.

Например, в PDO есть запрос, подобный этому

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

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

$ids_string = implode(',', $array_of_smth); // WITHOUT WHITESPACES BEFORE AND AFTER THE COMMA
$stmt->bindParam('array', $ids_string);

и готово.

UPD: как некоторые люди указывали в комментариях к этому ответу, есть некоторые вопросы, которые следует четко сформулировать.

  1. FIND_IN_SET не использует индекс в таблице, и он все еще не реализован - см. эту запись в трекере ошибок MYSQL . Спасибо @BillKarwin за уведомление.
  2. Нельзя использовать строку с запятой внутри в качестве значения массива для поиска. Невозможно правильно проанализировать такую ​​строку после implode, так как вы используете символ запятой в качестве разделителя. Спасибо @VaL за примечание.

В общем, если вы не сильно зависите от индексов и не используете строки с запятой для поиска, мое решение будет намного проще, проще и быстрее, чем решения, перечисленные выше.

30 голосов
/ 26 марта 2014

Поскольку я делаю много динамических запросов, это очень простая вспомогательная функция, которую я сделал.

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

Используйте это так:

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

Возвращает строку :id1,:id2,:id3, а также обновляет ваши $bindArray привязок, которые вам понадобятся, когда придет время выполнить ваш запрос. Легко!

17 голосов
/ 15 апреля 2014

Очень простой способ для postgres - использовать массив postgres ("{}"):

$ids = array(1,4,7,9,45);
$param = "{".implode(', ',$ids)."}";
$cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
$result = $cmd->execute(array($param));
16 голосов
/ 18 августа 2010

Решение от EvilRygy у меня не сработало. В Postgres вы можете сделать еще один обходной путь:


$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();
12 голосов
/ 19 февраля 2012

Я расширил PDO, чтобы сделать что-то похожее на то, что предлагает stefs, и мне было легче в долгосрочной перспективе:

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

Вы можете использовать это так:

$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();
12 голосов
/ 21 января 2015

Вот мое решение:

$total_items = count($array_of_items);
$question_marks = array_fill(0, $total_items, '?');
$sql = 'SELECT * FROM foo WHERE bar IN (' . implode(',', $question_marks ). ')';

$stmt = $dbh->prepare($sql);
$stmt->execute(array_values($array_of_items));

Обратите внимание на использование array_values. Это может исправить ключевые проблемы с заказом.

Я сливал массивы идентификаторов, а затем удалял дублирующиеся элементы. У меня было что-то вроде:

$ids = array(0 => 23, 1 => 47, 3 => 17);

И это не удалось.

11 голосов
/ 18 марта 2015

Если у вас есть другой параметр, вы можете сделать так:

$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$query = 'SELECT *
            FROM table
           WHERE X = :x
             AND id IN(';
$comma = '';
for($i=0; $i<count($ids); $i++){
  $query .= $comma.':p'.$i;       // :p0, :p1, ...
  $comma = ',';
}
$query .= ')';

$stmt = $db->prepare($query);
$stmt->bindValue(':x', 123);  // some value
for($i=0; $i<count($ids); $i++){
  $stmt->bindValue(':p'.$i, $ids[$i]);
}
$stmt->execute();
10 голосов
/ 28 мая 2009

Глядя на PDO: предопределенные константы нет PDO :: PARAM_ARRAY, который вам необходим, как указано в PDOStatement-> bindParam

bool PDOStatement :: bindParam (смешанный $ параметр, смешанный & $ variable [, int $ data_type [, int $ length [, mixed $ driver_options]]])

Так что я не думаю, что это достижимо.

...