Django与经理相关的重复查询

时间:2018-05-04 12:21:24

标签: django django-admin django-queryset prefetch

我正在努力优化我的ORM查询。我有两个应用程序,'app1'和'app2'。一类'app2'具有app1类的外键,如下所示:

#app1/models.py
class C1App1(WithDateAndOwner):
  def get_c2_app2(self):
    res = self.c2app2_set.all()
    if res.count() > 0:
      return res[0]
    else:
      return None

#app2/models.py
class C2App2(WithDateAndOwner):
  c1app1 = models.ForeignKey("app1.C1App1")
  is_ok = models.BooleanField(default=False)

现在,我在管理页面中显示所有C1App1实例的C2App2:

#app1/admin.py
@admin.register(C1App1)
class C1App1Admin(admin.MyAdmin):
  list_display = ("get_c2_app2")
  list_select_related = ()
  list_prefetch_related = ("c2app2_set",)
  list_per_page = 10

prefetch_related减少了此查询:

SELECT ••• FROM `app2_c2app2` WHERE `app2_c2app2`.`c1app1_id` = 711
  Duplicated 19 times.

为:

SELECT ••• FROM `app2_c2app2` WHERE `app2_c2app2`.`c1app1_id` IN (704, 705, 706, 707, 708, 709, 710, 711, 702, 703) ORDER BY `app2_c2app2`.`id` DESC

没关系。现在,如果我想在C2App2的属性'is_ok'上过滤查询:

#app1/models.py
class C1App1(WithDateAndOwner):
  def get_c2_app2(self):
    res = self.c2app2_set.filter(is_ok=False)
    if res.count() > 0:
      return res[0]
    else:
      return None

我仍然有这个预取的查询:

SELECT ••• FROM `c2app2_set` WHERE `app2_c2app2`.`c1app1_id` IN (704, 705, 706, 707, 708, 709, 710, 711, 702, 703) ORDER BY `app2_c2app2`.`id` DESC

但是对于每个显示的C1App1(10)实例都重复了一次。 :

SELECT ••• FROM `app2_c2app2` WHERE (`app2_c2app2`.`c1app1_id` = 711 AND `app2_c2app2`.`is_ok` = 1)
  Duplicated 13 times.

实际上,对于显示的10个中的3个ID,该查询也会再次重复,这导致这13个重复查询。为了不重复这些查询,我该怎么办?似乎prefetch_related在这里没有帮助。

1 个答案:

答案 0 :(得分:3)

prefetch_related仅在您使用.all()时有效。 如果您应用.filter()之类的任何其他转换,则将进行新的数据库查询。这是因为prefetch_related只是在列表中缓存所有相关实例,因此Django无法在列表中执行filter()。要解决您的问题,您应该使用Prefetch对象。 您可以将queryset参数传递给它。因此,不要使用list_prefetch_related,而是覆盖管理类中的get_queryset方法。

def get_queryset(*args, **kwargs):
     qs = super().get_queryset(*args, **kwargs)
     qs = qs.prefetch_related(Prefetch('c2app2_set', queryset=C2App2.objects.filter(is_ok=False)))
     return qs

并且

class C1App1(WithDateAndOwner):
   def get_c2_app2(self):
      res = self.c2app2_set.all()
      if res.count() > 0:
         return res[0]
      else:
         return None