How to convert this raw query into Django ORM - django

SELECT * FROM SUbPlans LEFT JOIN orders on SUbPlans.empId=orders.selected_id where orders.user_id=2
I have this query that I want to convert into Django ORM.
I just want to do this using select_related. SUbPlans and orders are connected via foriegn key
my attempt:
user_sub_plans = SUbPlans.objects.select_related(id=request.user).first()

try to fetch your SUbPlans first and then all related orders using prefetch_related like
from django.db.models import Q
SUbPlans.objects.prefetch_related('orders').filter(Q(orders__isnull=True) | Q(orders__selected_id=2)).all()

Related

QuerySet in Django

How can I perform this SQL query
SELECT *
FROM table_name
WHERE column_name != value
in the QuerySet of Django?
I tried this but isn't correct way:
To execute sql queries just use raw method of Model like this:
posts = Post.objects.raw("SELECT * FROM table_name WHERE column_name != %s;", [value])
for post in posts:
# do stuff with post object
But i don't think you need raw query (unless you want to get rid of ORM overhead to fetch records quicker) you can just use ORM like this:
posts = Post.objects.all().exclude(column_name=value)
You can do it using Q:
from django.db.models import Q
posts = Post.objects.filter(~Q(column_name=value))
I think you want to do this query using Django ORM (If I am not wrong). You can do this using Q Expression in Django (https://docs.djangoproject.com/en/4.0/topics/db/queries/#s-complex-lookups-with-q-objects). For != you can use ~ sign. In your case, the query will look like this
Post.objects.filter(~Q(<column_name>=<value>))
Another way to use exclude method (https://docs.djangoproject.com/en/4.0/ref/models/querysets/#s-exclude)
Post.objects.exclude(<column_name>=<value>)
Both queries generate the same raw query in your case:
SELECT * FROM <table_name> WHERE NOT (<column_name>=<value)
If you want to do the raw query then you can use raw method (https://docs.djangoproject.com/en/4.0/topics/db/sql/)
posts = Post.objects.raw("SELECT * FROM table_name WHERE column_name != %s;", [value])
If you want to execute a custom raw query directly then use cursor from django.db connection (https://docs.djangoproject.com/en/4.0/topics/db/sql#s-executing-custom-sql-directly)

annotate group by in django

I'm trying to perform a query in django that is equivalent to this:
SELECT SUM(quantity * price) from Sales GROUP BY date.
My django query looks like this:
Sales.objects.values('date').annotate(total_sum=Sum('price * quantity'))
The above one throws error:
Cannot resolve keyword 'price * quantity' into field
Then I came up with another query after checking this https://stackoverflow.com/a/18220269/12113049
Sales.objects.values('date').annotate(total_sum=Sum('price', field='price*quantity'))
Unfortunately, this is not helping me much. It gives me SUM(price) GROUP BY date instead of SUM(quantity*price) GROUP BY date.
How do I query this in django?
You should be using F expressions to perform operations on fields:
from django.db.models import F
Sales.objects.values('date').annotate(total_sum=Sum(F('price') * F('quantity')))
Edit: assuming that price is a DecimalField and quantity is a IntegerField (of different types) you would need to specify output_field in Sum:
from django.db.models import DecimalField, F
Sales.objects.values('date').annotate(total_sum=Sum(F('price') * F('quantity'), output_field=DecimalField()))

Django count group by date from datetime

I'm trying to count the dates users register from a DateTime field. In the database this is stored as '2016-10-31 20:49:38' but I'm only interested in the date '2016-10-31'.
The raw SQL query is:
select DATE(registered_at) registered_date,count(registered_at) from User
where course='Course 1' group by registered_date;
It is possible using 'extra' but I've read this is deprecated and should not be done. It works like this though:
User.objects.all()
.filter(course='Course 1')
.extra(select={'registered_date': "DATE(registered_at)"})
.values('registered_date')
.annotate(**{'total': Count('registered_at')})
Is it possible to do without using extra?
I read that TruncDate can be used and I think this is the correct queryset however it does not work:
User.objects.all()
.filter(course='Course 1')
.annotate(registered_date=TruncDate('registered_at'))
.values('registered_date')
.annotate(**{'total': Count('registered_at')})
I get <QuerySet [{'total': 508346, 'registered_date': None}]> so there is something going wrong with TruncDate.
If anyone understands this better than me and can point me in the right direction that would be much appreciated.
Thanks for your help.
I was trying to do something very similar and was having the same problems as you. I managed to get my problem working by adding in an order_by clause after applying the TruncDate annotation. So I imagine that this should work for you too:
User.objects.all()
.filter(course='Course 1')
.annotate(registered_date=TruncDate('registered_at'))
.order_by('registered_date')
.values('registered_date')
.annotate(**{'total': Count('registered_at')})
Hope this helps?!
This is an alternative to using TruncDate by using `registered_at__date' and Django does the truncate for you.
from django.db.models import Count
from django.contrib.auth import get_user_model
metrics = {
'total': Count('registered_at__date')
}
get_user_model().objects.all()
.filter(course='Course 1')
.values('registered_at__date')
.annotate(**metrics)
.order_by('registered_at__date')
For Postgresql this transforms to the DB query:
SELECT
("auth_user"."registered_at" AT TIME ZONE 'Asia/Kolkata')::date,
COUNT("auth_user"."registered_at") AS "total"
FROM
"auth_user"
GROUP BY
("auth_user"."registered_at" AT TIME ZONE 'Asia/Kolkata')::date
ORDER BY
("auth_user"."registered_at" AT TIME ZONE 'Asia/Kolkata')::date ASC;
From the above example you can see that Django ORM reverses SELECT and GROUP_BY arguments. In Django ORM .values() roughly controls the GROUP_BY argument while .annotate() controls the SELECT columns and what aggregations needs to be done. This feels a little odd but is simple when you get the hang of it.

Alternative nullif in Django ORM

Use Postgres as db and Django 1.9
I have some model with field 'price'. 'Price' blank=True.
On ListView, I get query set. Next, I want to sort by price with price=0 at end.
How I can write in SQL it:
'ORDER BY NULLIF('price', 0) NULLS LAST'
How write it on Django ORM? Or on rawsql?
Ok. I found alternative. Write own NullIf with django func.
from django.db.models import Func
class NullIf(Func):
template = 'NULLIF(%(expressions)s, 0)'
And use it for queryset:
queryset.annotate(new_price=NullIf('price')).order_by('new_price')
Edit : Django 2.2 and above have this implemented out of the box. The equivalent code will be
from django.db.models.functions import NullIf
from django.db.models import Value
queryset.annotate(new_price=NullIf('price', Value(0)).order_by('new_price')
You can still ORDER BY PRICE NULLS LAST if in your select you select the price as SELECT NULLIF('price', 0). That way you get the ordering you want, but the data is returned in the way you want. In django ORM you would select the price with annotate eg TableName.objects.annotate(price=NullIf('price', 0) and for the order by NULLS LAST and for the order by I'd follow the recommendations here Django: Adding "NULLS LAST" to query
Otherwise you could also ORDER BY NULLIF('price', 0) DESC but that will reorder the other numeric values. You can also obviously exclude null prices from the query entirely if you don't require them.

Executing Anti Join in Django ORM

I have two models:
class Note(model):
<attribs>
class Permalink(model):
note = foreign key to Note
I want to execute a query: get all notes which don't have a permalink.
In SQL, I would do it as something like:
SELECT * FROM Note WHERE id NOT IN (SELECT note FROM Permalink);
Wondering how to do this in ORM.
Edit: I don't want to get all the permalinks out into my application. Would instead prefer it to run as a query inside the DB.
You should be able to use this query:
Note.objects.filter(permalink_set__isnull=True)
you can use:
Note.objects.exclude(id__in=Permalink.objects.all().values_list('id', flat=True))