Multiple aggregations on date ranges in Django - python

............Models............
class Product(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=140)
description = tinymce_models.HTMLField()
class Purchase(models.Model):
user = models.ForeignKey(User, blank=True, null=True)
product = models.ForeignKey(Product)
sale_date = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=6, decimal_places=2)
I am looking to get an output that says the sum of the purchase prices for a product in the past month and the past week, but want to do this for multiple products.
The output would look something like this that i could loop through in my templates...
product1 name-- product1 description -- sum of product1 weekly sales -- sum of product1 monthly sales
product2 name-- product2 description -- sum of product2 weekly sales -- sum of product2 monthly sales
Should I used raw sql? What would that query look like? Should i try to use sqlalchemy or can i do this in the Django ORM?

I suggest you to do it in sql statement, because sql performs better to manipulate data, sum and order it... Your code will manage to retrieve data from RDBMS and display it...
I don't know what is your RDBMS, but I give you an exemple in sql server, the syntaxe is around 95% the same in other RDBMS.
You query should looks like something like this :
SELECT
prod.product_id
,prod.description
,ISNULL(weekSales.priceSum,0) as weekSales
,ISNULL(monthSales.priceSum,0) as monthSales
FROM
Product prod
left join (
SELECT
product_id
,sum(price) as priceSum
FROM Purchase
WHERE DATEPART(wk,sale_date) = DATEPART(wk,GETDATE())
GROUP BY product_id) weekSales
on prod.product_id = weekSales.product_id
left join (
SELECT
product_id
,sum(price) as priceSum
FROM Purchase
WHERE
month(sale_date) = MONTH(GETDATE())
AND year(sale_date) = year(GETDATE())
GROUP BY product_id) monthSales
on prod.product_id = monthSales.product_id
Here you can see a demo of this query with SQLFiddle
In sql server,
GETDATE() return the current date
DATEPART(wk,[date]) return the week part of a date (WEEK([date]) function in mysql)
MONTH([date]) return the month part of a date
YEAR([date]) return the year part of a date
ISNULL([field],[value_if_null]) replace null value (IFNULL([field],[value_if_null]) in mysql)
You should be able to find similar functions in all RDBMS.
If you need some help in sql, comment above and specify your RDBMS ;)
Hope it helps.

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: Average of Subqueries as Multiple Annotations on a Query Result

I have three Django models:
class Review(models.Model):
rating = models.FloatField()
reviewer = models.ForeignKey('Reviewer')
movie = models.ForeignKey('Movie')
class Movie(models.Model):
release_date = models.DateTimeField(auto_now_add=True)
class Reviewer(models.Model):
...
I would like to write a query that returns the following for each reviewer:
The reviewer's id
Their average rating for the 5 most recently released movies
Their average rating for the 10 most recently released movies
The release date for the most recent movie they rated a 3 (out of 5) or lower
The result would be formatted:
<Queryset [{'id': 1, 'average_5': 4.7, 'average_10': 4.3, 'most_recent_bad_review': '2018-07-27'}, ...]>
I'm familiar with using .annotate(Avg(...)), but I can't figure out how to write a query that averages just a subset of the potential values. Similarly, I'm lost on how to annotate a query for the most recent <3 rating.
All of those are basically just some if statements in python code and when statements in your database assuming it is SQL-like, so, you can just use django's built-in Case and When functions, you'd probably combine them with Avg in your case and would need a new annotation field for every when, so your queryset would look roughly like
Model.objects.annotate(
average_5=Avg(Case(When(then=...), When(then=...)),
average_10=Avg(Case(When(then=...), When(then=...)),
)
with appropriate conditions inside when and appropriate then values.

Count records per day in a Django Model where date is Unix

I'm trying to create a query that counts how many queries per day there were on a certain Django table. I found a bunch of examples about it but none was dealing with Unix data. Here is what my model looks like:
class myData(models.Model):
user_id = models.IntegerField()
user = models.CharField(max_length=150)
query = models.CharField(max_length=100)
unixtime = models.IntegerField()
class Meta:
managed = False
db_table = 'myData'
So the result i'm trying to get is something like: {'27/06/2020': 10, '26/06/2020': 15 ... }
The doubt i have is: should i use a raw MYSQL query or should i use Django's ORM?
I tried to make it with a raw query, but didn't get the expected output:
select FROM_UNIXTIME(`unixtime`, '26.06.2020') as ndate,
count(id) as query_count
from myData
group by ndate
But it gave the following output:
ndate query_count
26/06/2020 1
26/06/2020 1
26/06/2020 1
26/06/2020 1
....
Can anyone help me out on this? It doesn't make the difference whether the query is made with raw mysql or Django ORM, i just need a simple way to do this
You should read up on how to use the function FROM_UNIXTIME(), specially the allowed format string options.
Your query should probably be modified to something like this:
select FROM_UNIXTIME(unixtime, '%Y/%m/%d') as ndate,
count(id) as query_count
from myData
group by ndate
Does that work for you?

How to get products available quantity for individual stock (Odoo11)

I am using odoo11(python3) and develop a custom module.
I would like to get products quantity (by hand or forecast) for an individual stock house.
Just I select any individual stockhouse and show product quantity for this stockhouse.
Actually, my target is two things
1.Products total available quantity
2.Products available quantity based on location
This is my code
class Threshold(models.Model):
_name="threshold.threshold"
main_location=fields.Many2many("stock.warehouse", string="Main Location")
product=fields.Many2many("product.template", string="Product")
category=fields.Many2one("product.category", string="Category")
attribute=fields.Many2many("product.attribute", string="Attribute")
threshold_value=fields.Integer(string="Threshold Value")
transfer_quantity=fields.Integer(string="Transfer Quantity")
status_button=fields.Selection([('0','Active'),('1','Dissmiss')], default='0', index=True, string="Status")
threshold_selection=fields.Selection([('0','Product'),('1','Category'),], default= '0', index=True, string="Threshold Selection")
product_quantity=fields.Integer(compute="_product_based_on_warehouse", store=True)
#api.depends('main_location','product_quantity')
def _product_based_on_warehouse(self):
count_products = 0
self.env['product.template'].with_context(warehouse=self.main_location).search([], limit=1)
self.product_quantity=products print(f'Product Quantity: {self.product_quantity}')
You have to load/browse the products with a special context value warehouse to get all the quantity values for all, one or multiple warehouses.
# load all products and use a warehouse id
products = self.env['product.product'].with_context(warehouse=1).search([])
# load one product and use a warehouse name
products = self.env['product.product'].with_context(warehouse="Main Warehouse").search([], limit=1)
# load product with ID 1 and use a list of warehouse IDs
products = self.env['product.product'].with_context(warehouse=[1,2]).browse([1])
You can use the 4 quantity fields of products to get the quantity values you need. There is also an option to use location as context value in the same manner as with warehouse.
If you want to know where this is coming from look into the methods _compute_quantities, _compute_quantities_dict and the most important one _get_domain_locations.

Django: Aggregating Across Submodels Fields

I currently have the following models, where there is a Product class, which has many Ratings. Each Rating has a date_created DateTime field, and a stars field, which is an integer from 1 to 10. Is there a way I can add up the total number of stars given to all products on a certain day, for all days?
For instance, on December 21st, 543 stars were given to all Products in total (ie. 200 on Item A, 10 on Item B, 233 on Item C). On the next day, there might be 0 stars, because there were no ratings for any Products.
I can imagine first getting a list of dates, and then filtering on each date, and aggregating each one, but this seems very intensive. Is there an easier way?
You should be able to do it all in one query, using values:
from datetime import date, timedelta
from django.db.models import Sum
end_date = date.now()
start_date = end_date - timedelta(days=7)
qs = Rating.objects.filter(date_created__gt=start_date, date_created__lt=end_date)
qs = qs.values('date_created').annotate(total=Sum('stars'))
print qs
Should output something like:
[{'date_created': '1-21-2013', 'total': 150}, ... ]
The SQL for it looks like this (WHERE clause omitted):
SELECT "myapp_ratings"."date_created", SUM("myapp_ratings"."stars") AS "total" FROM "myapp_ratings" GROUP BY "myapp_ratings"."date_created"
You'll want to use Django's aggregation functions; specifically, Sum.
>>> from django.db.models import Sum
>>>
>>> date = '2012-12-21'
>>> Rating.objects.filter(date_created=date).aggregate(Sum('stars'))
{'stars__sum': 543}
As a side note, your scenario actually doesn't need to use any submodels at all. Since the date_created field and the stars field are both members of the Rating model, you can just do a query over it directly.
You could always just perform some raw SQL:
from django.db import connection, transaction
cursor = connection.cursor()
cursor.execute('SELECT date_created, SUM(stars) FROM yourapp_rating GROUP BY date_created')
result = cursor.fetchall() # looks like [('date1', 'sum1'), ('date2', 'sum2'), etc]

Categories