mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	[1.11.x] Fixed #28399 -- Fixed QuerySet.count() for union(), difference(), and intersection() queries.
Backport of adab280cef from master
			
			
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							d9ef8ffb58
						
					
				
				
					commit
					9350f77c69
				
			| @@ -398,7 +398,7 @@ class SQLCompiler(object): | |||||||
|                     continue |                     continue | ||||||
|                 raise |                 raise | ||||||
|         if not parts: |         if not parts: | ||||||
|             return [], [] |             raise EmptyResultSet | ||||||
|         combinator_sql = self.connection.ops.set_operators[combinator] |         combinator_sql = self.connection.ops.set_operators[combinator] | ||||||
|         if all and combinator == 'union': |         if all and combinator == 'union': | ||||||
|             combinator_sql += ' ALL' |             combinator_sql += ' ALL' | ||||||
|   | |||||||
| @@ -416,12 +416,12 @@ class Query(object): | |||||||
|         # aren't smart enough to remove the existing annotations from the |         # aren't smart enough to remove the existing annotations from the | ||||||
|         # query, so those would force us to use GROUP BY. |         # query, so those would force us to use GROUP BY. | ||||||
|         # |         # | ||||||
|         # If the query has limit or distinct, then those operations must be |         # If the query has limit or distinct, or uses set operations, then | ||||||
|         # done in a subquery so that we are aggregating on the limit and/or |         # those operations must be done in a subquery so that the query | ||||||
|         # distinct results instead of applying the distinct and limit after the |         # aggregates on the limit and/or distinct results instead of applying | ||||||
|         # aggregation. |         # the distinct and limit after the aggregation. | ||||||
|         if (isinstance(self.group_by, list) or has_limit or has_existing_annotations or |         if (isinstance(self.group_by, list) or has_limit or has_existing_annotations or | ||||||
|                 self.distinct): |                 self.distinct or self.combinator): | ||||||
|             from django.db.models.sql.subqueries import AggregateQuery |             from django.db.models.sql.subqueries import AggregateQuery | ||||||
|             outer_query = AggregateQuery(self.model) |             outer_query = AggregateQuery(self.model) | ||||||
|             inner_query = self.clone() |             inner_query = self.clone() | ||||||
|   | |||||||
| @@ -822,11 +822,15 @@ of other models. Passing different models works as long as the ``SELECT`` list | |||||||
| is the same in all ``QuerySet``\s (at least the types, the names don't matter | is the same in all ``QuerySet``\s (at least the types, the names don't matter | ||||||
| as long as the types in the same order). | as long as the types in the same order). | ||||||
|  |  | ||||||
| In addition, only ``LIMIT``, ``OFFSET``, and ``ORDER BY`` (i.e. slicing and | In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, and ``ORDER BY`` (i.e. | ||||||
| :meth:`order_by`) are allowed on the resulting ``QuerySet``. Further, databases | slicing, :meth:`count`, and :meth:`order_by`) are allowed on the resulting | ||||||
| place restrictions on what operations are allowed in the combined queries. For | ``QuerySet``. Further, databases place restrictions on what operations are | ||||||
| example, most databases don't allow ``LIMIT`` or ``OFFSET`` in the combined | allowed in the combined queries. For example, most databases don't allow | ||||||
| queries. | ``LIMIT`` or ``OFFSET`` in the combined queries. | ||||||
|  |  | ||||||
|  | .. versionchanged:: 1.11.4 | ||||||
|  |  | ||||||
|  |     ``COUNT(*)`` support was added. | ||||||
|  |  | ||||||
| ``intersection()`` | ``intersection()`` | ||||||
| ~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~ | ||||||
|   | |||||||
| @@ -25,3 +25,6 @@ Bugfixes | |||||||
| * Corrected ``Field.has_changed()`` to return ``False`` for disabled form | * Corrected ``Field.has_changed()`` to return ``False`` for disabled form | ||||||
|   fields: ``BooleanField``, ``MultipleChoiceField``, ``MultiValueField``, |   fields: ``BooleanField``, ``MultipleChoiceField``, ``MultiValueField``, | ||||||
|   ``FileField``, ``ModelChoiceField``, and ``ModelMultipleChoiceField``. |   ``FileField``, ``ModelChoiceField``, and ``ModelMultipleChoiceField``. | ||||||
|  |  | ||||||
|  | * Fixed ``QuerySet.count()`` for ``union()``, ``difference()``, and | ||||||
|  |   ``intersection()`` queries. (:ticket:`28399`). | ||||||
|   | |||||||
| @@ -98,6 +98,27 @@ class QuerySetSetOperationTests(TestCase): | |||||||
|         qs2 = Number.objects.filter(num__gte=2, num__lte=3) |         qs2 = Number.objects.filter(num__gte=2, num__lte=3) | ||||||
|         self.assertNumbersEqual(qs1.union(qs2).order_by('-num'), [3, 2, 1, 0]) |         self.assertNumbersEqual(qs1.union(qs2).order_by('-num'), [3, 2, 1, 0]) | ||||||
|  |  | ||||||
|  |     def test_count_union(self): | ||||||
|  |         qs1 = Number.objects.filter(num__lte=1).values('num') | ||||||
|  |         qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num') | ||||||
|  |         self.assertEqual(qs1.union(qs2).count(), 4) | ||||||
|  |  | ||||||
|  |     def test_count_union_empty_result(self): | ||||||
|  |         qs = Number.objects.filter(pk__in=[]) | ||||||
|  |         self.assertEqual(qs.union(qs).count(), 0) | ||||||
|  |  | ||||||
|  |     @skipUnlessDBFeature('supports_select_difference') | ||||||
|  |     def test_count_difference(self): | ||||||
|  |         qs1 = Number.objects.filter(num__lt=10) | ||||||
|  |         qs2 = Number.objects.filter(num__lt=9) | ||||||
|  |         self.assertEqual(qs1.difference(qs2).count(), 1) | ||||||
|  |  | ||||||
|  |     @skipUnlessDBFeature('supports_select_intersection') | ||||||
|  |     def test_count_intersection(self): | ||||||
|  |         qs1 = Number.objects.filter(num__gte=5) | ||||||
|  |         qs2 = Number.objects.filter(num__lte=5) | ||||||
|  |         self.assertEqual(qs1.intersection(qs2).count(), 1) | ||||||
|  |  | ||||||
|     @skipUnlessDBFeature('supports_slicing_ordering_in_compound') |     @skipUnlessDBFeature('supports_slicing_ordering_in_compound') | ||||||
|     def test_ordering_subqueries(self): |     def test_ordering_subqueries(self): | ||||||
|         qs1 = Number.objects.order_by('num')[:2] |         qs1 = Number.objects.order_by('num')[:2] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user