Skip to content

Commit

Permalink
Nested query support.
Browse files Browse the repository at this point in the history
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
malcolmt committed Jan 5, 2009
1 parent 062a94e commit f747b61
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 7 deletions.
9 changes: 9 additions & 0 deletions django/db/models/query.py
Expand Up @@ -641,6 +641,15 @@ def _merge_sanity_check(self, other):
"""
pass

def as_sql(self):
"""
Returns the internal query's SQL and parameters (as a tuple).
This is a private (internal) method. The name is chosen to provide
uniformity with other interfaces (in particular, the Query class).
"""
obj = self.values("pk")
return obj.query.as_nested_sql()

class ValuesQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
Expand Down
12 changes: 12 additions & 0 deletions django/db/models/sql/query.py
Expand Up @@ -313,6 +313,18 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
params.extend(self.extra_params)
return ' '.join(result), tuple(params)

def as_nested_sql(self):
"""
Perform the same functionality as the as_sql() method, returning an
SQL string and parameters. However, the alias prefixes are bumped
beforehand (in a copy -- the current query isn't changed).
Used when nesting this query inside another.
"""
obj = self.clone()
obj.bump_prefix()
return obj.as_sql()

def combine(self, rhs, connector):
"""
Merge the 'rhs' query into the current one (with any 'rhs' effects
Expand Down
38 changes: 31 additions & 7 deletions docs/ref/models/querysets.txt
Expand Up @@ -1035,22 +1035,46 @@ SQL equivalent::
SELECT ... WHERE id IN (1, 3, 4);

You can also use a queryset to dynamically evaluate the list of values
instead of providing a list of literal values. The queryset must be
reduced to a list of individual values using the ``values()`` method,
and then converted into a query using the ``query`` attribute::
instead of providing a list of literal values::

q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
e = Entry.objects.filter(blog__in=q)
inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)

This queryset will be evaluated as subselect statement::

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')

The above code fragment could also be written as follows::

inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
entries = Entry.objects.filter(blog__in=inner_q)

.. versionchanged:: 1.1
In Django 1.0, only the latter piece of code is valid.

This second form is a bit less readable and unnatural to write, since it
accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``.
If your code doesn't require compatibility with Django 1.0, use the first
form, passing in a queryset directly.

.. warning::

This ``query`` attribute should be considered an opaque internal attribute.
It's fine to use it like above, but its API may change between Django
versions.

This queryset will be evaluated as subselect statement::
.. admonition:: Performance considerations

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
Be cautious about using nested queries and understand your database
server's performance characteristics (if in doubt, benchmark!). Some
database backends, most notably MySQL, don't optimize nested queries very
well. It is more efficient, in those cases, to extract a list of values
and then pass that into the second query. That is, execute two queries
instead of one::

values = Blog.objects.filter(
name__contains='Cheddar').values_list('pk', flat=True)
entries = Entry.objects.filter(blog__in=values)

gt
~~
Expand Down
7 changes: 7 additions & 0 deletions tests/regressiontests/queries/models.py
Expand Up @@ -1008,6 +1008,13 @@ class PointerB(models.Model):
# optimise the inner query without losing results.
>>> Annotation.objects.exclude(tag__children__name="t2")
[<Annotation: a2>]
Nested queries are possible (although should be used with care, since they have
performance problems on backends like MySQL.
>>> Annotation.objects.filter(notes__in=Note.objects.filter(note="n1"))
[<Annotation: a1>]
"""}

# In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__
Expand Down

0 comments on commit f747b61

Please sign in to comment.