【问题标题】:django orm - How to use select_related() on the Foreign Key of a Subclass from its Super Classdjango orm - 如何在其超类的子类的外键上使用 select_related()
【发布时间】:2012-06-13 18:09:40
【问题描述】:

我一直发现 Django orm 对子类模型的处理非常出色。这可能就是我遇到类似问题的原因。

取三个模型:

class A(models.Model):
    field1 = models.CharField(max_length=255)

class B(A):
    fk_field = models.ForeignKey('C')

class C(models.Model):
    field2 = models.CharField(max_length=255)

现在您可以查询A 模型并获取所有B 模型(如果可用):

the_as = A.objects.all()
for a in the_as:
    print a.b.fk_field.field2 #Note that this throws an error if there is no B record

问题在于您正在查看大量数据库调用来检索所有数据。

现在假设您想要检索数据库中所有 A 模型的 QuerySet,但同时包含所有子类记录和子类的外键记录,使用 select_related() 将您的应用程序限制为单个数据库调用.你会写一个这样的查询:

the_as = A.objects.select_related("b", "b__fk_field").all()

一个查询返回所有需要的数据!太棒了。

除非没有。因为这个版本的查询在做自己的过滤,即使select_related 根本不应该过滤任何结果:

set_1 = A.objects.select_related("b", "b__fk_field").all() #Only returns A objects with associated B objects
set_2 = A.objects.all() #Returns all A objects
len(set_1) > len(set_2) #Will always be False

我使用 django-debug-toolbar 检查查询并发现问题。生成的 SQL 查询使用INNER JOINC 表加入查询,而不是像其他子类字段一样使用LEFT OUTER JOIN

SELECT "app_a"."field1", "app_b"."fk_field_id", "app_c"."field2"
FROM "app_a" 
    LEFT OUTER JOIN "app_b" ON ("app_a"."id" = "app_b"."a_ptr_id") 
    INNER JOIN "app_c" ON ("app_b"."fk_field_id" = "app_c"."id");

似乎如果我只是将INNER JOIN 更改为LEFT OUTER JOIN,那么我会得到我想要的记录,但这在使用 Django 的 ORM 时对我没有帮助。

这是 Django 的 ORM 中 select_related() 中的错误吗?有什么解决方法,还是我只需要直接查询数据库并自己映射结果?我应该使用 Django-Polymorphic 之类的东西来做到这一点吗?

【问题讨论】:

    标签: django inheritance orm


    【解决方案1】:

    它看起来像一个错误,特别是它似乎忽略了 A->B 关系的可为空性质,例如,如果您在 A 中有一个对 B 的外键引用而不是子类化,那么该外键当然会可以为空,django 会使用左连接。您可能应该在 django 问题跟踪器中提出这个问题。您也可以尝试使用 prefetch_related 而不是 select_related 可能会解决您的问题。

    【讨论】:

      【解决方案2】:

      我找到了解决这个问题的方法,但我会等待一段时间来接受它,希望我能得到更好的答案。

      select_related('b__fk_field') 创建的INNER JOIN 需要从基础 SQL 中删除,这样结果就不会被数据库中的 B 记录过滤。所以新查询需要将select_related中的b__fk_field参数留出:

      the_as = A.objects.select_related('b')
      

      然而,这迫使我们每次从A 对象访问C 对象时调用数据库。

      for a in the_as:
          #Note that this throws an DoesNotExist error if a doesn't have an
          #associated b
          print a.b.fk_field.field2 #Hits the database everytime.
      

      解决此问题的方法是通过一个查询从数据库中获取我们需要的所有C 对象,然后让每个B 对象手动引用它们。我们可以这样做,因为访问检索到的B 对象的数据库调用将具有引用其关联C 对象的fk_field_id

      c_ids = [a.b.fk_field_id for a in the_as] #Get all the C ids
      the_cs = C.objects.filter(pk__in=c_ids) #Run a query to get all of the needed C records
      for c in the_cs:
          for a in the_as:
              if a.b.fk_field_id == c.pk: #Throws DoesNotExist if no b associated with a
                  a.b.fk_field = c
                  break
      

      我确信有一种功能性的方法可以在没有嵌套循环的情况下编写它,但这说明了正在发生的事情。这并不理想,但它为所有数据提供了绝对最少的数据库命中次数——这正是我想要的。

      【讨论】:

      • 这仍然是一个悬而未决的问题吗?
      猜你喜欢
      • 2010-12-09
      • 2019-02-23
      • 2013-01-10
      • 1970-01-01
      • 2013-01-01
      • 2018-02-08
      • 2021-06-28
      • 1970-01-01
      • 2020-03-21
      相关资源
      最近更新 更多