2012-02-16 276 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中的更改將需要重新創建視圖。