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.
-
selectThe
selectargument lets you put extra fields in theSELECTclause. 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
Entryobject will have an extra attribute,is_recent, a boolean representing whether the entry’spub_dateis greater than Jan. 1, 2006.Django inserts the given SQL snippet directly into the
SELECTstatement, 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
Blogobject anentry_countattribute, an integer count of associatedEntryobjects: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_blogtable in itsFROMclause.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
selectclauses. 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_paramsparameter. Sinceselect_paramsis a sequence and theselectattribute 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.OrderedDictfor theselectvalue, 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
%sinside your select string, use the sequence%%s. -
where/tablesYou can define explicit SQL
WHEREclauses — perhaps to perform non-explicit joins — by usingwhere. You can manually add tables to the SQLFROMclause by usingtables.whereandtablesboth take a list of strings. Allwhereparameters 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
tablesparameter if you’re specifying tables that are already used in the query. When you add extra tables via thetablesparameter, 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 extrawhereparameter 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 yourwhereaddition 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_byIf you need to order the resulting queryset using some of the new fields or tables you have included via
extra()use theorder_byparameter 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_nameor an alias for a column that you specified in theselectparameter 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_recentis true to the front of the result set (Truesorts beforeFalsein 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). -
paramsThe
whereparameter described above may use standard Python database string placeholders —'%s'to indicate parameters the database engine should automatically quote. Theparamsargument is a list of any extra parameters to be substituted.Example:
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Always use
paramsinstead of embedding values directly intowherebecauseparamswill 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.