计数多对多字段返回错误值(Django)

ohfgkhjo  于 2023-07-01  发布在  Go
关注(0)|答案(3)|浏览(92)

我有一个模型类Student

class Student(models.Model):
   ...

模型类Course

class Course(models.Model)
   students = models.ManyToManyField(Student)

现在我想根据与课程相关的Students的数量过滤Course。我试过:

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(Course.objects.all().first().students.count())
print(Course.objects.annotate(student_count = Count('students')).first().student_count

指纹

2
1

为什么这两种价值观不同?如何根据Students的数量筛选课程?

amrnrhlw

amrnrhlw1#

我测试了你的场景,结果是相同的两种方法:

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())
8wigbo56

8wigbo562#

您的问题来自注解零件。每次你添加注解部分到你的查询集,你也添加一个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'}

您可以在第二个查询中看到按部件分组。还要注意的是,你应该使用第二个来获得正确的结果(除了某些情况下,你知道你想要的结果在第一个查询集中的确切索引)。否则,总是会出现这样的情况,您可能会得到不同或错误的结果,因为您的第一个或最后一个记录可能会在注解中发生更改。

yzxexxkh

yzxexxkh3#

作为一个客人,我不能对此(或评论)投赞成票,但这是我(和其他人)遇到的一个真实的的问题。这里的答案都没有解释这个行为,但是下面有一个类似的bug:
Django query annotate after filter doesn't work correctly
注意这个问题不是上面的重复。在本例中,没有调用.filter(),因此直接在管理器上调用.annotate()。这看起来是正确的,所以我不清楚为什么Count()总是报告1。
也许Student模型中的某些东西(未显示)会导致问题,例如第一个答案中建议的默认顺序。

相关问题