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
This commit is contained in:
Malcolm Tredinnick 2008-12-10 05:19:27 +00:00
parent 7030ab9a72
commit a1cbeb9afb
4 changed files with 36 additions and 0 deletions

View File

@ -143,6 +143,14 @@ class BaseDatabaseOperations(object):
""" """
return '%s' return '%s'
def force_no_ordering(self):
"""
Returns a list used in the "ORDER BY" clause to force no ordering at
all. Returning an empty list means that nothing will be included in the
ordering.
"""
return []
def fulltext_search_sql(self, field_name): def fulltext_search_sql(self, field_name):
""" """
Returns the SQL WHERE clause to use in order to perform a full-text Returns the SQL WHERE clause to use in order to perform a full-text

View File

@ -133,6 +133,14 @@ class DatabaseOperations(BaseDatabaseOperations):
def drop_foreignkey_sql(self): def drop_foreignkey_sql(self):
return "DROP FOREIGN KEY" return "DROP FOREIGN KEY"
def force_no_ordering(self):
"""
"ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
columns. If no ordering would otherwise be applied, we don't want any
implicit sorting going on.
"""
return ["NULL"]
def fulltext_search_sql(self, field_name): def fulltext_search_sql(self, field_name):
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name

View File

@ -288,6 +288,8 @@ class BaseQuery(object):
if self.group_by: if self.group_by:
grouping = self.get_grouping() grouping = self.get_grouping()
result.append('GROUP BY %s' % ', '.join(grouping)) result.append('GROUP BY %s' % ', '.join(grouping))
if not ordering:
ordering = self.connection.ops.force_no_ordering()
if self.having: if self.having:
having, h_params = self.get_having() having, h_params = self.get_having()

View File

@ -6,6 +6,7 @@ import datetime
import pickle import pickle
import sys import sys
from django.conf import settings
from django.db import models from django.db import models
from django.db.models.query import Q, ITER_CHUNK_SIZE from django.db.models.query import Q, ITER_CHUNK_SIZE
@ -1053,3 +1054,20 @@ FieldError: Infinite loop caused by ordering.
[] []
""" """
if settings.DATABASE_ENGINE == "mysql":
__test__["API_TESTS"] += """
When grouping without specifying ordering, we add an explicit "ORDER BY NULL"
portion in MySQL to prevent unnecessary sorting.
>>> query = Tag.objects.values_list('parent_id', flat=True).order_by().query
>>> query.group_by = ['parent_id']
>>> sql = query.as_sql()[0]
>>> fragment = "ORDER BY "
>>> pos = sql.find(fragment)
>>> sql.find(fragment, pos + 1) == -1
True
>>> sql.find("NULL", pos + len(fragment)) == pos + len(fragment)
True
"""