Why select_related in Django ORM apply join before where clause? - python

Consider I have two tables namely
Class Department(model.Models):
id = models.AutoField(primary_key=True)
Class Employee(model.Models):
dep = models.ForeignKey(Department)
exp = models.IntegerField()
Now in views class I am using get_queryset function
def get_queryset(self):
return Employee.objects.using(schema).filter(exp_gte=date).select_related('dep')
Sql statement create is in this form
Select `Employee`.`dep`, `Employee`.`exp`, `Department`.`id` from `Employee`
Inner Join `Department` on `Department`.`id`= `Employee`.`dep`
Where `Employee`.`exp` >= date;
Why is join before where clause. And how can I apply join after where clause using Django ORM

Related

Can I write RIGHT JOIN and *ORDER BY queryset in Django ORM? [duplicate]

Here are my models
class Student:
user = ForeignKey(User)
department = IntegerField()
semester = IntegerField()
...
class Attendance:
student = ForeignKey(Student)
subject = ForeignKey(Subject)
month = IntegerField()
year = IntergerField()
present = IntegerField()
total = IntegerField()
students = Student.objects.filter(semester=semester)
How can I perform a right join between Student and Attendance models, so that I can get a
queryset with all of the students and attendances` if exists for a student, else null?
The documentation mentions left joins but not right joins.
change left join for table subject
queryset.query.alias_map['subject'].join_type = "RIGHT OUTER JOIN"
You can use such query:
queryset = Student.objects.all().select_related('attendance_set')
student = queryset[0]
# all attendances for the student
attendances = student.attendance_set.all()
With select_related you JOIN'ing Attendance table. Django does not have explicit join ORM method, but it does JOIN internally when you call select_related. Resulting queryset will contain all Student's with joined attendances, and when you will call attencande_set.all() on each student - no additional queries will be performed.
Check the docs for _set feature.
If you want to query only those students who has at least one attendance, you can use such query:
from django.models import Count
(Student.objects.all()
.select_related('attendance_set')
.annotate(n_attendances=Count('attendance_set'))
.filter(n_attendances__gt=0))

How to use inner join on Subquery() Dajngo ORM?

I have two models:
class FirstModel(models.Model():
some_fields...
class SecondModel(models.Model):
date = models.DateTimeField()
value = models.IntegerField()
first_model = models.ForeignKey(to="FirstModel", on_delete=models.CASCADE)
and I need to do the following query:
select sum(value) from second_model
inner join (
select max(date) as max_date, id from second_model
where date < NOW()
group by id
) as subquery
on date = max_date and id = subquery.id
I think I can do it using Subquery
subquery = Subquery(SecondModel.objects.values("first_model")
.annotate(max_date=Max("date"))
.filter(date__lt=Func(function="NOW")))
and F() expressions but it only can resolve model fields, not a subquery
Question
Is it possible to implement using Django ORM only?
Also, can I evaluate the sum of values from the second model for all values in the first model by annotating this value? Like
FirstModel.objects.annotate(sum_values=sum_with_inner_join_query).all()

Django: Filter a Queryset made of unions not working

I defined 3 models related with M2M relationsships
class Suite(models.Model):
name = models.CharField(max_length=250)
title = models.CharField(max_length=250)
icon = models.CharField(max_length=250)
def __str__(self):
return self.title
class Role(models.Model):
name = models.CharField(max_length=250)
title = models.CharField(max_length=250)
suites = models.ManyToManyField(Suite)
services = models.ManyToManyField(Service)
Actions = models.ManyToManyField(Action)
users = models.ManyToManyField(User)
def __str__(self):
return self.title
In one of my views I tried to collect all the Suites related to an specific User. The user may be related to several Roles that can contain many Suites. And then filter Suites by name. But the filter seem to have no effects
queryset = Suite.objects.union(*(role.suites.all() for role in
self.get_user().role_set.all()))
repr(self.queryset)
'<QuerySet [<Suite: energia>, <Suite: waste 4 thing>]>'
self.queryset = self.queryset.filter(name="energia")
repr(self.queryset)
'<QuerySet [<Suite: energia>, <Suite: waste 4 thing>]>'
The query atribute inside the queryset not alter its content before executin the filter:
(SELECT "navbar_suite"."id", "navbar_suite"."name", "navbar_suite"."title", "navbar_suite"."icon" FROM "navbar_suite") UNION (SELECT "navbar_suite"."id", "navbar_suite"."name", "navbar_suite"."title", "navbar_suite"."icon" FROM "navbar_suite" INNER JOIN "navbar_role_suites" ON ("navbar_suite"."id" = "navbar_role_suites"."suite_id") WHERE "navbar_role_suites"."role_id" = 1)
(SELECT "navbar_suite"."id", "navbar_suite"."name", "navbar_suite"."title", "navbar_suite"."icon" FROM "navbar_suite") UNION (SELECT "navbar_suite"."id", "navbar_suite"."name", "navbar_suite"."title", "navbar_suite"."icon" FROM "navbar_suite" INNER JOIN "navbar_role_suites" ON ("navbar_suite"."id" = "navbar_role_suites"."suite_id") WHERE "navbar_role_suites"."role_id" = 1)
As stated in django docs, only count(), order_by(), values(), values_list() and slicing of union queryset is allowed. You can't filter on union queryset.
That means, you have to apply filters on queries before applying union on them.
Also, you can achieve your goal without even using union():
Suite.objects.filter(role_set__users=self.get_user(), name="energia")
You may need to adjust field name in filter if you've used related_name or related_query_name in definition of suites M2M field in Role model.
I had the same issue and ended up using the union query as a subquery so that the filters could work:
yourModelUnionSubQuerySet = YourModelQS1.union(YourModelQS2)
yourModelUnionQuerySet = YourModel.objects.filter(id__in=yourModelUnionSubQuerySet.values('id'))
There is a simple solution. Just use
self.queryset = self.queryset | <querySet you want to append>
instead of
self.queryset = self.queryset.union(<QuerySet you want to append>)
Worked for me. I hope this is understandable. After this you will be able to use filter.

Django __str__ reduce SQL queries

I have two models in my Django project
class BookSerie(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
class BookVolume(models.Model):
isbn = models.CharField(max_length=17)
volumeNumber = models.PositiveIntegerField()
serie = models.ForeignKey(BookSerie)
def __str__(self):
return self.serie.title+" volume "+str(self.volumeNumber)+" - "+str(self.isbn)
I only use __ str __ for my admin panel but when I use this code in my view (serie with id=1 have 5 volumes) :
def serieDetails(request, title):
try:
seriequery = BookSerie.objects.get(slug=title)
BookVolume.objects.filter(serie=seriequery).order_by('volumeNumber')
except BookSerie.DoesNotExist:
raise Http404("Serie does not exist")
return render(request, 'book/serieDetails.html', {'serie': seriequery, 'volumes' : volumesquery})
I have an important issue :
Query SELECT ••• FROM "book_bookserie" WHERE "book_bookserie"."id" = '1' is performed 5 times (django debug toolbar give this code line return self.serie.title+" volume "+str(self.volumeNumber)+" - "+str(self.isbn)
Query
SELECT ••• FROM "book_bookvolume" WHERE "book_bookvolume"."serie_id" = '1' ORDER BY "book_bookvolume"."volumeNumber" ASC
is performed 2 times
In your BookVolume's __str__ you access self.serie.title. This hits the database every time, as the according BookSerie record must be retrieved. One way to reduce queries here is to use select_related when you query your BookVolume:
# any reason why you don't store this QuerySet to a variable?
BookVolume.objects.filter(serie=seriequery).order_by('volumeNumber').select_related('serie')
# better:
seriequery.bookvolume_set.order_by('volumeNumber').select_related('serie')
From the docs:
select_related...
... will “follow” foreign-key relationships, selecting additional related-object data when it executes its query. This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.

how to let django achieve inner join

there are two tables:
class TBLUserProfile(models.Model):
userid = models.IntegerField(primary_key=True)
relmusicuid = models.IntegerField()
fansnum = models.IntegerField()
class TSinger(models.Model):
fsinger_id = models.IntegerField()
ftbl_user_profile = models.ForeignKey(TBLUserProfile, db_column='Fsinger_id')
I want to get Tsinger info and then order by TBLUserProfile.fansnum, I know how to write sql query: select * from t_singer INNER JOIN tbl_user_profile ON (tbl_user_profile.relmusicuid=t_singer.Fsinger_id) order by tbl_user_profile.fansnum, but I don't want to use model raw function. relmusicuid is not primary key otherwise I can use ForeignKey to let it work. How can I use django model to achieve this?
You can do like this :
Tsinger.objects.all().order_by('ftbl_user_profile__fansnum')
For information about Django JOIN :
https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/

Categories