django __in查询查询集的效率

时间:2012-01-12 01:03:09

标签: django django-models django-queryset

我在Django中设置了一个复杂的数据库模型,我必须根据过滤数据进行一些计算。我有一个Test对象,一个TestAttempt对象和一个UserProfile对象(外键返回测试,外键返回用户配置文件)。我在TestAttempt上运行一种方法来计算测试分数(基于用户提供的多个选项与每个测试相关的正确答案进行比较)。然后我在Test上运行的另一种方法,根据每个关联的TestAttempt来计算平均测试分数。但有时我只想要基于相关{{1}提供的子集的平均值。 1}}与一组特定的TestAttempt相关联。因此,不要以这种方式计算特定测试的平均测试分数:

UserProfiles

然后平均这些值。 我这样做一个查询:

[x.score() for x in self.test_attempts.all()]

其中[x.score() for x in self.test_attempts.filter(profile__id__in=user_id_list).all()] 是UserProfile id的特定子集,我希望以列表的形式找到平均测试分数。我的问题是这样的:如果user_id_list确实是user_id_list的整个集合(因此过滤器将返回与UserProfile相同的内容),并且大部分时间都是如此,是否需要检查这种情况,如果是这样,根本不执行过滤器?或__in查找是否足够有效,即使self.test_attempts.all()包含所有用户,运行过滤器也会更有效。另外,我是否需要担心生成的test_attempts distinct()?或者他们不可能用我的queryset结构重复复制?

编辑:对于有兴趣查看原始SQL查询的任何人,如果没有过滤器,它看起来像这样:

user_id_list

以及过滤器:

SELECT "mc_grades_testattempt"."id", "mc_grades_testattempt"."date", 
"mc_grades_testattempt"."test_id", "mc_grades_testattempt"."student_id" FROM 
"mc_grades_testattempt" WHERE "mc_grades_testattempt"."test_id" = 1

请注意,数组(1,2,3)只是一个例子

3 个答案:

答案 0 :(得分:2)

根据我对文档的理解,所有查询都是在实际使用之前构建的。因此,例如,test_attempts.all()生成SQL代码一次,当您执行查询时,实际通过执行.count()for t in test_attempts.all():等操作来获取数据,它会在数据库上运行查询如果使用get(),则返回Queryset对象或仅返回Object。考虑到这一点,对数据库的调用次数将完全相同,而实际调用将是不同的。正如您在编辑后的帖子中所显示的那样,原始查询是不同的,但在Django访问数据之前,它们都以相同的方式生成。从Django的角度来看,它们都将以相同的方式创建,然后在数据库上执行。在我看来,最好不要测试all()情况,因为你必须运行两个查询来确定。我相信您应该使用您拥有的代码运行并跳过检查all()方案,您将其描述为最常见的情况。大多数现代数据库引擎以这样的方式运行查询:添加的连接不会妨碍性能指标,因为它们无论如何都以最佳顺序处理查询。

答案 1 :(得分:2)

  1. 简短回答是 - 基准。在不同情况下测试并测量负载。这将是最好的答案。

  2. 这里不能有重复。

  3. 检查两个情况真的是个问题吗?这是hypotetic代码:

    def average_score(self, user_id_list=None):
        qset = self.test_attempts.all()
        if user_id_list is not None:
            qset = qset.filter(profile__id__in=user_id_list)
        scores = [x.score() for x in qset]
        # and compute the average
    
  4. 我不知道score方法做了什么,但你不能计算数据库级别的平均值吗?它会给你带来更多值得注意的性能提升。

  5. 不要忘记缓存。

答案 2 :(得分:2)

使用Annotation而不是循环查询集,这会为user_id_list中的每个项创建一个新的数据库命中,并在python中执行平均值。

ms = MyModel.objects.annotate(Avg('some_field'))
ms[0].avg__some_field # prints the average for that instance

将返回查询集,其中平均可用作查询集中对象的属性。使用ORM可能需要您对外键关系进行结构更改,哪个模型包含哪些数据以便使注释方便。如果有必要,这种重新排序将产生有益的副作用(数据喜欢以某种方式生活),所以这是一个很好的练习。