pythondjangodjango-filterdjango-annotatedjango-subquery

How to get table alias for inner join in django


I'm using django 2.1, python 3.6 and SQL Server 2012 as backend. I have following models:

class ModelA(models.Model):
    name = models.CharField(...)
    value = models.PositiveIntegerField(...)

class ModelB(models.Model):
    name = models.CharField(...)
    values = models.ManyToManyField(ModelA, through='ModelC')

class ModelC(models.Model):
    model_a = models.ForeignKey(ModelA, ...)
    model_b = models.ForeignKey(ModelB, ...)
    info_a = models.CharField(...)
    info_b = models.CharField(...)

How can I achieve following SQL query:

SELECT t1.model_a_id AS a_id, t3.value AS a_value
FROM ModelB AS t0
INNER JOIN ModelC t1 ON t1.model_b_id = t0.id
INNER JOIN ModelC t2 ON t2.model_b_id = t0.id
INNER JOIN ModelA t3 ON t3.id = t2.model_a_id
INNER JOIN ModelC t4 ON t4.model_b_id = t0.id
WHERE t1.model_a_id in (1,2) AND t2.model_a_id in (8,9,10,11) AND t4.model_a_id in (21,22)

What I have so far:

ModelB.objects.filter(values__in=[1,2]).filter(values__in=[8,9,10,11]).filter(values__in=[21,22])

Which produces the correct filtered QuerySet. But how can I get the correct fields?

I tried to use annotate function but I failed. Using django's Subquery as described in the docs generates database error, because SQL Server does not support subqueries in the SELECT part.

Any recommendations? Thanks!


Solution

  • I solved it without falling back to raw sql. I used django's FilteredRealtion objects in combination of additional annotate like this:

    from django.db.models import Q, F, FilteredRelation
    
    qs = ModelB.objects.filter(values__in=[21,22])
    qs = qs.filter(values__in=[1,2])
    qs = qs.filter(values__in=[8,9,10,11])
    qs = qs.annotate(_a_id=FilteredRelation('modelc', condition=Q(values__in=[8,9,10,11])),
                     _a_value=FilteredRelation('modelc', condition=Q(values__in=[1,2])))
    qs = qs.annotate(a_id=F('_a_id__model_a'), a_value=F('_a_value__model_a__value'))
    qs = qs.values('a_id', 'a_value')