I have 3 models (A,B,C):
class A(models.Model):
url = models.URLField()
uuid = models.UUIDField()
name = models.CharField(max_length=400)
id = models.IntegerField()
class B(models.Model):
user = models.ForeignKey(C, to_field='user_id',
on_delete=models.PROTECT,)
uuid = models.ForeignKey(A, to_field='uuid',
on_delete=models.PROTECT,)
and I want to perform the following SQL query using the Django ORM:
SELECT A.id, COUNT(A.id), COUNT(foo.user)
FROM A
LEFT JOIN (SELECT uuid, user FROM B where user = '<a_specific_user_id>') as foo
ON A.uuid = foo.uuid_id
WHERE name = '{}'
GROUP by 1
HAVING COUNT(A.id)> 1 AND COUNT(A.id)>COUNT(foo.user)
My problem is mainly with LEFT JOIN. I know I can form a LEFT JOIN by checking for the existence of null fields on table B:
A.objects.filter(name='{}', b__isnull=True).values('id', 'name')
but how can I LEFT JOIN on the specific sub-query I want?
I tried using Subquery() but it seems to populate the final WHERE statement and not pass my custom sub-query in the LEFT JOIN.
For anyone stumbling upon this in the future. I directly contacted the Django irc channel and it's confirmed that, as of now, it's not possible to include a custom subquery in a LEFT JOIN clause, using the Django ORM.
I have a SQL query that joins 2 tables and I'm trying to replace it with Django ORM. The query:
SELECT * FROM students r JOIN class_teachers c ON c.class_room_id = r.class_room_id
Pretty simple. Purpose of it is to get all values from students and the Teachers ID from class_teachers table.
I created 3 models that are using pre built tables in the db:
class ClassRoom(models.Model):
class_room = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
class ClassTeachers(models.Model):
class_room = models.ForeignKey(ClassRoom)
teacher = models.ForeignKey(Teachers)
class Students(models.Model):
class_room = models.ForeignKey(ClassRoom)
name = models.CharField(max_length=32)
But with this setup I can't figure out how to create a two table join like before. The closest I got was with this which is horrible... but works:
dd = ClassTeachers.objects.filter(
company_id=company_id,
).annotate(
name=F('classroom__classteachers__name'),
all_student_fields...
).values(
'teacher_id',
'name',
all_student_fields...
)
and it creates a 3 table join:
SELECT st.*, ct.teacher_id
FROM class_teachers ct INNER JOIN class_room cr ON (ct.class_room_id = cr.class_room_id)
LEFT OUTER JOIN students st ON (cr.class_room_id = st.class_room_id)
WHERE ct.class_room_id = '123';
Is there a way to create a 2 table join getting the teacher id and all student table fields without the need to join the ClassRoom table?
if this possible to rewrite this PostgreSQL query to Django ORM with no raw SQL, i tried it by myself but i haven't succeeded.
Here is Fund's model
class Fund(models.Model):
name = models.CharField(max_length=320, unique=True)
fund_manager = models.CharField()
..............
And here is NAV's model
class NAV(models.Model):
price = models.DecimalField(max_digits=9, verbose_name=_('NAV'))
date = models.DateField(verbose_name=_('date'))
fund = models.ForeignKey('Fund', related_name='navs')
created_date = models.DateTimeField(default=now)
And here is sql query that works and needs to be rewritten to Django ORM
select * from finance_fund ORDER BY(
(select price from finance_nav where date='2016-11-08' and fund_id=finance_fund.id) /
(select price from finance_nav where date='2016-11-07' and fund_id=finance_fund.id)
)
So basically i need to order by division result of two simple queries of backward relations of Foreign key
Thanks in advance !
Only with raw SQL. Try something like:
qs = Fund.objects.annotate(x=RawSQL("""
(select price from finance_nav where finance_fund.id=fund_id and date=%s limit 1)
/ (select price from finance_nav where finance_fund.id=fund_id and date=%s limit 1)
""",('2016-11-08', '2016-11-07'))).order_by('x')
I have a model with simple relation
class Tasks(models.Model):
initiator = models.ForeignKey(User, on_delete = models.CASCADE)
class TaskResponsiblePeople(models.Model):
task = models.ForeignKey('Tasks')
auth_user = models.ForeignKey(User)
And I need to write an analogue of an SQL query as follows:
select a.initiator, b.auth_user
from Tasks a
inner join TaskResponsiblePeople b
on TaskResponsiblePeople.task_id = task.id
where Tasks.initiator = 'value A' OR TaskResponsiblePeople.auth_user = 'value B'
The problem is that the OR statement deals with two different tables and I've got no idea about the right Django syntax to mimique the above-stated raw-SQL query. Help me out please !
UPDATE 1
According to the below-stated answer, I use the following code:
people = TaskResponsiblePeople.objects.filter(Q(task__initiator = request.user.id)|Q(auth_user = request.user.id)).select_related('auth_user')
print people.query
# The result of the print copy-pasted from console
# SELECT * FROM `task_responsible_people`
# LEFT OUTER JOIN `tasks` ON (`task_responsible_people`.`task_id` = `tasks`.`id`)
# LEFT OUTER JOIN `auth_user` T4
# ON (`task_responsible_people`.`auth_user_id` = T4.`id`)
# WHERE (`tasks`.`initiator_id` = 7 OR
# 'task_responsible_people`.`auth_user_id` = 7)
tasks = Tasks.objects.prefetch_related(
Prefetch('task_responsible_people', queryset=people, to_attr='people'))
However, in the final resultset I can still see records where neither initiator nor auth_user are equal to request.user (equal to 7 in this case)
I avoid using ".values" because of the potential need to serialize and transform the queryset into json.
I think you can do it this way if you just want those specific columns:
from django.db.models import Q
qs = Tasks.objects.filter(Q(initiator=userA) | Q(taskresponsiblepeople__auth_user=userB))\
.values('initiator', 'taskresponsiblepeople__auth_user')
To examine the generated query you can look at:
print(qs.query)
I don't have the models in my database but it should generate a query similar to following:
SELECT "tasks"."initiator_id", "taskresponsiblepeople"."auth_user_id"
FROM "tasks" LEFT OUTER JOIN "taskresponsiblepeople"
ON ( "tasks"."id" = "taskresponsiblepeople"."tasks_id" )
WHERE ("tasks"."initiator_id" = userA_id
OR "taskresponsiblepeople"."auth_user_id" = userB_id))
I have a table which contains list of some web sites and a table with statistics of them.
class Site(models.Model):
domain_name = models.CharField(
max_length=256,
unique=True,
)
class Stats(models.Model):
date = models.DateField()
site = models.ForeignKey('Site')
google_pr = models.PositiveIntegerField()
class Meta:
unique_together = ('site', 'date')
I want to see all sites and statistics for a concrete date. If a stats record for the date doesn't exist, then the selection must contain only site.
If I use:
Site.objects.filter(stats__date=my_date)
I will not get sites which have no records for my_date in stats table. Because in this case the SQL query will be like the following:
SELECT *
FROM site
LEFT OUTER JOIN stats ON site.id = stats.site_id
WHERE stats.date = 'my_date'
The query condition will exclude records with NULL-dates and sites without stats will be not included to the selection.
In my case I need join stats table, which has already been filtered by date:
SELECT *
FROM site
LEFT OUTER JOIN
(SELECT *
FROM stats
WHERE stats.date = 'my-date') AS stats
ON site.id = stats.site_id
How can I translate this query to Django ORM?
Thanks.
In Django v2.0 use FilteredRelation
Site.objects.annotate(
t=FilteredRelation(
'stats', condition=Q(stats__date='my-date')
).filter(t__google_pr__in=[...])
I had a similar problem and wrote the following utility function for adding left outer join on a subqueryset using Django ORM.
The util is derived from a solution given to add custom left outer join to another table (not subquery) using Django ORM. Here is that solution: https://stackoverflow.com/a/37688104/2367394
Following is the util and all related code:
from django.db.models.fields.related import ForeignObject
from django.db.models.options import Options
from django.db.models.sql.where import ExtraWhere
from django.db.models.sql.datastructures import Join
class CustomJoin(Join):
def __init__(self, subquery, subquery_params, parent_alias, table_alias, join_type, join_field, nullable):
self.subquery_params = subquery_params
super(CustomJoin, self).__init__(subquery, parent_alias, table_alias, join_type, join_field, nullable)
def as_sql(self, compiler, connection):
"""
Generates the full
LEFT OUTER JOIN (somequery) alias ON alias.somecol = othertable.othercol, params
clause for this join.
"""
params = []
sql = []
alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
params.extend(self.subquery_params)
qn = compiler.quote_name_unless_alias
qn2 = connection.ops.quote_name
sql.append('%s (%s)%s ON (' % (self.join_type, self.table_name, alias_str))
for index, (lhs_col, rhs_col) in enumerate(self.join_cols):
if index != 0:
sql.append(' AND ')
sql.append('%s.%s = %s.%s' % (
qn(self.parent_alias),
qn2(lhs_col),
qn(self.table_alias),
qn2(rhs_col),
))
extra_cond = self.join_field.get_extra_restriction(
compiler.query.where_class, self.table_alias, self.parent_alias)
if extra_cond:
extra_sql, extra_params = compiler.compile(extra_cond)
extra_sql = 'AND (%s)' % extra_sql
params.extend(extra_params)
sql.append('%s' % extra_sql)
sql.append(')')
return ' '.join(sql), params
def join_to(table, subquery, table_field, subquery_field, queryset, alias):
"""
Add a join on `subquery` to `queryset` (having table `table`).
"""
# here you can set complex clause for join
def extra_join_cond(where_class, alias, related_alias):
if (alias, related_alias) == ('[sys].[columns]',
'[sys].[database_permissions]'):
where = '[sys].[columns].[column_id] = ' \
'[sys].[database_permissions].[minor_id]'
children = [ExtraWhere([where], ())]
return where_class(children)
return None
foreign_object = ForeignObject(to=subquery, from_fields=[None], to_fields=[None], rel=None)
foreign_object.opts = Options(table._meta)
foreign_object.opts.model = table
foreign_object.get_joining_columns = lambda: ((table_field, subquery_field),)
foreign_object.get_extra_restriction = extra_join_cond
subquery_sql, subquery_params = subquery.query.sql_with_params()
join = CustomJoin(
subquery_sql, subquery_params, table._meta.db_table,
alias, "LEFT JOIN", foreign_object, True)
queryset.query.join(join)
# hook for set alias
join.table_alias = alias
queryset.query.external_aliases.add(alias)
return queryset
join_to is the utility function you want to use. For your query you can use it in as follows:
sq = Stats.objects.filter(date=my_date)
q = Site.objects.filter()
q = join_to(Site, sq, 'id', 'site_id', q, 'stats')
And following statement would print a query similar to you example query (with subquery).
print q.query
Look at it this way: you want to see statistics with accompanying site data for certain date, which translates to:
Stats.objects.filter(date=my_date).select_related('site')