Django中的GROUP_CONCAT等价物

问题描述 投票:14回答:9

假设我有下表名为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
9个回答
3
投票

Django ORM不支持这个;如果你不想使用原始SQL,那么你需要group and join


34
投票

您可以创建自己的聚合函数(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


7
投票

注意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')

4
投票

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

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

3
投票

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


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'),
...     types_list=GroupConcat('type'),
... ).order_by('-count').values('type', 'count', 'types_list')
[{'type': 'apple', 'count': 2, 'types_list': 'fuji,mac'},
 {'type': 'orange', 'count': 1, 'types_list': 'navel'}]

2
投票

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

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


1
投票

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中实现的原因。


1
投票

要完成@WeizhongTu的答案,请注意您不能将关键字SEPARATOR与SQLITE一起使用。如果您使用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)
© www.soinside.com 2019 - 2024. All rights reserved.