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

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

526

Решение

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

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

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

редактировать: и Крис (комментарии), и кто-то не могут поверить, что цикл 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);
?>

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

245

Другие решения

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

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

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

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

Например, в 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 за примечание.

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

44

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

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

29

Решение от 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();
16

Очень простой способ для 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));
15

Я расширил 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

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

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

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

12
По вопросам рекламы [email protected]