我有一个 Postgres sql 查询,如下所示
SELECT Date_trunc('hour', batch_time)
+ Date_part('minute', batch_time) :: INT / 10 * interval '10 min',
Round(Avg(min_metric) :: NUMERIC, 2),
metric_notify
FROM (SELECT batch_time,
Min("uptime") AS min_metric,
Coalesce(CASE
WHEN notify LIKE '%HIGH%' THEN 'HIGH'
ELSE ''
END, '') AS metric_notify
FROM lt_store
WHERE batch_time >= '2024-01-14 09:01:26.656059'
AND batch_time <= '2024-01-15 09:01:26.656055'
GROUP BY 1,
3
ORDER BY 1 ASC) AS subq
GROUP BY 1,
3
如您所见,有两个查询。在内部查询中,我使用 group by 按时间对指标进行
min
聚合,在外部查询中,我使用 10 分钟时间间隔截断对指标进行
avg
聚合。
现在我计划在 django 查询中复制相同的内容,这就是我所做的
result1 = LtStore.objects.filter(batch_time__gte='2024-01-14 09:01:26.656059',
batch_time__lte='2024-01-15 09:01:26.656055'
).values ('batch_time').annotate(min_metric=Round(Min('uptime')),
metric_notify=Coalesce(Max(
Case(
When(notify__contains='HIGH',
then=Value('HIGH')),
default=Value(''),
)),
Value('')
),
).order_by("batch_time")
trunc_query = "date_trunc('hour', LtStore.batch_time) + date_part('minute', LtStore.batch_time)::int / 10 * interval '10 min'"
result2 = LtStore.objects.annotate(
min_metric=Subquery(result1.values('min_metric')[:1]),
metric_notify=Subquery(result1.values('metric_notify')[:1])
).extra({"trunc": trunc_query}).values(
'trunc', 'metric_notify'
).annotate(
avg_metric=Avg('min_metric')
).order_by("trunc")
上面的 django 查询有效,但与 sql 查询相比,我得到了不同的结果。
我做错了什么?
您的 Django 查询和 SQL 查询在结构上不同,这可能会导致不同的结果。以下区域可能会导致问题:
分组和聚合:
batch_time
和 metric_notify
进行分组,然后在外部查询中按 batch_time
(截断为 10 分钟间隔)和 metric_notify
进行分组。batch_time
查询中按 result1
进行分组,而不是按 metric_notify
进行分组。这可能会导致最小指标的计算方式出现差异。子查询用法:
Subquery(result1.values('min_metric')[:1])
) 来提取最小指标和指标通知状态。但是,此方法不会复制与 SQL 查询相同的逻辑。您的 SQL 查询计算每个组的最小和平均指标,而您的 Django 子查询只是从 result1
查询中获取第一个值,这可能与外部查询所需的分组不匹配。时间截断:
batch_time
截断为 10 分钟间隔,但在 Django 查询中,您使用的 .extra({"trunc": trunc_query})
可能不会以相同的方式执行截断。确保两个查询之间的时间截断逻辑一致非常重要。要解决这些问题,您可以在 Django 查询中尝试进行以下调整:
result1
查询中的分组与 SQL 查询匹配,包括 batch_time
和 metric_notify
。这是 Django 查询的修订版本,它尝试与您的 SQL 查询更紧密地结合:
from django.db.models import Min, Avg, Value, Case, When
from django.db.models.functions import Round, Coalesce
# Inner query equivalent
result1 = LtStore.objects.filter(
batch_time__gte='2024-01-14 09:01:26.656059',
batch_time__lte='2024-01-15 09:01:26.656055'
).annotate(
metric_notify=Coalesce(
Case(
When(notify__contains='HIGH', then=Value('HIGH')),
default=Value(''),
),
Value('')
)
).values('batch_time', 'metric_notify').annotate(
min_metric=Round(Min('uptime
'))
).order_by('batch_time', 'metric_notify')
# Outer query equivalent
from django.db.models.expressions import RawSQL
trunc_query = RawSQL("date_trunc('hour', batch_time) + date_part('minute', batch_time)::int / 10 * interval '10 min'", [])
result2 = result1.annotate(
trunc=trunc_query
).values(
'trunc', 'metric_notify'
).annotate(
avg_metric=Avg('min_metric')
).order_by('trunc')
此修订后的 Django 查询尝试更紧密地复制 SQL 查询的逻辑:
batch_time
) 中按 metric_notify
和 result1
进行分组。RawSQL
在外部查询中执行时间截断(result2
),这应该更符合 SQL 查询的逻辑。annotate
和values
方法用于复制SQL查询的聚合和分组逻辑。请记住,复杂的 SQL 逻辑有时很难在 Django 的 ORM 中精确复制,并且可能仍然存在需要调整的细微差别。确保彻底测试并根据需要进行调整,以确保结果符合您的期望。