Query with two reverse relationships in Django - sql

If I have these Django models:
class ModelA(Model):
pass
class ModelB(Model):
a = ForeignKey(ModelA, ...)
d = ForeignKey(ModelD, ...)
r = IntegerField()
q = IntegerField()
class ModelC(Model):
a = ForeignKey(ModelA, ...)
d = ForeignKey(ModelD, ...)
r = IntegerField()
class ModelD(Model):
pass
How can I do a Django ORM query equivalent to this:
select ModelB.q, ModelA.id, ModelD.id
from ModelA
join ModelB on ModelB.a_id=ModelA.id
left join ModelC on (ModelC.a_id=ModelB.a_id and ModelC.r=ModelB.r
and ModelC.d_id=ModelB.d_id)
join ModelD on ModelB.d_id=ModelD.id
where ModelC.id is null
I.e. get ModelBs whose fields don't match any ModelCs, where both ModelB and ModelC have foreign keys to two other models.

Related

JPA Criteria 2 embebbed JOINs and condition between them

I'm gonna adapt my problem to a simple example. Lets say I have 2 entities: Employee and EmployeeCompany (the class that defines the relation of a Employee with a Company).
I need to retrieve the Employee entities that are associated with 2 EmployeeCompany entities, and these 2 EmployeeCompany entities must share the same value for a specific attribute (let's call this attribute "X"). EmployeeCompany entities have also embebbed identifiers, so the entities definition would look something like this:
Employee:
Long idEmployee;
List<EmployeeCompany> employeeCompanies;
EmployeeCompany:
// Embebbed ID
EmployeeCompanyId id;
Long X;
EmployeeCompanyId:
Long idEmployee;
Long idCompany;
This is what I'm currently doing to make the 2 JOINs with the EmployeeCompany tables starting from the Employee entity:
Root<T> root = criteria.from(Employee.class);
Join<Object, Object> joinFirstEmployeeCompanyId = root.join("employeeCompanies").join("id");
Predicate finalPredicate = builder.equal(joinFirstEmployeeCompanyId.get("idCompany"), idCompany1);
Join<Object, Object> joinSecondEmployeeCompanyId = root.join("employeeCompanies").join("id");
finalPredicate = builder.and(finalPredicate, builder.equal(joinSecondEmployeeCompanyId.get("idCompany"), idCompany2));
My problem is, I don't know how I can specify now that between these 2 joins, they must meet the X attribute condition (both EmployeeCompany entities must have the same value for the X attribute).
In case it helps, this is what the SQL query would look like:
SELECT * FROM Employee e
INNER JOIN EmployeeCompany ec1 ON ec1.idemployee = e.idemployee AND ec1.idcompany = :idCompany1
INNER JOIN EmployeeCompany ec2 ON ec2.idemployee = e.idemployee AND ec2.idcompany = :idCompany2
WHERE c1.X = c2.X
I solved my issue this way:
Root<T> root = criteria.from(Employee.class);
Join<Object, Object> joinFirstEmployeeCompany = root.join("employeeCompanies");
joinFirstEmployeeCompany.on(builder.equal(joinFirstEmployeeCompany.get("id").get("idCompany"), idCompany1));
Join<Object, Object> joinSecondEmployeeCompany = root.join("employeeCompanies");
joinSecondEmployeeCompany.on(builder.equal(joinSecondEmployeeCompany.get("id").get("idCompany"), idCompany2));
Predicate finalPredicate = builder.equal(joinFirstEmployeeCompany.get("X"), joinSecondEmployeeCompany.get("X"));

How do I filter Django models to only include models that appear in a child (with a ForeignKey) table?

I have these two Django models:
class Animals(models.Model):
id = models.AutoField(primary_key=True)
name = models.TextField(unique=True)
class Meta:
db_table = 'animals'
class AnimalSounds(models.Model):
id = models.AutoField(primary_key=True)
animal = models.ForeignKey(Animal, on_delete=models.PROTECT)
sound = models.TextField(unique=True)
class Meta:
db_table = 'animal_sounds'
Now my SQL query would look something like this:
SELECT * FROM animals WHERE animals.id IN (
SELECT animal_id FROM animal_sounds
)
How would I do this using Django models? Something like this conceptually:
Animals.objects.filter(id__in=AnimalSounds.objects.all('ids'))
You can check if there is at least one AnimalSound related with:
Animal.objects.filter(animalsounds__isnull=False).distinct()
This will perform a query like:
SELECT DISTINCT animal.*
FROM animal AS ani
JOIN animalsounds AS ans ON ans.animal_id = ani.id
Or you can use your approach with:
Animals.objects.filter(
id__in=AnimalSounds.objects.values('animal_id', flat=True)
)
But I prefer the former since it is more declarative and shorter.
EDIT: If you want to select all Animals that have a related AnimalSounds object where sound is 'woof', you can write it like:
# animals that produce woof (and perhaps other sounds)
Animal.objects.filter(animalsounds__sound='woof').distinct()

Django equivalent for SQL query using INNER JOIN -clause

I would like to know the django equivalent for the SQL-query that uses the INNER JOIN -clause. I have two models that are linked with ForeignKey.
class Item(models.Model):
item_name = models.CharField(max_length=100)
item_is_locked = models.BooleanField(default=False)
class Request(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
item_owner = models.ForeignKey(settings.AUTH_USER_MODEL)
message_body = models.TextField(max_length=5000, null=True)
I want to get fields from the Request-table which has the "item_is_locked" value set to false in Item-table
If using SQL-query I would use this:
SELECT Request.item_owner,Request.message_body FROM Request INNER JOIN Item ON Request.item_id=Item.id AND Item.item_is_locked=False;
You can use filter and only to get desired result.
Try:
Request.objects.filter(item__item_is_locked=False).only('item_owner', 'message_body')

django self join query using aliases

am trying to use queryset to perform the following query without using raw SQL. any idea how can do that?
select * from category_main a, category_list b, category_main c where b.main_id=c.id and a.id=c.parent_id
UPDATED
below are my models
class Main(models.Model):
slug = models.SlugField()
is_active = models.BooleanField(default=True)
site = models.ForeignKey(Site)
parent = models.ForeignKey('self', blank=True, null=True, limit_choices_to={'parent' : None})
class Meta:
unique_together = (("slug", "parent"))
def __unicode__(self):
return self.slug
class List(models.Model):
main = models.ForeignKey(Main)
slug = models.SlugField(unique=True)
is_active = models.BooleanField(default=True)
parent = models.ForeignKey('self', blank=True, null=True)
def __unicode__(self):
return self.slug
UPDATE
Hi, I just managed to find a query that does that for me, I used advised below to join main with main's parent and from there I joined list with main list using the below
Main.objects.select_related('main', 'parent').filter(list__is_active=True, maini18n__language='en', list__listi18n__language='en').query.__str__()
'SELECT `category_main`.`id`, `category_main`.`slug`, `category_main`.`is_active`, `category_main`.`site_id`, `category_main`.`parent_id`, T5.`id`, T5.`slug`, T5.`is_active`, T5.`site_id`, T5.`parent_id` FROM `category_main` INNER JOIN `category_maini18n` ON (`category_main`.`id` = `category_maini18n`.`main_id`) INNER JOIN `category_list` ON (`category_main`.`id` = `category_list`.`main_id`) INNER JOIN `category_listi18n` ON (`category_list`.`id` = `category_listi18n`.`list_id`) LEFT OUTER JOIN `category_main` T5 ON (`category_main`.`parent_id` = T5.`id`) WHERE (`category_maini18n`.`language` = en AND `category_list`.`is_active` = True AND `category_listi18n`.`language` = en )'
the returned query mapped everything I need, accept its not being added to the select statement, is there a way so i can force it to select columns from category_list.* ?
This does basically what you want:
lists = List.objects.select_related('main', 'parent')
Note you have to explicitly state the relationships to follow in select_related here, because your parent relationship has null=True which isn't followed by default.
This will give you a set of List objects, but pre-fetch the related Main and List objects which you can reference as normal without hitting the db again.

Django LEFT OUTER JOIN on TWO columns where one isn't a foreign key

I have two models like so:
class ObjectLock(models.Model):
partner = models.ForeignKey(Partner)
object_id = models.CharField(max_length=100)
class Meta:
unique_together = (('partner', 'object_id'),)
class ObjectImportQueue(models.Model):
partner = models.ForeignKey(Partner)
object_id = models.CharField(max_length=100)
... # other fields
created = models.DateTimeField(auto_now_add = True)
modified = models.DateTimeField(auto_now = True, db_index=True)
class Meta:
ordering = ('modified', 'created')
There is nothing notable about the third model mentioned above (Partner).
I'd like to get something like:
SELECT * FROM ObjectImportQueue q LEFT OUTER JOIN ObjectLock l ON
q.partner_id=l.partner_id AND q.object_id=l.object_id WHERE l.object_id
IS NULL and l.partner_id IS NULL;
I came across this page that tells how to do custom joins, and I tried passing in a tuple of the column names to join in place of the column name to join, and that didn't work. The Partner table shouldn't need to be included in the resulting sql query but I will accept an answer that does include it as long as it effectively does what I'm trying to do with one query.
If you're using Django 1.2+ and know the SQL you want, you could always fall back to a Raw Query.
I also meet a similar question.but finally,I found I asked a wrong question to be solve.
in the Django ORM,the condition of SQL join is base on what the models.Model fields defined.
there are Many-to-one relationships (ForeignKey),Many-to-many relationships(ManyToManyField),One-to-one relationships(OneToOneField).
in your situation.ObjectLockModel and ObjectImportQueueModel have the same part of fields, the partnerfield and object_idfield.yon should use One-to-one relationships.
you can change your Model like this:
class ObjectImportQueue(models.Model):
partner = models.ForeignKey(Partner)
object_id = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add = True)
modified = models.DateTimeField(auto_now = True, db_index=True)
def __unicode__(self):
return u"%s:%s" % (self.partner, self.object_id)
class Meta:
ordering = ('modified', 'created')
class ObjectLock(models.Model):
lock = models.OneToOneField(ObjectImportQueue, null=True)
class Meta:
unique_together = (('lock',),)
order of Model is import,OneToOneField argument model must come first.
>>> p1 = Partner.objects.get(pk=1)
>>> p2 = Partner.objects.get(pk=2)
>>> Q1 = ObjectImportQueue.objects.create(partner=p1,object_id='id_Q1')
>>> Q2 = ObjectImportQueue.objects.create(partner=p2,object_id='id_Q2')
>>> ObjectImportQueue.objects.filter(lock__isnull=True)
[<ObjectImportQueue: Partner object:id_Q1>, <ObjectImportQueue: Partner object:id_Q2>]
>>> L1 = ObjectLock.objects.create(lock=Q1)
>>> ObjectImportQueue.objects.filter(lock__isnull=True)
[<ObjectImportQueue: Partner object:id_Q2>]
ObjectLock.objects.createlock a object
ObjectImportQueue.objects.filter(lock__isnull=True) select object don't be lock.
if you use the appropriate relationships, generate the ORM query will be easy.In Django,Define the relationships during you build the Model is better than use Query statement to relation the relationships between tables.
I just found a solution to this problem.
You have to create a view that does the join for you
CREATE VIEW ImporQueueLock AS (
SELECT q.id, l.id
FROM ObjectImportQueue q
LEFT OUTER JOIN ObjectLock l
ON q.partner_id=l.partner_id AND q.object_id=l.object_id
)
Then make a django model for that view
class ImportQueueLock(models.Model):
queue = models.ForeignKey(ObjectImportQueue, db_column='q')
lock = models.ForeignKey(ObjectLock, db_column='l')
Then make a ManyToMany on your Django model from ObjectLock to ObjectImportQueue through ImportQueueLock
class ObjectLock(models.Model):
partner = models.ForeignKey(Partner)
object_id = models.CharField(max_length=100)
queue = models.ManyToManyField(ObjectImportQueue, through = ImportQueueLock)
and you will be able to do
ObjectLock.objects.filter(importqueuelock__objectimportqueue__ .....)