extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
Sometimes, the Django query syntax by itself can’t easily express a complex WHERE
clause. For these edge cases, Django provides the extra()
QuerySet
modifier — a hook for injecting specific clauses into the SQL generated by a QuerySet
.
Use this method as a last resort
This is an old API that we aim to deprecate at some point in the future. Use it only if you cannot express your query using other queryset methods. If you do need to use it, please file a ticket using the QuerySet.extra keyword with your use case (please check the list of existing tickets first) so that we can enhance the QuerySet API to allow removing extra()
. We are no longer improving or fixing bugs for this method.
For example, this use of extra()
:
>>> qs.extra( ... select={'val': "select col from sometable where othercol = %s"}, ... select_params=(someparam,), ... )
is equivalent to:
>>> qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
The main benefit of using RawSQL
is that you can set output_field
if needed. The main downside is that if you refer to some table alias of the queryset in the raw SQL, then it is possible that Django might change that alias (for example, when the queryset is used as a subquery in yet another query).
Warning
You should be very careful whenever you use extra()
. Every time you use it, you should escape any parameters that the user can control by using params
in order to protect against SQL injection attacks . Please read more about SQL injection protection.
By definition, these extra lookups may not be portable to different database engines (because you’re explicitly writing SQL code) and violate the DRY principle, so you should avoid them if possible.
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.
-
select
The
select
argument lets you put extra fields in theSELECT
clause. It should be a dictionary mapping attribute names to SQL clauses to use to calculate that attribute.Example:
Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
As a result, each
Entry
object will have an extra attribute,is_recent
, a boolean representing whether the entry’spub_date
is greater than Jan. 1, 2006.Django inserts the given SQL snippet directly into the
SELECT
statement, so the resulting SQL of the above example would be something like:SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent FROM blog_entry;
The next example is more advanced; it does a subquery to give each resulting
Blog
object anentry_count
attribute, an integer count of associatedEntry
objects:Blog.objects.extra( select={ '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 already contain the
blog_blog
table in itsFROM
clause.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 FROM blog_blog;
Note that the parentheses required by most database engines around subqueries are not required in Django’s
select
clauses. Also note that some database backends, such as some MySQL versions, don’t support subqueries.In some rare cases, you might wish to pass parameters to the SQL fragments in
extra(select=...)
. For this purpose, use theselect_params
parameter. Sinceselect_params
is a sequence and theselect
attribute is a dictionary, some care is required so that the parameters are matched up correctly with the extra select pieces. In this situation, you should use acollections.OrderedDict
for theselect
value, not just a normal Python dictionary.This will work, for example:
Blog.objects.extra( select=OrderedDict([('a', '%s'), ('b', '%s')]), select_params=('one', 'two'))
If you need to use a literal
%s
inside your select string, use the sequence%%s
. -
where
/tables
You can define explicit SQL
WHERE
clauses — perhaps to perform non-explicit joins — by usingwhere
. You can manually add tables to the SQLFROM
clause by usingtables
.where
andtables
both take a list of strings. Allwhere
parameters are “AND”ed to any other search criteria.Example:
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
...translates (roughly) into the following SQL:
SELECT * FROM blog_entry WHERE (foo='a' OR bar='a') AND (baz='a')
Be careful when using the
tables
parameter if you’re specifying tables that are already used in the query. When you add extra tables via thetables
parameter, Django assumes you want that table included 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 multiple times in an SQL statement, the second and subsequent occurrences must use aliases so the database can tell them apart. If you’re referring to the extra table you added in the extrawhere
parameter this is going to cause errors.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 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
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 the query produced and rewrite yourwhere
addition to use the alias 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 name to not change. -
order_by
If you need to order the resulting queryset using some of the new fields or tables you have included via
extra()
use theorder_by
parameter toextra()
and pass in a sequence of strings. These strings should either be model fields (as in the normalorder_by()
method on querysets), of the formtable_name.column_name
or an alias for a column that you specified in theselect
parameter toextra()
.For example:
q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) q = q.extra(order_by = ['-is_recent'])
This would sort all the items for which
is_recent
is true to the front of the result set (True
sorts beforeFalse
in a descending ordering).This shows, by the way, that you can make multiple calls to
extra()
and it will behave as you expect (adding new constraints each time). -
params
The
where
parameter described above may use standard Python database string placeholders —'%s'
to indicate parameters the database engine should automatically quote. Theparams
argument is a list of any extra parameters to be substituted.Example:
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Always use
params
instead of embedding values directly intowhere
becauseparams
will ensure values are quoted correctly according to your particular backend. For example, quotes will be escaped correctly.Bad:
Entry.objects.extra(where=["headline='Lennon'"])
Good:
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Warning
If you are performing queries on MySQL, note that MySQL’s silent type coercion may cause unexpected results when mixing types. If you query on a string type column, but with an integer value, MySQL will coerce the types of all values in the table to an integer before performing the comparison. For example, if your table contains the values 'abc'
, 'def'
and you query for WHERE mycolumn=0
, both rows will match. To prevent this, perform the correct typecasting before using the value in a query.
Please login to continue.