Django Orm:自定义选择具有分组值的聚合函数的列

时间:2014-01-22 10:52:13

标签: python sql django postgresql orm

我的postgres数据库表看起来(简化)如下:timeframe :: timestamp,value :: integer。

我有一个分组查询(分组时间范围:1小时,1天,一周等),我有不同的聚合函数。

示例查询在sql中显示如下:

SELECT
    date_trunc(%s, timeframe),
    SUM(CASE WHEN metric = 'visitors' THEN value ELSE 0 END) / NULLIF(SUM(CASE WHEN metric = 'total' THEN value ELSE 0 END), 0) as value
FROM aggregation_metrichour
GROUP BY date_trunc(%s, timeframe)

我可以使用原始查询,但我需要orm动态过滤特定日期范围,其他一些信息并进行授权。

我的查询中唯一会改变的部分是价值是计算机的方式,我在这里使用不同的东西,如产品总和,平均值和总和。

我试图用orm构建它,但失败了。

我想要做的是在我的django查询集中添加一个自定义选择列,其中包括一些带有聚合函数的奇特原始计算。 对我来说最好的方法是只计算一个字符串并将其添加到查询集中。

这是我到目前为止所做的:

简单的默认聚合函数

result = MetricHour.objects \
    .extra(select={'date': 'date_trunc(%s, timeframe)'}, select_params=[interval]) \
    .values('date') \
    .annotate(value=Sum('value'))

 print 'simple', result.query
 # simple SELECT (date_trunc(day, timeframe)) AS "date",
 # SUM("aggregation_metrichour"."value") AS "value" FROM
 # "aggregation_metrichour" GROUP BY (date_trunc(day, timeframe))

使用额外选择

result = MetricHour.objects \
    .extra(select={'date': 'date_trunc(%s, timeframe)'}, select_params=[interval]) \
    .extra(select={'value': "SUM(CASE WHEN metric = 'visitors' THEN value ELSE 0 END) / NULLIF (SUM(value), 0)"}) \
    .values('date', 'value')

print 'extra-select', result.query # wont add a group by
# extra-select SELECT (date_trunc(day, timeframe)) AS "date",
# (SUM(CASE WHEN metric = 'visitors' THEN value ELSE 0 END) /
# NULLIF (SUM(value), 0)) AS "value" FROM "aggregation_metrichour"

自定义聚合函数

然后我找到this stackoverflow question关于如何编写自己的聚合函数。但是我认为django中的代码改变了。我现在需要在VisitorRate(models.Aggregate)中设置一个名称作为字符串,我不知道如何添加新的自定义类型

class VisitorRateSql(models.sql.aggregates.Sum):
    sql_template = "SUM(CASE WHEN metric = visitors' THEN value ELSE 0 END) / NULLIF (SUM(value), 0)"

class VisitorRate(models.Sum):
    name = 'Sum'
    sql = VisitorRateSql
    def add_to_query(self, query, alias, col, source, is_summary):
            aggregate = VisitorRateSql(col,
                                       source=source,
                                       is_summary=is_summary,
                                       **self.extra)
            query.aggregates[alias] = aggregate


result = MetricHour.objects \
    .extra(select={'date': 'date_trunc(%s, timeframe)'}, select_params=[interval]) \
    .values('date') \
    .annotate(value=VisitorRate('value'))

print "Annotate Class", result.query
# Annotate Class SELECT (date_trunc(day, timeframe)) AS "date",
# SUM(CASE WHEN metric = visitors' THEN value ELSE 0 END) /
# NULLIF (SUM(value), 0) AS "value" FROM "aggregation_metrichour"
# GROUP BY (date_trunc(day, timeframe))

更新:我没有完全理解models.Aggregate函数 这看起来更好。但是我确实需要为我的用例简化这个。我想将sql_template直接提供给注释函数

我想知道如何轻松地为我的分组查询添加新的自定义选择查询! 任何帮助表示赞赏!

1 个答案:

答案 0 :(得分:2)

我找到了一个很好的解决方案,其参数化类可用作聚合函数:

def custom_aggregation(select_query):
    class SqlAggregate(models.sql.aggregates.Aggregate):
        sql_function = ''
        sql_template = select_query

    class VisitorRate(models.Aggregate):
        sql = SqlAggregate
        def add_to_query(self, query, alias, col, source, is_summary):
            aggregate = self.sql(col,
                                 source=source,
                                 is_summary=is_summary,
                                 **self.extra)
            query.aggregates[alias] = aggregate

    return VisitorRate


    aggregate_query = "SUM(CASE WHEN metric = visitors' THEN value ELSE 0 END) / NULLIF (SUM(value), 0)"
    AggregationFunction = custom_aggregation(aggregate_query)

    result = MetricHour.objects \
        .extra(select={'date': 'date_trunc(%s, timeframe)'}, select_params=[interval]) \
        .values('date') \
        .annotate(value=AggregationFunction('value'))