Django:如何使用已过滤的ForeignKey字段计数来注释查询集?

flo*_*fan 34 python django django-models

Django新手问题:)

我有以下模型 - 每个评论都是针对一个产品,每个产品都有一个部门:

class Department(models.Model):
    code = models.CharField(max_length=16)
class Product(models.Model):
    id = models.CharField(max_length=40, primary_key=True, db_index=True)
    dept = models.ForeignKey(Department, null=True, blank=True, db_index=True)
class Review(models.Model):
    review_id = models.CharField(max_length=32, primary_key=True, db_index=True) 
    product = models.ForeignKey(Product, db_index=True) 
    time = models.DateTimeField(db_index=True) 
Run Code Online (Sandbox Code Playgroud)

我想对日期范围(2012-01-01到2012-01-08)进行Django查询,并返回所有部门的列表,用部门ID注释,以及该部门的产品数量已经过审核在那个日期范围内.

这有点煎炸了我的大脑:)

我可以得到一个时间范围的所有评论:

 reviews = Review.filter(time__range=["2012-01-01", "2012-01-08"])
Run Code Online (Sandbox Code Playgroud)

然后我猜每个评论都有一个产品领域,每个产品都有一个部门代码.但是,我如何按产品和代码,计数和部门ID对它们进行分组?

或者,最好是请求部门,然后以某种方式用产品计数注释它们?

Jos*_*ton 39

避免extraraw尽可能.该聚集的文档有将近这种使用情况:

直接来自文档:

# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...]
>>> pubs[0].num_books
73
Run Code Online (Sandbox Code Playgroud)

因此,要针对您的特定示例进行修改:

depts = Department.objects.
            filter(product__review__time__range=["2012-01-01", "2012-01-08"]).
            annotate(num_products=Count('product'))
Run Code Online (Sandbox Code Playgroud)

单独行上的函数调用只是为了提高可读性,你应该相应地移动它们.我没有测试过这个,但我认为它应该可行.

  • 很确定这将是一个FieldError.无法将关键字"产品"解析为字段.选择是:代码 (4认同)

Tim*_*ony 6

在过去的几天里我不得不做几个类似的查询,并且使用extraqueryset函数最简单的方法是使用过滤的产品计数来注释查询集中的每个对象:

start = ..  # need to be formatted correctly
end = ...
departments = Departments.objects.all().extra(select = {
     'product_count' : """ SELECT COUNT(*) FROM appname_department
                           JOIN appname_product
                               ON appname_product.dept_id = appname_department.id
                           JOIN appname_review 
                               ON appname_review.product_id = appname_product.id
                           WHERE appname_review.time BETWEEN %s AND %s
                       """
}, params=[start, end])
Run Code Online (Sandbox Code Playgroud)

{% for department in departments %}
    {{ department.product_count }}
{% endfor %}
Run Code Online (Sandbox Code Playgroud)

  • 此示例易受SQL注入攻击 (5认同)

Sar*_*iev 6

在 Django 中,>= 2.0这可以通过

depts = Department.objects.all().annotate(
    num_products=Count('product', filter=Q(product__review__time__range=["2012-01-01", "2012-01-08"]))
)
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请阅读文档https://docs.djangoproject.com/en/2.0/topics/db/aggregation/#filtering-on-annotations


Fra*_*llo 0

聚合文档 https://docs.djangoproject.com/en/dev/topics/db/aggregation/#cheat-sheet

可能有一种使用聚合或注释的方法,但我更喜欢这个:

departments = Department.objects.all()
for dept in departments : 
    # Get the number of reviewed products for a given range and department
    num_products = dept.product_set.filter(review__time__range=["2012-01-01", "2012-01-08"]).count()
Run Code Online (Sandbox Code Playgroud)

如果您绝对需要它作为模型的函数:

class Department(models.Model) :
    ...
    def num_products(self, start_date, end_date) : 
        return self.product_set.filter(review__time__range=[start_date, end_date]).count()
Run Code Online (Sandbox Code Playgroud)

编辑

我认为如果你要做一个原始查询(类似这样)

sql = """SELECT COUNT(Product.*) as num_products, Department.*
    FROM Department
    LEFT OUTER JOIN Product ON Product.department = Department.id
    LEFT OUTER JOIN Review ON Product.id = Review.product
    WHERE Review.time BETWEEN "2012-01-01" AND "2012-01-08"
    GROUP BY Department.id"""

Department.objects.raw(sql)
Run Code Online (Sandbox Code Playgroud)

然后 num_products 将成为结果中每个 Dept 实例的属性。

您可能需要稍微调整一下字段+表名

  • 布莱格。每个部门一次查询。 (15认同)