Умеешь ли писать сырые SQL-запросы и когда это необходимо?

«Умеешь ли писать сырые SQL-запросы и когда это необходимо?» — вопрос из категории Базы данных, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, умею и регулярно пишу raw SQL-запросы, когда того требует задача. В PHP для этого использую в основном PDO как наиболее универсальный и безопасный способ.

Когда raw SQL необходим или предпочтителен:

  1. Сложные аналитические запросы: WITH (CTE), оконные функции, сложные JOIN и агрегации, которые ORM (например, Eloquent) может выразить неэффективно или вообще не может.
    // Пример: Ранжирование заказов по сумме для каждого клиента
    $sql = "
        SELECT 
            customer_id,
            order_id,
            total_amount,
            RANK() OVER (PARTITION BY customer_id ORDER BY total_amount DESC) as rank_in_customer
        FROM orders
        WHERE status = 'completed'
    ";
    $stmt = $pdo->query($sql);
    $report = $stmt->fetchAll(PDO::FETCH_ASSOC);
  2. Миграции и работы со схемой БД: Создание индексов, изменение типов колонок, оптимизационные правки.
    // В миграции Laravel
    DB::statement('CREATE INDEX idx_user_email_active ON users (email) WHERE deleted_at IS NULL');
  3. Массовые операции (Bulk operations): INSERT ... ON DUPLICATE KEY UPDATE или COPY в PostgreSQL для загрузки больших объемов данных, что значительно быстрее постраничного сохранения через ORM.

Безопасный подход с PDO (обязательный):

// 1. Подготовленный запрос с именованными плейсхолдерами - защита от SQL-инъекций
$sql = "
    UPDATE products 
    SET price = :price, updated_at = NOW() 
    WHERE category_id = :category_id AND stock > 0
";
$stmt = $pdo->prepare($sql);

// 2. Явная привязка значений с указанием типа
$stmt->bindValue(':price', 99.99, PDO::PARAM_STR);
$stmt->bindValue(':category_id', 5, PDO::PARAM_INT);

// 3. Выполнение
$stmt->execute();

// 4. Обработка ошибок
if (!$stmt) {
    $errorInfo = $pdo->errorInfo();
    throw new RuntimeException("SQL Error: {$errorInfo[2]}");
}

Когда НЕ стоит писать raw SQL:

  • Для простых CRUD-операций (создание, чтение одной сущности) — ORM читабельнее и быстрее в разработке.
  • Если запрос можно выразить через Query Builder (например, Laravel's DB::table()) — он часто безопаснее и поддерживает разную СУБД.

Мое правило: Использую ORM для рутинных операций с данными, но без колебаний перехожу на тщательно написанный и протестированный raw SQL для сложной логики выборки, отчетности или оптимизации производительности.