Django ORM:加入QuerySets

时间:2012-03-11 16:32:42

标签: python django orm django-models

我试图将Django ORM用于SQL中需要JOIN的任务。一世 已经有一个解决方法,可以通过多个查询完成相同的任务 和一些非DB处理,但我对运行时复杂性不满意。

首先,我想简要介绍一下我的相关部分 模型。在那之后,我将用英语,SQL和(低效)Django ORM解释任务。

模型

在我的CMS模型中,帖子是多语言的:对于每个帖子和每种语言,可以有一个帖子内容的实例。此外,在编辑帖子时,我不是UPDATE,而是INSERT新版本。

因此,PostContentpostlanguageversion上是唯一的。这是班级:

class PostContent(models.Model):
    """ contains all versions of a post, in all languages. """
    language = models.ForeignKey(Language)
    post = models.ForeignKey(Post)           # the Post object itself only
    version = models.IntegerField(default=0) # contains slug and id.
    # further metadata and content left out

    class Meta:
        unique_together = (("resource", "language", "version"),)

SQL中的任务

这就是任务:我希望使用ORM获取每种语言中所有帖子的最新版本列表。在SQL中,这会转换为{在JOINGROUP BY的子查询上{1}},以获得MAXversion的每对唯一对的resource的最大值。这个问题的完美答案是许多ORM调用产生以下SQL语句:

language

Django中的解决方案

我目前使用Django ORM的解决方案不会产生这样的JOIN,而是两个单独的SQL 查询,其中一个查询可能变得非常大。我首先执行子查询(上面的内部SELECT id, post_id, version, v FROM cms_postcontent, (SELECT post_id as p, max(version) as v, language_id as l FROM cms_postcontent GROUP BY post_id, language_id ) as maxv WHERE post_id=p AND version=v AND language_id=l; ):

SELECT

现在,我没有加入maxv = PostContent.objects.values('post','language').annotate( max_version=Max('version')) ,而是明确要求maxv中的每一个帖子 为maxv的每个元组筛选PostContent.objects.all()。生成的SQL看起来像

post, language, max_version

在Django中:

SELECT * FROM PostContent WHERE 
       post=P1 and language=L1 and version=V1 
    OR post=P2 and language=L2 and version=V2
    OR ...;

如果from django.db.models import Q conjunc = map(lambda pc: Q(version=pc['max_version']).__and__( Q(post=pc['post']).__and__( Q(language=pc['language']))), maxv) result = PostContent.objects.filter( reduce(lambda disjunc, x: disjunc.__or__(x), conjunc[1:], conjunc[0])) 足够小,例如在检索单个帖子时,这可能是 一个很好的解决方案,但查询的大小和创建它的时间线性增长 帖子数量。解析查询的复杂性至少也是线性的。

除了使用原始SQL之外,还有更好的方法吗?

1 个答案:

答案 0 :(得分:0)

只要查询集查询相同的模型,您就可以使用|运算符加入(在联合意义上)查询集。

然而,听起来你想要像PostContent.objects.order_by('version').distinct('language')这样的东西;由于您在1.3.1中无法做到这一点,因此请考虑将valuesdistinct()结合使用以获得所需的效果。