注释自定义 SQL 函数(类似于 date_trunc)到 Django ORM 查询集
Annotate Custom SQL Function (similar to date_trunc) to Django ORM Queryset
我正在使用 timescaledb
,它基本上只是 postgres
的扩展。它带有一个名为 time_bucket
的 SQL 函数。我想使用这个函数结合ORM生成如下查询:
SELECT
time_bucket('1 minute', time) AS tb,
AVG(s0)
FROM measurements
WHERE
time >= to_timestamp(1) AND
time <= to_timestamp(2)
GROUP BY tb
ORDER BY tb ASC;
models.py
:
class Measurement(models.Model):
device_id = models.IntegerField(primary_key=True)
time = models.DateTimeField()
s0 = models.FloatField(blank=True, null=True)
s1 = models.FloatField(blank=True, null=True)
到目前为止我的尝试:
class TimeBucket(Func):
function = 'time_bucket'
template = '%(function)s(\'{bucket_width}\', %(expressions)s)'.format(bucket_width='1 minute')
(Measurement.objects
.values('time')
.annotate(tb=TimeBucket('time'))
.annotate(s_desc=Avg('s0'))
.filter(
time__gte=datetime.fromtimestamp(start),
time__lte=datetime.fromtimestamp(end))
.order_by('tb')
)
结果:
SELECT
"measurements"."time",
time_bucket('1 minute', "measurements"."time") AS "tb",
(AVG("measurements"."s0")) AS "s_desc"
FROM "measurements"
WHERE (
"measurements"."time" <= 2447-10-02 14:17:01+00:00 AND
"measurements"."time" >= 1970-01-01 00:00:01+00:00
)
GROUP BY "measurements"."time", time_bucket('1 minute', "measurements"."time")
ORDER BY "tb" ASC
如你所见,还有两个难看的点:
- 如何在
GROUP BY
中使用别名 tb
而不是重复它?
- 我只需要查询
time_bucket
和s0
。如何在不中断查询的情况下摆脱 time
?
un-truncated列time
可以在过滤器before[=30]中使用仅 =] 第一个 annotate(...)
以后再也不会了。
必须在.values()
(例如.values('tb')
)任何聚合函数之前使用截断时间注释。
qs = (
Measurement.objects
.filter(...)
.annotate(tb=TimeBucket('time'))
.values('tb')
.annotate(s_desc=Avg('s0'))
.order_by('tb')
)
About alias in GROUP BY:别名一般只能在所有数据库的ORDER BY中使用,但只有特定的数据库才允许在GROUP BY或WHERE中使用(仅MySQL和Postgresql)。这可能是因为 GROUP BY 和 WHERE 子句在 SELECT 子句之前计算。这可以通过子查询绕过,但根本没有用。我确信每个现代数据库驱动程序的标准查询计划优化器都会重用 GROUP BY 中的辅助表达式,并且它从不重复计算函数。如果 SQL 是手动编写并由人类读取的,则别名很有用,但仅对某些后端通过别名在 Django ORM 编译器中实现它没有用。
编辑:此解决方案适用于 Django >= 1.11 并在 Django 3.0 中也经过测试。
(有关 Django 版本的信息不正确。我不记得 Django <=1.10 中的解决方案,但我确定它比 Django 1.11 中的更复杂。也许必须写一个额外的最终 .values('tb', 's_desc'))
我正在使用 timescaledb
,它基本上只是 postgres
的扩展。它带有一个名为 time_bucket
的 SQL 函数。我想使用这个函数结合ORM生成如下查询:
SELECT
time_bucket('1 minute', time) AS tb,
AVG(s0)
FROM measurements
WHERE
time >= to_timestamp(1) AND
time <= to_timestamp(2)
GROUP BY tb
ORDER BY tb ASC;
models.py
:
class Measurement(models.Model):
device_id = models.IntegerField(primary_key=True)
time = models.DateTimeField()
s0 = models.FloatField(blank=True, null=True)
s1 = models.FloatField(blank=True, null=True)
到目前为止我的尝试:
class TimeBucket(Func):
function = 'time_bucket'
template = '%(function)s(\'{bucket_width}\', %(expressions)s)'.format(bucket_width='1 minute')
(Measurement.objects
.values('time')
.annotate(tb=TimeBucket('time'))
.annotate(s_desc=Avg('s0'))
.filter(
time__gte=datetime.fromtimestamp(start),
time__lte=datetime.fromtimestamp(end))
.order_by('tb')
)
结果:
SELECT
"measurements"."time",
time_bucket('1 minute', "measurements"."time") AS "tb",
(AVG("measurements"."s0")) AS "s_desc"
FROM "measurements"
WHERE (
"measurements"."time" <= 2447-10-02 14:17:01+00:00 AND
"measurements"."time" >= 1970-01-01 00:00:01+00:00
)
GROUP BY "measurements"."time", time_bucket('1 minute', "measurements"."time")
ORDER BY "tb" ASC
如你所见,还有两个难看的点:
- 如何在
GROUP BY
中使用别名tb
而不是重复它? - 我只需要查询
time_bucket
和s0
。如何在不中断查询的情况下摆脱time
?
un-truncated列time
可以在过滤器before[=30]中使用仅 =] 第一个 annotate(...)
以后再也不会了。
必须在.values()
(例如.values('tb')
)任何聚合函数之前使用截断时间注释。
qs = (
Measurement.objects
.filter(...)
.annotate(tb=TimeBucket('time'))
.values('tb')
.annotate(s_desc=Avg('s0'))
.order_by('tb')
)
About alias in GROUP BY:别名一般只能在所有数据库的ORDER BY中使用,但只有特定的数据库才允许在GROUP BY或WHERE中使用(仅MySQL和Postgresql)。这可能是因为 GROUP BY 和 WHERE 子句在 SELECT 子句之前计算。这可以通过子查询绕过,但根本没有用。我确信每个现代数据库驱动程序的标准查询计划优化器都会重用 GROUP BY 中的辅助表达式,并且它从不重复计算函数。如果 SQL 是手动编写并由人类读取的,则别名很有用,但仅对某些后端通过别名在 Django ORM 编译器中实现它没有用。
编辑:此解决方案适用于 Django >= 1.11 并在 Django 3.0 中也经过测试。
(有关 Django 版本的信息不正确。我不记得 Django <=1.10 中的解决方案,但我确定它比 Django 1.11 中的更复杂。也许必须写一个额外的最终 .values('tb', 's_desc'))