过滤Django ORM中的Aggregate

时间:2009-12-11 16:25:15

标签: django django-models aggregate

我有一个看起来像这样的函数:

def post_count(self):
        return self.thread_set.aggregate(num_posts=Count('post'))['num_posts']

我只想计算状态标记为“有效”的帖子。有没有一种简单的方法可以在Count函数之前添加过滤器?

模型定义:

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100, blank=True, primary_key=True)
    ordering = models.IntegerField(max_length=3, default=0)

    @property
    def thread_count(self):
        return self.thread_set.all().count()

    @property
    def post_count(self):
        return self.thread_set.aggregate(num_posts=Count('post'))['num_posts']

class Thread(models.Model):
    user = models.ForeignKey(User)
    category = models.ForeignKey(Category)
    title = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100)
    content = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    latest_activity = models.DateTimeField(auto_now_add=True)

class Post(models.Model):
    thread = models.ForeignKey(Thread)
    parent = models.ForeignKey('Post', null=True, blank=True)
    display_name = models.CharField(max_length=100)
    email = models.EmailField(db_index=True)
    ip_address = models.IPAddressField(null=True, blank=True)
    content = models.TextField()
    status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved')
    created = models.DateTimeField()

5 个答案:

答案 0 :(得分:10)

好了,既然问题包括模型定义,我向你提出这应该有效,除非你的Django版本不支持我在这里使用的某些功能(在这种情况下,请告诉我!):< / p>

Post.objects.filter(thread__in=thread_set, status='active').aggregate(num_posts=Count('id'))

Django允许__in过滤器使用QuerySet来决定IN子句在SQL中应该是什么样子,所以如果你传递thread__in=thread_set,Django将过滤帖子,以便只有那些其thread字段指向idthread_set个帖子之一的aggregate字段仍然可供WHERE thread_id IN ...来电查看。

这应该用只有一个数据库查询来过滤帖子里面的Category,而不是每个线程有一个查询,这确实很可怕。如果发生任何其他事情,这将是Django中的一个错误......

结果应该是最多两次查询以建立thread_set的帖子 - 一个用于获取Thread,另一个用于计算帖子。另一种方法是根据category的{​​{1}}字段和Post的{​​{1}}字段过滤线程/帖子连接,我不一定要这样做要快得多。 (我说'最多',因为我猜他们可以自动融合......虽然我不认为这会发生在当前的Django上。无法检查ATM,抱歉。)

编辑: Django's QuerySet API referencestatus过滤器上说明了这一点:


  

<强> IN

在给定的列表中。

示例:

__in

SQL等价物:

Entry.objects.filter(id__in=[1, 3, 4])

您还可以使用查询集动态评估值列表,而不是提供文字值列表:

SELECT ... WHERE id IN (1, 3, 4);

此查询集将作为subselect语句进行评估:

inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)

上面的代码片段也可以写成如下:

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')

在Django 1.1中更改:在Django 1.0中,只有后一段代码有效。

第二种形式的可读性和不自然性,因为它访问内部查询属性并需要一个ValuesQuerySet。如果您的代码不需要与Django 1.0兼容,请使用第一个表单,直接传入查询集。


所以,我猜Django 能够将单个查询传递给数据库中的问题。如果db的查询分析器做得很好,效果可能非常接近最佳。 : - )

答案 1 :(得分:0)

是。去做就对了。这应该按预期工作:

self.thread_set.filter(active_status=1).aggregate(num_posts=Count('post'))['num_posts']

任何原始查询都会返回QuerySet,因此对于复杂的条件匹配,any available methods that return QuerySets可以非常无限地链接在一起。自aggregate() does not return a QuerySet起,您需要确保它是链中的最后一个。

答案 2 :(得分:0)

我一直在寻找类似的东西,并没有找到一个很好的解决方案。我正在使用这样的东西:

def post_count(self):
        return len(Post.objects.filter(someModel = self).filter(active_status = 1))

这不是很好,但我不认为Django允许您根据辅助模型聚合和注释进行过滤。我会检查是否有人想出更好的解决方案。

答案 3 :(得分:0)

您可能希望查看编写自定义Manager对象:

http://docs.djangoproject.com/en/1.1/topics/db/managers/

我没有使用aggregate(),但这可以让您编写自定义管理器以提供已过滤的active_thread_set,然后执行self.active_thread_set.aggregate(...)。如果没有,它将允许您执行自定义SQL并在num_posts对象上添加Thread属性(请参阅PollManager.with_counts()示例。)

答案 4 :(得分:0)

可以稍微改变一下吗?

如下图所示,您可以将一个post_count属性添加到Thread类中,该类计算线程中的活动帖子。

此post_count可用于计算类别中所有活动帖子的所有活动帖子。

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100, blank=True, primary_key=True)
    ordering = models.IntegerField(max_length=3, default=0)

    @property
    def thread_count(self):
        return self.thread_set.all().count()

    @property
    def post_count(self): # <-- Changed
        return reduce(lambda x,y: x + y, [x.post_count for x in self.thread_set.all()])

class Thread(models.Model):
    user = models.ForeignKey(User)
    category = models.ForeignKey(Category)
    title = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100)
    content = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    latest_activity = models.DateTimeField(auto_now_add=True)

    @property
    def post_count(self): # <---- Newly added
        return self.post_set.filter(status = 'ACTIVE').count()

class Post(models.Model):
    thread = models.ForeignKey(Thread)
    parent = models.ForeignKey('Post', null=True, blank=True)
    display_name = models.CharField(max_length=100)
    email = models.EmailField(db_index=True)
    ip_address = models.IPAddressField(null=True, blank=True)
    content = models.TextField()
    status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved')
    created = models.DateTimeField()