Django 中的 GROUP_CONCAT 等效项

Posted

技术标签:

【中文标题】Django 中的 GROUP_CONCAT 等效项【英文标题】:GROUP_CONCAT equivalent in Django 【发布时间】:2012-05-07 14:44:56 【问题描述】:

假设我有一张名为fruits 的表格:

id | type   | name
-----------------
 0 | apple  | fuji
 1 | apple  | mac
 2 | orange | navel

我的目标是最终计算出不同types 的数量和names 的逗号分隔列表:

apple, 2, "fuji,mac"
orange, 1, "navel"

这可以通过 mysql 中的GROUP_CONCAT 轻松完成,但我在使用 Django 等效项时遇到了问题。这是我到目前为止所拥有的,但我缺少GROUP_CONCAT 的东西:

query_set = Fruits.objects.values('type').annotate(count=Count('type')).order_by('-count')

如果可能,我想避免使用原始 SQL 查询。

任何帮助将不胜感激!

谢谢! =)

【问题讨论】:

【参考方案1】:

您可以创建自己的聚合函数 (doc)

from django.db.models import Aggregate

class Concat(Aggregate):
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(distinct)s%(expressions)s)'

    def __init__(self, expression, distinct=False, **extra):
        super(Concat, self).__init__(
            expression,
            distinct='DISTINCT ' if distinct else '',
            output_field=CharField(),
            **extra)

并将其简单地用作:

query_set = Fruits.objects.values('type').annotate(count=Count('type'),
                       name = Concat('name')).order_by('-count')

我正在使用 django 1.8 和 mysql 4.0.3

【讨论】:

注意 Django (>=1.8) 提供 Database functions 为了完整起见,还有:django.contrib.postgres.aggregates.StringAgg,以防你想在 Postgres 中使用同样的内容 这个函数也存在于django-mysql:django-mysql.readthedocs.io/en/latest/… 对于 django 2.2 我需要将 allow_distinct = True 添加到 Concat 类 请注意:默认情况下,MySQL 会将 GROUP_CONCAT 结果截断为 1024 个字符 - 我花了一些时间弄清楚为什么我得到不存在的 id。你可以设置group_concat_max_len,例子在这里:***.com/questions/36475140/…【参考方案2】:

注意 Django (>=1.8) 提供 Database functions 支持。 https://docs.djangoproject.com/en/dev/ref/models/database-functions/#concat

这里是Shashank Singla的增强版

from django.db.models import Aggregate, CharField

class GroupConcat(Aggregate):
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(distinct)s%(expressions)s%(ordering)s%(separator)s)'

    def __init__(self, expression, distinct=False, ordering=None, separator=',', **extra):
        super(GroupConcat, self).__init__(
            expression,
            distinct='DISTINCT ' if distinct else '',
            ordering=' ORDER BY %s' % ordering if ordering is not None else '',
            separator=' SEPARATOR "%s"' % separator,
            output_field=CharField(),
            **extra
        )

用法:

LogModel.objects.values('level', 'info').annotate(
    count=Count(1), time=GroupConcat('time', ordering='time DESC', separator=' | ')
).order_by('-time', '-count')

【讨论】:

它对我不起作用。另一个很好的例子:gist.github.com/ludoo/ca6ed07e5c8017272701 @Iliaw495Nikitin 这在我使用 Django 1.10.x 的项目中效果很好 在 Django 1.11.x 中运行良好。谢谢!【参考方案3】:

使用 Django-MySQL 包中的GroupConcat ( https://django-mysql.readthedocs.org/en/latest/aggregates.html#django_mysql.models.GroupConcat )我维护。有了它,您可以像这样简单地做到这一点:

>>> from django_mysql.models import GroupConcat
>>> Fruits.objects.annotate(
...     count=Count('type'),
...     name_list=GroupConcat('name'),
... ).order_by('-count').values('type', 'count', 'name_list')
['type': 'apple', 'count': 2, 'name_list': 'fuji,mac',
 'type': 'orange', 'count': 1, 'name_list': 'navel']

【讨论】:

我知道这是一个旧答案,但我有点困惑。可能是我错过了一些东西。那么 GroupConcat 如何知道它将连接字段 'name' 的值?因为我没有看到您在查询中的任何位置指示字段“名称”,但我们正在连接字段“名称”中的值 太棒了!你的回答很有帮助。我会给你一票。 :) @adam-chainz【参考方案4】:

如果您使用的是 PostgreSQL,则可以使用 ArrayAgg 将所有值聚合到一个数组中。

https://www.postgresql.org/docs/9.5/static/functions-aggregate.html

【讨论】:

【参考方案5】:

如果您不介意在模板中执行此操作,Django 模板标记 regroup 可以完成此操作

【讨论】:

【参考方案6】:

从 Django 1.8 开始,您可以使用 Func() expressions。

query_set = Fruits.objects.values('type').annotate(
    count=Count('type'),
    name=Func(F('name'), 'GROUP_BY')
).order_by('-count')

【讨论】:

【参考方案7】:

Django ORM 不支持这个;如果您不想使用原始 SQL,则需要 group and join。

【讨论】:

我的一位同事维护了一个开源项目,该项目公开了 mysql 的特定功能,如 django 中的 GROUP_CONCAT。看看github.com/adamchainz/django-mysql 请注意,对于 Postgres,我们有 django.contrib.postgres.agggregates.StringAgg。【参考方案8】:

Django ORM 不支持,但您可以构建自己的聚合器。

这实际上非常简单,这里有一个操作方法的链接,使用GROUP_CONCAT for SQLite:http://harkablog.com/inside-the-django-orm-aggregates.html

但是请注意,可能需要分别处理不同的 SQL 方言。例如SQLite docs say about group_concat

连接元素的顺序是任意的

而MySQL allows you to specify the order。

我想这可能是 GROUP_CONCAT 目前没有在 Django 中实现的原因。

【讨论】:

【参考方案9】:

补充@WeizhongTu的答案,注意不能在SQLITE中使用关键字SEPARATOR。如果您使用 MySQL 和 SQLite 进行测试,您可以编写:

class GroupConcat(Aggregate):
    function = 'GROUP_CONCAT'
    separator = ','

    def __init__(self, expression, distinct=False, ordering=None, **extra):
        super(GroupConcat, self).__init__(expression,
                                          distinct='DISTINCT ' if distinct else '',
                                          ordering=' ORDER BY %s' % ordering if ordering is not None else '',
                                          output_field=CharField(),
                                          **extra)

    def as_mysql(self, compiler, connection, separator=separator):
        return super().as_sql(compiler,
                              connection,
                              template='%(function)s(%(distinct)s%(expressions)s%(ordering)s%(separator)s)',
                              separator=' SEPARATOR \'%s\'' % separator)

    def as_sql(self, compiler, connection, **extra):
        return super().as_sql(compiler,
                              connection,
                              template='%(function)s(%(distinct)s%(expressions)s%(ordering)s)',
                              **extra)

【讨论】:

【参考方案10】:

如果您使用任何针对 MySQL 的建议解决方案,我只想说一句警告:默认情况下,MySQL 会将 GROUP_CONCAT 结果截断为 1024 个字符。我花了一些时间弄清楚为什么我得到了不存在的 id(它们被截断了存在的 id)。

您可以通过在 Django 设置中设置 group_concat_max_len 来避免限制。一个例子在这里:Include multiple statements in Django's raw queries

【讨论】:

【参考方案11】:

这是在 Django ORM 中工作的最佳方式

f1 = Fruits.objects.values('type').annotate(count = Count('type'),namelist= GroupConcat('namelist')).distinct()

【讨论】:

以上是关于Django 中的 GROUP_CONCAT 等效项的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql GROUP_CONCAT 等效?

s-s-rS - Group_Concat 使用表达式等效?

猪中的组 concat 等效项?

angular.js 中的 Django formset 等效项

Django Average 和 Group By 中的 MYSQL 等效查询

Django 10 模型层 model 元信息,对象优化查询,自定义group_concat,基于jq的ajex异步请求