Django ORM

Case/When в Django ORM | Курс Django ORM урок 3.5

Case/When в Django ORM | Курс Django ORM урок 3.5
Mikhail
Автор
Mikhail
Опубликовано 16.03.2026
0,0
Views 1

Цель урока

Разобрать Case и When, условные выражения в Django ORM, которые транслируются в SQL CASE WHEN. Научиться использовать их в аннотациях, обновлениях и сортировке.

Необходимые знания


Синтаксис

from django.db.models import Case, When, Value, IntegerField

Case(
    When(условие, then=значение),
    When(условие, then=значение),
    default=значение_по_умолчанию,
    output_field=IntegerField(),
)

Транслируется в:

CASE
    WHEN условие THEN значение
    WHEN условие THEN значение
    ELSE значение_по_умолчанию
END

Case/When в annotate()

Текстовые метки

from django.db.models import Case, When, Value, CharField

# Добавить метку наличия товара
products = Product.objects.annotate(
    availability=Case(
        When(stock=0, then=Value("out_of_stock")),
        When(stock__lte=5, then=Value("low_stock")),
        default=Value("in_stock"),
        output_field=CharField(),
    )
)

for p in products:
    print(f"{p.name}: {p.availability}")


SELECT shop_product.*,
       CASE
           WHEN stock = 0 THEN 'out_of_stock'
           WHEN stock <= 5 THEN 'low_stock'
           ELSE 'in_stock'
       END AS availability
FROM shop_product
ORDER BY name;

Числовые вычисления

from django.db.models import DecimalField
from decimal import Decimal

# Цена со скидкой в зависимости от категории
products = Product.objects.annotate(
    discounted_price=Case(
        When(category__slug="phones", then=F("price") * Decimal("0.9")),
        When(category__slug="laptops", then=F("price") * Decimal("0.85")),
        default=F("price"),
        output_field=DecimalField(max_digits=10, decimal_places=2),
    )
)


SELECT shop_product.*,
       CASE
           WHEN shop_category.slug = 'phones' THEN price * 0.9
           WHEN shop_category.slug = 'laptops' THEN price * 0.85
           ELSE price
       END AS discounted_price
FROM shop_product
LEFT OUTER JOIN shop_category ON shop_product.category_id = shop_category.id
ORDER BY name;

Числовые приоритеты для сортировки

from django.db.models import IntegerField

# Сортировка заказов: сначала processing, потом pending, потом остальные
orders = Order.objects.annotate(
    status_priority=Case(
        When(status="processing", then=Value(1)),
        When(status="pending", then=Value(2)),
        default=Value(3),
        output_field=IntegerField(),
    )
).order_by("status_priority", "-created_at")


SELECT shop_order.*,
       CASE
           WHEN status = 'processing' THEN 1
           WHEN status = 'pending' THEN 2
           ELSE 3
       END AS status_priority
FROM shop_order
ORDER BY status_priority ASC, created_at DESC;

Case/When в update()

# Обновить статус в зависимости от текущего значения
from django.db.models import F

Order.objects.update(
    status=Case(
        When(status="pending", then=Value("processing")),
        When(status="processing", then=Value("shipped")),
        default=F("status"),  # остальные не меняем
        output_field=CharField(max_length=20),
    )
)


UPDATE shop_order
SET status = CASE
    WHEN status = 'pending' THEN 'processing'
    WHEN status = 'processing' THEN 'shipped'
    ELSE status
END;

Один UPDATE для всей таблицы вместо нескольких отдельных запросов.


When с Q objects

When принимает как kwargs (простые условия), так и Q объекты для сложных:

from django.db.models import Q

Product.objects.annotate(
    price_tier=Case(
        When(Q(price__lt=100) | Q(stock=0), then=Value("budget_or_unavailable")),
        When(price__gte=100, price__lt=500, then=Value("mid_range")),
        When(price__gte=500, then=Value("premium")),
        default=Value("unknown"),
        output_field=CharField(),
    )
)


CASE
    WHEN (price < 100 OR stock = 0) THEN 'budget_or_unavailable'
    WHEN price >= 100 AND price < 500 THEN 'mid_range'
    WHEN price >= 500 THEN 'premium'
    ELSE 'unknown'
END

Условный Count

Комбинация Case/When с Sum, альтернативный способ условной агрегации (до появления filter=Q(...) в агрегатах):

from django.db.models import Sum, IntegerField

# Подсчет по категориям рейтинга
Product.objects.annotate(
    high_rating_count=Sum(
        Case(
            When(reviews__rating__gte=4, then=Value(1)),
            default=Value(0),
            output_field=IntegerField(),
        )
    ),
    low_rating_count=Sum(
        Case(
            When(reviews__rating__lte=2, then=Value(1)),
            default=Value(0),
            output_field=IntegerField(),
        )
    ),
)

Сейчас чище делать Count("reviews", filter=Q(reviews__rating__gte=4)) (урок 3.3). Case/When + Sum, более универсальный способ, работающий для любых вычислений, не только подсчета.


Практическое задание

  1. Аннотируйте продукты полем stock_status: "out" если stock=0, "low" если stock <= 10, "ok" иначе. Отфильтруй продукты со статусом "low".

  2. Напишите один update() который переводит заказы: "pending""processing" и "shipped""delivered" одним запросом SQL.

  3. Отсортируй продукты так: сначала те у которых stock=0, потом остальные по убыванию цены. Используйте annotate() с Case для приоритета сортировки.


Возможные ошибки

Не указан output_field

# Django не может вывести тип, будет ошибка
Product.objects.annotate(
    tier=Case(
        When(price__gt=500, then=Value("premium")), 
        default=Value("budget")
    )
)
# ValueError: Cannot resolve expression type

# Правильно
Product.objects.annotate(
    tier=Case(
        When(price__gt=500, then=Value("premium")),
        default=Value("budget"),
        output_field=CharField(),
    )
)

Порядок When важен, первое совпадение выигрывает

# Неверно, stock=0 попадет в первое условие (stock__lte=5), не дойдет до второго
Case(
    When(stock__lte=5, then=Value("low")),
    When(stock=0, then=Value("out")),  # никогда не выполнится
    default=Value("ok"),
)

# Правильно более специфичное условие первым
Case(
    When(stock=0, then=Value("out")),
    When(stock__lte=5, then=Value("low")),
    default=Value("ok"),
)

Связь со следующим уроком

В уроке 3.6 разберем оконные функции: Window, Rank, RowNumber, Lead, Lag. Это наиболее мощный инструмент для аналитических запросов: ранжирование, накопительные суммы, сравнение строки с соседними.


<< Урок 3.4

Урок 3.6 >>


Подписывайтесь на мой Telegram канал

Если вам нужен ментор и вы хотите научиться веб-разработке, узнать подробнее

Авторизуйтесь, чтобы оставить комментарий.

Комментариев: 0

Нет комментариев.

Тут может быть ваша реклама

Пишите info@aisferaic.ru

Похожие туториалы