> Как выполняется чистый SQL в Django, например через execute (Python)

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

Компании: Стилсофт

Стек: Python

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

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

Чистый SQL в Django выполняется через django.db.connection - объект соединения с БД. Для одного запроса используется connection.cursor() и метод execute(), для нескольких - executemany(). Результат читается через fetchone(), fetchall() или fetchmany(). Важно: после выполнения запроса, изменяющего данные, нужно вызвать transaction.commit() или использовать with transaction.atomic(). Для raw SQL по моделям есть Model.objects.raw().

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

connection - это прокси к активному соединению с базой данных, настроенному в settings.DATABASES. Доступ к нему - через from django.db import connection. Курсор создаётся вызовом connection.cursor(), после чего можно выполнять SQL.

Основные методы курсора:

  • execute(sql, params=None) - выполняет один запрос. Параметры передаются как список или кортеж, Django сам подставит их с экранированием (через %s для большинства БД, % для SQLite).
  • executemany(sql, param_list) - выполняет один запрос многократно с разными параметрами.
  • fetchone(), fetchall(), fetchmany(size) - чтение результатов.
  • rowcount - количество затронутых строк (для INSERT/UPDATE/DELETE).

Важные нюансы:

  • Параметры нельзя подставлять через f-строки или конкатенацию - это риск SQL-инъекций. Только через %s в SQL и список параметров.
  • Для запросов на запись (INSERT, UPDATE, DELETE) нужно управлять транзакцией. По умолчанию Django в autocommit-режиме, но если используется ATOMIC_REQUESTS или ручной transaction.atomic(), коммит произойдёт при выходе из блока.
  • Для чтения данных из моделей лучше использовать Model.objects.raw('SELECT ...') - он возвращает QuerySet с инстансами моделей и поддерживает ленивую загрузку.
  • Если нужно выполнить SQL в другом соединении (например, для реплики), используется connections['replica'].cursor().

На практике

Типичные сценарии:

  • Сложные агрегации, которые не выражаются через ORM (оконные функции, CTE, специфичный синтаксис БД).
  • Массовые вставки или обновления, где ORM слишком медленный.
  • Вызов хранимых процедур.
  • Миграции данных или разовые скрипты.

Рекомендации:

  • Всегда использовать параметризацию - даже для констант.
  • Для чтения использовать fetchall() с осторожностью: при больших результатах - fetchmany() или итерация по курсору.
  • Обернуть запросы на запись в transaction.atomic() - это гарантирует откат при ошибке.
  • После execute для SELECT курсор можно итерировать напрямую: for row in cursor:.
  • Для отладки можно включить connection.queries (в DEBUG=True), но это память, поэтому в проде не использовать.

Пример кода

PYTHON
from django.db import connection, transaction
# Простой SELECT
with connection.cursor() as cursor:
cursor.execute("SELECT id, name FROM users WHERE age > %s", [18])
rows = cursor.fetchall()
for row in rows:
print(row[0], row[1])
# INSERT с транзакцией
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute(
"INSERT INTO orders (user_id, total) VALUES (%s, %s)",
[user_id, total]
)
order_id = cursor.lastrowid
# executemany для массовой вставки
data = [(1, 100), (2, 200), (3, 300)]
with connection.cursor() as cursor:
cursor.executemany(
"INSERT INTO order_items (order_id, price) VALUES (%s, %s)",
data
)
# raw() для моделей
from myapp.models import User
users = User.objects.raw("SELECT * FROM myapp_user WHERE age > %s", [18])
for user in users:
print(user.name)

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

Начни с connection.cursor(), затем покажи параметризацию и работу с транзакциями. Упомяни raw() как альтернативу для моделей. Подчеркни безопасность: никогда не подставлять значения напрямую в строку SQL. Если спросят про производительность - скажи, что executemany эффективнее цикла из execute, но для очень больших объёмов лучше использовать bulk-операции ORM или COPY (для PostgreSQL). Добавь, что для чтения больших результатов стоит использовать fetchmany или итерацию по курсору, чтобы не держать всё в памяти.

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

  • Понимание, что connection - это не глобальный синглтон, а прокси к текущему соединению.
  • Знание разницы между execute и executemany.
  • Умение работать с транзакциями: когда нужен явный commit, когда atomic.
  • Осознание рисков SQL-инъекций и правильной параметризации.
  • Понимание, когда чистый SQL оправдан, а когда лучше ORM.
  • Знание raw() и его ограничений (например, нельзя использовать для JOIN с другими моделями без доп. полей).

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

  • Конкатенация строк в SQL: f"SELECT * FROM users WHERE id = {user_id}" - критическая уязвимость.
  • Забывают про transaction.atomic() для запросов на запись - при ошибке данные частично изменятся.
  • Используют fetchall() для огромных выборок - память переполняется.
  • Путают %s с ? - для разных БД синтаксис плейсхолдеров разный (Django сам подставляет, но в raw SQL нужно писать %s).
  • Вызывают cursor.execute без with - курсор не закрывается, соединение не возвращается в пул.
  • Думают, что raw() возвращает обычный QuerySet - на самом деле он не поддерживает filter(), order_by() и другие методы ORM.

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

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