IN() в PDO: как передать массив без боли и ошибок

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

«Can I bind an array to an IN() condition?» — вопрос, который мучает каждого второго PHP-разработчика

Вы когда-нибудь писали запрос вроде SELECT * FROM users WHERE id IN (?) и пытались передать туда массив через bindValue()? Если да — вы точно знаете это чувство разочарования, когда PDO возвращает всего одну строку вместо всех нужных. Или ещё хуже — выбрасывает ошибку.

Этот вопрос на Stack Overflow собрал тысячи просмотров, и неспроста. Казалось бы, чего проще: передал массив, получил результат. Но PDO (PHP Data Objects) — штука строгая. Он не умеет «разворачивать» массивы в список значений для IN() автоматически. И вот тут начинается самое интересное: разработчики придумывают топ ошибок, которые мы сегодня разберём. А главное — научимся их избегать.

В этой статье мы не просто дадим готовое решение. Мы пройдём путь от неправильного кода к безопасному, производительному и красивому. Поехали!

Ошибка №1: Наивная передача массива через bindValue

Самый частый «грабли» — попытка скормить массив прямо в bindValue().

// ❌ НЕПРАВИЛЬНО
$ids = [1, 2, 3, 5];
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (?)");
$stmt->bindValue(1, $ids, PDO::PARAM_STR);
$stmt->execute();
$users = $stmt->fetchAll();

Что произойдёт? PHP преобразует массив в строку "Array", и запрос превратится в WHERE id IN ("Array"). Результат — ноль строк или ошибка, в зависимости от драйвера. PDO просто не понимает, что массив нужно разложить на отдельные плейсхолдеры.

Почему так происходит? PDO ожидает скалярное значение (строку, число, null). Массив — это составной тип, и он не подходит для прямого биндинга.

Как исправить?

Нужно вручную создать плейсхолдеры для каждого элемента массива. Никакой магии, только код.

// ✅ ПРАВИЛЬНО
$ids = [1, 2, 3, 5];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
$users = $stmt->fetchAll();

Здесь мы создаём строку вида ?,?,?,? и передаём массив прямо в execute(). PDO сам подставит значения по порядку. Просто и безопасно.

Ошибка №2: Игнорирование типов данных

Допустим, вы используете именованные плейсхолдеры. И тут возникает соблазн сделать так:

// ❌ НЕПРАВИЛЬНО
$ids = [1, 2, 3];
$placeholders = [];
foreach ($ids as $i => $id) {
    $placeholders[] = ":id_$i";
}
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";
$stmt = $pdo->prepare($sql);
foreach ($ids as $i => $id) {
    $stmt->bindValue(":id_$i", $id, PDO::PARAM_INT);
}
$stmt->execute();

Вроде бы всё верно. Но есть нюанс: если массив пустой, запрос станет WHERE id IN (), что вызовет синтаксическую ошибку SQL. Вторая проблема — если в массиве есть строки, а вы указали PDO::PARAM_INT, PDO может привести их к нулю.

Решение: всегда проверяйте массив на пустоту и динамически определяйте тип.

// ✅ ПРАВИЛЬНО
$ids = [1, 2, 3];
if (empty($ids)) {
    return []; // или выбросить исключение
}
$placeholders = [];
foreach ($ids as $i => $id) {
    $placeholders[] = ":id_$i";
}
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";
$stmt = $pdo->prepare($sql);
foreach ($ids as $i => $id) {
    $type = is_int($id) ? PDO::PARAM_INT : PDO::PARAM_STR;
    $stmt->bindValue(":id_$i", $id, $type);
}
$stmt->execute();

Ошибка №3: SQL-инъекция через «ручное» экранирование

Некоторые разработчики в отчаянии пишут что-то вроде:

// ❌ ОПАСНО
$ids = [1, 2, 3];
$ids_escaped = array_map('intval', $ids); // только для чисел!
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $ids_escaped) . ")";
$stmt = $pdo->query($sql);

Для чисел это ещё прокатывает. Но если в массиве строки (скажем, email'ы), intval превратит их в ноль. А если вы используете addslashes() или mysqli_real_escape_string() — вы всё равно рискуете, особенно с многобайтовыми кодировками.

Золотое правило PDO: никогда не вставляйте данные напрямую в SQL. Только через плейсхолдеры. Даже если кажется, что «я же экранирую».

Ошибка №4: Забыли про производительность

Когда массив большой (сотни или тысячи элементов), создание сотен плейсхолдеров может замедлить запрос. PDO и база данных будут обрабатывать каждый плейсхолдер отдельно, что увеличивает время подготовки.

Альтернатива: используйте временную таблицу или подзапрос. Но это уже для продвинутых сценариев. Для большинства случаев (до 100-200 элементов) метод с плейсхолдерами работает отлично.

Если массив действительно огромный — разбейте его на чанки по 100-200 элементов и выполните несколько запросов, а потом объедините результаты в PHP.

// ✅ ДЛЯ БОЛЬШИХ МАССИВОВ
$allIds = range(1, 5000);
$chunks = array_chunk($allIds, 200);
$results = [];
foreach ($chunks as $chunk) {
    $placeholders = implode(',', array_fill(0, count($chunk), '?'));
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
    $stmt->execute($chunk);
    $results = array_merge($results, $stmt->fetchAll());
}

Ошибка №5: Неправильная работа с NULL в массиве

Если в массиве есть NULL, запрос WHERE id IN (1, NULL, 3) не вернёт строки,

Блоги

Рекомендаций по книгам