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

Ответ

Когда 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 прибегайте только в случае необходимости.

Ответ 18+ 🔞

Ох, блядь, смотри-ка, какой вопрос подкинули. Ну что ж, давай разжую, как есть, про эти ваши сырые запросы в Джанге. Ситуация, в общем, стандартная: сидит программист, пыхтит над ORM, а она, сука, упёрлась рогом и не хочет делать какую-нибудь навороченную хуйню вроде оконных функций или рекурсивных CTE. Ну, или JOIN'ы там такие, что сам чёрт ногу сломит. Что делать? Правильно, брать в руки SQL и ебашить напрямую в базу. Но не как попало, а с умом, а то так и до инъекций недалеко.

Так, блядь, как же в Джанге с SQL'ем работать, если ORM сдала назад?

  1. Manager.raw() — для ленивых, но осторожных. Это когда тебе нужно просто выбрать данные, но чтобы они вернулись в виде твоих родных моделей, а не какой-то левой хуйни. Как будто ORM, но под капотом — твой кривой запрос.

    from django.db import models
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
    
    # Смотри, сука, главное — параметры через %s, а не строки склеивать!
    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]) # Вот этот список — наше всё. Безопасность, блядь.

    Выглядит как ORM, пахнет как ORM, а внутри — твой личный SQL. Удобно, но только для SELECT'ов.

  2. Прямое подключение (django.db.connection) — для отчаянных. Это когда тебе вообще похуй на модели. Надо что-то посчитать, обновить, процедуру какую-нибудь дернуть. Полная свобода, а значит и полная ответственность, чувак.

    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/%'])  # Опять эти долбаные %s, не забывай!
            row = cursor.fetchone()  # Выковыриваем одну строку
        return row[0]  # И возвращаем то, что нам нужно

    Здесь ты уже сам всё контролируешь. И сам же наступаешь на все грабли, если невнимательный.

  3. RawSQL — хитрая жопа. Это когда тебе в целом QuerySet норм, но вот одну колонку нужно посчитать какой-то ебаной формулой, которую ORM в жизни не сгенерит. Встраиваем кусочек SQL прямо в аннотацию.

    from django.db.models.expressions import RawSQL
    
    queryset = Person.objects.annotate(
        custom_field=RawSQL("some_column - %s", (param,)) # И опять параметры! Видишь закономерность?
    )

    Точечный укол SQL'ем прямо в тело ORM. Изящно, но опасно, если перестараться.

А теперь, блядь, самое важное — почему это всё надо делать с оглядкой?

  • SQL-инъекции — это пиздец. Я не шучу. Если ты будешь склеивать строки типа f"WHERE name = '{user_input}'", то тебя выебут, обворуют и сольют базу в паблик быстрее, чем ты скажешь "в рот меня чих-пых". Всегда, блядь, ВСЕГДА используй параметризованные запросы (%s). ORM за тебя это делает, а тут ты сам.
  • Переносимость накрылась медным тазом. Написал кучу сырого SQL под PostgreSQL? Готовься к боли, если завтра начальство скажет переезжать на MySQL. ORM — это абстракция, она защищает от этого дерьма. Сырой SQL — это прямой билет в ад конкретной СУБД.
  • Поддержка превращается в пытку. Миграции могут сломаться, потому что ORM не знает про твои левые таблицы или колонки в сыром запросе. Отладка усложняется в три раза.

Так что, золотое правило, ёпта: Сначала выеби мозг документации Джанги. Посмотри на F(), Q(), Window, Subquery — может, ORM таки сможет то, что тебе нужно, просто ты не знал как спросить. К сырому SQL иди только тогда, когда все остальные пути отрезаны, и ты точно знаешь, что делаешь. А иначе — сам виноват будет.