Using Multiple Annotations in Django - django

Here is my data base:
class User(models.Model):
Name = models.TextField(null=True, blank=True, default=None)
class Salary(models.Model):
value = models.PositiveIntegerField(default=1)
name = models.ForeignKey(User, related_name='salarys', on_delete=models.CASCADE)
class Expense(models.Model):
value = models.PositiveIntegerField(default=1)
name = models.ForeignKey(User, related_name='expenses', on_delete=models.CASCADE)
I want to add all the salary and expenses of a user.
queryset = User.objects.annotate(total_salary=Sum('salarys__value', distinct=True) ,total_expense=Sum('expenses__value', distinct=True))
Here is my data:
User table
id=1; name= ram
Salary table
id =1; value = 12000
id = 2; value = 8000
Expense table
id =1; value=5000
id=2; value = 3000
expected output : total_Salary = 20000; total_expense=8000 output
obtained : total_salary= 40000; total_expense = 16000
Every output is multiplied the number of times the data in another table. Can anyone help me through this

looks like it is not possible if you are trying to combine like aggregates. Only works for Count with distinct. For closure, please refer to docs: https://docs.djangoproject.com/en/4.0/topics/db/aggregation/#combining-multiple-aggregations
Since annotate returns a queryset instead of a dict(in case of aggregate), you can try chaining multiple like values().annotate("field1").annotate("field2"). Worked for me, though please check sql from your queryset to debug in case you cannot run your query as desired.

Can you try this ?
queryset = User.objects.all().annotate(total_salary=Sum('salarys__value') ,total_expense=Sum('expenses__value'))

Related

Running aggregate function on Django queryset union with renamed fields raises django.db.utils.DatabaseError

I am using Django 3.2
I am trying to create a query that queries two different models and does the following:
renames the returned columns (so the queries can be 'union'ed together
'unions' the two querysets (after column rename using annotate)
tries to run an aggregate function Sum on the union (this is where it barfs).
This is a simplified version of my codebase:
Models
class EventCategory(models.Model):
name = models.CharField(max_length=16)
class Event(models.Model):
name = models.CharField(max_length=32)
category = models.ForeignKey(EventCategory, on_delete=models.CASCADE)
class Tournament(models.Model):
name = models.CharField(max_length=32)
category = models.ForeignKey(EventCategory, on_delete=models.CASCADE)
prize_money = models.IntegerField()
class TournamentAward(models.Model):
awardee = models.ForeignKey(setting.AUTH_USER_MODEL, on_delete=models.CASCADE)
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Game(models.Model):
player = models.ForeignKey(setting.AUTH_USER_MODEL, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
payment = models.SmallPositiveInteger()
created_at = models.DateTimeField(auto_now_add=True)
Queries
payment_earned_today = Game.objects.filter(
player=user,
created_at__year=year,
created_at__month=month,
created_at__day=day
).annotate(category=models.F('event__category'))\
.only('category','payment')
tournament_prize_today = TournamentAward.objects.filter(
awardee=user,
created_at__year=year,
created_at__month=month,
created_at__day=day
).select_related('tournament__category')\
.annotate(category=models.F('tournament__category'))\
.annotate(payment=models.F('tournament__prize_money'))\
.only('category','payment')
# Union the two querysets ...
union_queryset = payment_earned_today.union( tournament_prize_today )
results = union_queryset.aggregate(total=models.Sum('payment'))
On the line when I try to calculate the total, I get the following error:
django.db.utils.DatabaseError: ORDER BY not allowed in subqueries of compound statements
How can I union two models and calculate an aggregate function on the union?
at first:
you don't need to rename fields.
filter_query = Q(created_at__year=year,created_at__month=month,created_at__day=day)
payment_earned_today = Game.objects.filter(player=user, filter_query).values('event__category'. 'payment')
at second:
you can set order_by before aggregate.
queryset.query.order_by=None
at third:
Why you don't go from category?
event_query = Q(event__game__created_at__year=year, event__game__created_at__month=month, event__game__created_at__day=day, event__game__player=user)
tournament_query = Q(tournament__tournamentaward__created_at__year=year, tournament__tournamentaward__created_at__month=month, tournament__tournamentaward__created_at__day=day, tournament__tournamentaward__awardee=user)
all_category = EventCategory.ojects.filter(event_query | tournament_query)
I don't understand if is an error in your last core row or not:
results is plural, but you have aggregate(sum), it give you only one result. Therefore:
all_category_with_summ_in_day_for_user = all_category.annotate(Sum('tournament__prize_money'), Sum('event__game__payment'))
# or
summs_for_all_category_in_day_for_user= all_category.aggregate(Sum('tournament__prize_money'), Sum('event__game__payment'))

adding .annotate foreign key in objects does not give distinct result

When i run following query its working fine
query= UserOrder.objects.filter(order_date__range=(
Request['from'], Request['to'])).values('user_id__username','user_id__email').annotate(
total_no_of_order=Count("pk")).annotate(total=Sum('total_cost'))
But i also want to know the number of services related to order
.annotate(total_no_of_services=Count("userorderservice__pk"))
by adding above code in query total_no_of_order and total_cost getting changed too and by putting distinct=True in total_cost its only considering for single unique total_cost value
Modles.py
class UserOrder(models.Model):
order_id = models.AutoField(primary_key=True)
user_id = models.ForeignKey(AppUser, on_delete=models.CASCADE)
total_cost = models.FloatField()
...
class UserOrderService(models.Model):
order_id = models.ForeignKey(UserOrder, on_delete=models.CASCADE)
...
A UserOrder can have many UserOrderService instances.
Therefore, the correct way to COUNT is:
.annotate(total_no_of_services=Count("userorderservice_set"))
Please have a look at reverse relations

How do I construct an order_by for a specific record in a ManyToOne field?

I'm trying to sort (order) by statistical data stored in a ManyToOne relationship. Suppose I have the following code:
class Product(models.Model):
info = ...
data = models.IntegerField(default=0.0)
class Customer(models.Model):
info = ...
purchases = models.ManyToManyField(Product, related_name='customers', blank=True)
class ProductStats(models.Model):
ALL = 0
YOUNG = 1
OLD = 2
TYPE = ((ALL, 'All'), (YOUNG, 'Young'), (OLD, 'Old'),)
stats_type = models.SmallIntegerField(choices=TYPE)
product = models.ForeignKey(Product, related_name='stats', on_delete=models.CASCADE)
data = models.FloatField(default=0.0)
Then I would like to sort the products by their stats for the ALL demographic (assume every product has a stats connected to it for ALL). This might look something like the following:
products = Product.objects.all().order_by('stats__data for stats__stats_type=0')
Currently the only solution I can think of is either to create a new stats class just for all and use a OneToOneField for Product. Or, add a OneToOneField for Product pointing to the ALL stats in ProductStats.
Thank you for your help.
How about like this using multiple fields in order_by:
Product.objects.all().order_by('stats__data', 'stats__stats_type')
# it will order products from stats 0, then 1 then 2
Or if you want to get data for only stats_type 0:
Product.objects.filter(stats__stats_type=0).order_by('stats__data')
You can annotate the value of the relevant demographic and order by that:
from django.db.models import F
Product.objects.all().filter(stats__stats_type=0).annotate(data_for_all=F('stats__data').order_by('data_for_all')

How to return all records but exclude the last item

I'm having problem filtering in django-models.
I want to return all records of a particular animal but excluding the last item based on the latest created_at value and sorted in a descending order.
I have this model.
class Heat(models.Model):
# Fields
performer = models.CharField(max_length=25)
is_bred = models.BooleanField(default=False)
note = models.TextField(max_length=250, blank=True, null=True)
result = models.BooleanField(default=False)
# Relationship Fields
animal = models.ForeignKey(Animal, related_name='heats', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
last_updated = models.DateTimeField(auto_now=True, editable=False)
I was able to achieved the desired result by this raw sql script. But I want a django approach.
SELECT
*
FROM
heat
WHERE
heat.created_at != (SELECT MAX((heat.created_at)) FROM heat)
AND heat.animal_id = '2' ORDER BY heat.created_at DESC;
Please help.
It will be
Heat.objects.order_by("-created_at")[1:]
For a particular animal it will then be:
Heat.objects.filter(animal_id=2).order_by("-created_at")[1:]
where [1:] on a queryset has a regular python slice syntax and generates the correct SQL code. (In this case simply removes the first / most recently created element)
Upd: as #schwobaseggl mentioned, in the comments, slices with negative index don't work on django querysets. Therefore the objects are reverse ordered first.
I just converted your SQL query to Django ORM code.
First, fetch the max created_at value using aggregation and do an exclude.
from django.db.models import Max
heat_objects = Heat.objects.filter(
animal_id=2
).exclude(
created_at=Heat.objects.all().aggregate(Max('created_at'))['created_at__max']
)
Get last record:
obj= Heat.objects.all().order_by('-id')[0]
Make query:
query = Heat.objects.filter(animal_id=2).exclude(id=obj['id']).all()
The query would be :
Heat.objects.all().order_by('id')[1:]
You could also put any filter you require by replacing all()

Django annotate usage over an model instance other than query item

I have a query such that
em =Employer.objects.filter(id=1).annotate(overall_value = Sum('companyreview__overallRating'))
em[0].overall_value
As you see I want to sum of overallRating field of all companyreview objects whose employer has id = 1.
The query above does what I want but I am sure that there is a way to get the sum from an Employer instance.
How can I implement this query like
em =Employer.objects.get(id=1)
rate = em.companyreview_set.all().annotate(overall_value = Sum('overallRating'))
rate.overall_value
?
Thanks
Use aggregate:
e.companyreview_set.aggregate(overall_value = Sum('overall_rating'))
For:
class Employer(models.Model):
name = models.CharField(max_length=100)
class CompanyReview(models.Model):
employer = models.ForeignKey(Employer)
overall_rating = models.IntegerField()