如何查询作为组由在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上做一些技巧,但我只是好奇如何在不打补丁的情况下做到这一点

479006 次浏览

你需要做自定义SQL的例子在这段代码:

自定义SQL via subquery

或者在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)

现在可以遍历结果变量以检索结果。请注意,group_by没有被记录,在Django的未来版本中可能会被更改。

和…为什么要使用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不支持自由分组查询。我是用一种非常糟糕的方式学会的。ORM不是设计来支持你想做的事情的,不使用自定义SQL。你被限制在:

  • RAW sql(即MyModel.objects.raw())
  • cr.execute句子(和结果的手工解析)。
  • .annotate()(在aggregating lines_count=Count('lines'))这样的例子中,按句子分组在. annotation()的子模型中执行)。

对于一个qs查询集,你可以调用qs.query.group_by = ['field1', 'field2', ...],但如果你不知道你在编辑什么查询,也不能保证它会工作,不会破坏queryset对象的内部结构,这是有风险的。此外,它是一个内部的(未被记录的)API,你不应该直接访问,否则会有代码与未来的Django版本不再兼容的风险。

下面的模块允许你对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开始可用

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

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>

看起来是这样的:

    <李>印度
    • 孟买:19000000
    • 加尔各答:15000000
    <李>美国
    • 纽约:2000万
    • 芝加哥:7000000
    <李>日本
    • 东京:33000000

我相信它也适用于QuerySets。

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

edit:注意,如果字典列表没有按键排序,regroup标记会像你期望的那样工作。它是迭代工作的。因此,在将其传递给regroup标记之前,根据石斑鱼的键对列表(或查询集)进行排序。

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

首先你需要导入Sum 然后. . < / p >

换句话说,如果你需要“删除重复的内容”;基于一些字段,或者只是查询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、子查询、第三方库或模板标签,在我看来是python化的和显式的。

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

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)

我希望这能有所帮助。