Как сделать обратный знак в запросе к базе данных django в совпадающих строках?

У меня есть модель Django, которая выглядит следующим образом:

direction_choices = (
    ("up", "Up"),
    ("down", "Down"),
    ("left", "Left"),
    ("right", "Right"),
)

class Movement:
    direction = models.CharField(max_length=5, choices=direction_choices)
    distance = models.PositiveSmallIntegerField()

Допустим, данные в моей базе данных выглядят следующим образом:

Movement(direction="up", 4)
Movement(direction="up", 6)
Movement(direction="down", 3)
Movement(direction="down", 1)
Movement(direction="left", 7)
Movement(direction="right", 4)
Movement(direction="left", 1)
Movement(direction="right", 8)

Я хочу сделать запрос к базе данных, который будет вычислять общее расстояние, пройденное по оси Y.

В нашем примере это будет: 4+6-3-1 = 6

Я могу сделать что-то вроде:

Movement.objects.all().filter(
    direction__in=("up", "down")
    ).aggregate(
        total=Sum('distance')
    )

что дает мне:

{'total': 16}

Я не уверен, как сообщить базе данных, что поля "вниз" должны быть умножены на -1, чтобы их знаки были обратными перед суммированием итогов.

Я знаю, что это легко сделать в Python с помощью цикла for или понимания списка. Как можно заставить базу данных делать это?

Вы можете агрегировать с:

from django.db.models import Q

Movement.objects.filter(
    direction__in=('up', 'down')
).aggregate(
    total=Sum('distance', filter=Q(direction='up'))
        - Sum('distance', filter=Q(direction='down'))
)

или вы можете работать через аннотацию:

from django.db.models import Case, F, When

Movement.objects.filter(
    direction__in=('up', 'down')
).alias(
    dir=Case(
        When(direction='down', then=-F('distance')),
        default=F('distance')
    )
).aggregate(
    total=Sum('dir')
)

Я бы сделал это, используя группировку по, чтобы суммировать только в пределах каждого направления, а затем выполнил окончательное вычитание в Python:

result = Movement.objects.values('direction').order_by('direction').annotate(total_distance=Sum('distance'))
# Transform to dict
result = {row['direction']: row['total_distance'] for row in result}
y_axis = result['up'] - result['down']
x_axis = result['right'] - result['left']

В чем преимущество такого подхода? Получается более эффективный запрос, в котором нужно выполнить итерацию данных только один раз, что полезно, если у вас много строк.

Вернуться на верх