与管理器相关的 Django 重复查询

Posted

技术标签:

【中文标题】与管理器相关的 Django 重复查询【英文标题】:Django duplicate queries with manager related 【发布时间】:2018-10-14 22:29:44 【问题描述】:

我正在优化我的 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 个 id 中的 3 个,该查询也再次重复,这导致了这 13 个重复的查询。我该怎么做才能不重复这些查询?似乎 prefetch_related 在这里不再有帮助了。

【问题讨论】:

你试过.select_related()而不是.prefetch_related()吗? c2app2_set 放入list_select_related?是的,我做错了,它不起作用,因为c2app2 不是c1app1 的外键。恰恰相反。 【参考方案1】:

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

【讨论】:

以上是关于与管理器相关的 Django 重复查询的主要内容,如果未能解决你的问题,请参考以下文章

Django Queryset:按相关项目管理器过滤

在已过滤的查询集上使用 Django 自定义管理器功能

django_上下文管理器

django ORM中的RelatedManager(关联管理器)

django 模型-----模型成员

Django中模型