Как выполнять сложные SQL-запросы, не поддерживаемые Django ORM?

«Как выполнять сложные SQL-запросы, не поддерживаемые Django ORM?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Когда Django ORM недостаточно для сложных запросов (оконные функции, рекурсивные CTE, специфичные JOIN), используются сырые SQL-запросы или расширения ORM.

Способы работы с сырым SQL в Django:

  1. Manager.raw(): Выполнение SELECT-запросов с маппингом результатов в модели.

    from django.db import models
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
    
    # Сырой запрос с параметрами для безопасности
    people = Person.objects.raw('''
        SELECT id, first_name, last_name
        FROM myapp_person
        WHERE last_name = %s
        ORDER BY first_name DESC
    ''', [requested_last_name])
  2. Прямое подключение к БД (django.db.connection): Для запросов, не возвращающих модели (UPDATE, вызов функций).

    from django.db import connection
    
    def my_custom_sql(self):
        with connection.cursor() as cursor:
            cursor.execute("""
                SELECT COUNT(*) AS url_count
                FROM myapp_url
                WHERE path LIKE %s
            """, ['/api/%'])
            row = cursor.fetchone()
        return row[0]
  3. Выражения RawSQL: Встраивание SQL-фрагментов прямо в QuerySet.

    from django.db.models.expressions import RawSQL
    
    queryset = Person.objects.annotate(
        custom_field=RawSQL("some_column - %s", (param,))
    )

Почему осторожно?

  • SQL-инъекции: Всегда используйте параметризованные запросы (%s), а не конкатенацию строк.
  • Переносимость: Сырой SQL привязывает код к конкретной СУБД (PostgreSQL, MySQL).
  • Поддержка: Усложняет миграции и может обойти валидацию ORM.

Лучшая практика: Сначала изучите возможности ORM (F(), Q(), Window, Subquery). К сырому SQL прибегайте только в случае необходимости.