2009-10-29 79 views
5

优化查询我有以下的模型结构:Django的ORM:涉及到许多一对多的关系

class Container(models.Model): 
    pass 

class Generic(models.Model): 
    name = models.CharacterField(unique=True) 
    cont = models.ManyToManyField(Container, null=True) 
    # It is possible to have a Generic object not associated with any container, 
    # thats why null=True 

class Specific1(Generic): 
    ... 

class Specific2(Generic): 
    ... 

... 

class SpecificN(Generic): 
    ... 

说,我需要检索所有Specific型车型,具有与特定容器的关系。

对于这个问题的SQL或多或少是微不足道的,但这不是问题。不幸的是,我在处理ORM(尤其是Django的ORM)方面经验不足,所以我可能会在这里错过一个模式。

当一个强力的方式进行, -

c = Container.objects.get(name='somename') # this gets me the container 
items = c.generic_set.all() 
# this gets me all Generic objects, that are related to the container 
# Now what? I need to get to the actual Specific objects, so I need to somehow 
# get the type of the underlying Specific object and get it 
for item in items: 
    spec = getattr(item, item.get_my_specific_type()) 

这导致一吨分贝命中(每个通用的记录,这涉及到一个容器),所以这显然是没有办法的办法去做吧。现在,它可能,也许,可以通过获取SpecificX直接做对象:

s = Specific1.objects.filter(cont__name='somename') 
# This gets me all Specific1 objects for the specified container 
... 
# do it for every Specific type 

这样的数据库将再次为每个特定类型(可以接受的,我猜)被击中。

我知道,.select_related()不适用于m2m关系,因此在这里没有多大帮助。

重申,最终结果必须是SpecificX对象的集合(非泛型)。

+0

在反思,这个问题似乎有点毫无意义的我,现在的我已经提供了唯一可能的答案。毕竟,没有办法从任意字段的许多表中获得联合结果集。好吧,显然,这是一种方式,但它很丑,涉及动态sql和/或“select *”。我认为。 – shylent 2009-10-29 12:39:47

+0

您的问题在这里的确与优化ManyToMany关系无关,并且与优化多表继承的查询无关。这确实是一个难题。 – 2009-10-29 14:30:22

+0

现在我想起来了,让我相信这个问题是关于M2M关系的,事实上,select_related并不会遍历多对多的关系。 – shylent 2009-10-29 19:08:46

回答

2

我想你已经概述了两种简单的可能性。您可以对Generic执行单个过滤器查询,然后将每个项目转换为其特定的子类型(结果为n + 1个查询,其中n是返回的项目数量),或者对每个特定的表格进行单独的查询(结果k查询,其中k是特定类型的数量)。

它实际上是值得基准,看看其中哪些在现实中更快。第二种似乎更好,因为它可能(更少)查询,但每个查询都必须使用m2m中间表执行连接。在前一种情况下,您只能执行一个连接查询,然后执行许多简单连接查询。一些数据库后端在大量小型查询中执行得更好,而不是更少,更复杂。

如果第二个实际上对于您的用例来说确实快得多,并且您愿意做一些额外的工作来清理代码,那么应该可以为Generic模型编写一个自定义管理器方法,从给定查询集的相关特定表中提取所有子类型数据,每个子类型表只使用一个查询;类似于this snippet通过批量预取来优化通用外键。这将为您提供与第二个选项相同的查询,DRYer语法是您的第一个选项。

+0

不知道谁降低了这一点。是的,我会研究编写自定义管理器的可能性,但是,正如我所说的,我对ORM的经验非常有限(尽管我没有SQL问题),所以内部仍然有点给我一个黑匣子。无论如何,我会去看看我能做些什么。 – shylent 2009-10-29 15:18:24

1

不是一个完整的答案,但你可以这样做

items= list(items) 
for item in items: 
    spec = getattr(item, item.get_my_specific_type()) 

,而不是该回避命中了大量的:

for item in items: 
    spec = getattr(item, item.get_my_specific_type()) 

事实上,通过强制强制转换为一个Python列表,你强制django orm加载你的查询集中的所有元素。然后它在一个查询中执行此操作。

+0

这是一个很好的提示(在文档中提到,我已阅读:)。也不太明显。 – shylent 2009-10-29 12:31:07

+3

嗯,这是不正确的。在这种情况下投射到列表中没有任何区别。在这两个版本中,只有一个查询是针对通用表完成的,并且在两个版本中,针对每个项目的SpecificX表执行一个查询。两者的查询数量相同。 – 2009-10-29 14:28:28