2011-04-12 194 views
1

我有郵政和標籤型號:Django的:優化多對多查詢

class Tag(models.Model): 
    """ Tag for blog entry """ 
    title   = models.CharField(max_length=255, unique=True) 

class Post(models.Model): 
    """ Blog entry """ 
    tags   = models.ManyToManyField(Tag) 
    title   = models.CharField(max_length=255) 
    text   = models.TextField() 

我需要輸出博客條目列表和一組對每個崗位的標籤。我希望能夠與剛剛兩個查詢要做到這一點,利用這種工作流程:

  1. 獲取帖子列表
  2. 獲取在這些職位中使用的標籤列表
  3. 鏈接標籤蟒蛇的職位
我有最後一個步驟的麻煩

,這裏是我想出的代碼,但給了我'Tag' object has no attribute 'post__id'

#getting posts 
posts = Post.objects.filter(published=True).order_by('-added')[:20] 
#making a disc, like {5:<post>} 
post_list = dict([(obj.id, obj) for obj in posts]) 
#gathering ids to list 
id_list = [obj.id for obj in posts] 

#tags used in given posts 
objects = Tag.objects.select_related('post').filter(post__id__in=id_list) 
relation_dict = {} 
for obj in objects: 
    #Here I get: 'Tag' object has no attribute 'post__id' 
    relation_dict.setdefault(obj.post__id, []).append(obj) 

for id, related_items in relation_dict.items(): 
    post_list[id].tags = related_items 

你能看到一個錯誤嗎?我如何使用django ORM來解決這個任務,或者我將不得不編寫一個自定義SQL?

編輯:

我能夠與原始查詢來解決這個問題:

objects = Tag.objects.raw(""" 
    SELECT 
     bpt.post_id, 
     t.* 
    FROM 
     blogs_post_tags AS bpt, 
     blogs_tag AS t 
    WHERE 
     bpt.post_id IN (""" + ','.join(id_list) + """) 
     AND t.id = bpt.tag_id 
""") 
relation_dict = {} 
for obj in objects: 
    relation_dict.setdefault(obj.post_id, []).append(obj) 

我會greatfull的人誰將指向如何避免它。

回答

4

這裏就是我通常在這種情況下做的:

posts = Post.objects.filter(...)[:20] 

post_id_map = {} 
for post in posts: 
    post_id_map[post.id] = post 
    # Iteration causes the queryset to be evaluated and cached. 
    # We can therefore annotate instances, e.g. with a custom `tag_list`. 
    # Note: Don't assign to `tags`, because that would result in an update. 
    post.tag_list = [] 

# We'll now need all relations between Post and Tag. 
# The auto-generated model that contains this data is `Post.tags.through`. 
for t in Post.tags.through.select_related('tag').filter(post_id__in=post): 
    post_id_map[t.post_id].tag_list.append(t.tag) 

# Now you can iterate over `posts` again and use `tag_list` instead of `tags`. 

這將是更好,如果這種模式在某種程度上封裝,所以你可能想補充一點,它會爲你一個QuerySet方法(例如select_tags())。

+0

Daniel Roseman在他的django高效項目中封裝了它:https://github.com/danielroseman/django-efficient – 2011-04-17 23:32:59

+2

Django 1.2中沒有爲我工作。需要做到這一點:'Post.tags.through.objects.select_related('tag')...' – millerdev 2011-05-20 14:57:00

+0

很好,先生! – rafek 2012-06-24 04:23:27

1

如果你必須有它,它在兩個查詢,我認爲你需要自定義的SQL:

def custom_query(posts): 
    from django.db import connection 
    query = """ 
    SELECT "blogs_post_tags"."post_id", "blogs_tag"."title" 
    FROM "blogs_post_tags" 
    INNER JOIN "blogs_tags" ON ("blogs_post_tags"."tag_id"="blogs_tags"."id") 
    WHERE "blogs_post_tags"."post_id" in %s 
    """ 
    cursor=connection.cursor() 
    cursor.execute(query,[posts,]) 
    results = {} 
    for id,title in cursor.fetchall(): 
    results.setdefault(id,[]).append(title) 
    return results 

recent_posts = Post.objects.filter(published=True).order_by('-added')[:20] 
post_ids = recent_posts.values_list('id',flat=True) 
post_tags = custom_query(post_ids) 

recent_posts是安置自己的QuerySet,應該從一個查詢緩存。
post_tags是來自一個查詢的帖子ID到標籤標題的映射。