Мне любопытно узнать, возможно ли связать массив значений с заполнителем с помощью 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();
?>
Что, безусловно, делает работу, но просто интересно, есть ли встроенное решение, которое мне не хватает?
Я думаю, что Соулмердж прав. вам придется построить строку запроса.
<?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);
?>
(опять же, я не проверял это)
Для чего-то быстрого:
//$db = new PDO(...);
//$ids = array(...);
$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);
Это так важно использовать 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: Как некоторые люди указали в комментариях к этому ответу, есть некоторые вопросы, которые должны быть четко изложены.
FIND_IN_SET
не использует индекс в таблице, и он еще не реализован — смотрите эта запись в трекере ошибок MYSQL. Спасибо @BillKarwin за уведомление.implode
так как вы используете символ запятой в качестве разделителя. Спасибо @VaL за примечание.В общем, если вы не сильно зависите от индексов и не используете строки с запятой для поиска, мое решение будет намного проще, проще и быстрее, чем решения, перечисленные выше.
Поскольку я делаю много динамических запросов, это очень простая вспомогательная функция, которую я сделал.
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
привязок, которые вам понадобятся, когда придет время выполнить ваш запрос. Легко!
Решение от 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();
Очень простой способ для 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));
Я расширил 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();
Вот мое решение:
$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);
И это не удалось.