【问题标题】:Django ORM how to get raw values grouped by a fieldDjango ORM如何获取按字段分组的原始值
【发布时间】: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 模型。
  • 我已经基于此添加了一个答案,以防它对您有用,但如果它不起作用,它可能对其他人有用。
  • 您能展示一下您认为会创建此数据结构的 SQL 查询吗? GROUP BY 只会为每个结果行返回一行读数...

标签: python python-3.x django django-orm


【解决方案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_aggjson_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},
       ...]}
    
    

    【讨论】:

      猜你喜欢
      • 2010-12-01
      • 2018-02-22
      • 2011-04-08
      • 1970-01-01
      • 2013-09-22
      相关资源
      最近更新 更多