Django Query API select_related в запросе с несколькими таблицами
Представьте, что я пытаюсь подсчитать количество сортов деревьев в определенном парке, а деревья сгруппированы в определенных полях на территории парка. Одна из загвоздок заключается в том, что деревья можно пересаживать, поэтому я хочу подсчитывать только существующие деревья и пустые места (removed IS NULL
для деревьев, которые посажены в данный момент, и LEFT
и RIGHT JOIN
для мест, которые еще не посажены). После блуждания по множеству постов и документации Django select_related
, я не могу понять, как сделать это через API запросов Django. У меня есть идентификатор парка, поэтому я буду делать это, начиная с модели Parks в views.py
.
Я упускаю что-то очевидное (или неочевидное) в API запросов Django?
Следующий SQL делает то, что я хотел бы:
WITH p AS (
SELECT t.cultivar, l.id AS loc
FROM trees t
JOIN locations l ON t.location = l.id
JOIN fields d ON l.field = d.id
WHERE d.park = 'SOME_PARK_ID'
AND t.removed IS NULL
)
SELECT c.name, count(*)
FROM p
LEFT JOIN cultivars c ON p.cultivar = c.id
RIGHT JOIN locations l ON p.loc = l.id
GROUP BY name;
Например:
+--------+-------+
| name | count |
|--------+-------|
| <null> | 2 |
| HGG | 2 |
| BOX | 3 |
| GRV | 1 |
+--------+-------+
Возможно views.py
:
class ParkDetail( DetailView ):
model = Parks
# Would like to get a count of cultivars here
def get_context_data( self, **kwargs ): # ... Lost here
qry = Parks.objects.select_related( 'tree__location__field' ).filter( tree.removed is None )
tree_count = qry.annotate( Count( ... ) )
Германские части models.py
:
class Parks( models.Model ):
name = models.CharField( max_length = 64, blank = False )
class Fields( models.Model ):
park = models.ForeignKey( Parks, on_delete = models.CASCADE )
class Locations( models.Model ):
field = models.ForeignKey( Fields, on_delete = models.CASCADE )
class Cultivars( models.Model ):
name = models.CharField( max_length = 64, blank = False )
class Trees( models.Model ):
location = models.ForeignKey( Locations, on_delete = models.CASCADE )
cultivar = models.ForeignKey( Cultivars, on_delete = models.PROTECT )
planted = models.DateField( blank = False )
removed = models.DateField( blank = True, null = True )
Не понимаю, зачем здесь нужен .select_related(…)
. Вы можете подсчитать Trees
не удаленных для данного park_id
с помощью:
from django.db.models import Count
Cultivars.objects.filter(
tree__removed=None, tree_location__field__park_id=my_park_id
).annotate(tree_count=Count('tree'))
У Cultivars
, возникающего из этого QuerySet
, будет дополнительный атрибут .tree_count
с именем и количеством не удаленных Trees
с my_park_id
в качестве park_id
.
Если вы также хотите получить Cultivars
, для которых не было посажено ни одного дерева, мы можем работать с:
from django.db.models import Case, Count, F, Value, When
Cultivars.objects.annotate(
tree_count=Count(
Case(
When(
tree__removed=None,
tree_location__field__park_id=my_park_id,
then=F('tree'),
),
default=Value(None),
)
)
)