如何在django中查询GROUP BY?

sim*_*rsh 304 python django django-models

我查询一个模型,

Members.objects.all()
Run Code Online (Sandbox Code Playgroud)

它返回说

Eric, Salesman, X-Shop
Freddie, Manager, X2-Shop
Teddy, Salesman, X2-Shop
Sean, Manager, X2-Shop
Run Code Online (Sandbox Code Playgroud)

我想要的是,知道最好的Django方法来触发group_by查询到我的数据库,就像,

Members.objects.all().group_by('designation')
Run Code Online (Sandbox Code Playgroud)

哪个当然不起作用.我知道我们可以在"django/db/models/query.py"上做一些技巧,但我很想知道如何在没有修补的情况下做到这一点.

Guð*_*r H 446

如果您要进行聚合,可以使用ORM聚合功能:

from django.db.models import Count
Members.objects.values('designation').annotate(dcount=Count('designation'))
Run Code Online (Sandbox Code Playgroud)

这导致类似的查询

SELECT designation, COUNT(designation) AS dcount
FROM members GROUP BY designation
Run Code Online (Sandbox Code Playgroud)

输出将是形式

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]
Run Code Online (Sandbox Code Playgroud)

  • 我有一个问题,这个查询只返回指定和dcount,如果我想得到表的其他值呢? (47认同)
  • 请注意,如果您的排序是指定以外的字段,则在不重置排序的情况下它将无法工作.请参见http://stackoverflow.com/a/1341667/202137 (17认同)
  • @Gidgidonihah是的,这个例子应该是`Members.objects.order_by('disignation').values('designation').annotate(dcount = Count('designation'))` (11认同)
  • @Harry:你可以链接它.类似于:`Members.objects.filter(date = some_date).values('designation').annotate(dcount = Count('designation')) (5认同)
  • 我有一个问题,这个查询只返回指定和dcount,如果我想得到表的其他值呢? (5认同)
  • 你会如何添加另一个过滤器,让我们说按日期查找不同的值? (3认同)
  • 我可以在没有`count`的情况下执行查询吗? (2认同)
  • @Clayton:将其他字段放在值括号内,如下所示:Members.objects.values('designation','field2','field3')。annotate(dcount = Count('designation')) (2认同)
  • 这必须(虽然可选)以`.order_by(designation)`结尾,原因是取决于模型或数据库,它们将是一个隐式的`order_by`,完全打破了分组机制。所以在这里明确保存。 (2认同)
  • 为了解释@nehemiah 的评论(我花了 15 分钟才意识到我在一个我不熟悉的代码库中遇到了这种情况),模型上的 `ordering` 元属性会自动将这些字段添加到 `GROUP BY` 中到 `.values()` 中的那个。将 `.order_by()` 插入查询可以防止添加这些额外的字段。 (2认同)

Mic*_*ael 49

一个简单的解决方案,但不是正确的方法是使用RAW-SQL:

http://docs.djangoproject.com/en/dev/topics/db/sql/#topics-db-sql

另一种解决方案是使用group_by属性:

results = Members.objects.raw('SELECT * FROM myapp_members GROUP BY designation')
Run Code Online (Sandbox Code Playgroud)

您现在可以迭代结果变量以检索结果.请注意,group_by没有记录,可能会在将来的Django版本中更改.

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

  • 请注意,这不再适用于Django 1.9.http://stackoverflow.com/questions/35558120/query-group-by-in-django-1-9 (6认同)
  • 嗨,如果您不使用聚合,您可以使用order_by模拟group_by并消除您不需要的条目.当然,这是一种仿真,只有在不使用大量数据时才可用.由于他没有谈到聚合,我认为这可能是一个解决方案. (2认同)
  • 这是一种使用 ORM 的 hack 方式。您不必实例化新查询集并手动传入旧查询集。 (2认同)

ino*_*tia 21

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

<ul>
    {% for country in country_list %}
        <li>{{ country.grouper }}
            <ul>
            {% for city in country.list %}
                <li>{{ city.name }}: {{ city.population }}</li>
            {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

  • 印度
    • 孟买:19,000,000
    • 加尔各答:15,000,000
  • 美国
    • 纽约:20,000,000
    • 芝加哥:7,000,000
  • 日本
    • 东京:33,000,000

它也适用于QuerySet我相信的.

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

  • 太棒了!我已经搜索了很多简单的方法来做到这一点。它也适用于查询集,这就是我使用它的方式。 (2认同)
  • 如果您从数据库大数据集读取然后只使用聚合值,这是完全错误的。 (2认同)

小智 9

您也可以直接使用内置的 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)}")
Run Code Online (Sandbox Code Playgroud)

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

  • 性能怎么样? (11认同)

Lui*_*lli 7

Django 不支持 free group by queries。我以非常糟糕的方式学到了它。ORM 的设计目的不是在不使用自定义 SQL 的情况下支持您想做的事情。您仅限于:

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

在一个QuerySet qs,你可以调用qs.query.group_by = ['field1', 'field2', ...],但它是有风险的,如果你不知道查询你编辑的内容,并没有保证能正常工作,而不是打破QuerySet对象的内部。此外,它是一个内部(未记录的)API,您不应该直接访问它,以免代码与未来的 Django 版本不再兼容。

  • 事实上,你不仅在自由分组方面受到限制,所以尝试 SQLAlchemy 而不是 Django ORM。 (3认同)

ram*_*win 7

文档说您可以使用值对 queryset 进行分组。

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
Run Code Online (Sandbox Code Playgroud)

您可以使用以下代码找到所有书籍并按名称对它们进行分组:

Book.objects.values('name').annotate(Count('id')).order_by() # ensure you add the order_by()
Run Code Online (Sandbox Code Playgroud)

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


Van*_*ale 6

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

通过子查询自定义SQL

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

添加额外的Manager方法


Ris*_*nha 6

以下模块允许您对 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(...)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)

'书/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>
Run Code Online (Sandbox Code Playgroud)

annotate/ aggregatebasic Django 查询的不同之处在于使用相关字段的属性,例如book.author.last_name.

如果需要已分组的实例的PK,请添加以下注释:

.annotate(pks=ArrayAgg('id'))
Run Code Online (Sandbox Code Playgroud)

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