【问题标题】:GROUP_CONCAT equivalent in DjangoDjango 中的 GROUP_CONCAT 等效项
【发布时间】: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 查询。

任何帮助将不胜感激!

谢谢! =)

【问题讨论】:

    标签: mysql django group-concat


    【解决方案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,例子在这里:stackoverflow.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()
                

                【讨论】:

                  猜你喜欢
                  • 2013-05-03
                  • 2021-09-21
                  • 2015-02-06
                  • 1970-01-01
                  • 2016-08-22
                  • 1970-01-01
                  • 2016-07-13
                  相关资源
                  最近更新 更多