6

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

4

Описание проблемы:

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

3

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

1

Для быстрого решения:

//$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-инъекций.

0

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

0

Чтобы использовать массивы в 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 в указанном массиве. Это удобный и эффективный способ выполнения выборки.

0

Решение от 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. Так вы можете эффективно фильтровать записи в таблице на основе массива идентификаторов.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь