#python #django #query-optimization #rails-postgresql
#python #django #оптимизация запросов #rails-postgresql
Вопрос:
У меня есть модели Post и Tag:
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()
Мне нужно вывести список записей в блоге и набор тегов для каждого поста. Я хотел бы иметь возможность сделать это всего с помощью двух запросов, используя этот рабочий процесс:
- Получаем список записей
- Получите список тегов, используемых в этих сообщениях
- Привязывайте теги к сообщениям в python
У меня возникли проблемы с последним шагом, вот код, который я придумал, но in дает мне '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)
Я был бы признателен всем, кто укажет, как этого избежать.
Ответ №1:
Вот что я обычно делаю в такой ситуации:
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()
), который делает это за вас.
Комментарии:
1. Дэниел Розман прекрасно инкапсулировал это в свой проект, ориентированный на django: github.com/danielroseman/django-efficient
2. У меня не сработало в Django 1.2. Необходимо было это сделать:
Post.tags.through.objects.select_related('tag')...
Ответ №2:
Если вам необходимо использовать это в двух запросах, я думаю, вам понадобится пользовательский 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
ваш Post QuerySet, должен кэшироваться из одного запроса.
post_tags
это сопоставление идентификатора записи с заголовками тегов из одного запроса.