4

Let us say I have a very simple model like this one:

class Test(models.Model):
    category = models.CharField(unique=True)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

Basically, I want to use Django's ORM API to get the average duration per category. The SQL query will look something like this:

SELECT category, AVG(end_time - start_time) AS avg_duration
FROM test_table
GROUP BY category

I tried using the following code, but according to the docs, F() expressions in .annotate() is only available in Djano 1.8.

Test.objects.values('category', 'start_time', 'end_time').annotate(avg_duration=Avg(F(end_time) - F(start_time))

I also tried using .extra() like this, but I get a FieldError.

Test.objects.extra(select={'duration':'end_time - start_time'}).values('category', 'duration').annotate(avg_duration=Avg('duration'))

From the looks of things, the 2nd attempt suggests that the annotate function cannot resolve column aliases. Is this really the case, or am I missing something?

Furthermore, short of creating an additional column that stores derived information (duration per entry), using Django 1.8, and/or using raw SQL, what other alternatives can you guys recommend? Any help will very much be appreciated. Thanks!

2 Answers 2

2

Try this:

Test.objects.values('category').extra(select={'avg_duration':'AVG(end_time - start_time)'})

Yep, it won't happen, because

Any extra() call made after a values() call will have its extra selected fields ignored.

You can't use it before .values() either, because you will be forced to include it into the .values():

If you use a values() clause after an extra() call, any fields defined by a select argument in the extra() must be explicitly included in the values() call.

so this way any .extra select will be included in the grouping.

But you can't also make such kind of Avg without .extra so i think the only solution which had left is to use .raw

Test.objects.raw('\
    SELECT id, category, AVG(end_time - start_time) AS avg_duration \
    FROM test_table \
    GROUP BY category'
)
Sign up to request clarification or add additional context in comments.

3 Comments

I tried your suggestion and also tried this one fearing that the required columns for .extra() might not be detected. Test.objects.values('category', 'end_time', 'start_time').extra(select={'avg_duration':'AVG(end_time - start_time)'}) The results I got was similar to a basic select query like this: SELECT category, end_time, start_time FROM test_table Nice suggestion, but I think this is why it did not work: "Any extra() call made after a values() call will have its extra selected fields ignored." From the Django docs
Yep you are right. I played for a while with the shell testing different variations but cant find a solution different from .raw ;(
I really appreciate the suggestions and the effort! I will be marking this as answered for the benefit of others using Django 1.7.
0

If you are using Django 1.8 or higher, you could use something like this:

from django.db.models import Avg, F

Test.objects.annotate(avg_duration=Avg(F('end_time') - F('start_time')))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.