I'd like to sum all the event durations per day. This is my model:
class Event(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
Sample data:
import datetime
from random import randint
for i in range(0, 1000):
start = datetime.datetime(
year=2016,
month=1,
day=randint(1, 10),
hour=randint(0, 23),
minute=randint(0, 59),
second=randint(0, 59)
)
end = start + datetime.timedelta(seconds=randint(30, 1000))
Event.objects.create(start=start, end=end)
I can get the event count per day like so:
(I know extra is bad, but I'm using 1.9 at the moment. When I upgrade I'll move to using TruncDate)
Event.objects.extra({'date': 'date(start)'}).order_by('date').values('date').annotate(count=Count('id'))
[{'count': 131, 'date': datetime.date(2016, 1, 1)},
{'count': 95, 'date': datetime.date(2016, 1, 2)},
{'count': 99, 'date': datetime.date(2016, 1, 3)},
{'count': 85, 'date': datetime.date(2016, 1, 4)},
{'count': 87, 'date': datetime.date(2016, 1, 5)},
{'count': 94, 'date': datetime.date(2016, 1, 6)},
{'count': 97, 'date': datetime.date(2016, 1, 7)},
{'count': 111, 'date': datetime.date(2016, 1, 8)},
{'count': 97, 'date': datetime.date(2016, 1, 9)},
{'count': 104, 'date': datetime.date(2016, 1, 10)}]
I can annotate to add the duration:
In [3]: Event.objects.annotate(duration=F('end') - F('start')).first().duration
Out[3]: datetime.timedelta(0, 470)
But I can't figure out how to sum this annotation the same way I can count events. I've tried the following but I get a KeyError on 'duration'.
Event.objects.annotate(duration=F('end') - F('start')).extra({'date': 'date(start)'}).order_by('date').values('date').annotate(total_duration=Sum('duration'))
And If I add duration to the values clause then it no longer groups by date.
Is this possible in a single query and without adding a duration field to the model?
I was about to write an answer that Django ORM does not support this. And yes, then I spent another hour on this problem (in addition to the 1,5 hours already spent before starting to write this answer), but as it turns out, Django does support it. And without hacking. Good news!
import datetime as dt
from django.db import models
from django.db.models import F, Sum, When, Case
from django.db.models.functions import TruncDate
from app.models import Event
a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(
day_duration=Sum(Case(
When(date=TruncDate(F('start')), then=F('end') - F('start')),
default=dt.timedelta(), output_field=models.DurationField()
))
)
And some preliminary tests to (hopefully) prove that this stuff actually does what you asked.
In [71]: a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(day_duration=Sum(Case(
...: When(date=TruncDate(F('start')), then=F('end') - F('start')),
...: default=dt.timedelta(), output_field=models.DurationField()
...: ))
...: )
In [72]: for e in a:
...: print(e)
...:
{'day_duration': datetime.timedelta(0, 41681), 'date': datetime.date(2016, 1, 10)}
{'day_duration': datetime.timedelta(0, 46881), 'date': datetime.date(2016, 1, 3)}
{'day_duration': datetime.timedelta(0, 48650), 'date': datetime.date(2016, 1, 1)}
{'day_duration': datetime.timedelta(0, 52689), 'date': datetime.date(2016, 1, 8)}
{'day_duration': datetime.timedelta(0, 45788), 'date': datetime.date(2016, 1, 5)}
{'day_duration': datetime.timedelta(0, 49418), 'date': datetime.date(2016, 1, 7)}
{'day_duration': datetime.timedelta(0, 45984), 'date': datetime.date(2016, 1, 9)}
{'day_duration': datetime.timedelta(0, 51841), 'date': datetime.date(2016, 1, 2)}
{'day_duration': datetime.timedelta(0, 63770), 'date': datetime.date(2016, 1, 4)}
{'day_duration': datetime.timedelta(0, 57205), 'date': datetime.date(2016, 1, 6)}
In [73]: q = dt.timedelta()
In [74]: o = Event.objects.filter(start__date=dt.date(2016, 1, 7))
In [75]: p = Event.objects.filter(start__date=dt.date(2016, 1, 10))
In [76]: for e in o:
...: q += (e.end - e.start)
In [77]: q
Out[77]: datetime.timedelta(0, 49418) # Matches 2016.1.7, yay!
In [78]: q = dt.timedelta()
In [79]: for e in p:
...: q += (e.end - e.start)
In [80]: q
Out[80]: datetime.timedelta(0, 41681) # Matches 2016.1.10, yay!
NB! This works from version 1.9, I don't think you can do this with the earlier versions because the TruncDate function is missing. And before 1.8 you don't have the Case and When thingies as well of course.
Related
if request.method == 'POST':
product=request.POST.get('product')
upload_month = request.POST.get('upload_month')
un_month= Planning_quantity_data.objects.values('month').filter(product=product,upload_month=upload_month).distinct()
print(un_month)
<QuerySet [{'month': 'Mar_22'}, {'month': 'Apr_22'}, {'month': 'May_22'}, {'month': 'Jun_22'}]>
I want to get only the values without key and store it in a new list in
views.py file:
like newlist = ['Mar_22' , 'Apr_22', 'May_22','Jun_22']
while I am using
un_month1=list(un_month.values())
print(un_month1)
It is showing like something this:
[{'id': 1, 'upload_month': 'Mar_22', 'product': 'MAE675', 'material_code': 'MAE675 (MEMU â OB) RCF', 'order_type': 'Onhand', 'BOM_CODE': '675MEMU', 'month': 'Mar_22', 'quantity': 3, 'po_value': '37/5', 'remarks': 'Qty in Rakes. 3-5 rakes partial qty dispatched', 'empid': None}, {'id': 2, 'upload_month': 'Mar_22', 'product': 'MAE675', 'material_code': 'MAE675 (MEMU â OB) RCF', 'order_type': 'Onhand', 'BOM_CODE': '675MEMU', 'month': 'Apr_22', 'quantity': 3, 'po_value': '37/5', 'remarks': 'Qty in Rakes. 3-5 rakes partial qty dispatched', 'empid': None}, {'id': 3, 'upload_month': 'Mar_22', 'product': 'MAE675', 'material_code': 'MAE675 (MEMU â OB) RCF', 'order_type': 'Onhand', 'BOM_CODE': '675MEMU', 'month': 'May_22', 'quantity': 3, 'po_value': '37/5', 'remarks': 'Qty in Rakes. 3-5 rakes partial qty dispatched', 'empid': None}]
If you use values_list() [django-docs] with a single field, you can use flat=True to return a QuerySet of single values, I mean:
if request.method == 'POST':
product=request.POST.get('product')
upload_month = request.POST.get('upload_month')
newlist = list(Planning_quantity_data.objects.filter(product=product,upload_month=upload_month).values_list('month', flat=True))
print(newlist)
And this will print just ['Mar_22', 'Apr_22', 'May_22', 'Jun_22'] for you.
I am trying to query and the group is the Order of the last 6 months.
and this is my models:
class Order(models.Model):
created_on = models.DateTimeField(_("Created On"), auto_now_add=True)
and this is my method to parse month:
from django.db.models import Func
class Month(Func):
"""
Method to extract month
"""
function = 'EXTRACT'
template = '%(function)s(MONTH from %(expressions)s)'
output_field = models.IntegerField()
And this is my query:
current_date = date.today()
months_ago = 6
six_month_previous_date = current_date - timedelta(days=(months_ago * 365 / 12))
order = Order.objects.filter(
created_on__gte=six_month_previous_date,
).annotate(
month=Month('created_on')
).values(
'month'
).annotate(
count=Count('id')
).values(
'month',
'count'
).order_by(
'month'
)
In my database order table, there is only on entry:
So it is returning
[{'month': 10, 'count': 1}]
But i dont want like this, i want like these of last 6 month, if in one month, there is no sales, it should return the count: 0
Like thise bellow:
[
{'month': 10, 'count': 1},
{'month': 9, 'count': 0}
{'month': 8, 'count': 0}
{'month': 7, 'count': 0}
{'month': 6, 'count': 0}
{'month': 5, 'count': 0}
]
A database works under the closed world assumption, so it will not insert rows with 0. You can however post-process the list.
from django.utils.timezone import now
order = Order.objects.filter(
created_on__gte=six_month_previous_date,
).values(
month=Month('created_on')
).annotate(
count=Count('id')
).order_by('month')
order = {r['month']: r['count'] for r in order}
month = now().month
result = [
{'month': (m % 12)+1, 'count': order.get((m % 12) + 1, 0)}
for m in range(month-1, month-8, -1)
]
Note that Django already has an ExtractMonth function [Django-doc].
This is my model:
class Sales(models.Model):
title = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
I am trying to get last 7 days data like i want to see how many sales occurred in last Friday, Saturday, Monday, etc date.
Only for the last 7 days but it should appear day by day,
like:
friday: 40
mondey:80
widnesday: 88 etc date
I am not getting how to do this...
I have tried like this below:
some_day_last_week = timezone.now().date() - timedelta(days=7)
sales = Sales.objects.filter(
created_at__gte=some_day_last_week,
).values(
'created_at__date'
).annotate(
created_count=Count('created_at__date')
)
Can anyone help me with this case?
You need to use TruncDay in your case:
from django.db.models.functions import TruncDay
sales = Sales.objects.filter(
created_at__gte=some_day_last_week,
).annotate(
day=TruncDay('created_at')
created_count=Count('created_at__date')
).values(
'day',
'created_count',
)
As a result you will get:
[
{'day': datetime.date(2019, 12, 1), 'created_count': 4.0},
{'day': datetime.date(2019, 12, 2), 'created_count': 10.0},
{'day': datetime.date(2019, 12, 3), 'created_count': 5.0},
{'day': datetime.date(2019, 12, 4), 'created_count': 1.0},
{'day': datetime.date(2019, 12, 5), 'created_count': 8.0},
{'day': datetime.date(2019, 12, 6), 'created_count': 4.0},
]
In order to get a date name you can convert datetime object with date.strftime("%A"):
for i in result:
print(i['day'].strftime("%A"))
# result
Sunday
Monday
...
Friday
I'm having trouble performing a simple transformation with the django orm.
Desired outcome should look like this:
2018-08
2018-07
2018-06
...
And is created with this sql:
select
distinct
strftime('%Y',a."Buchung") || "-" ||
strftime('%m',a."Buchung") as YearMonth
from
hhdata_transaktion a
order by
1 desc
I need it for a ModelChoiceField as queryset, so I'm bound to the ORM here?
My try
from django.db.models.functions import TruncMonth, TruncYear
Transaktion.objects
.annotate(year=TruncYear('Buchung'),
month=TruncMonth('Buchung'))
.distinct()
.order_by('-year', '-month')
.values('year','month')
returns:
<QuerySet [{'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 8, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 7, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 6, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 5, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 4, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 3, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 2, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 1, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 12, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 11, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 10, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 9, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 8, 1)}]>
I have the feeling I'm miles away from the desired result..
If you want to obtain the year or month, you can use ExtractYear [Django-doc] and ExtractMonth [Django-doc] respectively. Truncating will give you the start of the year or month.
So we can rewrite the query to:
from django.db.models.functions import ExtractMonth, ExtractYear
qs = Transaktion.objects.annotate(
year=ExtractYear('Buchung'),
month=ExtractMonth('Buchung')
).order_by('-year', '-month').values('year','month').distinct()
Although it is possible to do the processing at SQL level, I think it will make work more complex. For example if you concatenate the numbers in SQL, it will probably require some work to get leading zeros for months (less than 10). Furthermore it is likely that the query contains "SQL dialect"-specific features making it less portable.
Therefore I suggest to do the post processing at the Django/Python level. For exampe with:
from django.db.models.functions import ExtractMonth, ExtractYear
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField()
# ...
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
qs = Transaktion.objects.annotate(
year=ExtractYear('Buchung'),
month=ExtractMonth('Buchung')
).order_by('-year', '-month').values('year','month').distinct()
self.fields['my_choice_field'].choices = [
(row['year']*100+row['month'], '{}-{:02d}'.format(row['year'], row['month'])
for row in qs
]
Here we thus generate a list of 2-tuples where the first element is some sort of number we use to identify the choice (I here multiplied the year by 100, such that 201804 is april 2018). The second element of the tuple is the string that determines the format.
If you want a list of strings like 2018-06, something like that should work:
[ '%i-%02i' % (x.Buchung.year, x.Buchung.month) for x in Transaktion.objects.order_by(-Buchung) ]
SELECT `av`.`answer_id`, SUM(IF(`av`.`helpful`=1, 1, -1)) as `score`
FROM `questions_answervote` AS `av`
JOIN `questions_answer` AS `a`
ON `a`.`id`=`av`.`answer_id` AND `a`.`question_id`='775819'
GROUP BY `av`.`answer_id`
HAVING SUM(IF(`av`.`helpful`=1, 1, -1)) > 0
I have three models: Question, Answer and AnswerVote. Every Question has a set of Answers and every Answer has a set of AnswerVotes. AnswerVote has a field helpful which is a boolean.
What I am trying to do is get all the helpful Answers for a Question by calculating a score for each of the Answers (+1 for helpful=True, -1 for helpful=False). An Answer is helpful if it has a score > 0.
Any help would be appreciated!
If you have the following:
from django.db import models
class Question(models.Model):
data = models.CharField(max_length=30)
class Answer(models.Model):
question = models.ForeignKey(Question)
def __str__(self):
return str(self.__dict__)
class AnswerVotes(models.Model):
answer = models.ForeignKey(Answer)
helpful = models.SmallIntegerField(default=0)
def __str__(self):
return str(self.__dict__)
It may seem a bit like a work around but why simply not do an average of all votes helpfullness. Instead of -1 to 1 range, you will be somewhere between 0 and 1 with raw values in the DB. Simply treat 0.5 as the middle ground in your case and you should get the same behavior.
Answer.objects.annotate(score = Avg('answervotes__helpful'))
{'score': None, 'id': 1, 'question_id': 1}
{'score': 1.0, 'id': 2, 'question_id': 1}
{'score': 0.5, 'id': 3, 'question_id': 1}
{'score': 0.3333333333333333, 'id': 4, 'question_id': 2}
{'score': 0.5, 'id': 5, 'question_id': 3}
{'score': 0.4, 'id': 6, 'question_id': 3}
Answer.objects.annotate(score = Avg('answervotes__helpful')).filter(score__gte=0.5)
{'score': 1.0, 'id': 2, 'question_id': 1}
{'score': 0.5, 'id': 3, 'question_id': 1}
{'score': 0.5, 'id': 5, 'question_id': 3}
Django's ORM doesn't do HAVING, so you will have to drop to raw SQL for this one.