Как сделать обратный знак в запросе к базе данных 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']
В чем преимущество такого подхода? Получается более эффективный запрос, в котором нужно выполнить итерацию данных только один раз, что полезно, если у вас много строк.