> Используете ли нативные SQL-запросы или ORM (PHP)

Уровень: senior · Роль: backend · Язык: PHP · Категория: Технические вопросы

Компании: vanta

Стек: PHP

> Пример ответа

Короткий ответ

В production-проектах я использую гибридный подход: ORM (Doctrine/Eloquent) для типовых CRUD-операций и нативные SQL-запросы для сложных выборок, отчётов и операций с высокой нагрузкой. Выбор зависит от конкретной задачи, а не от предпочтений. ORM даёт безопасность типов и маппинг, нативный SQL - контроль над планом выполнения и производительностью. Критично понимать, когда каждый из инструментов уместен.

Подробное объяснение

ORM и нативные SQL-запросы решают разные задачи. ORM абстрагирует работу с БД, предоставляя объектный доступ к данным, автоматические миграции, гидрацию сущностей и защиту от SQL-инъекций через query builder. Однако ORM генерирует неоптимальный SQL для сложных join'ов, агрегаций или оконных функций - часто получаются N+1 запросы или избыточные выборки.

Нативные SQL-запросы дают полный контроль над текстом запроса, позволяют использовать специфичные для конкретной СУБД возможности (например, LATERAL JOIN в PostgreSQL, FOR UPDATE SKIP LOCKED, полнотекстовый поиск). Минусы - ручное маппинг результатов, риск SQL-инъекций при неаккуратном использовании, потеря переносимости между СУБД.

Практическое правило: если запрос умещается в 2-3 join'а и не требует агрегаций - ORM. Если нужны подзапросы, оконные функции, динамические условия или работа с большими объёмами - нативный SQL. Также стоит учитывать, что в крупных системах часто используется CQRS: запись через ORM, чтение через оптимизированные SQL-запросы или даже отдельные read-модели.

На практике

В типичном PHP-проекте на Symfony/Doctrine я делаю так:

  • Стандартные CRUD-операции, работа с агрегатами - через Doctrine repositories и DQL.
  • Сложные отчёты, аналитика, экспорт данных - через DBAL с нативным SQL и параметризованными запросами.
  • Для высоконагруженных read-путей - raw SQL с явными индексами и EXPLAIN.
  • В Laravel - Eloquent для простых моделей, Query Builder для средних по сложности запросов, DB::select() для тяжёлых.

Важно: даже при использовании ORM я всегда проверяю сгенерированный SQL (в Doctrine - $query->getSQL(), в Laravel - toSql()), чтобы убедиться в отсутствии N+1 и лишних выборок.

Пример кода

Пример на Symfony/Doctrine, когда ORM не справляется - нужен нативный SQL для отчёта с оконной функцией:

PHP
// Нативный SQL через DBAL
$sql = "
SELECT
u.id,
u.name,
COUNT(o.id) AS order_count,
SUM(o.total) AS total_sum,
RANK() OVER (PARTITION BY u.region_id ORDER BY SUM(o.total) DESC) AS region_rank
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.created_at >= :start_date
WHERE u.active = 1
GROUP BY u.id, u.name, u.region_id
HAVING COUNT(o.id) > 0
ORDER BY total_sum DESC
LIMIT 100
";
$stmt = $this->connection->executeQuery($sql, [
'start_date' => $startDate,
]);
$rows = $stmt->fetchAllAssociative();

А вот пример, где ORM уместен - простая выборка с фильтрацией:

PHP
// Doctrine ORM
$users = $this->entityManager
->getRepository(User::class)
->createQueryBuilder('u')
->where('u.active = :active')
->andWhere('u.createdAt > :date')
->setParameter('active', true)
->setParameter('date', $date)
->orderBy('u.createdAt', 'DESC')
->setMaxResults(50)
->getQuery()
->getResult();

Как отвечать на собеседовании

Начните с чёткого ответа: вы используете оба подхода, но осознанно. Затем приведите конкретные критерии выбора - сложность запроса, производительность, читаемость кода. Обязательно упомяните, что проверяете сгенерированный ORM SQL и используете EXPLAIN для нативных запросов. Хорошо добавить пример из практики: когда ORM создавал N+1 проблему и вы переписали на raw SQL, или наоборот - когда нативный SQL усложнял поддержку и вы перешли на ORM.

Покажите понимание trade-off: ORM - скорость разработки и безопасность, SQL - контроль и производительность. Если спросят про конкретный фреймворк - приведите детали реализации (Doctrine DBAL, Eloquent Query Builder). Не бойтесь признать, что иногда используете гибридные решения, например, raw SQL внутри repository-метода.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание сильных и слабых сторон обоих подходов, а не слепое следование одному из них.
  • Умение принимать архитектурные решения на основе контекста задачи.
  • Практический опыт: знает ли кандидат про N+1, lazy loading, пагинацию, индексы.
  • Способность объяснить trade-off простыми словами и защитить свою позицию.
  • Глубину: знает ли про query builder, DBAL, гидрацию, identity map.

Типичные ошибки

  • Категоричное "я всегда использую только ORM" или "только нативные SQL" - показывает отсутствие гибкости.
  • Утверждение, что ORM полностью защищает от SQL-инъекций - неверно, если использовать raw expressions или неправильно конкатенировать строки.
  • Игнорирование производительности: кандидат не упоминает N+1, lazy loading, отсутствие проверки плана запроса.
  • Использование ORM для сложных отчётов с join'ами на 5+ таблиц - признак отсутствия практического опыта.
  • Неумение объяснить, когда именно стоит перейти на нативный SQL - интервьюер ждёт конкретных критериев, а не общих слов.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью