Django聚合查询

时间:2012-02-16 01:20:10

标签: sql django django-models

我有一个模型Page,上面可以有Post个。我想要做的是获取每个Page,以及该页面上的最新Post。如果Page没有Post,我仍然需要该页面。 (听起来很熟悉?这是SQL中的LEFT JOIN

以下是我目前的情况:

Page.objects.annotate(most_recent_post=Max('post__post_time'))

这只会获得Page次,但不会获得Post次。我怎样才能得到Post

型号:

class Page(models.Model):
    name = models.CharField(max_length=50)
    created = models.DateTimeField(auto_now_add = True)
    enabled = models.BooleanField(default = True)

class Post(models.Model):
    user = models.ForeignKey(User)
    page = models.ForeignKey(Page)
    post_time = models.DateTimeField(auto_now_add = True)

3 个答案:

答案 0 :(得分:2)

根据两者之间的关系,您应该能够轻松follow the relationships,并使用select_related提高效果

考虑到这一点:

class Page(models.Model):
    ...

class Post(models.Model):
    page = ForeignKey(Page, ...)

您可以使用posts有效地关注转发关系(即获取所有select_related及其相关网页):

Post.objects.select_related('page').all()

这将导致只有一个(更大的)查询,其中预取了所有页面对象。

反向情况下(与您一样),您想要获取所有pages及其关联的postsselect_related将无效。有关您可以执行的操作的详细信息,请参阅thisthisthis问题。

答案 1 :(得分:1)

可能您最好的选择是使用django文档中描述的技术:Following Links Backward

完成后:

pages = Page.objects.annotate(most_recent_post=Max('post__post_time'))
posts = [page.post_set.filter(post_time=page.most_recent_post) for page in pages]

然后帖子[0]应该有最新的帖子[0]等。我不知道这是否是最有效的解决方案,但这是另一篇文章中提到的关于缺少左连接的解决方案在django。

答案 2 :(得分:1)

您可以创建一个database view,其中包含所有Page列以及必要的最新Post列:

CREATE VIEW `testapp_pagewithrecentpost` AS
    SELECT testapp_page.*, testapp_post.*  -- I suggest as few post columns as possible here
    FROM `testapp_page` LEFT JOIN `testapp_page` 
    ON test_page.id = test_post.page_id 
    AND test_post.post_time = 
        ( SELECT MAX(test_post.post_time) 
          FROM test_post WHERE test_page.id = test_post.page_id );

然后,您需要创建一个标记为managed = False的模型(以便manage.py sync不会中断)。您还可以使用inheritance from abstract Model来避免列重复:

class PageWithRecentPost(models.Model):   # Or extend abstract BasePost ?
    # Page columns goes here
    # Post columns goes here
    # We use LEFT JOIN, so all columns from the 
    # 'post' model will need blank=True, null=True 

    class Meta:
        managed = False  # Django will not handle creation/reset automatically

通过这样做,您可以执行您最初想要的操作,因此只需一个查询从两个表中获取:

pages_with_recent_post = PageWithRecentPost.objects.filter(...)
for page in pages_with_recent_post:
    print page.name        # Page column
    print page.post_time   # Post column

然而,这种方法并非没有缺点:

  • 这是特定于数据库引擎的
  • 您需要添加VIEW创建SQL to your project
  • 如果你的模型很复杂,你很可能需要解决表格列名冲突。
  • 基于数据库视图的模型很可能是只读的(INSERT / UPDATE将失败)。
  • 它增加了项目的复杂性。允许多个查询是一个非常简单的解决方案。
  • Page/Post中的更改需要重新创建视图。