【问题标题】:Eliminating Queries in Django消除 Django 中的查询
【发布时间】:2014-08-03 11:52:32
【问题描述】:

我有一种情况,我想为主题列表中的每个主题获取最受欢迎的评论。就目前而言,我正在 for 循环中进行查询,可以预见这会很慢。有没有办法消除由此产生的大量查询?

使用 Django 的 prefetch_related queryset 方法是不可接受的,因为它会检索与线程相关的所有 cmets(可能很多)。这尤其成问题,因为我每个线程只需要一条评论(只有最受欢迎的一条)。

这是我的模型的简化版本(为简洁起见,删除了一堆不相关的信息)。

class Thread(models.Model):
    def description(self):
        """ Returns most popular post based on votes. """
        return self.posts.annotate(_popularity=models.Count('votes')).order_by('-_popularity')[0]

class Post(models.Model):
    thread = models.ForeignKey('Thread', related_name='posts')
    text = models.CharField(max_length=settings.MAX_POST_LENGTH)

class Vote(models.Model):
    post = models.ForeignKey('Post', related_name='votes')

获取所有描述的代码实际上就是这个。 threadsThread 对象的已评估查询集。

def descriptions(threads):
     for thread in threads:
         yield thread.description()

所以基本上我有一些线程,我希望得到一个列表,其中包含每个线程最流行的评论。我希望使用少于 N 的查询来执行此操作,其中 N 是线程数。

【问题讨论】:

  • 你碰巧在使用 Postgres 吗?
  • 在生产中是的,尽管我使用 SQLite 进行开发。

标签: python django query-optimization django-queryset querying


【解决方案1】:

我相信至少有两种解决方案。一种(因为您使用的是 Postgres)是使用 distinct。另一种是下拉到原始sql。前者更简单,所以我将为其编写代码示例。

most_popular_posts = Post.objects.all().annotate(
    popularity=Count('votes__id', distinct=True)
).select_related('thread').distinct('thread_id').order_by(
    '-thread_id', '-popularity'
)

【讨论】:

  • 谢谢,我避免使用此解决方案,因为它是特定于数据库的。但是,在进行了相当多的搜索/思考之后,我很确定这是解决我的问题的最佳/唯一解决方案,而不是使用原始 SQL。
  • 说实话,我可能花了一个小时写这个小答案,并在得出这个答案之前经历了 5 种不同的解决方案。这是一个有趣的边缘案例。
  • 相同(尽管我花了大约 3 个),我在文档中看到了 distinct('some_field') 并且由于它是特定于数据库的而感到担忧。我希望我忽略了一些东西,或者有一个聪明的方法来解决这个问题。不过,我认为你做到了。再次感谢您。
【解决方案2】:

在我看来,您已经非常接近正确答案了。

def handle_popular_posts(threads):
  most_popular_posts = Posts.objects                 \
        .filter(thread__id__in=threads)              \
        .annotate(_popularity=models.Count('votes')) \
        .order_by('-_popularity').select_related('thread')
  for post in most_popular_posts:
     #your_code_here...

我添加了.select_related('thread'),因为我相信你会想要关于父线程的信息,而没有select_related Django 会在你每次尝试访问超出id 的线程信息时进行新的查询。

这个查询应该非常有效,因为我在一个有数百行的数据库上运行了一个类似的案例,并且花费了 ~10ms。在此数据库上,使用 id 进行一次获取大约需要 ~5ms

【讨论】:

  • 这不是返回一个线程的所有帖子吗?在问题中它说OP不想这样做。
猜你喜欢
  • 1970-01-01
  • 2011-05-03
  • 2017-12-07
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 2011-06-01
  • 2011-08-18
  • 2016-12-29
相关资源
最近更新 更多