sql - 使用 Django 的 ORM 进行复杂的 GROUP BY

标签 sql django postgresql django-models django-orm

我有一个跟踪电力消耗的 Django 应用程序,但我很难想出一种使用 Django 的 ORM 来获取一些信息的方法。

我的具体用例是这样的:我有一组用电量读数,每个读数都有一个日期时间字段、消耗量和成本(以及其他一些读数,但这些是相关的)。我需要对按月、年、电表和电价分组的消耗和成本值进行求和。换句话说,我需要能够获得每个月、每年、每个价格的总能源消耗值和相应的成本(如果您查看帖子下面的表格,会更容易理解)。

这是我的 ElectricityReading 模型及其父 Reading 模型(分开是因为我们还有水和天然气的消耗读数,这些读数也源自 Reading ):

from model_utils.models import TimeStampedModel
# Other imports here...

class Reading(TimeStampedModel):
    meter = models.ForeignKey(Meter)
    datetime = models.DateTimeField() # Terrible property name, I know :)

    class Meta:
        abstract = True

class ElectricityReading(Reading):
    price = models.ForeignKey(ElectricityPrice)
    consumption = models.DecimalField(max_digits=18, decimal_places=3,
                                  null=True, blank=True, default=None)
    cost = models.DecimalField(max_digits=18, decimal_places=3, null=True,
                           blank=True, default=None)

现在我正在使用这个原始 SQL 来执行此操作,它是根据几个参数构建的:

SELECT
    (EXTRACT(YEAR FROM datetime)) AS reading_date_year,
    (EXTRACT(MONTH FROM datetime)) AS reading_date_month,
    SUM(consumption) as total_consumption,
    SUM(cost) as total_cost,
    COUNT(id) as num_readings,
    price_id
FROM electricity_reading
WHERE meter_id IN (10)
    AND datetime >= '2015-10-01 00:00'
    AND datetime <= '2015-12-31 23:59'
GROUP BY reading_date_year, reading_date_month, price_id, meter_id
ORDER BY meter_id, reading_date_year, reading_date_month, price_id

此 SQL 查询结果类似于以下数据(为了更好的格式而编造值和简化的列名称):

╔══════╦═══════╦═════════════╦══════╦══════════════╦═══════╗
║ year ║ month ║ consumption ║ cost ║ num_readings ║ price ║
╠══════╬═══════╬═════════════╬══════╬══════════════╬═══════╣
║ 2015 ║    10 ║         600 ║  804 ║          456 ║     1 ║
║ 2015 ║    10 ║         728 ║  471 ║         1998 ║     2 ║
║ 2015 ║    10 ║         848 ║  792 ║         1266 ║     3 ║
║ 2015 ║    10 ║         256 ║  705 ║          744 ║     5 ║
║ 2015 ║    11 ║         528 ║  377 ║          630 ║     1 ║
║ 2015 ║    11 ║         016 ║  687 ║         1680 ║     2 ║
║ 2015 ║    11 ║         240 ║  826 ║         1289 ║     3 ║
║ 2015 ║    11 ║         736 ║  522 ║          720 ║     5 ║
║ 2015 ║    12 ║         584 ║  627 ║          608 ║     1 ║
║ 2015 ║    12 ║         776 ║  078 ║         1627 ║     2 ║
║ 2015 ║    12 ║         600 ║  401 ║         1410 ║     3 ║
║ 2015 ║    12 ║         864 ║  842 ║          744 ║     5 ║
╚══════╩═══════╩═════════════╩══════╩══════════════╩═══════╝

Using Django's ORM, I think the code I need is something along the lines of the following:

objs = ElectricityReading.objects\
    .filter(
        meter=10,
        datetime__gte='2015-05-01 00:00',
        datetime__lte='2015-08-31 23:59'
    ).only('price_id')\
    .annotate(reading_date_year=YearTransform('datetime'))\
    .annotate(reading_date_month=MonthTransform('datetime'))\
    .annotate(total_consumption=Sum('consumption'))\
    .annotate(total_cost=Sum('cost'))\
    .annotate(num_readings=Count('id'))\
    .order_by('meter_id', 'reading_date_year', 'reading_date_month', 'price_id')

但是它生成的 SQL 不是我需要的:

SELECT
    id,
    price_id,
    EXTRACT('year' FROM datetime AT TIME ZONE 'Europe/Lisbon') AS reading_date_year,
    EXTRACT('month' FROM datetime AT TIME ZONE 'Europe/Lisbon') AS reading_date_month,
    SUM(consumption) AS total_consumption, SUM(cost) AS total_cost,
    COUNT(id) AS num_readings
FROM geratriz_electricityreading
WHERE (
    datetime >= '2015-05-01 00:00:00+01:00'
    AND datetime <= '2015-08-31 23:59:00+01:00'
    AND meter_id = 10)
GROUP BY
    id,
    EXTRACT('year' FROM datetime AT TIME ZONE 'Europe/Lisbon'),
    EXTRACT('month' FROM datetime AT TIME ZONE 'Europe/Lisbon')
ORDER BY meter_id ASC, reading_date_year ASC, reading_date_month ASC, price_id ASC

这会导致从数据库返回更多行,因为它们没有按照我需要的方式进行分组。

我似乎无法使用 Django 的 ORM 复制的 SQL 查询部分是末尾的 GROUP BY 子句。 Django 坚持按 ID 分组,但我似乎找不到一种方法让它按meter_id 和price_id 分组。

考虑到我已经在这方面花费了多少时间,我倾向于说我想要完成的事情根本不可能用 Django 的 ORM 实现,但我希望有人会告诉我我错过了一些东西。

最佳答案

尝试使用values()

objs = ElectricityReading.objects\
    .filter(
        meter=10,
        datetime__gte='2015-05-01 00:00',
        datetime__lte='2015-08-31 23:59'
    .values('price_id')\
    .annotate(reading_date_year=YearTransform('datetime'))\
    .annotate(reading_date_month=MonthTransform('datetime'))\
    .annotate(total_consumption=Sum('consumption'))\
    .annotate(total_cost=Sum('cost'))\
    .annotate(num_readings=Count('id'))\
    .order_by('meter_id', 'reading_date_year', 'reading_date_month', 'price_id')

这应该按 price_id 对结果进行分组。如果您一次显示多个仪表而不是 meter=10,那么您可以执行 values('price_id', 'meter') ,它会在两个字段上进行分组。

关于sql - 使用 Django 的 ORM 进行复杂的 GROUP BY,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34748530/

相关文章:

MySQL查询: UPDATE on duplicate key,追加数据

mysql - 选择每个 ID 的前 3 次出现

sql - 查询删除 WordPress 中早于 X 天的帖子

python - 默认启用 Django 格式本地化

java - JOOQ 查询的时间戳精度

sql - 是否可以在多个更新中使用 WITH 子句而不将其复制到每个更新中?

sql - 如何在 SELECT 语句中设置货币和小数的格式

即使使用模型实例实例化,Django 表单也显示为未绑定(bind)

Django 1.5.5 始终显示原始 (en) 字符串(不翻译)

postgresql - 在 DBD::Pg 中是否有等效的 $dbh->sqlite_create_function