Let me first present model it will be easier to explain my question
Class A:
points = Int
Class B:
fk = ForignKey(A)
Let us assume that we have many A and B objects
top_a = A.objects.all().order_by("points")[:3]
result = B.objects.filter(fk__in=list(top_a))
Is there possibility of getting same results as above one but doing it in 1 single query? Result will be all of instances of B which have fk to one of A from top 3
Yes you can do like:
result = B.objects.all().order_by('fk__points')[:3]
Related
I have model like this:
class A:
....
class B:
....
a = model.ForeignKey(A, related_name='a')
....
Let's assume there is an B object.
I can get A object like this:
b = B()
a = b.a
Then what is the simplest way to get all B object related with A?
Additionally,
I can get a list of A.
list_a = A.objects.filter()
Then what is the simplest way of getting a list of B which relates with A object in the list_a?
One more reverse case: I have a list of B:
list_b = B.objects.filter()
Then what is the simplest and optimized way to get the list of A object related to the B object in the list_b?
B.objects.filter(a__in=a_list)
note that you can filter on related objects like this (instead if executing two queries do it in one)
for example if your a_list is a query like this:
a_list = A.objects.filter(field=2)
you can filter B like this:
B.objects.filter(a__field=2)
which is more readable and also django can optimize it too)
Update: you can query reversed relations the same way
A.objects.filter(b__in=b_list)
A.objects.filter(b__field=2)
note that it's better to change your code to
a = model.ForeignKey(A, related_name='b')
b is the name of the field in reveres relations so an_a_instance.b.all() returns all instances of b which are pointing at given a_instance
Given a simple set of models as follows:
class A(models.Model):
pass
class B(models.Model):
parent = models.ForeignKey(A, related_name='b_set')
class C(models.Model):
parent = models.ForeignKey(B, related_name='c_set')
I am looking to create a query set of the A model with two annotations. One annotation should be the number of B rows that have the A row in question as their parent. The other annotation should denote the number of B rows, again with the A object in question as parent, which have at least n objects of type C in their c_set.
As an example, consider the following database and n = 3:
Table A
id
0
1
Table B
id parent
0 0
1 0
Table C
id parent
0 0
1 0
2 1
3 1
4 1
I'd like to be able to get a result of the form [(0, 2, 1), (1, 0, 0)] as the A object with id 0 has two B objects of which one has at least three related C objects. The A object with id 1 has no B objects and therefore also no B objects with at least three C rows.
The first annotation is trivial:
A.objects.annotate(annotation_1=Count('b_set'))
What I am trying to design now is the second annotation. I have managed to count the number of B rows per A where the B object has at least a single C object as follows:
A.objects.annotate(annotation_2=Count('b_set__c_set__parent', distinct=True))
But I cannot figure out a way to do it with a minimum related set size other than one. Hopefully someone here can point me in the right direction. One method I was thinking of was somehow annotating the B objects in the query instead of the A rows as is the default of the annotate method but I could not find any resources on this.
This is a complicated query at limits of Django 1.11. I decided to do it by two queries and to combine results to one list that can be used by a view like a queryset:
from django.db.models import Count
sub_qs = (
C.objects
.values('parent')
.annotate(c_count=Count('id'))
.order_by()
.filter(c_count__gte=n)
.values('parent')
)
qs = B.objects.filter(id__in=sub_qs).values('parent_id').annotate(cnt=Count('id'))
qs_map = {x['parent_id']: x['cnt'] for x in qs}
rows = list(A.objects.annotate(annotation_1=Count('b_set')))
for row in rows:
row.annotation_2 = qs_map.get(row.id, 0)
The list rows is the result. The more complicated qs.query is compiled to a relative simple SQL:
>>> print(str(qs.query))
SELECT app_b.parent_id, COUNT(app_b.id) AS cnt
FROM app_b
WHERE app_b.id IN (
SELECT U0.parent_id AS Col1 FROM app_c U0
GROUP BY U0.parent_id HAVING COUNT(U0.id) >= 3
)
GROUP BY app_b.parent_id; -- (added white space and removed double quotes)
This simple solution can be easier modified and tested.
Note: A solution by one query also exists, but doesn't seem useful. Why: It would require Subquery and OuterRef(). They are great, however in general Count() from aggregation is not supported by queries that are compiled together with join resolution. A subquery can be separated by lookup ...__in=... to can be compiled by Django, but then it is not possible to use OuterRef(). If it is written without OuterRef() then it is a so complicated not optimal nested SQL that the time complexity would be probably O(n2) by size of A table for many (or all) database backends. Not tested.
I have the following models where B has a many-to-one relationship with A:
class A(model.Model):
name = models.IntegerField()
class B(models.Model
a = models.ForeignKey(A, db_column='a_id')
When I use a queryset on A, is there a way to exclude the rows in A that have no rows in B?
Use isnull :
A.objects.filter(b__isnull=False).distinct()
Using distinct() prevents duplicate entries, otherwise each a appears once for every b which is linked to it.
no_rows_in_b = B.objects.all().select_related('a')
will get you all the B's with A's
Then you can cycle through them and output the A's
If you want non-repeats:
no_rows_in_b = B.objects.all().distinct('a').select_related('a')
Then:
for rec in no_rows_in_b:
print(rec.a)
Notice that if you want to be more explicit, you could do something like this:
A.objects.exclude(b__isnull=True).distinct()
using exclude instead of filter and using the True boolean arg.
I have a Django model that looks something like this:
class Response(models.Model):
transcript = models.TextField(null=True)
class Coding(models.Model):
qid = models.CharField(max_length = 30)
value = models.CharField(max_length = 200)
response = models.ForeignKey(Response)
coder = models.ForeignKey(User)
For each Response object, there are two coding objects with qid = "risk", one for coder 3 and one for coder 4. What I would like to be able to do is get a list of all Response objects for which the difference in value between coder 3 and coder 4 is greater than 1. The value field stores numbers 1-7.
I realize in hindsight that setting up value as a CharField may have been a mistake, but hopefully I can get around that.
I believe something like the following SQL would do what I'm looking for, but I'd rather do this with the ORM
SELECT UNIQUE c1.response_id FROM coding c1, coding c2
WHERE c1.coder_id = 3 AND
c2.coder_id = 4 AND
c1.qid = "risk" AND
c2.qid = "risk" AND
c1.response_id = c2.response_id AND
c1.value - c2.value > 1
from django.db.models import F
qset = Coding.objects.filter(response__coding__value__gt=F('value') + 1,
qid='risk', coder=4
).extra(where=['T3.qid = %s', 'T3.coder_id = %s'],
params=['risk', 3])
responses = [c.response for c in qset.select_related('response')]
When you join to a table already in the query, the ORM will assign the second one an alias, in this case T3, which you can using in parameters to extra(). To find out what the alias is you can drop into the shell and print qset.query.
See Django documentation on F objects and extra
Update: It seems you actually don't have to use extra(), or figure out what alias django uses, because every time you refer to response__coding in your lookups, django will use the alias created initially. Here's one way to look for differences in either direction:
from django.db.models import Q, F
gt = Q(response__coding__value__gt=F('value') + 1)
lt = Q(response__coding__value__lt=F('value') - 1)
match = Q(response__coding__qid='risk', response__coding__coder=4)
qset = Coding.objects.filter(match & (gt | lt), qid='risk', coder=3)
responses = [c.response for c in qset.select_related('response')]
See Django documentation on Q objects
BTW, If you are going to want both Coding instances, you have an N + 1 queries problem here, because django's select_related() won't get reverse FK relationships. But since you have the data in the query already, you could retrieve the required information using the T3 alias as described above and extra(select={'other_value':'T3.value'}). The value data from the corresponding Coding record would be accessible as an attribute on the retrieved Coding instance, i.e. as c.other_value.
Incidentally, your question is general enough, but it looks like you have an entity-attribute-value schema, which in an RDB scenario is generally considered an anti-pattern. You might be better off long-term (and this query would be simpler) with a risk field:
class Coding(models.Model):
response = models.ForeignKey(Response)
coder = models.ForeignKey(User)
risk = models.IntegerField()
# other fields for other qid 'attribute' names...
Suppose I have the following Django models:
class myObj1(models.Model):
myField1 = models.IntegerField()
class myObj2(models.Model):
myLocalObj1 = models.ManyToManyField(myObj1)
Furthermore, suppose I have a list of unique myObj1s:
a = myObj1(myField=1)
b = myObj1(myField=2)
c = myObj1(myField=3)
myTargetList = [a, b, c]
Now, I would like to write a Django query using Q objects such that it returns all the myObj2s that have any member of myTargetList as myLocalObj1. Furthermore, I don't know the exact size of myTargetList in advance.
How should I do it? This obviously won't work:
myObj2.objects.filter(Q(myLocalObj1__in=myTargetList))
EDIT: To make this a little more in line with what you are looking for (although I would not necessarily recommend doing things this way), you could:
vallist=[]
for b in myTargetList:
vallist.append(b.myField)
myObj2.objects.filter(myLocalObj1__in=myObj1.objects.filter(myField__in=vallist))
This is available in the Django docs here:
https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships