2

I have been trying to group the results of table into Hourly format using DateTimeField.

SQL:

SELECT strftime('%H', created_on), count(*)
FROM users_test
GROUP BY strftime('%H', created_on);

This query works fine, but the corresponding Django query does not.

Django queries I've tried:

Test.objects.extra({'hour': 'strftime("%%H", created_on)'}).values('hour').annotate(count=Count('id'))
# SELECT (strftime("%H", created_on)) AS "hour", COUNT("users_test"."id") AS "count" FROM "users_test" GROUP BY (strftime("%H", created_on)), "users_test"."created_on" ORDER BY "users_test"."created_on" DESC

It adds additional group by "users_test"."created_on", which I guess is giving incorrect results.

It would be great if anyone can explain me this and provide a solution as well.

Environment:

  • Python 3
  • Django 1.8.1

Thanks in Advance

References (Possible Duplicates) (But None helping out):

Community
  • 1
  • 1
Ankit Popli
  • 2,809
  • 3
  • 37
  • 61

1 Answers1

6

To fix it, append order_by() to query chain. This will override model Meta default ordering. Like this:

Test
.objects
.extra({'hour': 'strftime("%%H", created_on)'})
.order_by()                                        #<------ here
.values('hour')
.annotate(count=Count('id'))

In my environment ( Postgres also ):

>>> print ( Material
         .objects
         .extra({'hour': 'strftime("%%H", data_creacio)'})
         .order_by()
         .values('hour')
         .annotate(count=Count('id'))
         .query )

  SELECT (strftime("%H", data_creacio)) AS "hour", 
         COUNT("material_material"."id") AS "count" 
    FROM "material_material" 
GROUP BY (strftime("%H", data_creacio))

Learn more in order_by django docs:

If you don’t want any ordering to be applied to a query, not even the default ordering, call order_by() with no parameters.

Side note: using extra() may introduce SQL injection vulnerability to your code. Use this with precaution and escape any parameters that user can introduce. Compare with docs:

Warning

You should be very careful whenever you use extra(). Every time you use it, you should escape any parameters that the user can control by using params in order to protect against SQL injection attacks . Please read more about SQL injection protection.

gonczor
  • 3,994
  • 1
  • 21
  • 46
dani herrera
  • 48,760
  • 8
  • 117
  • 177
  • This answer is quite old, but you're risking introducing SQL injection. It may not be the case in this particular example, but please note that it can be tricky. `You should be very careful whenever you use extra(). Every time you use it, you should escape any parameters that the user can control by using params in order to protect against SQL injection attacks .` https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.extra – gonczor Jun 11 '18 at 12:33
  • Hi @gonczor, nice shot. Do you have >2k. Are you so kind to edit the answer? – dani herrera Jun 11 '18 at 12:39
  • Done. Glad I could help :) – gonczor Jun 11 '18 at 12:44
  • Thanks to improve SO. – dani herrera Jun 11 '18 at 12:45