在Django子查询中,我可以引用“父”查询吗?

时间:2013-03-16 19:25:52

标签: subquery where django-orm

在Django ORM中创建子查询很简单(只使用QuerySet作为另一个查询的一部分),但该子查询是否可以引用“父”(外部,主要)查询中的字段?< / p>

有关我正在尝试实现的完整示例,请参阅此工作SQL Fiddle。我将其分解为两个问题(other one here)。在这种情况下,我有一个模型Whole,表示必须达到的值。有几个Part以自己的(计算)值为其贡献。我想检索已经完成的所有Whole(即total_value与各个值的总和不同)。

select w.*
  from whole w
  where w.total_value != (
    select sum(value expression)
      from part p
      where p.whole_id = w.id
      group by p.whole_id
  );

我不知道如何(或者甚至可能)使用Django ORM来做到这一点。我使用__in看到了many examples子查询(并且可以通过print qs.query确认结果确实作为单个查询运行),但仅当两个查询都是相互独立。这里,子查询受父查询(w.id)中的字段约束。我想过使用F()Q(),甚至是extra,但还是不知道该怎么办......

这是一个SSCCE,如果有人想要试验它:DownloadBrowse。它与上面链接的SQL fiddle具有相同的模型和数据。


更新:对于我的特定情况,我发现不需要执行子查询,我可以使用group byhaving(作为this SQL Fiddle示出):

q = Q(part__isnull=True) | ~Q(partial=F('total_value'))
qs = Whole.objects.annotate(partial=Sum(...)).filter(q).distinct()

# And if total_value can be zero:
qs = qs.exclude(part__isnull=True, total_value=0)

子查询的一般情况仍然没有解决(没有使用一些原始SQL,如my answer below所示)。

1 个答案:

答案 0 :(得分:2)

我使用最少原始SQL设计的解决方案使用extrawhere

  • 首先创建内部查询;使用extra指定自定义where组件,将受限字段与外部查询中的字段进行比较,将显示在那里(可能需要对表名进行硬编码/别名):

    qs1 = Part.objects.extra(where=['whole_id = "applabel_whole"."id"'])...
    

    然后对其进行剩余的操作(在这种情况下,使用valuesannotate进行分组,聚合和返回单个字段)。

  • 然后在外部查询中包含内部查询的生成SQL(使用.query),同时使用extrawhere

    qs = Whole.objects.extra(where=['total_value != ({})'.format(qs1.query)])
    

extra调用中的代码片段可能无法移植(例如:某些后端使用!=,其他人使用<>,引用表名的正确方法可能不同,等等),但内部查询的其余部分应为(因为它是由ORM生成的)。

结果查询对应于我正在寻找的内容(除了the other question中涵盖的聚合部分)。为便于阅读而格式化的SQL:

>>> qs1 = Part.objects.extra(
        where=['whole_id = "aggregation_subquery_whole"."id"']
    ).values('whole_id').annotate(sum=Sum('before__value')).values('sum')

>>> qs = Whole.objects.extra(where=['total_value != ({})'.format(qs1.query)])

>>> print qs.query

SELECT "aggregation_subquery_whole"."id",
       "aggregation_subquery_whole"."total_value" 
FROM "aggregation_subquery_whole"
WHERE total_value != (
    SELECT SUM("aggregation_subquery_sequence"."value") AS "sum"
    FROM "aggregation_subquery_part"
        LEFT OUTER JOIN "aggregation_subquery_sequence" ON
           ("aggregation_subquery_part"."before_id" =
            "aggregation_subquery_sequence"."id") 
    WHERE whole_id = "aggregation_subquery_whole"."id"
    GROUP BY "aggregation_subquery_part"."whole_id"
)