Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GROUP_CONCAT equivalent in Django

Say I have the following table called fruits:

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

My goal is to ultimately come up with a count of the different types and a comma-delimited list of the names:

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

This can be easily done with GROUP_CONCAT in MySQL but I'm having trouble with the Django equivalent. This is what I have so far but I am missing the GROUP_CONCAT stuff:

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

I would like to avoid using raw SQL queries if possible.

Any help would be greatly appreciated!

Thanks! =)

like image 758
Allen Liu Avatar asked Apr 26 '12 20:04

Allen Liu


6 Answers

You can create your own Aggregate Function (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)

and use it simply as:

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

I am using django 1.8 and mysql 4.0.3

like image 165
Shashank Singla Avatar answered Nov 18 '22 19:11

Shashank Singla


NOTICE that Django (>=1.8) provides Database functions support. https://docs.djangoproject.com/en/dev/ref/models/database-functions/#concat

Here is an enhanced version of 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
        )

Usage:

LogModel.objects.values('level', 'info').annotate(
    count=Count(1), time=GroupConcat('time', ordering='time DESC', separator=' | ')
).order_by('-time', '-count')
like image 32
WeizhongTu Avatar answered Nov 18 '22 19:11

WeizhongTu


Use GroupConcat from the Django-MySQL package ( https://django-mysql.readthedocs.org/en/latest/aggregates.html#django_mysql.models.GroupConcat ) which I maintain. With it you can do it simply like:

>>> 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'}]
like image 8
Adam Johnson Avatar answered Nov 18 '22 19:11

Adam Johnson


If you are using PostgreSQL, you can use ArrayAgg to aggregate all of the values into an array.

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

like image 4
Jin Avatar answered Nov 18 '22 21:11

Jin


If you don't mind doing this in your template the Django template tag regroup accomplishes this

like image 3
daseme Avatar answered Nov 18 '22 19:11

daseme


As of Django 1.8 you can use Func() expressions.

query_set = Fruits.objects.values('type').annotate(
    count=Count('type'),
    name=Func(F('name'), 'GROUP_BY')
).order_by('-count')
like image 3
Joel Davis Avatar answered Nov 18 '22 19:11

Joel Davis