Могу ли я привязать массив к условию IN ()?

Мне любопытно узнать, можно ли привязать массив значений к заполнителю с помощью 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();
?>

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


person Andru    schedule 28.05.2009    source источник
comment
Полное руководство по привязке массива к условию IN (), включая случай, когда у вас есть другие заполнители в запросе   -  person Your Common Sense    schedule 18.04.2017
comment
Вопрос был закрыт как дубликат этого вопроса. Я перевернул флаг дублирования, потому что этот вопрос на 4 года старше, имеет в 4 раза больше просмотров, в 3 раза больше ответов и в 12 раз больше. Это явно лучшая цель.   -  person miken32    schedule 07.03.2020
comment
Любой, кто смотрит на это в 2020 году: для этого вы можете попробовать github.com/morris/dop.   -  person morris4    schedule 09.05.2020


Ответы (22)


Вам нужно будет построить строку запроса.

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

И chris (комментарии), и somethingisintrouble предположили, что цикл 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);
person stefs    schedule 28.05.2009
comment
Это интересное решение, и хотя я предпочитаю его перебирать идентификаторы и вызывать PDO :: quote (), я думаю, что индекс '?' заполнители испортятся, если в запросе сначала появятся какие-либо другие заполнители, верно? - person Andru; 28.05.2009
comment
да, это было бы проблемой. но в этом случае вы можете создать именованные параметры вместо?. - person stefs; 28.05.2009
comment
Во второй строке этого кода есть несоответствие). Знак) после 1 должен стоять в конце. Хотя спасибо - очень полезно! :-) - person Dan; 30.07.2010
comment
Старый вопрос, но я считаю, что стоит отметить, что $foreach и bindValue() не требуются - просто выполните с массивом. Например: $stmt->execute($ids); - person Chris; 30.05.2012
comment
вы также можете проверить, что идентификаторы в массиве имеют правильный тип, если они получены из параметра запроса. Использование чего-то вроде is_numeric($id) в foreach. - person Mortimer; 08.06.2012
comment
Создание заполнителей должно выполняться следующим образом str_repeat('?,', count($array) - 1). '?'; - person Xeoncross; 25.11.2012
comment
+1 но в качестве предложения: $stmt->execute(array_values($ids)) потому что, если вы использовали позиционные параметры, но $ids является ассоциативным массивом, он сломается. - person Bill Karwin; 09.07.2014
comment
Просто совет для тех, кто не знает: нельзя смешивать именованные и безымянные параметры. Поэтому, если вы используете именованные параметры в своем запросе, переключите их на?, А затем увеличьте смещение индекса bindValue, чтобы оно соответствовало положению IN? С тем, где они находятся относительно вашего другого? параметры - person justinl; 22.10.2014
comment
Одна из проблем использования IN таким образом заключается в том, что вы должны знать, сколько значений имеется при подготовке запроса. Это означает, что вы должны подготовить другой оператор каждый раз, когда изменяется количество значений массива. - person Will B.; 18.02.2015
comment
@fyrye: это правда, но, честно говоря, мне редко приходится повторно использовать подготовленные операторы в течение цикла генерации одной страницы - я в основном использую их из соображений безопасности, не обязательно для производительности (я не уверен, что операторы подготовки имеют значение вне долго работающих приложений). вы всегда можете использовать массив для их кеширования. - person stefs; 20.02.2015
comment
@stefs Я также использую его таким же образом, предоставляя комментарий как предупреждение тем, кто ожидает, что он сработает, когда они изменят количество значений. Например, в сложных объектных моделях. Я также рекомендую ваше предложение о хранении операторов в массиве в качестве временного решения проблемы. - person Will B.; 21.02.2015
comment
Если мне нужно вставить две разные строки, разделенные запятыми, в два разных состояния IN в sql, как мне тогда это сделать? - person Reality-Torrent; 05.02.2016
comment
У меня была переменная $varID = '1, 2, 3, 4';, но она не сработала, когда я установил $ids = array($varID);, я использовал вместо $ids = explode(',', $varID);, и она сработала. Спасибо за помощь. - person cyclone200; 16.05.2016
comment
Что делать, если у меня есть два in clauses? - person Eslam Sameh Ahmed; 30.07.2016
comment
@EslamSamehAhmed применяется тот же принцип. создать как inQueries, так и массив - добавить идентификаторы. - person stefs; 01.08.2016
comment
При выполнении с массивом числовых идентификаторов все значения обрабатываются как PDO :: PARAM_STR (PDOStatement :: execute), а msql должен преобразовать их как целые числа. - person showdev; 07.06.2019
comment
извините за PDO. он не поддерживает значения массива. я думаю, что они должны его обновить. - person Saleh Mosleh; 14.06.2019

Для чего-нибудь быстрого:

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

$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);
person uɥƃnɐʌuop    schedule 23.05.2012
comment
Отлично, я не подумал использовать аргумент input_parameters таким образом. Для тех, чьи запросы имеют больше параметров, чем список IN, вы можете использовать array_unshift и array_push, чтобы добавить необходимые аргументы в начало и конец массива. Кроме того, я предпочитаю $input_list = substr(str_repeat(',?', count($ids)), 1); - person orca; 31.05.2012
comment
Вы также можете попробовать str_repeat('?,', count($ids) - 1) . '?'. На один вызов функции меньше. - person orca; 02.06.2012
comment
Я использовал это с array_diff, чтобы получить массив, который я хотел удалить, и это дало мне ошибку, потому что 1-й элемент массива не имел значения индекса 0, поэтому мне пришлось сначала использовать array_values, чтобы сбросить ключи. - person Klemen Tusar; 11.09.2012
comment
Не показывать уверенности в безопасности этого. Это будет работать, но мне кажется, что это противоречит цели подготовленных операторов и выглядит уязвимым для инъекций. - person erfling; 30.11.2015
comment
@erfling, это подготовленный оператор, откуда будет вводиться инъекция? Я буду более чем счастлив внести какие-либо исправления, если вы подкрепите это какими-нибудь реальными доказательствами. - person uɥƃnɐʌuop; 30.11.2015
comment
Подготовка оператора не предотвращает инъекцию. Вы должны привязать параметры. Если один из индексов $ ids, где DROP table users ;, вполне может быть выполнен. Ответ, получивший наибольшее количество голосов, предотвращает это за счет зацикливания и привязки. - person erfling; 01.12.2015
comment
@erfling, да, это правильно, и привязка параметров - это именно то, что мы делаем в этом примере, отправляя execute массив идентификаторов - person uɥƃnɐʌuop; 01.12.2015
comment
Да действительно. Как-то упустил тот факт, что вы передавали массив. Это действительно кажется безопасным и хорошим ответом. Мои извенения. - person erfling; 02.12.2015
comment
Почему count ($variables) - 1) . '?'; Почему не просто count($variable) - person Robert Rocha; 09.06.2016
comment
@RobertRocha, потому что он добавляет '?' в конце (без запятой). Без -1 это было бы что-то вроде ... IN (?,?,?,?,?,), что синтаксически неверно - person kit; 03.11.2016
comment
А как насчет нескольких WHERE операторов? Как 2_? execute(array($name, $ids)), похоже, вообще не работает .... - person Alex G; 12.06.2017
comment
@Miguel, это неправильно, это то, что называется использованием подготовленных операторов для обеспечения безопасности типов и предотвращения внедрения sql. Узнайте, как PDOStatement::execute работает здесь, в документации PHP - person uɥƃnɐʌuop; 10.07.2017

Неужели так важно использовать оператор 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 за заметку.

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

person Tim Tonkonogov    schedule 02.10.2013
comment
IN () может использовать индекс и считается сканированием диапазона. FIND_IN_SET () не может использовать индекс. - person Bill Karwin; 03.10.2013
comment
Это точка. Я этого не знал. Но в любом случае никаких требований к производительности и речи не идет. Для небольших таблиц это намного лучше и чище, чем отдельный класс для генерации запросов с разным количеством заполнителей. - person Tim Tonkonogov; 08.10.2013
comment
Да, но у кого сейчас не такой уж большой стол? ;-) - person Bill Karwin; 08.10.2013
comment
Еще одна проблема с этим подходом в том, что если внутри будет строка с запятой? Например... FIND_IN_SET(description,'simple,search') будет работать, но FIND_IN_SET(description,'first value,text, with coma inside') не удастся. Таким образом, функция будет искать "first value", "text", "with coma inside" вместо желаемого "first value", "text, with coma inside" - person VaL; 17.02.2015

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

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 привязки, которые вам понадобятся, когда придет время выполнить ваш запрос. Легкий!

person prograhammer    schedule 26.03.2014
comment
Это гораздо лучшее решение, поскольку оно не нарушает правила привязки параметров. Это намного безопаснее, чем наличие встроенного sql, предложенного некоторыми другими здесь. - person Dimitar Darazhanski; 07.12.2015

очень чистый способ для 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));
person ESCOBAR    schedule 15.04.2014
comment
это блокировать SQL-инъекцию? - person Fábio Zangirolami; 22.02.2018
comment
@ FábioZangirolami, это PDO, так что да. - person ESCOBAR; 23.02.2018
comment
ДА, PDO! через 4 года. Ваш ответ меня устроил, очень простой и эффективный. Спасибо!!! - person Fábio Zangirolami; 23.02.2018

Решение от 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();
person Community    schedule 18.08.2010
comment
Это не работает: ERROR: operator does not exist: integer = text. По крайней мере, вам нужно добавить явное приведение. - person collimarco; 15.12.2013

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

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

И это было неудачно.

person Progrock    schedule 21.01.2015
comment
Это было отличное решение. В отличие от построения строки запроса, здесь правильно используется привязка. Я настоятельно рекомендую это. - person Lindylead; 19.01.2020
comment
Примечание. Используя свое решение, вы можете добавлять элементы в переднюю или заднюю часть массива, чтобы вы могли включать другие привязки. $original_array Добавить элементы перед исходным массивом: array_unshift($original_array, new_unrelated_item); Добавить элементы после исходного массива: array_push($original_array, new_unrelated_item); Когда значения привязаны, новые_не связанные элементы будут размещены в правильных местах. Это позволяет вам смешивать элементы массива и элементы, не являющиеся массивом. ` - person Lindylead; 19.01.2020

Я расширил 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();
person Chris    schedule 18.02.2012
comment
почему бы не отбросить аргумент и не заняться $db_link->prepare_with_arrays(array('array1'=>array(1,2,3),'array2'=>array(7,8,9)))? это все еще небезопасно, если ваш :array находится внутри строки SQL (его не следует заменять). - person mpen; 22.10.2012
comment
Да, это звучит лучше, снижает вероятность того, что ваши токены и массивы данных не совпадают. Я, вероятно, скоро перепишу это, потому что это получит какое-то уведомление. - person Chris; 22.10.2012
comment
Я рассмотрел первую часть комментария Марка, но, как он указал, все еще небезопасно, если токен типа :array находится в строке запроса. - person Chris; 26.02.2013
comment
Примечание для всех будущих читателей: это решение никогда не следует использовать. Утверждения не предназначены для производственного кода - person Your Common Sense; 30.03.2013
comment
YCS: спасибо за обратную связь, интересуюсь вашим мнением о подходе, выходящем за рамки пригодности утверждений. - person Chris; 30.03.2013
comment
Идея почти такая же, но без утверждений и более простой и явный способ - не как исключение только для одного случая, а как общий способ построения каждого запроса. Каждый заполнитель помечен своим типом. Это делает ненужными догадки (например, if (is_array($data)) один), но делает обработку данных более точной. - person Your Common Sense; 30.03.2013
comment
Всем, кто читает комментарии: проблема, упомянутая @Your Common Sense, была исправлена ​​в версии 4. - person user2428118; 05.12.2014

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

// 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);
person RousseauAlexandre    schedule 13.09.2017
comment
Трудно быть уверенным, не попробовав это в реальном сценарии, но кажется нормальным. + 1 - person Anant Kumar Singh; 24.04.2019

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

$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();
person Daniel Miloca - Brazil    schedule 18.03.2015
comment
Спасибо за отличный ответ. Это было единственное, что действительно сработало для меня. Однако я увидел 1 ошибку. Переменная $ rs должна быть $ stmt - person Piet; 04.05.2016

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

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

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

person Community    schedule 28.05.2009
comment
Не знаю, работает ли это. Я предполагаю, что взорванная строка цитируется. - person soulmerge; 28.05.2009
comment
Вы правы, кавычки экранируются, так что это не сработает. Я удалил этот код. - person ; 28.05.2009

Я также понимаю, что этот поток устарел, но у меня была уникальная проблема, когда при преобразовании драйвера 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 - правильная функция.

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

person Sammaye    schedule 12.07.2012

Небольшая правка о коде 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
person Aaron Angelo Vicuna    schedule 18.02.2010
comment
Мне пришлось удалить -1 после count ($ ids), чтобы он работал для меня, иначе всегда будет отсутствовать один заполнитель. - person Marcel Burkhard; 15.11.2016

Какую базу данных вы используете? В 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, чтобы узнать больше о предмете и примерах этого сценария.

person Ryan Bair    schedule 28.05.2009

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

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

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

person Cave Johnson    schedule 14.03.2017

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

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

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

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

  2. Массив котировок ранее

    $ whereIn = array_map (массив ($ db, 'quote'), $ ids);

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

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

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

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

person Oleg Matei    schedule 20.06.2014

Пройдя через ту же проблему, я выбрал более простое решение (хотя и не такое элегантное, как 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 ));

Но я не тестировал это.

person alan_mm    schedule 04.04.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'];
}

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

person Lippai Zoltan    schedule 30.05.2013
comment
Забыл упомянуть, что это основано на ответе user2188977 ниже. - person Lippai Zoltan; 30.05.2013
comment
Я не уверен, что такое getOne (), похоже, он не является частью PDO. Я видел это только в PEAR. Что именно он делает? - person Lippai Zoltan; 01.06.2013
comment
@YourCommonSense можете ли вы опубликовать свою пользовательскую функцию в качестве ответа? - person nullability; 12.07.2013
comment
Я бы предложил передать тип данных BindArrayParam в ассоциативном массиве, поскольку вы, кажется, ограничиваете это целыми числами. - person Ian Brindley; 14.10.2013

Использование такого массива в 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 пустым, вы должны проверить его и соответствующим образом обработать этот случай (вернуть пустой массив или вернуть нулевой объект, или выбросить исключение, ...).

person Pedro Amaral Couto    schedule 22.03.2018

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

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

//builds placeholders to insert in IN()
foreach($array as $key=>$value) {
    $in_query = $in_query . ' :val_' . $key . ', ';
}

//gets rid of trailing comma and space
$in_query = substr($in_query, 0, -2);

$stmt = $db->prepare(
    "SELECT *
     WHERE id IN($in_query)";

//pind params for your placeholders.
foreach ($array as $key=>$value) {
    $stmt->bindParam(":val_" . $key, $array[$key])
}

$stmt->execute();
person Joseph_J    schedule 26.06.2014

вы сначала задали количество "?" в запросе, а затем "для" отправить такие параметры:

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);
person Ali Chegini    schedule 24.08.2017

В MySQL и PDO мы можем использовать массив JSON и JSON_CONTAINS() (https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-contains) для поиска.

$ids = [123, 234, 345, 456]; // Array of users I search
$ids = json_encode($ids); // JSON conversion

$sql = <<<SQL
    SELECT ALL user_id, user_login
    FROM users
    -- Cast is mandatory beaucause JSON_CONTAINS() waits JSON doc candidate
    WHERE JSON_CONTAINS(:ids, CAST(user_id AS JSON))
    SQL;

$search = $pdo->prepare($sql);
$search->execute([':ids' => $ids]);
$users = $search->fetchAll();

Также можно использовать JSON_TABLE() (https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table) для более сложных случаев и исследования данных JSON:

$users = [
    ['id' => 123, 'bday' => ..., 'address' => ...],
    ['id' => 234, 'bday' => ..., 'address' => ...],
    ['id' => 345, 'bday' => ..., 'address' => ...],
]; // I'd like to know their login

$users = json_encode($users);

$sql = <<<SQL
    SELECT ALL user_id, user_login
    FROM users
    WHERE user_id IN (
        SELECT ALL user_id
        FROM JSON_TABLE(:users, '$[*]' COLUMNS (
            -- Data exploration...
            -- (if needed I can explore really deeply with NESTED kword)
            user_id INT PATH '$.id',
            -- I could skip these :
            user_bday DATE PATH '$.bday',
            user_address TINYTEXT PATH '$.address'
        )) AS _
    )
    SQL;

$search = $pdo->prepare($sql);
$search->execute([':users' => $users]);
...
person JCH77    schedule 08.04.2021