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 等效项的主要内容,如果未能解决你的问题,请参考以下文章
s-s-rS - Group_Concat 使用表达式等效?
angular.js 中的 Django formset 等效项
Django Average 和 Group By 中的 MYSQL 等效查询
Django 10 模型层 model 元信息,对象优化查询,自定义group_concat,基于jq的ajex异步请求