2012-01-12 62 views
7

我在Django中建立了一个复杂的数据库模型,我必须根据过滤器数据做一些计算。我有一个Test对象,一个TestAttempt对象和一个UserProfile对象(外键返回测试,外键返回给用户配置文件)。我在TestAttempt上运行了一个方法,该方法计算测试分数(根据用户提供的选项数量与与每个测试相关的正确答案进行比较)。然后我在Test上运行另一种方法,该方法根据每个关联的TestAttempt的平均测试分数计算平均测试分数。但有时我只想要基于与特定集关联的关联TestAttempt的供应子集的平均值的UserProfiles。因此,不要像这样计算特定测试的平均测试分数:查询查询的Django __in查询效率

[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()] 

其中user_id_list为用户配置ID对,我想找到一个列表的形式平均测试成绩的特定子集。我的问题是:如果user_id_list确实是整套UserProfile的(因此过滤器将返回与self.test_attempts.all()相同的值),并且大多数情况下都是这样,是否支付检查此案件的费用,如果是这样根本不执行过滤器?或者__in查找效率足够高,即使user_id_list包含所有用户,运行过滤器效率也会更高。另外,我是否需要担心产生test_attempts distinct()?或者他们不可能用我的queryset的结构变成重复的?

编辑:任何人谁的兴趣看原始的SQL查询,它看起来像这样不使用滤镜:

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 

,这与过滤器:

SELECT "mc_grades_testattempt"."id", "mc_grades_testattempt"."date", 
"mc_grades_testattempt"."test_id", "mc_grades_testattempt"."student_id" FROM 
"mc_grades_testattempt" INNER JOIN "mc_grades_userprofile" ON 
("mc_grades_testattempt"."student_id" = "mc_grades_userprofile"."id") WHERE 
("mc_grades_testattempt"."test_id" = 1 AND "mc_grades_userprofile"."user_id" IN (1, 2, 3)) 

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

+0

这两种情况下生成的SQL是什么? – 2012-01-12 03:28:17

+0

不知道,我将如何输出特定查询集的SQL?编辑,找出它。给我一点时间找到它 – ecbtln 2012-01-12 03:33:15

+0

SQL查询已被添加 – ecbtln 2012-01-12 03:41:16

回答

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

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

2

使用Annotation代替遍历查询集,这是建立在user_id_list每一项新的数据库命中和蟒蛇进行平均。

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

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