如何在 Django 中以 GROUP BY 身份查询?

How to query as GROUP BY in Django?

提问人:simplyharsh 提问时间:3/10/2009 最后编辑:ivanleonczsimplyharsh 更新时间:4/12/2023 访问量:544221

问:

我查询一个模型:

Members.objects.all()

它返回:

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

我想知道的是知道最好的 Django 发射方式 对我的数据库的查询,例如:group_by

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

当然,这是行不通的。 我知道我们可以做一些技巧,但我只是想知道如何在不打补丁的情况下做到这一点。django/db/models/query.py

姜戈 django模型 分组依据

评论


答:

4赞 Van Gale 3/10/2009 #1

您需要执行自定义 SQL,如以下代码段所示:

通过子查询自定义 SQL

或者在自定义管理器中,如在线 Django 文档所示:

添加额外的 Manager 方法

评论

1赞 simplyharsh 3/10/2009
一种往返解决方案。如果我有一些扩展用途,我会使用它。但在这里,我只需要每个指定的成员数量,仅此而已。
79赞 3 revs, 3 users 82%Michael #2

一个简单的解决方案,但不是正确的方法是使用原始 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)

现在,您可以循环访问结果变量以检索结果。请注意,这没有记录在案,可能会在 Django 的未来版本中更改。group_by

和。。。为什么要使用 ?如果不使用聚合,则可以使用聚合来获得相似的结果。group_byorder_by

759赞 Guðmundur H 3/10/2009 #3

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

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

这将导致类似于

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

输出的形式为

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

如果不包括 ,则默认排序不是预期的,则可能会得到不正确的结果。order_by()

如果要在结果中包含多个字段,只需将它们作为参数添加到 中,例如:values

    .values('designation', 'first_name', 'last_name')

引用:

11赞 Luis Masuelli 11/13/2015 #4

Django 不支持自由分组查询。我以非常糟糕的方式学会了它。ORM 不是为了在不使用自定义 SQL 的情况下支持您想要做的事情而设计的。您只能:

  • RAW sql(即 MyModel.objects.raw())
  • cr.execute句子(以及对结果的手工解析)。
  • .annotate()(分组依据句子在 .annotate() 的子模型中执行,例如聚合 lines_count=Count('lines')))。

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

9赞 Risadinha 9/29/2016 #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>

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

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

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

注意:是 Postgres 特定的函数,从 Django 1.9 开始可用:https://docs.djangoproject.com/en/3.2/ref/contrib/postgres/aggregates/#arrayaggArrayAgg

7赞 ramwin 11/7/2017 #6

文档指出,您可以使用值对查询集进行分组。

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()

你可以在这里观看一些备忘单。

59赞 inostia 11/16/2017 #7

您还可以使用模板标签按属性进行分组。从文档中: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 countries_list %}

<ul>
    {% for country in countries_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

我相信它也适用于 s。QuerySet

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

编辑:请注意,如果您的词典列表未按键排序,则标签不会像您预期的那样工作。它以迭代方式工作。因此,在将列表(或查询集)传递给标签之前,请按石斑鱼的键对列表(或查询集)进行排序。regroupregroup

-7赞 Kiran S youtube channel 9/24/2018 #8
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))

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

1赞 Raekkeri 3/2/2021 #9

换句话说,如果您只需要根据某个字段“删除重复项”,而只是按原样查询 ORM 对象,我想出了以下解决方法:

from django.db.models import OuterRef, Exists

qs = Members.objects.all()
qs = qs.annotate(is_duplicate=Exists(
    Members.objects.filter(
        id__lt=OuterRef('id'),
        designation=OuterRef('designation')))
qs = qs.filter(is_duplicate=False)

因此,基本上我们只是通过使用一些方便的过滤(可能因您的模型和要求而异)来注释值,然后简单地使用该字段过滤掉重复项。is_duplicate

9赞 ralfzen 8/27/2021 #10

您也可以直接使用内置的 python:itertools.groupby

from itertools import groupby

designation_key_func = lambda member: member.designation
queryset = Members.objects.all().select_related("designation")

for designation, member_group in groupby(queryset, designation_key_func):
    print(f"{designation} : {list(member_group)}")

在我看来,不需要原始 sql、子查询、第三方库或模板标签,并且是 pythonic 和明确的。

1赞 Flimm 11/2/2021 #11

如果你想要模型对象,而不仅仅是普通值或字典,你可以做这样的事情:

members = Member.objects.filter(foobar=True)
designations = Designation.objects.filter(member__in=members).order_by('pk').distinct()

替换为模型名称的小写版本,后跟 。例如,如果模型名称为 ,请使用 。member__in__inCarcar__in

1赞 rumbarum 11/18/2021 #12

这有点复杂,但要让询问者他/她只点击一次 DB 时会期望什么。

from django.db.models import Subquery, OuterRef

member_qs = Members.objects.filter(
    pk__in = Members.objects.values('designation').distinct().annotate(
        pk = Subquery(
          Members.objects.filter(
            designation= OuterRef("designation")
        )
        .order_by("pk") # you can set other column, e.g. -pk, create_date...
        .values("pk")[:1]
        ) 
    )
   .values_list("pk", flat=True)
)
1赞 Özer 6/14/2022 #13

出于某种原因,上述解决方案对我不起作用。这是有效的:

dupes_query = MyModel.objects.all().values('my_field').annotate(
    count=Count('id')
).order_by('-count').filter(count__gt=1)

我希望它有所帮助。