ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Django 中查询为 GROUP BY?

我查询一个模型:

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 上做一些技巧,但我只是想知道如何在不打补丁的情况下做到这一点。


F
Flimm

如果您打算进行聚合,您可以使用 aggregation features of the 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')

参考:

Django 文档:values()、annotate() 和 Count

Django 文档:聚合,特别是标题为与默认排序或 order_by() 交互的部分


@Harry:你可以把它锁起来。类似于:Members.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
我有一个问题,这个查询只返回指定和 dcount,如果我也想获取表的其他值怎么办?
请注意,如果您的排序是指定以外的字段,则如果不重置排序,它将无法工作。请参阅stackoverflow.com/a/1341667/202137
@Gidgidonihah 是的,示例应为 Members.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
我有一个问题,这个查询只返回指定和 dcount,如果我也想获取表的其他值怎么办?
3
3 revs, 3 users 82%

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

您现在可以遍历 results 变量以检索结果。请注意,group_by 未记录在案,并且可能会在 Django 的未来版本中进行更改。

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


你能告诉我如何使用 order_by 吗?
嗨,如果您不使用聚合,您可以通过使用 order_by 来模拟 group_by 并消除您不需要的条目。当然,这是一种模拟,仅在使用的数据不多时才可用。由于他没有谈到聚合,我认为这可能是一个解决方案。
嘿,这太棒了 - 你能解释一下如何使用它似乎不起作用的 execute_sql..
请注意,这不再适用于 Django 1.9。 stackoverflow.com/questions/35558120/…
这是一种使用 ORM 的黑客方式。您不必手动实例化传入旧查询集的新查询集。
i
inostia

您还可以使用 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

孟买:19,000,000

加尔各答:15,000,000

美国 纽约:20,000,000 芝加哥:7,000,000

纽约:20,000,000

芝加哥:7,000,000

日本东京:33,000,000

东京:33,000,000

我相信它也适用于QuerySet

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

编辑:请注意,如果您的字典列表未按键排序,regroup 标记 会按预期工作。它迭代地工作。因此,在将列表(或查询集)传递给 regroup 标记之前,按 grouper 的键对列表(或查询集)进行排序。


太棒了!我已经搜索了很多简单的方法来做到这一点。它也适用于查询集,这就是我使用它的方式。
如果您从数据库中读取大量数据然后只使用聚合值,这是完全错误的。
@SławomirLenart 当然,这可能不如直接的数据库查询那么有效。但对于简单的用例,它可能是一个不错的解决方案
如果结果显示在模板中,这将起作用。但是,对于 JsonResponse 或其他间接响应。此解决方案将不起作用。
@Willysatrionugroho 如果您想在视图中执行此操作,例如,stackoverflow.com/questions/477820/… 可能适合您
L
Luis Masuelli

Django 不支持自由分组查询。我以非常糟糕的方式学习它。 ORM 并非旨在支持您想要做的事情,而不使用自定义 SQL。您仅限于:

RAW 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。
R
Risadinha

以下模块允许您对 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)

'书/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/3.2/ref/contrib/postgres/aggregates/#arrayagg


django-group-byvalues 方法的替代方法。我认为这是出于不同的目的。
@Lshi 这不是价值观的替代品,当然不是。 values 是 SQL select,而 group_by 是 SQL group by(顾名思义...)。为什么投反对票?我们在生产中使用此类代码来实现复杂的 group_by 语句。
它的 docgroup_by “行为与 values 方法基本相似,但有一个区别......”该文档没有提到 SQL GROUP BY,它提供的用例并不表明它与SQL GROUP BY。当有人明确表示这一点时,我会收回反对票,但该文档确实具有误导性。
阅读 the doc for values 后,我发现我错过了 values 本身就像一个 GROUP BY。我的错。我认为当 values 不足时,使用 itertools.groupby 比使用这个 django-group-by 更简单。
使用简单的 values 调用从上面执行 group by 是不可能的 - 有或没有 annotate 并且不从数据库中获取所有内容。您对 itertools.groupby 的建议适用于小型数据集,但不适用于您可能想要分页的数千个数据集。当然,此时您必须考虑一个包含准备好的(已经分组的)数据的特殊搜索索引,无论如何。
r
ralfzen

您也可以直接使用内置的 pythons 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、子查询、第三方库或模板标签,而且是 Python 和显式的。


性能怎么样??
d
djvg

documentation 表示您可以使用值对查询集进行分组。

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

您可以观看一些备忘单here


为什么需要 group_by() 来返回正确的结果?
V
Van Gale

您需要执行此代码段中示例的自定义 SQL:

Custom SQL via subquery

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

Adding extra Manager methods


一种往返解决方案。我会使用它,如果我有一些扩展使用它。但是在这里我只需要每个指定的成员数量就可以了。
没问题。我曾想过提及 1.1 聚合功能,但假设您使用的是发行版 :)
这一切都是关于使用原始查询,这显示了 Django 的 ORM 的弱点。
r
rumbarum

这有点复杂,但是让提问者只用一次 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)
)

R
Raekkeri

换句话说,如果您只需要基于某个字段“删除重复项”,否则只需按原样查询 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 值,然后简单地使用该字段来过滤掉重复项。


F
Flimm

如果您想要模型对象,而不仅仅是普通值或字典,您可以执行以下操作:

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

member__in 替换为您的型号名称的小写版本,然后是 __in。例如,如果您的型号名称是 Car,请使用 car__in


Ö
Özer

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

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

我希望它有所帮助。


K
Kiran S youtube channel
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))

首先你需要导入 Sum 然后..