提问人:simplyharsh 提问时间:3/10/2009 最后编辑:ivanleonczsimplyharsh 更新时间:4/12/2023 访问量:544221
如何在 Django 中以 GROUP BY 身份查询?
How to query as GROUP BY in Django?
问:
我查询一个模型:
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
答:
您需要执行自定义 SQL,如以下代码段所示:
或者在自定义管理器中,如在线 Django 文档所示:
评论
一个简单的解决方案,但不是正确的方法是使用原始 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_by
order_by
如果您打算进行聚合,则可以使用 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()
交互”的部分
Django 不支持自由分组查询。我以非常糟糕的方式学会了它。ORM 不是为了在不使用自定义 SQL 的情况下支持您想要做的事情而设计的。您只能:
- RAW sql(即 MyModel.objects.raw())
cr.execute
句子(以及对结果的手工解析)。.annotate()
(分组依据句子在 .annotate() 的子模型中执行,例如聚合 lines_count=Count('lines')))。
通过查询集,您可以调用,但如果您不知道要编辑的查询是什么,并且不能保证它会起作用并且不会破坏 QuerySet 对象的内部结构,则存在风险。此外,它是一个内部(未记录的)API,你不应该直接访问,而不会冒着代码不再与未来的Django版本兼容的风险。qs
qs.query.group_by = ['field1', 'field2', ...]
以下模块允许你对 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 查询的区别在于使用相关字段的属性,例如 .annotate
aggregate
book.author.last_name
如果您需要已组合在一起的实例的 PK,请添加以下注解:
.annotate(pks=ArrayAgg('id'))
注意:是 Postgres 特定的函数,从 Django 1.9 开始可用:https://docs.djangoproject.com/en/3.2/ref/contrib/postgres/aggregates/#arrayaggArrayAgg
文档指出,您可以使用值对查询集进行分组。
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()
你可以在这里观看一些备忘单。
您还可以使用模板标签按属性进行分组。从文档中: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
编辑:请注意,如果您的词典列表未按键排序,则标签不会像您预期的那样工作。它以迭代方式工作。因此,在将列表(或查询集)传递给标签之前,请按石斑鱼的键对列表(或查询集)进行排序。regroup
regroup
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))
首先,您需要导入 Sum 然后。。
换句话说,如果您只需要根据某个字段“删除重复项”,而只是按原样查询 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
您也可以直接使用内置的 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 和明确的。
如果你想要模型对象,而不仅仅是普通值或字典,你可以做这样的事情:
members = Member.objects.filter(foobar=True)
designations = Designation.objects.filter(member__in=members).order_by('pk').distinct()
替换为模型名称的小写版本,后跟 。例如,如果模型名称为 ,请使用 。member__in
__in
Car
car__in
这有点复杂,但要让询问者他/她只点击一次 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)
)
出于某种原因,上述解决方案对我不起作用。这是有效的:
dupes_query = MyModel.objects.all().values('my_field').annotate(
count=Count('id')
).order_by('-count').filter(count__gt=1)
我希望它有所帮助。
评论