Ticket #6422: distinct_on.8.diff
File distinct_on.8.diff, 12.0 KB (added by , 13 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS
a b 202 202 Marc Garcia <marc.garcia@accopensys.com> 203 203 Andy Gayton <andy-django@thecablelounge.com> 204 204 geber@datacollect.com 205 Jeffrey Gelens <jeffrey@gelens.org> 205 206 Baishampayan Ghose 206 207 Joshua Ginsberg <jag@flowtheory.net> 207 208 Dimitris Glezos <dimitris@glezos.com> -
django/db/backends/__init__.py
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
a b 376 376 supports_stddev = None 377 377 can_introspect_foreign_keys = None 378 378 379 # Support for the DISTINCT ON clause 380 can_distinct_on_fields = False 381 379 382 def __init__(self, connection): 380 383 self.connection = connection 381 384 … … 529 532 """ 530 533 raise NotImplementedError('Full-text search is not implemented for this database backend') 531 534 535 def distinct(self, fields): 536 """ 537 Returns an SQL DISTINCT clause which removes duplicate rows from the 538 result set. If any fields are given, only the given fields are being 539 checked for duplicates. 540 """ 541 if fields: 542 raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') 543 else: 544 return 'DISTINCT' 545 532 546 def last_executed_query(self, cursor, sql, params): 533 547 """ 534 548 Returns a string of the query last executed by the given cursor, with -
django/db/backends/postgresql_psycopg2/base.py
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
a b 76 76 has_select_for_update_nowait = True 77 77 has_bulk_insert = True 78 78 supports_tablespaces = True 79 can_distinct_on_fields = True 79 80 80 81 class DatabaseWrapper(BaseDatabaseWrapper): 81 82 vendor = 'postgresql' -
django/db/backends/postgresql_psycopg2/operations.py
diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py
a b 179 179 180 180 return 63 181 181 182 def distinct(self, fields): 183 if fields: 184 fields_sql = [] 185 186 for field in fields: 187 fields_sql.append( 188 self.quote_name(field.model._meta.db_table) + "." + \ 189 self.quote_name(field.column) 190 ) 191 192 return 'DISTINCT ON (%s)' % ', '.join(fields_sql) 193 else: 194 return 'DISTINCT' 195 182 196 def last_executed_query(self, cursor, sql, params): 183 197 # http://initd.org/psycopg/docs/cursor.html#cursor.query 184 198 # The query attribute is a Psycopg extension to the DB API 2.0. -
django/db/models/query.py
diff --git a/django/db/models/query.py b/django/db/models/query.py
a b 738 738 obj.query.add_ordering(*field_names) 739 739 return obj 740 740 741 def distinct(self, true_or_false=True):741 def distinct(self, *field_names): 742 742 """ 743 743 Returns a new QuerySet instance that will select only distinct results. 744 744 """ 745 745 obj = self._clone() 746 obj.query.distinct = true_or_false 746 obj.query.add_distinct_fields(field_names) 747 obj.query.distinct = True 748 747 749 return obj 748 750 749 751 def extra(self, select=None, where=None, params=None, tables=None, … … 1166 1168 """ 1167 1169 return self 1168 1170 1169 def distinct(self, true_or_false=True):1171 def distinct(self, fields=None): 1170 1172 """ 1171 1173 Always returns EmptyQuerySet. 1172 1174 """ -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
a b 76 76 params.extend(val[1]) 77 77 78 78 result = ['SELECT'] 79 79 80 if self.query.distinct: 80 result.append('DISTINCT') 81 result.append(self.connection.ops.distinct(self.query.distinct_fields)) 82 81 83 result.append(', '.join(out_cols + self.query.ordering_aliases)) 82 84 83 85 result.append('FROM') -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
a b 126 126 self.order_by = [] 127 127 self.low_mark, self.high_mark = 0, None # Used for offset/limit 128 128 self.distinct = False 129 self.distinct_fields = None 129 130 self.select_for_update = False 130 131 self.select_for_update_nowait = False 131 132 self.select_related = False … … 264 265 obj.order_by = self.order_by[:] 265 266 obj.low_mark, obj.high_mark = self.low_mark, self.high_mark 266 267 obj.distinct = self.distinct 268 obj.distinct_fields = self.distinct_fields 267 269 obj.select_for_update = self.select_for_update 268 270 obj.select_for_update_nowait = self.select_for_update_nowait 269 271 obj.select_related = self.select_related … … 392 394 Performs a COUNT() query using the current filter constraints. 393 395 """ 394 396 obj = self.clone() 395 if len(self.select) > 1 or self.aggregate_select :397 if len(self.select) > 1 or self.aggregate_select or (self.distinct and self.distinct_fields): 396 398 # If a select clause exists, then the query has already started to 397 399 # specify the columns that are to be returned. 398 400 # In this case, we need to use a subquery to evaluate the count. … … 1595 1597 self.select = [] 1596 1598 self.select_fields = [] 1597 1599 1600 def add_distinct_fields(self, field_names): 1601 self.distinct_fields = [] 1602 options = self.get_meta() 1603 1604 for name in field_names: 1605 field, source, opts, join_list, last, _ = self.setup_joins( 1606 name.split(LOOKUP_SEP), options, self.get_initial_alias(), False) 1607 self.distinct_fields.append(field) 1608 1598 1609 def add_fields(self, field_names, allow_m2m=True): 1599 1610 """ 1600 1611 Adds the given (model) fields to the select set. The field names are -
docs/ref/models/querysets.txt
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
a b 345 345 distinct 346 346 ~~~~~~~~ 347 347 348 .. method:: distinct( )348 .. method:: distinct([*fields]) 349 349 350 350 Returns a new ``QuerySet`` that uses ``SELECT DISTINCT`` in its SQL query. This 351 351 eliminates duplicate rows from the query results. … … 374 374 :meth:`values()` together, be careful when ordering by fields not in the 375 375 :meth:`values()` call. 376 376 377 .. versionadded:: 1.4 378 379 The possibility to pass positional arguments (``*fields``) is new in Django 1.4. 380 They are names of fields to which the ``DISTINCT`` should be limited. This 381 translates to a ``SELECT DISTINCT ON`` SQL query. 382 383 .. note:: 384 Note that the ability to specify field names is only available in PostgreSQL. 385 386 .. note:: 387 When fields names are given, you will have to add an :meth:`order_by` 388 call with the same field names as the leftmost arguments. 389 390 Examples:: 391 392 >>> Author.objects.distinct() 393 [...] 394 395 >>> Entry.objects.order_by('pub_date').distinct('pub_date') 396 [...] 397 398 >>> Entry.objects.order_by('blog').distinct('blog') 399 [...] 400 401 >>> Entry.objects.order_by('author', 'pub_date').distinct('author', 'pub_date') 402 [...] 403 404 >>> Entry.objects.order_by('blog__name', 'mod_date').distinct('blog__name', 'mod_date') 405 [...] 406 407 >>> Entry.objects.order_by('author', 'pub_date').distinct('author') 408 [...] 409 377 410 values 378 411 ~~~~~~ 379 412 -
tests/regressiontests/queries/models.py
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
a b 209 209 name = models.CharField("Name", max_length=20) 210 210 greatest_fan = models.ForeignKey("Fan", null=True, unique=True) 211 211 212 def __unicode__(self): 213 return self.name 214 212 215 class TvChef(Celebrity): 213 216 pass 214 217 … … 344 347 def __unicode__(self): 345 348 return "one2one " + self.new_name 346 349 350 class Staff(models.Model): 351 name = models.CharField(max_length=50) 352 organisation = models.CharField(max_length=100) 353 tags = models.ManyToManyField(Tag, through='StaffTag') 354 355 def __unicode__(self): 356 return self.name 357 358 class StaffTag(models.Model): 359 staff = models.ForeignKey(Staff) 360 tag = models.ForeignKey(Tag) 361 362 def __unicode__(self): 363 return u"%s -> %s" % (self.tag, self.staff) -
tests/regressiontests/queries/tests.py
diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
a b 18 18 ManagedModel, Member, NamedCategory, Note, Number, Plaything, PointerA, 19 19 Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten, 20 20 Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory, 21 SpecialCategory, OneToOneCategory )21 SpecialCategory, OneToOneCategory, Staff, StaffTag) 22 22 23 23 24 24 class BaseQuerysetTest(TestCase): … … 1739 1739 t4 = Tag.objects.create(name='t4', parent=t3) 1740 1740 t5 = Tag.objects.create(name='t5', parent=t3) 1741 1741 1742 p1_o1 = Staff.objects.create(name="p1", organisation="o1") 1743 p2_o1 = Staff.objects.create(name="p2", organisation="o1") 1744 p3_o1 = Staff.objects.create(name="p3", organisation="o1") 1745 p1_o2 = Staff.objects.create(name="p1", organisation="o2") 1746 1747 StaffTag.objects.create(staff=p1_o1, tag=t1) 1748 StaffTag.objects.create(staff=p1_o1, tag=t1) 1749 1750 celeb1 = Celebrity.objects.create(name="c1") 1751 celeb2 = Celebrity.objects.create(name="c2") 1752 1753 self.fan1 = Fan.objects.create(fan_of=celeb1) 1754 self.fan2 = Fan.objects.create(fan_of=celeb1) 1755 self.fan3 = Fan.objects.create(fan_of=celeb2) 1756 1742 1757 # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed 1743 1758 # (Python issue 1242657), so these cases return an empty list, rather than 1744 1759 # raising an exception. Not a lot we can do about that, unfortunately, due to … … 1810 1825 2500 1811 1826 ) 1812 1827 1828 @skipUnlessDBFeature('can_distinct_on_fields') 1829 def test_ticket6422(self): 1830 """QuerySet.distinct('field', ...) works""" 1831 # (qset, expected) tuples 1832 qsets = ( 1833 ( 1834 Staff.objects.distinct().order_by('name'), 1835 ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], 1836 ), 1837 ( 1838 Staff.objects.distinct('name').order_by('name'), 1839 ['<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], 1840 ), 1841 ( 1842 Staff.objects.distinct('organisation').order_by('organisation', 'name'), 1843 ['<Staff: p1>', '<Staff: p1>'], 1844 ), 1845 ( 1846 Staff.objects.distinct('name', 'organisation').order_by('name', 'organisation'), 1847 ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], 1848 ), 1849 ( 1850 Celebrity.objects.filter(fan__in=[self.fan1, self.fan2, self.fan3]).\ 1851 distinct('name').order_by('name'), 1852 ['<Celebrity: c1>', '<Celebrity: c2>'], 1853 ), 1854 ( 1855 StaffTag.objects.distinct('staff','tag'), 1856 ['<StaffTag: t1 -> p1>'], 1857 ), 1858 ( 1859 Tag.objects.order_by('parent__pk', 'pk').distinct('parent'), 1860 ['<Tag: t2>', '<Tag: t4>', '<Tag: t1>'], 1861 ), 1862 ( 1863 StaffTag.objects.select_related('staff').distinct('staff__name').order_by('staff__name'), 1864 ['<StaffTag: t1 -> p1>'], 1865 ), 1866 ) 1867 1868 for qset, expected in qsets: 1869 self.assertQuerysetEqual(qset, expected) 1870 self.assertEqual(qset.count(), len(expected)) 1871 1872 # and check the fieldlookup 1873 self.assertRaises( 1874 FieldError, 1875 lambda: Staff.objects.distinct('shrubbery') 1876 ) 1877 1878 1813 1879 class UnionTests(unittest.TestCase): 1814 1880 """ 1815 1881 Tests for the union of two querysets. Bug #12252.