Странное поведение в объединении значений набора запросов Django

#django #django-models #django-1.10

#django #django-модели #django-1.10

Вопрос:

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

Вот упрощенная версия структуры моей модели:

 class Answer(models.Model):
    person = models.ForeignKey(Person)
    points = models.PositiveIntegerField(default=100)
    correct = models.BooleanField(default=False)

class Person(models.Model):
    # irrelevant model fields
  

Пример набора данных:

 Person | Answer.Points
------ | ------
3      | 50
3      | 100
2      | 100
2      | 90

Person 4 has no answers and therefore, points
  

С помощью приведенного ниже запроса я могу получить сумму баллов для каждого человека:

 people_with_points = Person.objects.
        filter(answer__correct=True).
        annotate(points=Sum('answer__points')).
        values('pk', 'points')

<QuerySet [{'pk': 2, 'points': 190}, {'pk': 3, 'points': 150}]>
  

Но, поскольку у некоторых людей может не быть связанных Answer записей, у них будет 0 баллов, и с помощью приведенного ниже запроса я использую Coalesce для «подделки» их баллов, вот так:

 people_without_points = Person.objects.
        exclude(pk__in=people_with_points.values_list('pk')).
        annotate(points=Coalesce(Sum('answer__points'), 0)).
        values('pk', 'points')

<QuerySet [{'pk': 4, 'points': 0}]>
  

Оба они работают по назначению, но я хочу, чтобы они были в одном наборе запросов, поэтому я использую оператор объединения | для их объединения:

 everyone = people_with_points | people_without_points
  

Теперь о проблеме:

После этого у людей без очков их points значение превращается в None вместо 0.

 <QuerySet [{'pk': 2, 'points': 190}, {'pk': 3, 'points': 150}, {'pk': 4, 'points': None}]>
  

Кто-нибудь имеет представление о том, почему это происходит?

Спасибо!

Ответ №1:

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

 everyone.
    annotate(real_points=Concat(Coalesce(F('points'), 0), Value(''))).
    values('pk', 'real_points')

<QuerySet [{'pk': 2, 'real_points': 190}, {'pk': 3, 'real_points': 150}, {'pk': 4, 'real_points': 0}]>
  

Но я хочу понять, почему объединение не работает так, как я ожидал в моем первоначальном вопросе.

РЕДАКТИРОВАТЬ: я думаю, что я понял. Друг поручил мне использовать django-debug-toolbar для проверки моих SQL-запросов для дальнейшего изучения этой ситуации, и я обнаружил следующее:

Поскольку это объединение двух запросов, аннотация второго запроса почему-то не учитывается, и COALESCE значение 0 не используется. Перемещая это в первый запрос, оно распространяется на второй запрос, и я мог бы достичь ожидаемого результата.

В принципе, я изменил следующее:

 # Moved the "Coalesce" to the initial query
people_with_points = Person.objects.
    filter(answer__correct=True).
    annotate(points=Coalesce(Sum('answer__points'), 0)).
    values('pk', 'points')

# Second query does not have it anymore
people_without_points = Person.objects.
    exclude(pk__in=people_with_points.values_list('pk')).
    values('pk', 'points')

# We will have the values with 0 here!
everyone = people_with_points | people_without_points