Django ORM如何获取按字段分组的原始值
Posted
技术标签:
【中文标题】Django ORM如何获取按字段分组的原始值【英文标题】:Django ORM how to get raw values grouped by a field 【发布时间】:2020-10-27 03:58:15 【问题描述】:我有一个这样的模型:
class CPUReading(models.Model):
host = models.CharField(max_length=256)
reading = models.IntegerField()
created = models.DateTimeField(auto_now_add=True)
我正在尝试获得如下所示的结果:
"host 1": [
"created": DateTimeField(...),
"value": 20
,
"created": DateTimeField(...),
"value": 40
,
...
],
"host 2": [
"created": DateTimeField(...),
"value": 19
,
"created": DateTimeField(...),
"value": 10
,
...
]
我需要按主机分组并按created
排序。
我尝试了很多东西,包括使用values()
和annotate()
来创建GROUP BY
语句,但我认为我必须遗漏一些东西,因为为了使用GROUP BY
,我似乎需要使用一些我不想做的聚合函数。我需要reading
字段的实际值,该字段按主机字段分组并按创建的字段排序。
这或多或少是任何图表库需要数据的方式。
我知道我可以使用 python 代码或原始 sql 查询来实现它,但我更喜欢使用 django ORM,除非它明确禁止这种查询。
【问题讨论】:
在搞砸了一点之后,我认为 ORM 中没有任何内容,如果您希望在数据库中完成此操作,则需要使用原始查询。我认为如果你的数据库被规范化,你可能会更轻松,即你有一个Host
模型。
我已经基于此添加了一个答案,以防它对您有用,但如果它不起作用,它可能对其他人有用。
使用itertools.groupby and operator.itemgetter
您能展示一下您认为会创建此数据结构的 SQL 查询吗? GROUP BY 只会为每个结果行返回一行读数...
【参考方案1】:
据我所知,ORM 中没有任何东西可以让这变得简单。如果您想在没有原始查询的情况下在 ORM 中执行此操作,并且如果您愿意并且能够更改数据结构,则可以主要在 ORM 中解决此问题,并将 Python 代码保持在最低限度:
class Host(models.Model):
pass
class CPUReading(models.Model):
host = models.ForeignKey(Host, related_name="readings", on_delete=models.CASCADE)
reading = models.IntegerField()
created = models.DateTimeField(auto_now_add=True)
有了这个,你可以使用两个查询代码相当干净:
from collections import defaultdict
results = defaultdict(list)
hosts = Host.objects.prefetch_related("readings")
for host in hosts:
for reading in host.readings.all():
results[host.id].append(
"created": reading.created, "value": reading.reading
)
或者你可以用一个查询和一个循环更有效地做到这一点:
from collections import defaultdict
results = defaultdict(list)
readings = CPUReading.objects.select_related("host")
for reading in readings:
results[reading.host.id].append(
"created": reading.created, "value": reading.reading
)
【讨论】:
这可能是我能得到的最好答案。我真的对 django orm 的这一方面感到沮丧。除非有人在第二天左右使用数据库给我答案,否则我会给你赏金。【参考方案2】:假设您使用的是 PostgreSQL,您可以结合使用 array_agg
和 json_object
来实现您所追求的目标。
from django.contrib.postgres.aggregation import ArrayAgg
from django.contrib.postgres.fields import ArrayField, JSONField
from django.db.models import CharField
from django.db.models.expressions import Func, Value
class JSONObject(Func):
function = 'json_object'
output_field = JSONField()
def __init__(self, **fields):
fields, expressions = zip(*fields.items())
super().__init__(
Value(fields, output_field=ArrayField(CharField())),
Func(*expressions, template='array[%(expressions)s]'),
)
readings = dict(CPUReading.objects.values_list(
'host',
ArrayAgg(
JSONObject(
created_at='created_at',
value='value',
),
ordering='created_at',
),
))
【讨论】:
【参考方案3】:如果您想与 Django ORM 保持密切联系,您只需要记住它不会返回查询集而是字典,并且会动态评估,因此不要在声明性范围内使用它。不过接口和QuerySet.values()类似,还有一个额外的要求,就是需要先排序。
class PlotQuerySet(models.QuerySet):
def grouped_values(self, key_field, *fields, **expressions):
if key_field not in fields:
fields += (key_field,)
values = self.values(*fields, **expressions)
data =
for key, gen in itertools.groupby(values, lambda x: x.pop(key_field)):
data[key] = list(gen)
return data
PlotManager = models.Manager.from_queryset(PlotQuerySet, class_name='PlotManager')
class CpuReading(models.Model):
host = models.CharField(max_length=255)
reading = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
objects = PlotManager()
例子:
CpuReading.objects.order_by(
'host', 'created_at'
).grouped_values(
'host', 'created_at', 'reading'
)
Out[10]:
'a': ['created_at': datetime.datetime(2020, 7, 13, 16, 45, 23, 215005, tzinfo=<UTC>),
'reading': 0,
'created_at': datetime.datetime(2020, 7, 13, 16, 45, 23, 223080, tzinfo=<UTC>),
'reading': 1,
'created_at': datetime.datetime(2020, 7, 13, 16, 45, 23, 230218, tzinfo=<UTC>),
'reading': 2,
...],
'b': ['created_at': datetime.datetime(2020, 7, 13, 16, 45, 23, 241476, tzinfo=<UTC>),
'reading': 0,
'created_at': datetime.datetime(2020, 7, 13, 16, 45, 23, 242015, tzinfo=<UTC>),
'reading': 1,
'created_at': datetime.datetime(2020, 7, 13, 16, 45, 23, 242537, tzinfo=<UTC>),
'reading': 2,
...]
【讨论】:
以上是关于Django ORM如何获取按字段分组的原始值的主要内容,如果未能解决你的问题,请参考以下文章