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?
Related
Model:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
class Result(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
outcome = models.IntegerField()
time = models.DateTimeField()
Sql:
select * from person as p
inner join (
select person_id, max(time) as max_time, outcome from result
group by person_id
) r on p.id = r.person_id
where r.result in (2, 3)
I'm wanting to get the all person records where the last result outcome was either a 2 or 3. I added the raw sql above to further explain.
I looked at using a subquery to filter person records that have a matching result id
sub_query = Result.objects.values("person_id").annotate(max_time=Max("time"))
however using values strips out the other fields.
Ideally I'd be able to do this in one person queryset but I don't think that is the case.
The below query exclude all persons whose marks not equal to 2 OR 3, then it sorts the results in time descending order (latest will be on top) and finally get the details for person ...
from django.db.models import Q
results = Results.objects.filter(Q(outcome=3) | Q(outcome=2)).order_by('-time').values('person')
As a person may have multiple result records and I only want to check the last record, A subquery was the only way I could find to do this
last_result = Subquery(
Result.objects.filter(person_id=OuterRef("pk")).order_by("-time").values("result")[:1]
)
people = Person.objects.all().annotate(max_time=Max("result__time"), current_result=last_result).filter(current_result__in=[2,3)
First I create a sub query that will return the last result record. Then I add this as a field in the people query so that I can filter on that for only results with 2 or 3.
This was it will only return person records where the current result is a 2 or 3.
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'm trying to write a query in Django where I query a value from a table based on both the value of the foreign key and a modification to the foreign key. Consider an example database that stores the names, teams, and records of a team in one table that links to the league's revenue in another:
class League(models.Model):
id = models.PositiveSmallIntegerField(primary_key=True)
revenue = models.IntegerField(max_length=5)
class Team(models.Model):
name = models.TextField(max_length=20)
year = models.ForeignKey(League, max_length=4)
record = models.TextField(max_length=10)
How would I write a query so that I could create a table that included the name of the team, the year, this year's revenue, and the previous year's revenue?
You can define a many to many relationship:
class YearlyReport(models.Model):
year = models.CharField(max_length=4)
revenue = models.IntegerField(default=0)
class Team(models.Model):
name = models.CharField(max_length=20)
yearly_reports = models.ManyToManyField(YearlyReport)
And this is how you can add the reports:
t = Team(name='Test Team')
yearly_report_for_2018 = YearlyReport(revenue=100000, year='2018')
yearly_report_for_2017 = YearlyReport(revenue=50000, year='2017')
t.yearly_reports.add(yearly_report_for_2018)
t.yearly_reports.add(yearly_report_for_2017)
This is how you can get the reports:
r = YearlyReport.objects.get(team__name='Some Team')
print(r.year, r.revenue)
more in official docs here:
https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_many/
I have these three tables
class IdentificationAddress(models.Model):
id_ident_address = models.AutoField(primary_key=True)
ident = models.ForeignKey('Ident', models.DO_NOTHING, db_column='ident')
address = models.TextField()
time = models.DateTimeField()
class Meta:
managed = False
db_table = 'identification_address'
class IdentC(models.Model):
id_ident = models.AutoField(primary_key=True)
ident = models.TextField(unique=True)
name = models.TextField()
class Meta:
managed = False
db_table = 'ident_c'
class location(models.Model):
id_ident_loc = models.AutoField(primary_key=True)
ident = models.ForeignKey('IdentC', models.DO_NOTHING, db_column='ident')
loc_name = models.TextField()
class Meta:
managed = False
db_table = 'location
I want to get the last
address field (It could be zero) from IdentificationAddress model, the last _loc_name_ field (it matches at least one) from location model, name field (Only one) from IdentC model and ident field. The search is base on ident field.
I have been reading about many_to_many relationships and prefetch_related. But, they don't seem to be the best way to get these information.
If a use SQL syntax, this instruction does the job:
SELECT ident_c.name, ident_c.ident, identification_address.address, location.loc_name FROM identn_c FULL OUTER JOIN location ON ident_c.ident=location.ident FULL OUTER JOIN identification_address ON ident_c.ident=identification_address.ident;
or for this case
SELECT ident_c.name, ident_c.ident, identification_address.address, location.loc_name FROM identn_c LEFT JOIN location ON ident_c.ident=location.ident LEFT JOIN identification_address ON ident_c.ident=identification_address.ident;
Based on my little understanding of Django, JOIN instructions cannot be implemented. Hope I am wrong.
Django ORM take care of it if you set relationship between models.
for example,
models.py
class Aexample(models.Model):
name = models.CharField(max_length=20)
class Bexample(models.Model):
name = models.CharField(max_length=20)
fkexample = models.ForeignKey(Aexample)
shell
examplequery = Bexample.objects.filter(fkexample__name="hellothere")
SQL query
SELECT
"yourtable_bexample"."id",
"yourtable_bexample"."name",
"yourtable_bexample"."fkexample_id"
FROM "yourtable_bexample"
INNER JOIN "yourtable_aexample"
ON ("yourtable_bexample"."fkexample_id" = "yourtable_aexample"."id")
WHERE "yourtable_aexample"."name" = hellothere
you want to make query in Django like below
SELECT ident_c.name, ident_c.ident, identification_address.address, location.loc_name
FROM identn_c
LEFT JOIN location ON ident_c.ident=location.ident
LEFT JOIN identification_address ON ident_c.ident=identification_address.ident;
It means you want all rows from identn_c, right?. If you make proper relationship between your tables for your purpose, Django ORM takes care of it.
class IntentC(model.Model):
exampleA = models.ForeignKey(ExampleA)
exampleB = models.ForeignKey(ExampleB)
this command make query with JOIN Clause.
identn_instance = IdentC.objects.get(id=somenumber)
identn_instance.exampleA
identn_instance.exampleB
you can show every IntentC rows and relating rows in different tables.
for in in IntentC.objects.all(): #you can all rows in IntentC
print(in.exampleA.name)
#show name column in exampleA table
#JOIN ... ON intenctctable.example_id = exampleatable.id
print(in.exampleB.name) #show name column in exampleB table / JOIN ... ON
sql query:
SELECT *
FROM "notification_notification" AS T0
LEFT JOIN (SELECT *
FROM "notification_usernotification"
WHERE user_id = 1) AS T
ON (T0.id = T.notification_id)
models:
class Notification(models.Model):
subs_code = models.CharField()
subs_name = models.CharField()
users = models.ManyToManyField(settings.AUTH_USER_MODEL,
through='UserNotification')
class UserNotification(models.Model):
notification = models.ForeignKey(Notification)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
push_message = models.BooleanField()
Is it possible?
i try various technique, but i can't create that simple sql under django ORM;
notification_notification table AS T0
|---id---|-----subs_code-----|-----subs_name-----|
|---1----|-----system----------|----system------------|
|---2----|-----broadcast------|-----broadcast-------|
|---3----|-----not_need-------|-----not_need-------|
notification_usernotification table AS T1
|---id---|-notification_id-|-user_id-|-push_message-|
|---11--|---------1----------|----1------|--------true---------|
|---12--|---------2----------|----1------|--------false--------|
|---22--|---------2----------|-----2-----|--------true---------|
i use left join for that result:
Result:
|-T1.id-|-subs_code-|-subs_name-|-T1.id-|-notification_id-|-user_id-|-push_message-|
|---1----|---system----|---system-----|---11--|------------1-------|---1-------|----true-------------|
|---2----|---broadcast|---broadcast--|--12---|------------2-------|---1-------|----false-----------|
|---3----|---not_need-|---not_need--|--null-|---------null--------|---null----|----null-------------|
INNER JOIN is not valid there are ((
sqlfiddle
i think it possible only for raw sql
it write raw sql query.
it write by extra: see in here stackoverflow
Notification.objects.extra(
select={
'push_message':
'SELECT {tbl_1}.push_message::BOOLEAN FROM {tbl_1} '
'WHERE {tbl_1}.notification_id = {tbl_2}.id '
'and {tbl_1}.user_id = %s'.format(
tbl_1=UserNotification._meta.db_table,
tbl_2=Notification._meta.db_table)},
select_params=(user.id,))