Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Nested query support.

This extends previous functionality that allowed passing Query objects as the
rvals to filters. You can now pass QuerySets, which requires less poking at
opaque attributes. See the documentation of the "__in" lookup type for the
details.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9701 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f747b61c20cb2ab9bcbca2b272f3fba80cf39ac6 1 parent 062a94e
Malcolm Tredinnick authored January 05, 2009
9  django/db/models/query.py
@@ -641,6 +641,15 @@ def _merge_sanity_check(self, other):
641 641
         """
642 642
         pass
643 643
 
  644
+    def as_sql(self):
  645
+        """
  646
+        Returns the internal query's SQL and parameters (as a tuple).
  647
+
  648
+        This is a private (internal) method. The name is chosen to provide
  649
+        uniformity with other interfaces (in particular, the Query class).
  650
+        """
  651
+        obj = self.values("pk")
  652
+        return obj.query.as_nested_sql()
644 653
 
645 654
 class ValuesQuerySet(QuerySet):
646 655
     def __init__(self, *args, **kwargs):
12  django/db/models/sql/query.py
@@ -313,6 +313,18 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
313 313
         params.extend(self.extra_params)
314 314
         return ' '.join(result), tuple(params)
315 315
 
  316
+    def as_nested_sql(self):
  317
+        """
  318
+        Perform the same functionality as the as_sql() method, returning an
  319
+        SQL string and parameters. However, the alias prefixes are bumped
  320
+        beforehand (in a copy -- the current query isn't changed).
  321
+
  322
+        Used when nesting this query inside another.
  323
+        """
  324
+        obj = self.clone()
  325
+        obj.bump_prefix()
  326
+        return obj.as_sql()
  327
+
316 328
     def combine(self, rhs, connector):
317 329
         """
318 330
         Merge the 'rhs' query into the current one (with any 'rhs' effects
38  docs/ref/models/querysets.txt
@@ -1035,12 +1035,27 @@ SQL equivalent::
1035 1035
     SELECT ... WHERE id IN (1, 3, 4);
1036 1036
 
1037 1037
 You can also use a queryset to dynamically evaluate the list of values
1038  
-instead of providing a list of literal values. The queryset must be
1039  
-reduced to a list of individual values using the ``values()`` method,
1040  
-and then converted into a query using the ``query`` attribute::
  1038
+instead of providing a list of literal values::
1041 1039
 
1042  
-    q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
1043  
-    e = Entry.objects.filter(blog__in=q)
  1040
+    inner_qs = Blog.objects.filter(name__contains='Cheddar')
  1041
+    entries = Entry.objects.filter(blog__in=inner_qs)
  1042
+
  1043
+This queryset will be evaluated as subselect statement::
  1044
+
  1045
+    SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
  1046
+
  1047
+The above code fragment could also be written as follows::
  1048
+
  1049
+    inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
  1050
+    entries = Entry.objects.filter(blog__in=inner_q)
  1051
+
  1052
+.. versionchanged:: 1.1
  1053
+    In Django 1.0, only the latter piece of code is valid.
  1054
+
  1055
+This second form is a bit less readable and unnatural to write, since it
  1056
+accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``.
  1057
+If your code doesn't require compatibility with Django 1.0, use the first
  1058
+form, passing in a queryset directly.
1044 1059
 
1045 1060
 .. warning::
1046 1061
 
@@ -1048,9 +1063,18 @@ and then converted into a query using the ``query`` attribute::
1048 1063
     It's fine to use it like above, but its API may change between Django
1049 1064
     versions.
1050 1065
 
1051  
-This queryset will be evaluated as subselect statement::
  1066
+.. admonition:: Performance considerations
1052 1067
 
1053  
-    SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
  1068
+    Be cautious about using nested queries and understand your database
  1069
+    server's performance characteristics (if in doubt, benchmark!). Some
  1070
+    database backends, most notably MySQL, don't optimize nested queries very
  1071
+    well. It is more efficient, in those cases, to extract a list of values
  1072
+    and then pass that into the second query. That is, execute two queries
  1073
+    instead of one::
  1074
+
  1075
+        values = Blog.objects.filter(
  1076
+                name__contains='Cheddar').values_list('pk', flat=True)
  1077
+        entries = Entry.objects.filter(blog__in=values)
1054 1078
 
1055 1079
 gt
1056 1080
 ~~
7  tests/regressiontests/queries/models.py
@@ -1008,6 +1008,13 @@ class PointerB(models.Model):
1008 1008
 # optimise the inner query without losing results.
1009 1009
 >>> Annotation.objects.exclude(tag__children__name="t2")
1010 1010
 [<Annotation: a2>]
  1011
+
  1012
+Nested queries are possible (although should be used with care, since they have
  1013
+performance problems on backends like MySQL.
  1014
+
  1015
+>>> Annotation.objects.filter(notes__in=Note.objects.filter(note="n1"))
  1016
+[<Annotation: a1>]
  1017
+
1011 1018
 """}
1012 1019
 
1013 1020
 # In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__

0 notes on commit f747b61

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