2012-02-16 334 views
1

我有一个模型Page,它可以有Post s。我想要做的是获得每个Page,再加上该页面上最近的Post。如果Page没有Post s,我仍然需要该页面。 (听起来很熟悉?这是SQL中的一个LEFT JOIN)。Django聚合查询

这是我目前有:

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

这只是变得Page S,但它不会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) 
+0

'Post'与'Page'是什么关系?为了清晰起见 – dm03514 2012-02-16 01:26:06

+0

添加的模特 – babonk 2012-02-16 01:37:40

回答

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问题。

+0

我只想得到最新的帖子。 'page.post_set.all'似乎拥有所有这些,而不仅仅是那一个。我如何获得所需的一个? – babonk 2012-02-16 06:49:00

+0

'page.post_set.all()。latest()'在视图中或'page.post_set.all.latest'在模板中。您需要在模型的Meta类中指定一个'get_latest_by'字段:https://docs.djangoproject.com/en/dev/ref/models/querysets/#latest – 2012-02-16 11:02:25

+0

如果您使用该方法,甚至得到'most_recent_post = Max('post__post_time')'的意义何在?你可以从'post_set.all.latest.post_time'得到它,不是吗? – babonk 2012-02-16 19:48:04

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中缺少左连接的解决方案。

+0

这工作,但如果有一种方法可以用更少的查询来做到这一点,将不胜感激。 – babonk 2012-02-16 06:54:22

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不会打破)的模型。在短短的一个查询

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 

通过这样做,你可以做你最初想要的,所以取两个表:您还可以使用inheritance from abstract Model避免列重复

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 

但是这种做法是不缺点免费:

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