Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

If an SQL query doesn't specify any ordering, avoid the implicit sort

that happens with MySQL when a "GROUP BY" clause is included. This is a
backend-specific operation, so any other databases requiring similar
encouragement can have a function added to their own backend code.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@9637 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit a1cbeb9afbbf0f16e1ffa1891575fbc2c3edae74 1 parent 7030ab9
Malcolm Tredinnick authored December 10, 2008
8  django/db/backends/__init__.py
@@ -143,6 +143,14 @@ def field_cast_sql(self, db_type):
143 143
         """
144 144
         return '%s'
145 145
 
  146
+    def force_no_ordering(self):
  147
+        """
  148
+        Returns a list used in the "ORDER BY" clause to force no ordering at
  149
+        all. Returning an empty list means that nothing will be included in the
  150
+        ordering.
  151
+        """
  152
+        return []
  153
+
146 154
     def fulltext_search_sql(self, field_name):
147 155
         """
148 156
         Returns the SQL WHERE clause to use in order to perform a full-text
8  django/db/backends/mysql/base.py
@@ -133,6 +133,14 @@ def date_trunc_sql(self, lookup_type, field_name):
133 133
     def drop_foreignkey_sql(self):
134 134
         return "DROP FOREIGN KEY"
135 135
 
  136
+    def force_no_ordering(self):
  137
+        """
  138
+        "ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
  139
+        columns. If no ordering would otherwise be applied, we don't want any
  140
+        implicit sorting going on.
  141
+        """
  142
+        return ["NULL"]
  143
+
136 144
     def fulltext_search_sql(self, field_name):
137 145
         return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
138 146
 
2  django/db/models/sql/query.py
@@ -288,6 +288,8 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
288 288
         if self.group_by:
289 289
             grouping = self.get_grouping()
290 290
             result.append('GROUP BY %s' % ', '.join(grouping))
  291
+            if not ordering:
  292
+                ordering = self.connection.ops.force_no_ordering()
291 293
 
292 294
         if self.having:
293 295
             having, h_params = self.get_having()
18  tests/regressiontests/queries/models.py
@@ -6,6 +6,7 @@
6 6
 import pickle
7 7
 import sys
8 8
 
  9
+from django.conf import settings
9 10
 from django.db import models
10 11
 from django.db.models.query import Q, ITER_CHUNK_SIZE
11 12
 
@@ -1053,3 +1054,20 @@ class PointerB(models.Model):
1053 1054
 []
1054 1055
 
1055 1056
 """
  1057
+
  1058
+if settings.DATABASE_ENGINE == "mysql":
  1059
+    __test__["API_TESTS"] += """
  1060
+When grouping without specifying ordering, we add an explicit "ORDER BY NULL"
  1061
+portion in MySQL to prevent unnecessary sorting.
  1062
+
  1063
+>>> query = Tag.objects.values_list('parent_id', flat=True).order_by().query
  1064
+>>> query.group_by = ['parent_id']
  1065
+>>> sql = query.as_sql()[0]
  1066
+>>> fragment = "ORDER BY "
  1067
+>>> pos = sql.find(fragment)
  1068
+>>> sql.find(fragment, pos + 1) == -1
  1069
+True
  1070
+>>> sql.find("NULL", pos + len(fragment)) == pos + len(fragment)
  1071
+True
  1072
+
  1073
+"""

0 notes on commit a1cbeb9

Please sign in to comment.
Something went wrong with that request. Please try again.