Fixed #9033 - Add bullets to QuerySet extra() arguments. thanks julien for the suggestion and dwillis for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14816 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Timo Graham 2010-12-04 20:41:35 +00:00
parent 76c2c30daf
commit 00f36e0ebf
1 changed files with 107 additions and 107 deletions

View File

@ -709,151 +709,151 @@ principle, so you should avoid them if possible.
Specify one or more of ``params``, ``select``, ``where`` or ``tables``. None Specify one or more of ``params``, ``select``, ``where`` or ``tables``. None
of the arguments is required, but you should use at least one of them. of the arguments is required, but you should use at least one of them.
``select`` * ``select``
The ``select`` argument lets you put extra fields in the ``SELECT`` clause. The ``select`` argument lets you put extra fields in the ``SELECT`` clause.
It should be a dictionary mapping attribute names to SQL clauses to use to It should be a dictionary mapping attribute names to SQL clauses to use to
calculate that attribute. calculate that attribute.
Example:: Example::
Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
As a result, each ``Entry`` object will have an extra attribute, As a result, each ``Entry`` object will have an extra attribute,
``is_recent``, a boolean representing whether the entry's ``pub_date`` is ``is_recent``, a boolean representing whether the entry's ``pub_date`` is
greater than Jan. 1, 2006. greater than Jan. 1, 2006.
Django inserts the given SQL snippet directly into the ``SELECT`` Django inserts the given SQL snippet directly into the ``SELECT``
statement, so the resulting SQL of the above example would be something statement, so the resulting SQL of the above example would be something
like:: like::
SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent
FROM blog_entry; FROM blog_entry;
The next example is more advanced; it does a subquery to give each The next example is more advanced; it does a subquery to give each
resulting ``Blog`` object an ``entry_count`` attribute, an integer count resulting ``Blog`` object an ``entry_count`` attribute, an integer count
of associated ``Entry`` objects:: of associated ``Entry`` objects::
Blog.objects.extra( Blog.objects.extra(
select={ select={
'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id' 'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
}, },
) )
(In this particular case, we're exploiting the fact that the query will (In this particular case, we're exploiting the fact that the query will
already contain the ``blog_blog`` table in its ``FROM`` clause.) already contain the ``blog_blog`` table in its ``FROM`` clause.)
The resulting SQL of the above example would be:: The resulting SQL of the above example would be::
SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
FROM blog_blog; FROM blog_blog;
Note that the parenthesis required by most database engines around Note that the parenthesis required by most database engines around
subqueries are not required in Django's ``select`` clauses. Also note that subqueries are not required in Django's ``select`` clauses. Also note that
some database backends, such as some MySQL versions, don't support some database backends, such as some MySQL versions, don't support
subqueries. subqueries.
.. versionadded:: 1.0 .. versionadded:: 1.0
In some rare cases, you might wish to pass parameters to the SQL fragments In some rare cases, you might wish to pass parameters to the SQL fragments
in ``extra(select=...)``. For this purpose, use the ``select_params`` in ``extra(select=...)``. For this purpose, use the ``select_params``
parameter. Since ``select_params`` is a sequence and the ``select`` parameter. Since ``select_params`` is a sequence and the ``select``
attribute is a dictionary, some care is required so that the parameters attribute is a dictionary, some care is required so that the parameters
are matched up correctly with the extra select pieces. In this situation, are matched up correctly with the extra select pieces. In this situation,
you should use a ``django.utils.datastructures.SortedDict`` for the you should use a ``django.utils.datastructures.SortedDict`` for the
``select`` value, not just a normal Python dictionary. ``select`` value, not just a normal Python dictionary.
This will work, for example:: This will work, for example::
Blog.objects.extra( Blog.objects.extra(
select=SortedDict([('a', '%s'), ('b', '%s')]), select=SortedDict([('a', '%s'), ('b', '%s')]),
select_params=('one', 'two')) select_params=('one', 'two'))
The only thing to be careful about when using select parameters in The only thing to be careful about when using select parameters in
``extra()`` is to avoid using the substring ``"%%s"`` (that's *two* ``extra()`` is to avoid using the substring ``"%%s"`` (that's *two*
percent characters before the ``s``) in the select strings. Django's percent characters before the ``s``) in the select strings. Django's
tracking of parameters looks for ``%s`` and an escaped ``%`` character tracking of parameters looks for ``%s`` and an escaped ``%`` character
like this isn't detected. That will lead to incorrect results. like this isn't detected. That will lead to incorrect results.
``where`` / ``tables`` * ``where`` / ``tables``
You can define explicit SQL ``WHERE`` clauses -- perhaps to perform You can define explicit SQL ``WHERE`` clauses -- perhaps to perform
non-explicit joins -- by using ``where``. You can manually add tables to non-explicit joins -- by using ``where``. You can manually add tables to
the SQL ``FROM`` clause by using ``tables``. the SQL ``FROM`` clause by using ``tables``.
``where`` and ``tables`` both take a list of strings. All ``where`` ``where`` and ``tables`` both take a list of strings. All ``where``
parameters are "AND"ed to any other search criteria. parameters are "AND"ed to any other search criteria.
Example:: Example::
Entry.objects.extra(where=['id IN (3, 4, 5, 20)']) Entry.objects.extra(where=['id IN (3, 4, 5, 20)'])
...translates (roughly) into the following SQL:: ...translates (roughly) into the following SQL::
SELECT * FROM blog_entry WHERE id IN (3, 4, 5, 20); SELECT * FROM blog_entry WHERE id IN (3, 4, 5, 20);
Be careful when using the ``tables`` parameter if you're specifying Be careful when using the ``tables`` parameter if you're specifying
tables that are already used in the query. When you add extra tables tables that are already used in the query. When you add extra tables
via the ``tables`` parameter, Django assumes you want that table included via the ``tables`` parameter, Django assumes you want that table included
an extra time, if it is already included. That creates a problem, an extra time, if it is already included. That creates a problem,
since the table name will then be given an alias. If a table appears since the table name will then be given an alias. If a table appears
multiple times in an SQL statement, the second and subsequent occurrences multiple times in an SQL statement, the second and subsequent occurrences
must use aliases so the database can tell them apart. If you're must use aliases so the database can tell them apart. If you're
referring to the extra table you added in the extra ``where`` parameter referring to the extra table you added in the extra ``where`` parameter
this is going to cause errors. this is going to cause errors.
Normally you'll only be adding extra tables that don't already appear in Normally you'll only be adding extra tables that don't already appear in
the query. However, if the case outlined above does occur, there are a few the query. However, if the case outlined above does occur, there are a few
solutions. First, see if you can get by without including the extra table solutions. First, see if you can get by without including the extra table
and use the one already in the query. If that isn't possible, put your and use the one already in the query. If that isn't possible, put your
``extra()`` call at the front of the queryset construction so that your ``extra()`` call at the front of the queryset construction so that your
table is the first use of that table. Finally, if all else fails, look at table is the first use of that table. Finally, if all else fails, look at
the query produced and rewrite your ``where`` addition to use the alias the query produced and rewrite your ``where`` addition to use the alias
given to your extra table. The alias will be the same each time you given to your extra table. The alias will be the same each time you
construct the queryset in the same way, so you can rely upon the alias construct the queryset in the same way, so you can rely upon the alias
name to not change. name to not change.
``order_by`` * ``order_by``
If you need to order the resulting queryset using some of the new fields If you need to order the resulting queryset using some of the new fields
or tables you have included via ``extra()`` use the ``order_by`` parameter or tables you have included via ``extra()`` use the ``order_by`` parameter
to ``extra()`` and pass in a sequence of strings. These strings should to ``extra()`` and pass in a sequence of strings. These strings should
either be model fields (as in the normal ``order_by()`` method on either be model fields (as in the normal ``order_by()`` method on
querysets), of the form ``table_name.column_name`` or an alias for a column querysets), of the form ``table_name.column_name`` or an alias for a column
that you specified in the ``select`` parameter to ``extra()``. that you specified in the ``select`` parameter to ``extra()``.
For example:: For example::
q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
q = q.extra(order_by = ['-is_recent']) q = q.extra(order_by = ['-is_recent'])
This would sort all the items for which ``is_recent`` is true to the front This would sort all the items for which ``is_recent`` is true to the front
of the result set (``True`` sorts before ``False`` in a descending of the result set (``True`` sorts before ``False`` in a descending
ordering). ordering).
This shows, by the way, that you can make multiple calls to This shows, by the way, that you can make multiple calls to
``extra()`` and it will behave as you expect (adding new constraints each ``extra()`` and it will behave as you expect (adding new constraints each
time). time).
``params`` * ``params``
The ``where`` parameter described above may use standard Python database The ``where`` parameter described above may use standard Python database
string placeholders -- ``'%s'`` to indicate parameters the database engine string placeholders -- ``'%s'`` to indicate parameters the database engine
should automatically quote. The ``params`` argument is a list of any extra should automatically quote. The ``params`` argument is a list of any extra
parameters to be substituted. parameters to be substituted.
Example:: Example::
Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Always use ``params`` instead of embedding values directly into ``where`` Always use ``params`` instead of embedding values directly into ``where``
because ``params`` will ensure values are quoted correctly according to because ``params`` will ensure values are quoted correctly according to
your particular backend. (For example, quotes will be escaped correctly.) your particular backend. (For example, quotes will be escaped correctly.)
Bad:: Bad::
Entry.objects.extra(where=["headline='Lennon'"]) Entry.objects.extra(where=["headline='Lennon'"])
Good:: Good::
Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
defer defer
~~~~~ ~~~~~