如何在Django ORM中实现子查询?

问题描述 投票:0回答:1

我有一个 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 查询相比,我得到了不同的结果。

我做错了什么?

sql django postgresql subquery
1个回答
0
投票

您的 Django 查询和 SQL 查询在结构上不同,这可能会导致不同的结果。以下区域可能会导致问题:

  1. 分组和聚合:

    • 在 SQL 查询中,您在内部查询中按
      batch_time
      metric_notify
      进行分组,然后在外部查询中按
      batch_time
      (截断为 10 分钟间隔)和
      metric_notify
      进行分组。
    • 在 Django 查询中,您似乎仅在
      batch_time
      查询中按
      result1
      进行分组,而不是按
      metric_notify
      进行分组。这可能会导致最小指标的计算方式出现差异。
  2. 子查询用法:

    • Django 查询使用子查询 (
      Subquery(result1.values('min_metric')[:1])
      ) 来提取最小指标和指标通知状态。但是,此方法不会复制与 SQL 查询相同的逻辑。您的 SQL 查询计算每个组的最小和平均指标,而您的 Django 子查询只是从
      result1
      查询中获取第一个值,这可能与外部查询所需的分组不匹配。
  3. 时间截断:

    • SQL 查询在外部查询中将
      batch_time
      截断为 10 分钟间隔,但在 Django 查询中,您使用的
      .extra({"trunc": trunc_query})
      可能不会以相同的方式执行截断。确保两个查询之间的时间截断逻辑一致非常重要。

要解决这些问题,您可以在 Django 查询中尝试进行以下调整:

  • 确保
    result1
    查询中的分组与 SQL 查询匹配,包括
    batch_time
    metric_notify
  • 修改子查询的用法,以根据内部查询中定义的组正确聚合数据。
  • 仔细检查时间截断逻辑以确保其与 SQL 查询的逻辑匹配。

这是 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 中精确复制,并且可能仍然存在需要调整的细微差别。确保彻底测试并根据需要进行调整,以确保结果符合您的期望。

© www.soinside.com 2019 - 2024. All rights reserved.