如何在Django中以GROUP BY查询?


332

我查询一个模型:

Members.objects.all()

它返回:

Eric, Salesman, X-Shop
Freddie, Manager, X2-Shop
Teddy, Salesman, X2-Shop
Sean, Manager, X2-Shop

我想要的是知道group_by向我的数据库发送查询的最佳Django方法,例如:

Members.objects.all().group_by('designation')

当然,这不起作用。我知道我们可以在上做一些技巧django/db/models/query.py,但我只是很好奇知道如何在不打补丁的情况下进行操作。

Answers:


483

如果您打算进行聚合,则可以使用ORM聚合功能

from django.db.models import Count
Members.objects.values('designation').annotate(dcount=Count('designation'))

这导致查询类似于

SELECT designation, COUNT(designation) AS dcount
FROM members GROUP BY designation

并且输出将为以下形式

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]

6
@哈里:你可以锁上它。像这样的东西:Members.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
Eli

57
我有一个问题,该查询仅返回指定和dcount,如果我也想获取表的其他值怎么办?
2014年

19
请注意,如果您的排序不是指定的字段,则不重置排序将不起作用。参见stackoverflow.com/a/1341667/202137
Gidgidonihah 2014年

12
@Gidgidonihah是的,示例应为Members.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
bjunix 2014年

7
我有一个问题,该查询仅返回指定和dcount,如果我也想获取表的其他值怎么办?
Yann叶

55

一个简单的解决方案,但不是正确的方法是使用原始SQL

results = Members.objects.raw('SELECT * FROM myapp_members GROUP BY designation')

另一种解决方案是使用该group_by属性:

query = Members.objects.all().query
query.group_by = ['designation']
results = QuerySet(query=query, model=Members)

现在,您可以遍历结果变量以检索结果。请注意,该group_by文档未记录,在以后的Django版本中可能会更改。

还有...为什么要使用group_by?如果不使用聚合,则可以使用order_by来获得相似的结果。


您能告诉我如何使用order_by吗?
Simplyharsh

2
嗨,如果您不使用聚合,则可以使用order_by来模拟group_by并消除不需要的条目。当然,这是一种仿真,仅在不使用大量数据的情况下才可用。由于他没有谈论聚合,所以我认为这可能是一个解决方案。
迈克尔

嘿,这是伟大的-你可以请解释如何使用execute_sql它似乎并没有工作..
rh0dium

8
请注意,这不再适用于Django 1.9。 stackoverflow.com/questions/35558120/…–
grokpot

1
这是一种使用ORM的简单方法。您不必手动实例化传入旧查询集的新查询集。
伊恩·柯克帕特里克

32

您也可以使用regroup模板标记按属性分组。从文档:

cities = [
    {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'},
    {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'},
    {'name': 'New York', 'population': '20,000,000', 'country': 'USA'},
    {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'},
    {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'},
]

...

{% regroup cities by country as country_list %}

<ul>
    {% for country in country_list %}
        <li>{{ country.grouper }}
            <ul>
            {% for city in country.list %}
                <li>{{ city.name }}: {{ city.population }}</li>
            {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

看起来像这样:

  • 印度
    • 孟买:19,000,000
    • 加尔各答:15,000,000
  • 美国
    • 纽约:20,000,000
    • 芝加哥:7,000,000
  • 日本
    • 东京:33,000,000

QuerySet我相信它也可以使用。

来源:https : //docs.djangoproject.com/en/2.1/ref/templates/builtins/#regroup

编辑:请注意,如果词典列表未按键排序,则该regroup标签将无法正常运行。它迭代地工作。因此,在将列表(或查询集)传递给regroup标签之前,请先按石斑鱼的键对列表进行排序。


1
太棒了!我已经搜索了很多简单的方法来做到这一点。它也适用于查询集,这就是我使用它的方式。
卡门A

1
如果您从数据库中读取大量数据,然后仅使用聚合值,则这是完全错误的。
斯瓦沃米尔Lenart

@SławomirLenart当然,这可能不如直接的数据库查询有效。但是对于简单的用例来说,这可能是一个不错的解决方案
Inostia'3

如果结果显示在模板中,则将起作用。但是,对于JsonResponse或其他间接响应。此解决方案将不起作用。
威利·萨特里奥·努格罗霍

1
@Willysatrionugroho,如果您想在视图中这样做,例如stackoverflow.com/questions/477820/…可能对您
有用

7

您需要按照以下代码片段中的示例进行自定义SQL:

通过子查询自定义SQL

或在在线Django文档中显示的自定义管理器中:

添加额外的Manager方法


1
一种往返解决方案。如果我对此进行扩展使用的话,我会使用它的。但是在这里,我只需要每个指定的成员人数即可。
Simplyharsh

没问题。我考虑过要提到1.1聚合功能,但假设您使用的是发行版本:)
Van Gale,2009年

一切都与使用原始查询有关,这显示了Django ORM的弱点。
斯瓦沃米尔Lenart

5

Django不支持免费的按组分组查询。我以非常糟糕的方式学到了它。如果不使用自定义SQL,则ORM并非旨在支持您想做的事情。您仅限于:

  • 原始sql(即MyModel.objects.raw())
  • cr.execute 句子(以及结果的手工解析)。
  • .annotate() (按句段在.annotate()的子模型中执行句子分组,例如聚合lines_count = Count('lines')之类的示例))。

qs您可以调用整个qs.query.group_by = ['field1', 'field2', ...]查询集,但是如果您不知道要编辑的查询,并且不能保证该查询将起作用并且不会破坏QuerySet对象的内部,则可能会有风险。此外,它是一个内部(未记录)的API,您不应直接访问它,而不必担心代码不再与将来的Django版本兼容。


确实,您不仅受到免费分组的限制,所以请尝试使用SQLAlchemy而不是Django ORM。
斯瓦沃米尔Lenart

5

有一个模块可以让您对Django模型进行分组,并仍然在结果中使用QuerySet:https : //github.com/kako-nawao/django-group-by

例如:

from django_group_by import GroupByMixin

class BookQuerySet(QuerySet, GroupByMixin):
    pass

class Book(Model):
    title = TextField(...)
    author = ForeignKey(User, ...)
    shop = ForeignKey(Shop, ...)
    price = DecimalField(...)

class GroupedBookListView(PaginationMixin, ListView):
    template_name = 'book/books.html'
    model = Book
    paginate_by = 100

    def get_queryset(self):
        return Book.objects.group_by('title', 'author').annotate(
            shop_count=Count('shop'), price_avg=Avg('price')).order_by(
            'name', 'author').distinct()

    def get_context_data(self, **kwargs):
        return super().get_context_data(total_count=self.get_queryset().count(), **kwargs)

'book / books.html'

<ul>
{% for book in object_list %}
    <li>
        <h2>{{ book.title }}</td>
        <p>{{ book.author.last_name }}, {{ book.author.first_name }}</p>
        <p>{{ book.shop_count }}</p>
        <p>{{ book.price_avg }}</p>
    </li>
{% endfor %}
</ul>

annotate/ aggregate基本Django查询的区别在于使用了相关字段的属性,例如book.author.last_name

如果需要已分组在一起的实例的PK,请添加以下注释:

.annotate(pks=ArrayAgg('id'))

注意:ArrayAgg是Postgres特定的功能,可从Django 1.9开始使用:https : //docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg


django-group-by是该values方法的替代方法。我认为这是出于不同的目的。
LShi

1
@LShi当然不是值的替代品。values是SQL selectgroup_by而是SQL group by(顾名思义...)。为什么要下票?我们在生产中使用这种代码来实现复杂的group_by语句。
里沙迪尼亚

它的文档说:group_by“其行为大体上类似于values方法,但有一个区别……”该文档未提及SQL GROUP BY,它提供的用例并不表明它与SQL有关GROUP BY。当有人说清楚这一点时,我会退票,但该文件确实具有误导性。
LShi

阅读的文档values后,我发现我错过了它values本身的工作原理,就像GROUP BY。我的错。我认为itertools.groupbyvalues不足时使用此django-group-by比这更简单。
LShi

1
group by从上面进行简单的values调用是不可能的-有或没有annotate,也没有从数据库中获取所有内容。您的建议itertools.groupby适用于小型数据集,但不适用于您可能要分页的数千个数据集。当然,到那时,无论如何,您都必须考虑一个特殊的搜索索引,其中包含准备好的(已经分组的)数据。
Risadinha

0

文档说您可以使用值对queryset进行分组。

class Travel(models.Model):
    interest = models.ForeignKey(Interest)
    user = models.ForeignKey(User)
    time = models.DateTimeField(auto_now_add=True)

# Find the travel and group by the interest:

>>> Travel.objects.values('interest').annotate(Count('user'))
<QuerySet [{'interest': 5, 'user__count': 2}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited for 2 times, 
# and the interest(id=6) had only been visited for 1 time.

>>> Travel.objects.values('interest').annotate(Count('user', distinct=True)) 
<QuerySet [{'interest': 5, 'user__count': 1}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited by only one person (but this person had 
#  visited the interest for 2 times

您可以找到所有书籍,并使用以下代码按名称分组:

Book.objects.values('name').annotate(Count('id')).order_by() # ensure you add the order_by()

你可以在这里看一些指南。



-2
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))

首先,您需要导入Sum,然后..

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.