#Когда возникает ошибка
PDO может выдать SQLSTATE[HY093]: Invalid parameter number, когда SQL использует одну метку :term дважды, а подстановка выполняется нативно без эмуляции. Руководство PHP требует уникальные имена параметров для каждого вхождения при отключённой эмуляции. Сама ошибка бывает и из-за несовпадения количества меток со списком execute(), поэтому сначала нужно найти конкретный SQL.
#Причины и признаки
| Возможная причина | Как узнать | Что делать |
|---|---|---|
Одна метка повторена дваждыНапример, name = :term OR email = :term с нативной подготовкой. | Строка SQL содержит несколько :term, а execute привязывает только один ключ. | Назовите метки :term_name и :term_email и передайте обе. |
| Лишние или недостающие параметрыМассив execute() не совпадает с метками в SQL. | В тексте сообщения HY093 нет связи с дублем, но есть неправильное число меток. | Пересчитайте имена и ключи, не смешивайте вопросительные и именованные метки. |
| Различаются драйвер и режим подготовкиРазные окружения могут использовать разный PDO-драйвер или режим ATTR_EMULATE_PREPARES. | Запрос проходит в одном окружении, падает в другом. | Используйте уникальные метки, независимые от включения эмуляции. |
#Пошаговая диагностика и устранение
- Снимите SQLSTATE и место создания запросаВ закрытом журнале приложения найдите
SQLSTATE[HY093], файл и строкуPDOStatement->execute(); не публикуйте SQL с личными данными. Найдите соответствующий вызов$pdo->prepare($sql). На рабочей базе не тестируйте исходный запрос, если он меняет данные: начните с копии и с чтения SELECT. - Сосчитайте все параметры и их именаОткройте SQL в редакторе и найдите
:nameлибо?, не считая двоеточия в строковых литералах и комментариях. Проверьте повтор:termи массив аргументов execute(). Если используется один стиль?, количество значений должно соответствовать маркерам. Нельзя передать имя таблицы или SQL-выражение как параметр. - Проверьте режим подготовки без изменения конфигурацииВ отдельной диагностике под тем же пользователем и подключением проверьте драйвер через
$pdo->getAttribute(PDO::ATTR_DRIVER_NAME). Для драйверов, поддерживающих атрибут, можно посмотреть ATTR_EMULATE_PREPARES; отсутствие поддержки не означает ошибку. Не включайте эмуляцию повсеместно: она меняет поведение и скрывает проблему повторных имён. - Замените повторяющееся имя на независимые меткиВ тестовой копии измените SQL:
WHERE name = :term_name OR email = :term_email. В$stmt->execute([...])добавьте обе разные метки с тем же значением поиска. Если значение должно отличаться по полям (например, поиск по email и имени), нормализуйте их отдельно перед выполнением. - Защитите границы пользовательского поискаПроверьте, что строка поиска передаётся через параметры, а не склеивается с текстом SQL. Для поиска с LIKE экранирование символов
%и_определяется правилами СУБД, а это отдельная задача; не объявляйте пример с точным равенством универсальным решением для LIKE. Секрет подключения не сохраняйте в диагностических файлах. - Проверьте SELECT и остальные сценарииЗапустите изменённый подготовленный SELECT на тестовой базе с обычным запросом, пустым запросом и строкой с кавычкой. Ошибка HY093 должна исчезнуть, результаты должны соответствовать условиям. Для UPDATE/DELETE выполняйте сначала транзакцию на копии и проверку затронутых строк; не пробуйте исправление сразу на рабочей базе.
- Если ошибка осталась — сверяйте фактический SQL и аргументыСравните количество и точное написание всех именованных меток с ключами массива execute(). Изменения можно откатить восстановлением исходного файла из копии. При сложном динамическом SQL полезно изолировать короткий запрос к тестовой таблице, чтобы исключить не относящиеся к делу условия и порядок формирования текста.
#Проверяемый пример и команды
$pdo и таблицей users. Он демонстрирует исправленные уникальные метки, а не готовый сценарий подключения. Результат — массив строк, соответствующих фильтру.<?php
// $pdo — уже настроенное PDO-подключение к тестовой базе.
// Таблица users с колонками name и email должна существовать.
$term = 'anna@example.org';
$sql = 'SELECT id, name FROM users WHERE name = :term_name OR email = :term_email';
$stmt = $pdo->prepare($sql);
$stmt->execute([
'term_name' => $term,
'term_email' => $term,
]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($rows);php -l query-example.php#Проверка исправления и действия при неудаче
Исправление подтверждено, когда запрос с двумя разными именованными метками успешно выполняется при нативной подготовке и выводит ожидаемые строки. Если HY093 сохраняется, проверьте все метки в динамическом SQL и не переводите рабочее подключение на эмуляцию без отдельного обоснования.