Django ORM: Reverse relation query multiple model from single models - django

I am in trouble querying reverse relation, I learned a lot about select_related and prefetch_related, yet I failed to achieve this.
At first see my models:
from django.db import models
import uuid
class Person(models.Model):
alias = models.UUIDField(primary_key=True,default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=20)
class Appointment(models.Model):
patient = models.ForeignKey(Person, related_name="patient_for_appointment", on_delete=models.CASCADE)
data = models.CharField(max_length=20)
class Sales(models.Model):
customer = models.ForeignKey(Person, related_name="customer_for_sales", on_delete=models.CASCADE)
amount = models.FloatField()
class Prescription(models.Model):
patient = models.ForeignKey(Person, related_name="Patient_for_prescription", on_delete=models.CASCADE)
details = models.CharField(max_length=100)
I am trying to filter Person Model to check if the person has any prescription, sales, and appointment I want to get these all with single query like the person. will filter it with a person alias (primary key)
I can filter it with separate query like
patient_alias = '53fsdfsdf-fdsfds-df-fdf'
queryset = Appointment.objects.filter(
patient__alias=patient_alias
)
But I don't want this, coz, it has a performance issue. I don't want this with separate query.
I want to query only Person model to check if a person has Appointment, prescription or Sales
like Person.objects.filter(alias='a person alias)
Can anyone please help me to achieve this?
Much appreciated

We can select all Persons, except the ones that hav no Appointment, nor a Sales or Prescription with:
Person.objects.exclude(
patient_for_appointment=None,
customer_for_sales=None,
Patient_for_prescription=None
)
This will yield a query that looks like:
SELECT person.alias, person.name
FROM person
WHERE NOT (
person.alias IN (
SELECT U0.alias
FROM person U0
LEFT OUTER JOIN prescription U1 ON U0.alias = U1.patient_id
WHERE U1.id IS NULL
)
AND person.alias IN (
SELECT U0.alias
FROM person U0
LEFT OUTER JOIN sales U1 ON U0.alias = U1.customer_id
WHERE U1.id IS NULL AND U0.alias = person.alias
)
AND person.alias IN (
SELECT U0.alias
FROM person U0
LEFT OUTER JOIN appointment U1
ON U0.alias = U1.patient_id
WHERE U1.id IS NULL AND U0.alias = person.alias
)
)
Or we can make a union, like:
Person.objects.filter(
patient_for_appointment__isnull=False
).union(
Person.objects.filter(customer_for_sales__isnull=False),
Person.objects.filter(Patient_for_prescription__isnull=False)
)
This will result in a query that looks like:
(
SELECT person.alias, person.name
FROM person
INNER JOIN appointment ON person.alias = appointment.patient_id
WHERE appointment.id IS NOT NULL
) UNION (
SELECT person.alias, person.name FROM person
INNER JOIN sales ON person.alias = sales.customer_id
WHERE sales.id IS NOT NULL
) UNION (
SELECT person.alias, person.name
FROM person
INNER JOIN prescription ON person.alias = prescription.patient_id
WHERE prescription.id IS NOT NULL
)

Related

Exclude related objects before Count

For example I've got 3 models User, A, B.
class A(models.Model):
creator = models.ForeignKey(
'users.User', on_delete=models.CASCADE, related_name='A_set'
)
class B(models.Model):
user = models.ForeignKey(
'users.User', on_delete=models.CASCADE, related_name='B_set'
)
a_model = models.ForeignKey(
'a.A', on_delete=models.CASCADE, related_name='B_set'
)
I would like to get count of B where user isn't a creator of a_model object.
I've tried query:
`User.objects.prefetch_related('B_set').last().B_set.exclude(a_model__creator=F('user')).count()`
Here is my try with annotation:
User.objects.annotate(b_count=Count('B_set', filter=(~Q(B_set__A__creator=F('user')))))
But I am getting an error:
Cannot resolve keyword 'user' into field.
And then it suggest me fields that relate to User. Also I tried to change user to B_set__user with F() but it didn't help.
With your annotation:
User.objects.annotate(
b_count=Count('B_set', filter=(~Q(B_set__A__creator=F('user'))))
)
your F(..) attribute refers to a hypothetical User.user field, but a User has (probably) no user field.
If you want to refer to the "self" here, you can use F('pk') (or F('id'), given if id is the primary key), so you can write this expression as:
User.objects.annotate(
b_count=Count('B_set', filter=~Q(B_set__a__model__creator=F('pk')))
)
This then results in a query like:
SELECT user.*,
COUNT(
CASE WHEN NOT a.creator_id = user.id AND a.creator_id IS NOT NULL
THEN b.id ELSE NULL END
) AS b_count
FROM user
LEFT OUTER JOIN b ON user.id = b.user_id
LEFT OUTER JOIN a ON b.a_model_id=a.id
GROUP BY user.id

Join 2 tables with no ForeignKey link

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?

Django FULL OUTER JOIN

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

Django not in other table (Not using id)

I have:
Models:
class Category(models.Model):
description = models.CharField(unique=True, max_length=200)
myfield = models.CharField(unique=True, max_length=200)
class Car(models.Model):
categorys = models.ManyToManyField(Category)
myfield = models.CharField(unique=True, max_length=200)
I am trying to execute the following query:
Car.objects.filter(category__id = c.id )
.exclude(myfield__in =
Category.objects.all()
.only('myfield')
)
.order_by("id")
.first()
I expected to find a result like this:
SELECT ...
FROM `myapp_car`
INNER JOIN `myapp_car_categorys`
ON (`myapp_car`.`id` = `myapp_car_categorys`.`car_id`)
WHERE ( ...
AND NOT (`myapp_car`.`myfield` IN
(SELECT `myapp_category`.`myfield` FROM `myapp_category`)))
ORDER BY `myapp_car`.`id` ASC LIMIT 1;
But i find:
SELECT ...
FROM `myapp_car`
INNER JOIN `myapp_car_categorys`
ON (`myapp_car`.`id` = `myapp_car_categorys`.`car_id`)
WHERE ( ...
AND NOT (`myapp_car`.`myfield` IN
(SELECT `myapp_category`.`id` FROM `myapp_category`)))
ORDER BY `myapp_car`.`id` ASC LIMIT 1;
I need use myfield in select, not id:
(SELECT `myapp_category`.`myfield` FROM `myapp_category`)
How would I get this result?
Even if you use only it will return the objects of Category model. So if you use it in in filtering the category objects will be used for filtering and for SQL the ids of those category objects.
If you need to filter on the myfield values on all the category objects, then use values instead:
....exclude(myfield__in=Category.objects.values('myfield'))

Advanced select with django ORM

I am using the following model:
class Topping(models.Model):
name = models.CharField(max_length=30)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
def __str__(self): # __unicode__ on Python 2
return "%s (%s)" % (self.name, ", ".join(topping.name
for topping in self.toppings.all()))
And now I want only the elements for vegetarian menu, filtered by tomatoes
pizza_item = Pizza.objects.filter(toppings__name='tomatoes')
My select is:
SELECT `pizza`.`id`, `pizza`.`name`
FROM `pizza`
INNER JOIN `pizza_toppings` ON (
`pizza`.`id` = `pizza_toppings`.`pizza_id` )
INNER JOIN `web_topping` ON (
`pizza_toppings`.`topping_id` = `topping`.`id` )
WHERE `topping`.`name` = azucar
but i want get:
SELECT `pizza`.`id`, `pizza`.`name`, `topping`.`name`
FROM `pizza`
INNER JOIN `pizza_toppings` ON (
`pizza`.`id` = `pizza_toppings`.`pizza_id` )
INNER JOIN `web_topping` ON (
`pizza_toppings`.`topping_id` = `topping`.`id` )
WHERE `topping`.`name` = azucar
This last query works fine in mysql db. And works using pizza.objects.raw but i want get using django ORM
Is a select with topping.name i try it using prefetch_select('toppings'). but i cant get the same select.
Have you tried using the values method for Queryset ?
Something like :
pizza_item = Pizza.objects.filter(toppings__name='tomatoes').values("id", "name", "toppings__name")
I am not sure if that's doable. Because when you use Pizza.objects... you are limited to the fields that are in the Pizza model. Since the Pizza model does not contain toppings' name field. You cannot retrieve it. You can only retrieve toppings' id field:
pizza_item = Pizza.objects.filter(toppings__name='tomatoes').values('id', 'name', 'toppings')
Which will provide "toppings"."topping_id" in SELECT.
Also, since you have specified that toppings__name='tomatoes, all of toppings' name will be tomatoes in this queryset, so what is the point of having topping.name in your result?