Count how many times value appears in table - django

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}

Related

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 with dynamic column name

I have a model in django app, with the following structure:
class items(models.Model):
name = models.CharField(max_length=50)
location = models.CharField(max_length=3)
I wanted to create a pivot table for the count of each location per each name/item, which I managed to do as per the following:
queryset_res = items.objects.values('name')\
.annotate(NYC=Sum(Case(When(location='NYC', then=1),default=Value('0'),output_field=IntegerField())))\
.annotate(LND=Sum(Case(When(location='LND', then=1),default=Value('0'),output_field=IntegerField())))\
.annotate(ASM=Sum(Case(When(location='ASM', then=1),default=Value('0'),output_field=IntegerField())))\
.annotate(Total=Count('location'))\
.values('name', 'NYC', 'LSA','Total')\
.order_by('-Total')
This gives me how many times each name appears against each location which is all ok.
my question is how can I make the location dynamic, and so if new locations where added I don't have come back and change the code again! either from a list or from the model data itself
Many Thanks
AB
You can bind dynamic parameter with *[1, 2, 3], **{'key': 'value'} in python.
from django.db.models import Case, Count, Sum, IntegerField, Value, When
def get_annotation(key):
return {
key: Sum(
Case(
When(location=key, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
),
),
}
queryset_res = items.objects.values('name')
location_list = ['NYC', 'LSA', 'ASM', ...etc]
for key in location_list:
queryset_res = queryset_res.annotate(**get_annotation(key))
queryset_res = (
queryset_res.annotate(Total=Count("location"))
.values("name", "Total", *location_list)
.order_by("-Total")
)
Now you can implement a set of queries simply by changing location_list.

Sum of Subquery fields 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.

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')