Можно ли привязать массив к условию IN() в запросе PDO?
Описание проблемы:
Я хотел бы узнать, возможно ли привязать массив значений к плейсхолдеру с использованием 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); // пройтись по массиву и экранировать
$in = implode(',', $ids); // создать строку с разделением запятыми
$stmt = $db->prepare(
'SELECT *
FROM table
WHERE id IN(' . $in . ')'
);
$stmt->execute();
?>
Это отлично работает, но мне интересно, есть ли встроенное решение, которое я мог бы упустить?
5 ответ(ов)
Для вашего запроса вам придется вручную составить список placeholders, добавляя по одному placeholder для каждого элемента массива.
<?php
$ids = [1, 2, 3, 7, 8, 9];
$inQuery = str_repeat('?,', count($ids) - 1) . '?'; // получает ?,?,?,?,?,?
$stmt = $db->prepare("SELECT * FROM table WHERE id IN($inQuery)");
$stmt->execute($ids);
$data = $stmt->fetchAll();
Так как $inQuery
не принимает никаких входных значений и полностью состоит из постоянных значений (частей ?,
), можно безопасно использовать такую переменную в запросе.
Если в запросе есть другие placeholders, вы можете использовать функцию 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=?";
$stmt = $db->prepare($sql);
$params = array_merge([$foo], $arr, [$bar, $baz]);
$stmt->execute($params);
$data = $stmt->fetchAll();
Если вы используете именованные placeholders, код будет немного сложнее, так как вам нужно создать последовательность именованных placeholders, например :id0,:id1,:id2
. Тогда код будет выглядеть так:
// другие параметры, которые идут в запрос
$params = ["foo" => "foo", "bar" => "bar"];
$ids = [1,2,3];
$in = "";
$i = 0; // используем внешний счетчик
// потому что фактические ключи массива могут быть опасными
foreach ($ids as $item)
{
$key = ":id".$i++;
$in .= ($in ? "," : "") . $key; // :id0,:id1,:id2
$in_params[$key] = $item; // собираем значения в ассоциативный массив
}
$sql = "SELECT * FROM table WHERE foo=:foo AND id IN ($in) AND bar=:bar";
$stmt = $db->prepare($sql);
$stmt->execute(array_merge($params, $in_params)); // просто объединяем два массива
$data = $stmt->fetchAll();
К счастью, для именованных placeholders не нужно следовать строгому порядку, поэтому мы можем объединять наши массивы в любом порядке.
Для быстрого решения:
//$db = new PDO(...);
//$ids = array(...);
$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);
Этот код создает строку с заполнителями для подготовленного запроса, что позволяет выполнять выборку данных из таблицы myTable
по массиву идентификаторов $ids
. Важно использовать подготовленные выражения для предотвращения SQL-инъекций.
Конечно! Вот перевод вашего ответа на русский в стиле StackOverflow:
Поскольку я часто работаю с динамическими запросами, я создал очень простую вспомогательную функцию.
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
с привязками, который вам понадобится, когда придёт время выполнять запрос. Очень удобно!
Чтобы использовать массивы в PostgreSQL, вы можете воспользоваться возможностью работать с массивами напрямую. Вот чистый способ сделать это с использованием синтаксиса PostgreSQL для работы с массивами (формат "{...}"
):
$ids = array(1, 4, 7, 9, 45);
$param = "{" . implode(',', $ids) . "}";
$cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
$result = $cmd->execute(array($param));
В данном примере мы сначала создаем массив идентификаторов $ids
, затем формируем строку в формате массива PostgreSQL. После этого подготавливаем SQL-запрос с использованием оператора ANY
, который позволяет проверять, содержится ли id
в указанном массиве. Это удобный и эффективный способ выполнения выборки.
Решение от 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();
Этот запрос использует функцию string_to_array
, чтобы преобразовать строку с id в массив, который затем используется в условии WHERE
с оператором ANY
. Так вы можете эффективно фильтровать записи в таблице на основе массива идентификаторов.
Удаление элемента из массива в PHP
Сортировка двумерного массива по значению в столбце
Преобразование объекта PHP в ассоциативный массив
Удалить пустые элементы массива
PHP: Удаление элемента массива по значению (а не по ключу)