Sum of Subquery fields django - django

I have the following subquery:
Subquery(
ContestTaskRelationship.objects.filter(
contest=contest,
solved=OuterRef('id')
).values('cost').all()
)
I need then to annotate my QuerySet with sum of cost values returned by each Subquery. How to do that? Wrapping the subquery in Sum returns only the first element of each subquery.

I found a way without Subquery:
cost_sum=Sum(
Case(
When(
contest_task_relationship__contest=contest,
then='contest_task_relationship__cost'
),
default=V(0),
output_field=IntegerField()
)
)
Not an elegant way, but it works.

Related

Count how many times value appears in table

Within my app, I have some forms that allow the user to select from a dropdown and I'm trying to count how many times RED, GREEN and AMBER have been selected across 20 different fields.
I was looking at
from django.db.models import Count
queryset = MyModel.objects.all().annotate(count = Count('my_charfield'))
But I'm not sure how to count the values rather than the field type?
Thanks
You can use conditional aggregation. Something to this effect:
MyModel.objects.aggregate(
red=Sum(
Case(When(my_charfield="RED", then=1),
output_field=IntegerField())
),
green=Sum(
Case(When(my_charfield="GREEN", then=1),
output_field=IntegerField())
),
amber=Sum(
Case(When(account_type="AMBER", then=1),
output_field=IntegerField())
)
)
which will then return a dictionary of values
{red: 1, green: 10, amber: 101}

How do I annotate multiple reverse fields with filters and all?

I have "Product", "OrderItem" and "Stock". I would like the fastest query to determine the stock on hand and the total order quantity for each "Product".
I assumed it would be done like this:
qs = (
Product.objects.all()
.prefetch_related(
Prefetch(
"stock",
queryset=Stock.objects.filter(timestamp__lte=timezone.now()),
),
Prefetch("ordered_items", queryset=OrderItem.objects.all(),),
)
.annotate(
total_on_hand=Sum("stock__quantity"),
total_allocated=Sum("ordered_items__quantity_ordered"),
)
.order_by("id")
)
The above returns results, but the SQL doesn't include my "timestamp__lte" filter shown above. This invalidates the data.

Django annotate by foreign key's date field but exclude past dates

I want to have my queryset of Card model annotated with new field called available_on, which should be calculated as closest date in future of relative Booking model's field removal_date. It should consider only dates in future, how can I filter out removal_date dates that are in the past? What I have now is this.
def with_available_on(self):
qs = self.annotate(available_on=Case(
When(
bookings_count__gt=0,
slots_available__lt=1,
then=Min('bookings__removal_date')),
default=None
)
)
return qs
Also I want it to be calculated on database side if possible for performance purposes. Thanks
You can use the filter=… parameter [Django-doc] to filter the objects over which you span the aggregate in the Min aggregate [Django-doc]:
from django.db.models import Q
from django.db.models.functions import Now
def with_available_on(self):
qs = self.annotate(available_on=Case(
When(
bookings_count__gt=0, slots_available__lt=1,
then=Min(
'bookings__removal_date',
filter=Q(bookings__remval_date__gte=Now())
)
),
default=None)
)
return qs

How to limit records in tables with n to n relation before joining them , to avoid duplicates in django ORM?

Extending my previous question on stack-overflow. I have four tables:
A <--- Relation ---> B ---> Category
(So the relation between A and B is n to n, where the relation between B and Category is n to 1)
Relation stores 'Intensity' of A in B. I need to calculate the intensity of A in each Category and find the Maximum result. It is achievable using:
A.objects.values(
'id', 'Relation_set__B__Category_id'
).annotate(
AcIntensity=Sum(F('Relation_set__Intensity'))
).aggregate(
Max(F('AcIntensity'))
)['AcIntensity__max']
Now I need to filter the intensities based on some fields in B beforhand:
A.objects.values(
'id', 'Relation_set__B__Category_id'
).filter(
Relation_set__B__BType=type_filter
).annotate(
AcIntensity=Sum(F('Relation_set__Intensity'))
).aggregate(
Max(F('AcIntensity'))
)['AcIntensity__max']
However I need to avoid duplication resulted due to table join which messes the calculation up.(beside those field to define filtration, I do not need any fields in B)
Is there a way to achieve this using Django ORM?
Update
I think what I need is to limit the records in Relation table (based on B filters) before querying the database. How can I do that?
(Maybe using Prefetch_related and Prefetch?)
Finally I've done it using conditional aggregation.
You could find more details in this stack-overflow post.
So the final code would be:
result = A.objects.values(
'id', 'Relation_set__B__Category_id'
).annotate(
AcIntensity=Sum(
Case(
When(
q_statement_for_filter,then=F('Relation_set__Intensity'),
),
default=0,
output_field=FloatField()
)
)
).exclude(
AcIntensity=0
).aggregate(
Max(F('AcIntensity'))
)['AcIntensity__max']
Notice that 'q_statement_for_filter' cannot be an empty Q().

django: sort rows based on number of column matching

I have a model:-
class Userprofile(models.Model):
user=models.OneToOneField(settings.AUTH_USER_MODEL)
education=models.models.CharField(max_length=20,blank=True,null=True)
country=models.CharField(max_length=20,blank=True,null=True)
occupation=models.CharField(max_length=20,blank=True,null=True) ....
for a user profile (let's say: ('masters','India','student') I want to filter all the user profiles ordered by the number of fields it matches with the given user profile i.e first all 3 fields matching profiles then any 2 fields matching profiles and so on.Can anyone suggest a way to do this efficiently?
You can achieve this using conditional expressions.
from django.db.models import Value, Case, When, IntegerField, F
education, country, occupation = 'masters','India','student'
Userprofile.objects.annotate(education_matched=Case(
When(education=education, then=Value(1)),
default=Value(0),
output_field=IntegerField()
), country_matched=Case(
When(country=country, then=Value(1)),
default=Value(0),
output_field=IntegerField()
), occupation_matched=Case(
When(occupation=occupation, then=Value(1)),
default=Value(0),
output_field=IntegerField()
)).
annotate(matched=F('education_matched') + F('country_matched') + F('occupation_matched')).
order_by('matched')