Django ORM множественный эквивалент цепочки JOIN и агрегация
Для следующих моделей Django (лоты показаны просто как пример, могут быть более или менее вложенными):
class ModelA(models.Model):
value = models.IntegerField()
class ModelB(models.Model):
modelA = models.ForeignKey(ModelA, on_delete=models.CASCADE)
value = models.IntegerField()
class ModelC(models.Model):
modelB = models.ForeignKey(ModelB, on_delete=models.CASCADE)
value = models.IntegerField()
class ModelD(models.Model):
modelC = models.ForeignKey(ModelC, on_delete=models.CASCADE)
value = models.IntegerField()
class ModelE(models.Model):
modelD = models.ForeignKey(ModelD, on_delete=models.CASCADE)
value = models.IntegerField()
# etc...
Как мы можем использовать Django ORM для выполнения следующих операций:
например, все моделиЕ для данной моделиА, SQL эквивалент:
SELECT ModelE.*
FROM ModelA
JOIN ModelB ON ModelB.modelA = ModelA.id
JOIN ModelC ON ModelC.modelB = ModelB.id
JOIN ModelD ON ModelD.modelC = ModelC.id
JOIN ModelE ON ModelE.modelD = ModelD.id
WHERE ModelA.id = 1
например, сгруппировать все записи по некоторой модели, SQL-эквивалент:
SELECT ModelC.*, SUM(ModelE.value)
FROM ModelA
JOIN ModelB ON ModelB.modelA = ModelA.id
JOIN ModelC ON ModelC.modelB = ModelB.id
JOIN ModelD ON ModelD.modelC = ModelC.id
JOIN ModelE ON ModelE.modelD = ModelD.id
WHERE ModelA.id = 1
GROUP BY ModelC.id
Конкретный запрос, который я пытаюсь получить, эквивалентен следующему:
SELECT ModelC.value * SUM(ModelE.value)
FROM ModelA
JOIN ModelB ON ModelB.modelA = ModelA.id
JOIN ModelC ON ModelC.modelB = ModelB.id
JOIN ModelD ON ModelD.modelC = ModelC.id
WHERE ModelA.id = 1 AND ModelD.value >= 1 AND ModelD.value < 3
GROUP BY ModelC.id
Мне приходится использовать обходной путь на Python, который довольно неэффективен, но гораздо более понятен. Я надеялся, что есть способ сделать это с помощью Django ORM.
Не уверен, что это соответствует тому, что вы хотите.
Однако, возможно, вы сможете использовать этот код ORM, изменив его.
from django.db.models import F, Sum
queryset = (
ModelC.objects
.annotate(
sum_e_values=Sum('modeld__modele__value'),
result_value=F('value') * F('sum_e_values'),
)
.filter(
modelB__modelA_id=1,
modeld__value__gte=1,
modeld__value__lt=3,
)
.values('result_value')
)
print(queryset.query)
Выход:
SELECT ("myapp_modelc"."value" * SUM("myapp_modele"."value")) AS "result_value"
FROM "myapp_modelc"
LEFT OUTER JOIN "myapp_modeld" ON ("myapp_modelc"."id" = "myapp_modeld"."modelC_id")
LEFT OUTER JOIN "myapp_modele" ON ("myapp_modeld"."id" = "myapp_modele"."modelD_id")
INNER JOIN "myapp_modelb" ON ("myapp_modelc"."modelB_id" = "myapp_modelb"."id")
INNER JOIN "myapp_modeld" T6 ON ("myapp_modelc"."id" = T6."modelC_id")
WHERE ("myapp_modelb"."modelA_id" = 1
AND T6."value" < 3
AND T6."value" >= 1)
GROUP BY "myapp_modelc"."id",
"myapp_modelc"."modelB_id",
"myapp_modelc"."value"
Ответ оказался на удивление простым, но нигде не упоминается в явном виде. Спасибо @gypark за правильный подход!
Подход не точно соответствует заданному SQL, но дает такие же результаты.
Для первой проблемы работает следующий запрос ORM:
# Selects all ModelE for a given ModelA
ModelE.objects.filter(modelD__modelC__modelB__modelA_id = 1)
Это генерирует следующий эквивалентный SQL (измененный для ясности):
SELECT modele.id, modele.modelD_id, modele.name, modele.value
FROM modele
INNER JOIN modeld ON (modele.modelD_id = modeld.id)
INNER JOIN modelc ON (modeld.modelC_id = modelc.id)
INNER JOIN modelb ON (modelc.modelB_id = modelb.id)
INNER JOIN modela ON (modelb.modelA_id = modela.id)
WHERE modela.id = 1
Для второго:
ModelC.objects.filter(modelB__modelA_id=1).annotate(sumE=Coalesce(Sum('modeld__modele__value'), 0))
Что генерирует следующий SQL-эквивалент:
SELECT modelc.id, modelc.modelB_id, modelc.name, modelc.value, COALESCE(SUM(modele.value), 0) AS sumE
FROM modelc
INNER JOIN modelb ON (modelc.modelB_id = modelb.id)
LEFT OUTER JOIN modeld ON (modelc.id = modeld.modelC_id)
LEFT OUTER JOIN modele ON (modeld.id = modele.modelD_id)
WHERE modelb.modelA_id = 1
GROUP BY modelc.id, modelc.modelB_id, modelc.name, modelc.value
Для третьего:
ModelA.objects.filter(
id=1,
modelb__modelc__modeld__value__gte=1,
modelb__modelc__modeld__value__lt=3,
).aggregate(
sum=Coalesce(Sum(F('modelb__modelc__value') * F('modelb__modelc__modeld__modele__value')), 0)
)
Что дает следующий SQL:
SELECT COALESCE(SUM((modelc.value * modele.value)), 0) AS sum
FROM modela
INNER JOIN modelb ON (modela.id = modelb.modelA_id)
INNER JOIN modelc ON (modelb.id = modelc.modelB_id)
INNER JOIN modeld ON (modelc.id = modeld.modelC_id)
LEFT OUTER JOIN modele ON (modeld.id = modele.modelD_id)
WHERE (modela.id = 1 AND modeld.value >= 1 AND modeld.value < 3)
Просто примечание, Coalesce (from django.db.models.functions import Coalesce
) требуется для устранения проблемы возврата None, когда QuerySet пуст.