Django 列出模型中另一个字段分组的字段列表
Django making a list of a field grouping by another field in model
我有一个名为 MyModel 的模型,它有一些虚拟数据如下:
item date value
------------------------------
ab 8/10/12 124
ab 7/10/12 433
ab 6/10/12 99
abc 8/10/12 23
abc 7/10/12 80
我想以这样的方式查询这个模型,得到如下输出:
[{'item': 'ab', 'values': [ 124, 433, 99]},
{'item': 'abc', 'values': [ 23, 80]}]
我如何使用 django ORM 执行此操作?
(2016 年 4 月 4 日)更新:这是适用于 Django <= 1.7 的有效解决方案。对于较新的版本,请阅读文档中的 Creating your own Aggregate Functions。
使用来自 here (an article about the topic)
的自定义 Concat
聚合
定义这个:
class Concat(models.Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
#we send source=CharField to prevent Django from casting string to int
aggregate = SQLConcat(col, source=models.CharField(), is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
#for mysql
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'group_concat'
@property
def sql_template(self):
if self.extra.get('separator'):
return '%(function)s(%(field)s SEPARATOR "%(separator)s")'
else:
return '%(function)s(%(field)s)'
#For PostgreSQL >= 9.0
#Aways use with separator, e.g. .annotate(values=Concat('value', separator=','))
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'string_agg'
@property
def sql_template(self):
#the ::text cast is a hardcoded hack to work with integer columns
return "%(function)s(%(field)s::text, '%(separator)s')"
#For PostgreSQL >= 8.4 and < 9.0
#Aways use with separator, e.g. .annotate(values=Concat('value', separator=','))
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'array_to_string'
@property
def sql_template(self):
return "%(function)s(array_agg(%(field)s), '%(separator)s')"
#For PostgreSQL < 8.4 you should define array_agg before using it:
#CREATE AGGREGATE array_agg (anyelement)
#(
# sfunc = array_append,
# stype = anyarray,
# initcond = '{}'
#);
class MyModel(models.Model):
item = models.CharField(max_length = 255)
date = models.DateTimeField()
value = models.IntegerField()
所以现在你可以做:
>>> from my_app.models import MyModel, Concat
>>> MyModel.objects.values('item').annotate(values=Concat('value'))
[{'item': u'ab', 'values': u'124,433,99'}, {'item': u'abc', 'values': u'23,80'}]
要获取 values
作为整数列表,您需要手动 .split
并转换为 int
。类似于:
>>> my_list = MyModel.objects.values('item').annotate(values=Concat('value'))
>>> for i in my_list:
... i['values'] = [int(v) for v in i['values'].split(',')]
...
>>> my_list
[{'item': u'ab', 'values': [124, 433, 99]}, {'item': u'abc', 'values': [23, 80]}]
如果您的 Django 由 PostgreSQL 支持,您可以结合使用 extra
和 string_agg
。
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#extra
http://www.postgresql.org/docs/9.4/static/sql-expressions.html#SYNTAX-AGGREGATES
假设您发布的 table 是一个 ManyToManyRelation,item
实际上是对您的模型 MyModel
的引用,模型 table 是 mymodel
映射 table 为 mymodel_value
:
MyModel.objects.extra(select={
'values':
"""
SELECT string_agg(value, ', ' ORDER BY value)
FROM mymodel_value
WHERE mymodel.id=mymodel_value.item
"""
}).values('values')
生成的字典将有一个条目 values
,其中一个字符串作为值,是每个 item
.
的连接(聚合)值列表
在 Django shell (./manage.py shell
) 中试试这个。您可能必须向子选择添加更多 table 以防 ORM 尚未添加这些内容。 (主要模型 table 肯定已经存在。)这取决于模型关系的复杂程度。
打开数据库日志记录以检查 ORM 生成的查询。
我有一个名为 MyModel 的模型,它有一些虚拟数据如下:
item date value
------------------------------
ab 8/10/12 124
ab 7/10/12 433
ab 6/10/12 99
abc 8/10/12 23
abc 7/10/12 80
我想以这样的方式查询这个模型,得到如下输出:
[{'item': 'ab', 'values': [ 124, 433, 99]},
{'item': 'abc', 'values': [ 23, 80]}]
我如何使用 django ORM 执行此操作?
(2016 年 4 月 4 日)更新:这是适用于 Django <= 1.7 的有效解决方案。对于较新的版本,请阅读文档中的 Creating your own Aggregate Functions。
使用来自 here (an article about the topic)
的自定义Concat
聚合
定义这个:
class Concat(models.Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
#we send source=CharField to prevent Django from casting string to int
aggregate = SQLConcat(col, source=models.CharField(), is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
#for mysql
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'group_concat'
@property
def sql_template(self):
if self.extra.get('separator'):
return '%(function)s(%(field)s SEPARATOR "%(separator)s")'
else:
return '%(function)s(%(field)s)'
#For PostgreSQL >= 9.0
#Aways use with separator, e.g. .annotate(values=Concat('value', separator=','))
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'string_agg'
@property
def sql_template(self):
#the ::text cast is a hardcoded hack to work with integer columns
return "%(function)s(%(field)s::text, '%(separator)s')"
#For PostgreSQL >= 8.4 and < 9.0
#Aways use with separator, e.g. .annotate(values=Concat('value', separator=','))
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'array_to_string'
@property
def sql_template(self):
return "%(function)s(array_agg(%(field)s), '%(separator)s')"
#For PostgreSQL < 8.4 you should define array_agg before using it:
#CREATE AGGREGATE array_agg (anyelement)
#(
# sfunc = array_append,
# stype = anyarray,
# initcond = '{}'
#);
class MyModel(models.Model):
item = models.CharField(max_length = 255)
date = models.DateTimeField()
value = models.IntegerField()
所以现在你可以做:
>>> from my_app.models import MyModel, Concat
>>> MyModel.objects.values('item').annotate(values=Concat('value'))
[{'item': u'ab', 'values': u'124,433,99'}, {'item': u'abc', 'values': u'23,80'}]
要获取 values
作为整数列表,您需要手动 .split
并转换为 int
。类似于:
>>> my_list = MyModel.objects.values('item').annotate(values=Concat('value'))
>>> for i in my_list:
... i['values'] = [int(v) for v in i['values'].split(',')]
...
>>> my_list
[{'item': u'ab', 'values': [124, 433, 99]}, {'item': u'abc', 'values': [23, 80]}]
如果您的 Django 由 PostgreSQL 支持,您可以结合使用 extra
和 string_agg
。
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#extra http://www.postgresql.org/docs/9.4/static/sql-expressions.html#SYNTAX-AGGREGATES
假设您发布的 table 是一个 ManyToManyRelation,item
实际上是对您的模型 MyModel
的引用,模型 table 是 mymodel
映射 table 为 mymodel_value
:
MyModel.objects.extra(select={
'values':
"""
SELECT string_agg(value, ', ' ORDER BY value)
FROM mymodel_value
WHERE mymodel.id=mymodel_value.item
"""
}).values('values')
生成的字典将有一个条目 values
,其中一个字符串作为值,是每个 item
.
在 Django shell (./manage.py shell
) 中试试这个。您可能必须向子选择添加更多 table 以防 ORM 尚未添加这些内容。 (主要模型 table 肯定已经存在。)这取决于模型关系的复杂程度。
打开数据库日志记录以检查 ORM 生成的查询。