Подсчет полей "многие ко многим" возвращает неверное значение (Django)
У меня есть класс модели Student:
class Student(models.Model):
...
и класс модели Course:
class Course(models.Model)
students = models.ManyToManyField(Student)
Теперь я хочу отфильтровать Course на основе количества Students, связанных с курсом. Я пробовал:
Course.objects.annotate(student_count = Count('students'))
Но по какой-то причине student_count всегда возвращает единицу.
Допустим, я создаю курс и добавляю в него двух студентов:
s1 = Student.objects.create()
s2 = Student.objects.create()
m1 = Course.objects.create()
m1.students.add(s1)
m1.students.add(s2)
print(Student.objects.all().first().students.count())
print(Student.objects.annotate(student_count = Count('students')).first().student_count
Prints
2
1
Почему эти два значения разные? Как я могу отфильтровать курсы на основе количества students?
Я протестировал ваш сценарий и результат одинаков для двух подходов:
class SOTestCase(TestCase):
def setUp(self):
s1 = Student.objects.create()
s2 = Student.objects.create()
m1 = Course.objects.create()
m1.students.add(s1)
m1.students.add(s2)
@override_settings(DEBUG=True)
def test_query(self):
c1 = Course.objects.all().first()
c2 = Course.objects.annotate(student_count = Count('students')).first()
n1 = c1.students.count()
n2 = c2.student_count
self.assertEqual(n1, 2)
self.assertEqual(n2, 2)
Результат в порядке:
Ran 1 test in 0.017s
OK
Возможно, у вас есть ордер? Попробуйте удалить ordering:
c1 = (
Course
.objects
.order_by() #<-- this one
.first())
c2 = (
Course
.objects
.order_by() #<-- this one
.annotate(student_count = Count('students'))
.first())
Ваша проблема связана с аннотационной частью. Каждый раз, когда вы добавляете аннотационную часть в ваш набор запросов, вы также добавляете group_by в ваш запрос, и если вы не добавляете определенную group_by в ваш набор запросов, он автоматически добавляет ее за вас (т.е. group_by "id"). Тогда ваши результаты станут меньше, и он удалит дубликаты из результатов запроса (из-за group_by). Например, посмотрите на эти наборы запросов:
1- Course.objects.all()[3].students.count()
2- Course.objects.annotate(student_count=Count("students"))[3].student_count
Хотя я пытался получить третий результат из обоих моих наборов запросов, конечный результат отличается, потому что длина этих двух наборов запросов разная (Course.objects.all() и Course.objects.annotate(student_count=Count("students")). Итак, если вы посмотрите на эти запросы, которые связаны с нашими наборами запросов:
1- {'sql': 'SELECT *, COUNT("students"."id") AS "student_count" FROM "course" LEFT OUTER JOIN "students" ON ("course"."id" = "students"."course_id") GROUP BY "course"."id" ORDER BY "course"."id" ASC LIMIT 1 OFFSET 3'}
2- {'sql': 'SELECT COUNT(*) AS "__count" FROM "students" WHERE "students"."course_id" = 4'}
вы можете увидеть группировку по части во втором запросе. Также обратите внимание, что для получения правильного результата следует использовать второй запрос (за исключением некоторых ситуаций, когда вы знаете точный индекс желаемого результата в первом наборе запросов). Иначе всегда есть ситуации, когда вы можете получить разные или неправильные результаты, потому что ваша первая или последняя запись может измениться в аннотациях.