LEFT JOIN with OR condition in python django - python

All I want is to write the following SQL query in Python Django syntax. I researched a lot but could not get the result I want. Could you please help me.
SELECT * FROM thread
LEFT JOIN user
ON (thread.user1_id = user.id OR thread.user2_id = user.id)
WHERE user.id = 9
My model:
class Thread(models.Model):
last_message = models.TextField(max_length=100)
user1_id = models.IntegerField()
user2_id = models.IntegerField()

The exact query cannot be created using the Django ORM without resorting to some raw SQL. The problem is the OR in the join condition.
But, since your not selecting any rows from the user table, you can create a different query with the same results.
Start by defining your models using ForeignKeys.
from django.db import models
from django.contrib.auth.models import User
class Thread(models.Model):
last_message = models.TextField(max_length=100)
user1 = models.ForeignKey(User)
user2 = models.ForeignKey(User)
class Meta:
db_table = 'thread' # otherwise it will be: appname_thread
Then you can preform the query.
from django.db.models import Q
threads = Thread.objects.filter(Q(user1__id=9) | Q(user2__id=9))
The resulting SQL will be something like this:
SELECT * FROM thread
LEFT JOIN user AS user1
ON (thread.user1_id = user1.id)
LEFT JOIN user AS user2
ON (thread.user2_id = user2.id)
WHERE (user1.id = 9 OR user2.id = 9)
You can improve this by avoiding the join altogether.
# note the single underscore!
threads = Thread.objects.filter(Q(user1_id=9) | Q(user2_id=9))
Generating the following SQL.
SELECT * FROM thread
WHERE (user1_id = 9 OR user2_id = 9)
If you need to also obtain the user instances in the same query, in which case you really need the join, you have two options.
Either use the first query that includes joins and afterwards select the correct user based in the id in Python.
for thread in threads:
if thread.user1.id == 9:
user = thread.user1
else:
user = thread.user2
Or use raw SQL to get the exact query you want. In this case, you can use the models without the ForeignKeys as you defined them.
threads = Thread.objects.raw('''
SELECT *, user.id AS user_id, user.name as user_name FROM thread
LEFT JOIN user
ON (thread.user1_id = user.id OR thread.user2_id = user.id)
WHERE user.id = 9''')
for thread in threads:
# threads use the ORM
thread.id
# user fields are extra attributes
thread.user_id
thread.user_name

Related

SQLalchemy to get data from multiple table in single object

I have 3 tables, users, projects and project_users is_admin table and I am trying to write ORM to get data from them.
My Models are here: https://pastebin.com/ZrmhKyNL
In simple SQL, we could join and select particular columns and get the desired output. But in ORM when I write query like this:
sql = """ select * from
projects p, users u, projects_users pu
where
p.name = '%s' and
p.id = pu.project_id and
pu.user_id = u.id and
p.is_active = true and
u.is_active = true
""" % project_name
and it works well and returns response in this format:
[
{
All columns of above 3 tables.
}
]
But when I try to convert this to sqlalchamey ORM, it doesn't work well:
return (
db.query(User)
.filter(Project.id == ProjectsUser.project_id)
.filter(ProjectsUser.user_id == User.id)
.filter(Project.is_active == True)
.filter(User.is_active == True)
.filter(Project.name == project_name)
.all()
)
I want is_admin value to be returned along with user object. This seems very common use case, but I couldn't find any solution related to SQLalchemy ORM.
If this is a one-to-one relationship, meaning that for every user you can have one and only one entry in the admin table, you could add a column to serve as back-reference:
class User(Base):
id = ...
name = ...
last_login = ...
admin = relationship("Admin", uselist=False, back_populates="user")
class Admin(Base):
id = ...
is_admin = ...
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship("User", back_populates="admin")
Then you can query only the table user, and access the values from the relationship via user.admin.is_admin.
Read more:
One To One - SqlAlchemy docs
relationship.back_populates - SqlAlchemy docs

django left join with where clause subexpression

I'm currently trying to find a way to do something with Django's (v1.10) ORM that I feel should be possible but I'm struggling to understand how to apply the documented methods to solve my problem.
Edit: So here's the sql that I've hacked together to return the data that I'd like from the dbshell, with a postgresql database now, after I realised that my original sqlite3 backed sql query was incorrect:
select
voting_bill.*,vv.vote
from
voting_bill
left join
(select
voting_votes.vote,voting_votes.bill_id
from
voting_bill
left join
voting_votes
on
voting_bill.id=voting_votes.bill_id
where
voting_votes.voter_id = (select id from auth_user where username='richard' or username is Null)
)
as
vv
on
voting_bill.id=vv.bill_id;
Here's the 'models.py' for my voting app:
from django.db import models
from django.contrib.auth.models import User
class Bill(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
result = models.BooleanField()
status = models.BooleanField(default=False)
def __str__(self):
return self.name
class Votes(models.Model):
vote = models.NullBooleanField()
bill = models.ForeignKey(Bill, related_name='bill',
on_delete=models.CASCADE,)
voter = models.ForeignKey(User, on_delete=models.CASCADE,)
def __str__(self):
return '{0} {1}'.format(self.bill, self.voter)
I can see that my sql works as I expect with the vote tacked onto the end, or a null if the user hasn't voted yet.
I was working to have the queryset in this format so that I can iterate over it in the template to produce a table and if the result is null I can instead provide a link which takes the user to another view.
I've read about select_related and prefetch_related, but as I said, I'm struggling to work out how I translate this to how I can do this in SQL.
Hope I correctly understood your problem. Try this:
votes = Votes.objects.filter(voter__username='django').select_related('bill')
You can use this. But I think you do not need select_related in this case.
bills_for_user = Bill.objects.filter(votes__voter__username='django').select_related('votes').distinct()
Now you can iterate your bills_for_user
for bill in bills_for_user:
bill_name = bill.name
bill_description = bill.description
bill_result = bill.result
bill_status = bill.status
# and there are several variants what you can with votes
bill_votes = bill.votes_set.all() # will return you all votes for this bill
bill_first_vote1 = bill.votes_set.first() # will return first element in this query or None if its empty
bill_first_vote2 = bill.votes_set.all()[0] # will return first element in this query or Error if its empty
bill_last_vote = bill.votes_set.last()[0] # will return last element in this query or None if its empty
# you can also filter it for example by voting
bill_positive_votes = bill.votes_set.filter(vote=True) # will return you all votes for this bill with 'vote' = True
bill_negative_votes = bill.votes_set.filter(vote=False) # will return you all votes for this bill with 'vote' = False
bill_neutral_votes = bill.votes_set.filter(vote=None) # will return you all votes for this bill with 'vote' = None

Django ORM join subtable in query

I want use Django ORM. I build SQL query:
select itinerary.id, count(users.home_location_id) from itinerary_itinerary as itinerary left join (select to_applicationuser_id as id, users.home_location_id from custom_auth_applicationuser_friends as friends join custom_auth_applicationuser as users on friends.to_applicationuser_id = users.id where from_applicationuser_id = 28)
as users on itinerary.location_id = users.home_location_id
WHERE user_id = 28
GROUP BY itinerary.id, users.home_location_id
Could anybody tell me how make left join with table from subquery?
28 is current user_id.
I use something like:
Itinerary.object.filter(user_id=28).extra(
tables=['(select to_applicationuser_id as id, users.home_location_id from custom_auth_applicationuser_friends as friends join custom_auth_applicationuser as users on friends.to_applicationuser_id = users.id where from_applicationuser_id = 28) as users'],
where=['itinerary.location_id = users.home_location_id']
)
But I got error
ProgrammingError relation "(select to_applicationuser_id as id,
users.home_location_id fro" does not exist
UPD
Models (it is just simple scheme):
class ApplicationUser(models.Model):
name = models.CharField(max_length=255)
home_location = models.ForeignKey(Location)
friends = models.ManyToManyFieled('self')
class Location(models.Model):
loc_name = models.CharFiled(max_length=255)
class Itinerary(models.Model):
user = models.ForeignKey(ApplicationUser)
location = models.ForeignKey(Location)
When you add tables with extra, they get added to the from list, which does not accept an sql statement.
I don't think you need to use extra at all here, you can get a similar query with the ORM without the need to join on a select statement. The following code, using filtering and annotations, will give the same results as much as I was able to understand your query:
ApplicationUser.objects.filter(
Q(itinerary__location_id = F('friends__home_location_id')) |
Q(friends__home_location__isnull=True),
id=28,
).values_list(
'itinerary__id', 'friends__home_location_id'
).annotate(location_count = Count('friends__home_location_id')
).values_list('itinerary__id', 'location_count')

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/

Django: How to use select_related to INNER JOIN FK's FK

29 Dec: updated models
I have got three models as follows:
class Job(models.Model):
job_number = models.CharField(max_length=20, primary_key=True)
class Project(models.Model):
job = models.ForeignKey(Job, null=True) # updated (null=True)***
source = models.ForeignKey(Source) # added***
class Task(models.Model):
project = models.ForeignKey(Project)
class Source(models.Model): # added***
blahblah...
And I would like to get the job number for a task. Something like below:
job = Job.objects.all().select_related()
jobno = job[0].project.job.job_number
I'm not sure how many times the query above will hit the DB. But I guess it will be more than twice, won't it?
select_related can only pre-cache the foreign key for 2 tables to my understanding. Any one can suggest the best practice in this case to reduce the number of times hitting the DB?
select_related() joins all these three models in one query:
>>> from app.models import Task
>>> task = Task.objects.all().select_related()[0]
>>> task.project.job.job_number
u'123'
>>> from django.db import connection
>>> len(connection.queries)
1
>>> connection.queries
[{u'time': u'0.002', u'sql': u'QUERY = u\'SELECT "app_task"."id", "app_task"."project_id", "app_project"."id", "app_project"."job_id", "app_job"."job_number" FROM "app_task" INNER JOIN "app_project" ON ( "app_task"."project_id" = "app_project"."id" ) INNER JOIN "app_job" ON ( "app_project"."job_id" = "app_job"."job_number" ) LIMIT 1\' - PARAMS = ()'}]
>>>
Readable SQL:
SELECT "app_task"."id", "app_task"."project_id", "app_project"."id",
"app_project"."job_id", "app_job"."job_number"
FROM "app_task"
INNER JOIN "app_project" ON ( "app_task"."project_id" = "app_project"."id" )
INNER JOIN "app_job" ON ( "app_project"."job_id" = "app_job"."job_number" )
You can use a filter:
task = Task.objects.all().select_related().filter(
project__id__isnull=False,
job__id__isnull=False)

Categories