PHP - использование PDO с массивом предложений IN
Я использую PDO для выполнения оператора с предложением IN
, которое использует для него значения массива:
$in_array = array(1, 2, 3);
$in_values = implode(',', $in_array);
$my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (".$in_values.")");
$my_result->execute();
$my_results = $my_result->fetchAll();
Вышеприведенный код работает отлично, но мой вопрос в том, почему это не так:
$in_array = array(1, 2, 3);
$in_values = implode(',', $in_array);
$my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (:in_values)");
$my_result->execute(array(':in_values' => $in_values));
$my_results = $my_result->fetchAll();
Этот код вернет элемент, который my_value
равен первому элементу в $in_array
(1), но не остальным элементам в массиве (2 и 3).
Ответы
Ответ 1
PDO не очень хорош в таких вещах. Вам нужно создать строку с вопросительными знаками динамически и вставить в запрос.
$in = str_repeat('?,', count($in_array) - 1) . '?';
$sql = "SELECT * FROM my_table WHERE my_value IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($in_array);
$data = $stm->fetchAll();
Если в запросе есть другие заполнители, вы можете использовать следующий подход (код берется из моего учебника PDO):
Вы можете использовать функцию array_merge()
, чтобы объединить все переменные в один массив, добавив другие переменные в виде массивов в порядке их появления в вашем запросе:
$arr = [1,2,3];
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE foo=? AND column IN ($in) AND bar=? AND baz=?";
$stm = $db->prepare($sql);
$params = array_merge([$foo], $arr, [$bar, $baz]);
$stm->execute($params);
$data = $stm->fetchAll();
Если вы используете именованные заполнители, код будет немного более сложным, так как вам нужно создать последовательность именных заполнителей, например. :id0,:id1,:id2
. Таким образом, код будет выглядеть следующим образом:
// other parameters that are going into query
$params = ["foo" => "foo", "bar" => "bar"];
$ids = [1,2,3];
$in = "";
foreach ($ids as $i => $item)
{
$key = ":id".$i;
$in .= "$key,";
$in_params[$key] = $item; // collecting values into key-value array
}
$in = rtrim($in,","); // :id0,:id1,:id2
$sql = "SELECT * FROM table WHERE foo=:foo AND id IN ($in) AND bar=:bar";
$stm = $db->prepare($sql);
$stm->execute(array_merge($params,$in_params)); // just merge two arrays
$data = $stm->fetchAll();
К счастью, для названных заполнителей нам не обязательно следовать строгий порядок, поэтому мы можем объединить наши массивы в любом порядке.
Ответ 2
Переменная замена в подготовленных инструкциях PDO не поддерживает массивы. Это один за другим.
Вы можете обойти эту проблему, создав необходимое количество заполнителей на основе длины массива.
$variables = array ('1', '2', '3');
$placeholders = str_repeat ('?, ', count ($variables) - 1) . '?';
$query = $pdo -> prepare ("SELECT * FROM table WHERE column IN($placeholders)");
if ($query -> execute ($variables)) {
// ...
}
Ответ 3
Поскольку PDO, похоже, не дает хорошего решения, вы можете также рассмотреть возможность использования DBAL, который в основном следует API PDO, но также добавляет некоторые полезные функции http://docs.doctrine-project.org/projects/doctrine-dbal/ан/последние/ссылка/данных Автозагрузка и-manipulation.html # список-оф-параметров преобразования
$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
array(array(1, 2, 3, 4, 5, 6)),
array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
);
Вероятно, есть некоторые другие пакеты, которые не добавляют сложности и не скрывают взаимодействия с базой данных (как и большинство ORM), но в то же время облегчают небольшие типичные задачи.
Ответ 4
Я смог сделать это так. Идея - это одно значение, поступающее из формы поиска. Они что-то ищут, и значение может находиться в одном из следующих двух полей: thisField, thatField или равно someField.
$randomValue = "whatever";
$query = "SELECT * FROM myTable WHERE ((:randomValue in(thisField) or :randomValue in(thatField)) or (someField = :randomValue));";
$statement = $this->mySQL_db->prepare($query);
$statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
$statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
$statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
Ответ 5
Как я понимаю, это связано с тем, что PDO будет обрабатывать содержимое $in_values как отдельный элемент и будет вполне соответствующим образом. PDO увидит 1,2,3 как одну строку, поэтому запрос будет выглядеть примерно как
SELECT * FROM table WHERE my_value IN ( "1,2,3" )
Вы можете подумать, что изменение implode на наличие цитат и запятых исправит его, но это не так. PDO увидит кавычки и изменит, как она цитирует строку.
Что касается того, почему ваш запрос соответствует первому значению, у меня нет объяснений.
Ответ 6
Я только что столкнулся с этой проблемой и закодировал небольшую обертку. Это не самый красивый или лучший код, я уверен, но он может помочь кому-то, вот он:
function runQuery(PDO $PDO, string $sql, array $params = [])
{
if (!count($params)) {
return $PDO->query($sql);
}
foreach ($params as $key => $values) {
if (is_array($values)) {
// get placeholder from array, e.g. ids => [7,12,3] would be ':ids'
$oldPlaceholder = ':'.$key;
$newPlaceholders = '';
$newParams = [];
// loop through array to create new placeholders & new named parameters
for($i = 1; $i <= count($values); $i++) {
// this gives us :ids1, :ids2, :ids3 etc
$newKey = $oldPlaceholder.$i;
$newPlaceholders .= $newKey.', ';
// this builds an associative array of the new named parameters
$newParams[$newKey] = $values[$i - 1];
}
//trim off the trailing comma and space
$newPlaceholders = rtrim($newPlaceholders, ', ');
// remove the old parameter
unset($params[$key]);
// and replace with the new ones
$params = array_merge($params, $newParams);
// amend the query
$sql = str_replace($oldPlaceholder, $newPlaceholders, $sql);
}
}
$statement = $PDO->prepare($sql);
$statement->execute($params);
return $statement;
}
Например, передавая их в:
SELECT * FROM users WHERE userId IN (:ids)
array(1) {
["ids"]=>
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
}
становится:
SELECT * FROM users WHERE userId IN (:ids1, :ids2, :ids3)
array(3) {
[":ids1"]=>
int(1)
[":ids2"]=>
int(2)
[":ids3"]=>
int(3)
}
Это не пуленепробиваемый, но как единственный разработчик для моих нужд, он все равно работает, пока все равно.
Ответ 7
Вот решение для неназванных заполнителей (?). Если вы передадите $ sql с вопросительным знаком типа "A =? AND B IN (?)" И $ args, где некоторые элементы являются массивами, например [1, [1,2,3]], он вернет строку SQL с соответствующим номером заполнителей - "A =? AND B IN (?,?,?)". Параметр $ args нужен только для того, чтобы найти, какой элемент является массивом и сколько заполнителей ему нужно. Вы можете найти небольшой класс расширения PDO с помощью этого метода, который будет выполнять ваш запрос: https://github.com/vicF/pdo/blob/master/src/PDO.php
public function replaceArrayPlaceholders($sql, $args)
{
$num = 0;
preg_match_all('/\?/', $sql, $matches, PREG_OFFSET_CAPTURE); // Captures positions of placeholders
//echo $matches[0][1][1];
$replacements = [];
foreach($args as $arg) {
if(is_array($arg)) {
$replacements[$matches[0][$num][1]] = implode(',',array_fill(0, count($arg), '?')); // Create placeholders string
}
$num++;
}
krsort($replacements);
foreach($replacements as $position => $placeholders) {
$sql = substr($sql, 0, $position).$placeholders.substr($sql, $position+1); // Replace single placeholder with multiple
}
return $sql;
}
Ответ 8
Альтернативная версия PHP Delusions (@your-common-sense) с использованием замыканий:
$filter = ["min_price" => "1.98"];
$editions = [1,2,10];
$editions = array_combine(
array_map(function($i){ return ':id'.$i; }, array_keys($editions)),
$editions
);
$in_placeholders = implode(',', array_keys($editions));
$sql = "SELECT * FROM books WHERE price >= :min_price AND edition IN ($in_placeholders)";
$stm = $pdo->prepare($sql);
$stm->execute(array_merge($filter,$editions));
$data = $stm->fetchAll();