我查询一个模型:

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上做一些技巧,但我只是好奇如何不打补丁就能做到。


当前回答

出于某种原因,上面提到的解决方案并不适合我。这是有效的方法:

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

我希望这能有所帮助。

其他回答

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

与注释/聚合基本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

如果你想进行聚合,你可以使用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('designation', 'first_name', 'last_name')

引用:

Django文档:values(), annotation()和Count Django文档:聚合,特别是标题为“与默认排序或order_by()的交互”的部分。

你也可以使用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化的和显式的。

还可以使用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标记就不能正常工作。它是迭代工作的。因此,在将石斑鱼的键传递给regroup标记之前,对列表(或查询集)进行排序。

换句话说,如果你只需要根据某些字段“删除重复项”,或者只是查询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值,然后简单地使用该字段过滤掉重复项。