ironfroggy
ironfroggy

Reputation: 8125

How can I query a model by if there is a subclass instance?

I have these two simple models, A and B:

from django.db import models

class A(models.Model):
  name = models.CharField(max_length=10)

class B(A):
  age = models.IntegerField()

Now, how can I query for all instances of A which do not have an instance of B?

The only way I found requires an explicitly unique field on each subclass, which is NOT NULL, so that I can do A.objects.filter(b__this_is_a_b=None), for example, to get instances that are not also B instances. I'm looking for a way to do this without adding an explicit silly flag like that.

I also don't want to query for all of the objects and then filter them in Python. I want to get the DB to do it for me, which is basically something like SELECT * FROM A WHERE A.id in (SELECT id from B)

Upvotes: 2

Views: 789

Answers (3)

michel.iamit
michel.iamit

Reputation: 5916

Since some version of django or python this works as well:

A.Objects.all().filter(b__isnull=True)

because if a is an A object a.b gives the subclass B of a when it exists

I Know this is an old question, but my answer might help new searchers on this subject.

see also:

multi table inheritance

And one of my own questions about this: downcasting a super class to a sub class

Upvotes: 2

tgray
tgray

Reputation: 8976

I don't work with django, but it looks like you want the isinstance(obj, type) built-in python method.

Edit:
Would A.objects.exclude(id__exact=B__id) work?

Upvotes: 0

AdamKG
AdamKG

Reputation: 14091

I'm not sure it's possible to do this purely in the DB with Django's ORM, in a single query. Here's the best I've been able to do:

A.objects.exclude(id__in=[r[0] for r in B.objects.values_list("a_ptr_id")])

This is 2 DB queries, and works best with a simplistic inheritance graph - each subclass of A would require a new database query.


Okay, it took a lot of trial and error, but I have a solution. It's ugly as all hell, and the SQL is probably worse than just going with two queries, but you can do something like so:

A.objects.exclude(b__age__isnull=True).exclude(b__age_isnull=False)

There's no way to get Django to do the join without referencing a field on b. But with these successive .exclude()s, you make any A with a B subclass match one or the other of the excludes. All you're left with are A's without a B subclass.

Anyway, this is an interesting use case, you should bring it up on django-dev...

Upvotes: 1

Related Questions