2013-03-16 3 views
1

Простое создание подзапросов в Django ORM (просто используйте QuerySet как часть другого запроса), но возможно ли, чтобы этот подзапрос ссылался на поля в «родительском» (внешнем, основном) запросе?В подзапросе Django можно ли ссылаться на «родительский» запрос?

Полный пример того, что я пытаюсь достичь, см. В этой статье SQL Fiddle. Я разделил его на два вопроса (other one here). В этом случае у меня есть модель Whole, которая представляет значение, которое должно быть достигнуто. Несколько Part s вносят вклад в это с собственным (рассчитанным) значением. Я хочу получить все Whole s с не еще не закончено (то есть 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. Я видел manyexamples подзапросов с использованием __in (и мог подтвердить print qs.query, что результат действительно запущен как один запрос), но только тогда, когда оба запроса не зависят друг от друга. Здесь подзапрос ограничивается полем в родительском запросе (w.id). Я думал об использовании F(), Q() или даже extra, но не вполне может понять, что делать ...

Вот SSCCE, в случае, если кто хочет поэкспериментировать с ним: Download или Browse. Он имеет те же модели и данные, что и связанный выше SQL fiddle.


Update: для моего конкретного случая, я узнал, что нет необходимости делать подзапрос, я могу просто использовать group by и having (как 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).

ответ

2

Решение, которое я придумал с наименьшим сырой SQL использует extra и where:

  • Сначала нужно создать внутренний запрос; использовать extra указать пользовательский компонент where, сравнивая ограниченное поле для одного внешнего запроса, в нем будут появляться там (возможно, потребуется Кодирую имя таблицы/псевдоним):

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

    Затем сделать оставшиеся операции на нем (в данном случае, используя values и annotate для группировки, агрегации и возврата одного поля).

  • Затем включите сгенерированного SQL внутреннего запроса (с использованием .query) во внешнем запросе, а также с помощью extra и where:

    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" 
) 
Смежные вопросы