4

我一直发现 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 JOINLEFT OUTER JOIN,那么我会得到我想要的记录,但这在使用 Django 的 ORM 时对我没有帮助。

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

4

2 回答 2

2

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

于 2012-12-11T16:47:59.980 回答
0

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

需要从基础 SQL 中删除INNER JOIN创建的,以便结果不会被数据库中的记录过滤。所以新的查询需要把参数留在外面:select_related('b__fk_field')Bb__fk_fieldselect_related

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

但是,这迫使我们每次C从对象访问对象时都调用数据库A

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检索到的对象的数据库调用将具有fk_field_id引用其关联C对象的 :

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

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

于 2012-06-14T19:05:12.860 回答