Django calculate Avg for a computed field - python

I have a model as below.
class Transaction(models.Model):
time1 = models.DateTimeField(null=True)
time2 = models.DateTimeField(null=True)
#property
def time_diff(self):
return time2.total_seconds() - time1.total_seconds()
I need to get the average of the (time2 - time1) in seconds for a list of records, if both values are not null.
time_avg = transactions.aggregate(total=Avg('time_diff',field='time_diff'))
This gives an error saying 'time_diff' is not a valid field. I want to keep this column as a derived property. Not a stored column.

You first need to find difference and then take avarage. You can use django F function for this like this.
from django.db.models import F
result = Transaction.objects.filter('your_filter_here').annotate(time_diff=F('time1')-F('time2')).aggregate(Avg('time_diff'))

Have you tried
time_avg = transactions.aggregate(Avg('time_diff')).values()

Related

Calculate the Average delivery time (days) Django ORM

I want to calculate the average delivery time (in days) of products using ORM single query (The reason of using single query is, I've 10000+ records in db and don't want to iterate them over loops). Here is the example of models file, I have:
class Product(models.Model):
name = models.CharField(max_length=10)
class ProductEvents(models.Model):
class Status(models.TextChoices):
IN_TRANSIT = ("in_transit", "In Transit")
DELIVERED = ("delivered", "Delivered")
product = models.ForiegnKey(Product, on_delete=models.CASCADE)
status = models.CharField(max_length=255, choices=Status.choices)
created = models.DateTimeField(blank=True)
To calculate the delivery time for 1 product is:
product = Product.objects.first()
# delivered_date - in_transit_date = days_taken
duration = product.productevent_set.get(status='delivered').created - product.productevent_set.get(status='in_transit').created
I'm here to get your help to getting started myself over this so, that I can calculate the average time between all of the Products. I'd prefer it to done in a single query because of the performance.
A basic solution is to annotate each Product with the minimum created time for related events that have the status "in-transit and select the maximum time for events with the delivered status then annotate the diff and aggregate the average of the diffs
from django.db.models import Min, Max, Q, F, Avg
Product.objects.annotate(
start=Min('productevents__created', filter=Q(productevents__status=ProductEvents.Status.IN_TRANSIT)),
end=Max('productevents__created', filter=Q(productevents__status=ProductEvents.Status.DELIVERED))
).annotate(
diff=F('end') - F('start')
).aggregate(
Avg('diff')
)
Returns a dictionary that should look like
{'diff__avg': datetime.timedelta(days=x, seconds=x)}

django : using if else or while else inside Sum function with annotation? Cannot compute Sum('<CombinedExpression:..') is an aggregate

i want to set a condition to my Sum function inside annotate , and i tried to use Case When but it didnt work in my case
this is my models.py
class MyModel(models.Model):
name = models.ForeignKey(Product, on_delete=models.CASCADE)
order = models.IntegerField()
price = models.IntegerField()
class Prodcut(models.Model):
name = models.CharField(max_lenth=20)
cost = models.IntegerField()
price = models.IntegerField()
i want to something like this
total = F('price')*F('order')
base = (F(name__cost')+F('name__price')) * F('order')
if total> base:
income = Sum(F('total') - F('base'))
i tried this
MyModel.objects.values('name__name').annotate(total=(Sum(F('price') * F('order'),output_field=IntegerField())),
base=(Sum((F('name__price')+F('name__cost'))*F('order'),output_field=IntegerField())
),
income=Sum(
Case(When(total__gt=F('base') , then=Sum(F('total') - F('base'))),default=0),output_field=IntegerField()),)
but this raise this error:
Cannot compute Sum('<CombinedExpression: F(total) - F(base)>'): '<CombinedExpression: F(total) - F(base)>' is an aggregate
i dont want to use .filter(income__gt=0) because it stops quantity from counting
and i dont want to counting income to those products which loss its sold
for example
i make a post on MyModel(name=mouse ,order=2,price=20) and in my Product model i have these information for mouse product Product(name=mouse,cost=4,price=10) , when i calculate to find income for this product : (2 *20) - ((4+10)*2) => 40 - 28 = 12 , but sometimes happen the result will be a negative price when (2*10) - ((4+10)*2) => 20 - 28 = -8
*i use mysql v:8 for database
i want to prevent negative numbers to add to my income with respect the other columns quantity
The problem is that you cannot use an aggregate (total and base) inside yet another aggregate in the same query. There is only one GROUP BY clause and Django cannot automatically produce a valid query here. As far as I've understood, you need to firstly calculate total and base, find each MyModel income, and only then produce an aggregate:
MyModel.objects.annotate(
total=F('price') * F('order'),
base=(F('name__price') + F('name__cost')) * F('order'),
income=Case(
When(total__gt=F('base'), then=F('total') - F('base')),
default=0,
output_field=IntegerField()
)
).values('name__name').annotate(income=Sum('income'))
P.S. Please, format your code so people can read it without difficulties :)
P.P.S I can probably see another way, you don't need Sum() for the income because total and base are sums already
MyModel.objects.values('name__name').annotate(
total=Sum(F('price') * F('order')),
base=Sum((F('name__price') + F('name__cost')) * F('order')),
).annotate(
income=Case(
When(total__gt=F('base'), then=F('total') - F('base')),
default=0,
output_field=IntegerField()
)
)
Try this, maybe some twists needed, idea is using Conditional Expressions
from django.db.models import Case, When, Value, IntegerField
MyModel.objects.values('name__name').annotate(
total = F('price')*F('order')
base = (F('name__cost') + F('name__price')) * F('order')
).annotate(
income = Case(
When(total__gt=F('base'), then=Sum(F('total')-F('base'))
), default = F('total'), output_field=IntegerField())
)

Django App : Perform arithmetic operation on two fields in database and store result in third field in DB while adding data in form

I have One form in my application with different fields from which i need to subtracts value of one field from other and store result in third field on the fly in Database.
Example i have 2 fields :
1. Cost of PR Basic & 2. Cost of PO Basic
Need to calculate Delta : Cost of PO Basic - Cost of PR Basic.
Delta is also field in database table.
In models.py i have
class PR_Data(models.Model)
Cost_PR_Basic_INR = models.DecimalField(max_digits=19,decimal_places=2)
Cost_Of_PO_Basic_INR = models.DecimalField(max_digits=19,decimal_places=2)
Delta = models.DecimalField(max_digits=19,decimal_places=2, editable=True)
So how can i calculate delta from values entered in other two fields and store result in Delta field.
Thanks in advance..!!
In your views.py:
do something like this:
pr_data = PR_Data()
pr_data.Cost_PR_Basic_INR = <value1>
pr_data.Cost_Of_PO_Basic_INR = <value2>
pr_data.Delta = <value1> - <value2>
pr_data.save()
just an idea how it should work, not the complete code ;)

Django get most recent value AND aggregate values

I have a model which I want to get both the most recent values out of, meaning the values in the most recently added item, and an aggregated value over a period of time. I can get the answers in separate QuerySets and then unite them in Python but I feel like there should be a better ORM approach to this. Anybody know how it can be done?
Simplified example:
Class Rating(models.Model):
movie = models.ForeignKey(Movie, related_name="movieRatings")
rating = models.IntegerField(blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
I wish to get the avg rating in the past month and the most recent rating per movie.
Current approach:
recent_rating = Rating.objects.order_by('movie_id','-timestamp').distinct('movie')
monthly_ratings = Rating.objects.filter(timestamp__gte=datetime.datetime.now() - datetime.timedelta(days=30)).values('movie').annotate(month_rating=Avg('rating'))
And then I need to somehow join them on the movie id.
Thank you!
Try this solution based on Subquery expressions:
from django.db.models import OuterRef, Subquery, Avg, DecimalField
month_rating_subquery = Rating.objects.filter(
movie=OuterRef('movie'),
timestamp__gte=datetime.datetime.now() - datetime.timedelta(days=30)
).values('movie').annotate(monthly_avg=Avg('rating'))
result = Rating.objects.order_by('movie', '-timestamp').distinct('movie').values(
'movie', 'rating'
).annotate(
monthly_rating=Subquery(month_rating_subquery.values('monthly_avg'), output_field=DecimalField())
)
I suggest you add a property method (monthly_rating) to your rating model using the #property decorator instead of calculating it in your views.py :
#property
def monthly_rating(self):
return 'calculate your avg rating here'

GROUP BY in Django Queries

Dear StackOverFlow community:
I need your help in executing following SQL query.
select DATE(creation_date), COUNT(creation_date) from blog_article WHERE creation_date BETWEEN SYSDATE() - INTERVAL 30 DAY AND SYSDATE() GROUP BY DATE(creation_date) AND author="scott_tiger";
Here is my Django Model
class Article(models.Model):
title = models.CharField(...)
author = models.CharField(...)
creation_date = models.DateField(...)
How can I form aforementioned Django query using aggregate() and annotate() functions. I created something like this -
now = datetime.datetime.now()
date_diff = datetime.datetime.now() + datetime.timedelta(-30)
records = Article.objects.values('creation_date', Count('creation_date')).aggregate(Count('creation_date')).filter(author='scott_tiger', created_at__gt=date_diff, created_at__lte=now)
When I run this query it gives me following error -
'Count' object has no attribute 'split'
Any idea who to use it?
Delete Count('creation_date') from values and add annotate(Count('creation_date')) after filter.
Try
records = Article.objects.filter(author='scott_tiger', created_at__gt=date_diff,
created_at__lte=now).values('creation_date').aggregate(
ccd=Count('creation_date')).values('creation_date', 'ccd')
You need to use creation_date__count or customized name(ccd here) to refer the count result column, after aggregate().
Also, values() before aggregate limits group by columns and last value() declares the columns to be selected. There is no need to group by COUNT which is based on group of rows already.

Categories