1. =======================
    
  2. Conditional Expressions
    
  3. =======================
    
  4. 
    
  5. .. currentmodule:: django.db.models.expressions
    
  6. 
    
  7. Conditional expressions let you use :keyword:`if` ... :keyword:`elif` ...
    
  8. :keyword:`else` logic within filters, annotations, aggregations, and updates. A
    
  9. conditional expression evaluates a series of conditions for each row of a
    
  10. table and returns the matching result expression. Conditional expressions can
    
  11. also be combined and nested like other :doc:`expressions <expressions>`.
    
  12. 
    
  13. The conditional expression classes
    
  14. ==================================
    
  15. 
    
  16. We'll be using the following model in the subsequent examples::
    
  17. 
    
  18.     from django.db import models
    
  19. 
    
  20.     class Client(models.Model):
    
  21.         REGULAR = 'R'
    
  22.         GOLD = 'G'
    
  23.         PLATINUM = 'P'
    
  24.         ACCOUNT_TYPE_CHOICES = [
    
  25.             (REGULAR, 'Regular'),
    
  26.             (GOLD, 'Gold'),
    
  27.             (PLATINUM, 'Platinum'),
    
  28.         ]
    
  29.         name = models.CharField(max_length=50)
    
  30.         registered_on = models.DateField()
    
  31.         account_type = models.CharField(
    
  32.             max_length=1,
    
  33.             choices=ACCOUNT_TYPE_CHOICES,
    
  34.             default=REGULAR,
    
  35.         )
    
  36. 
    
  37. ``When``
    
  38. --------
    
  39. 
    
  40. .. class:: When(condition=None, then=None, **lookups)
    
  41. 
    
  42. A ``When()`` object is used to encapsulate a condition and its result for use
    
  43. in the conditional expression. Using a ``When()`` object is similar to using
    
  44. the :meth:`~django.db.models.query.QuerySet.filter` method. The condition can
    
  45. be specified using :ref:`field lookups <field-lookups>`,
    
  46. :class:`~django.db.models.Q` objects, or :class:`~django.db.models.Expression`
    
  47. objects that have an ``output_field`` that is a
    
  48. :class:`~django.db.models.BooleanField`. The result is provided using the
    
  49. ``then`` keyword.
    
  50. 
    
  51. .. versionchanged:: 4.0
    
  52. 
    
  53.     Support for lookup expressions was added.
    
  54. 
    
  55. Some examples::
    
  56. 
    
  57.     >>> from django.db.models import F, Q, When
    
  58.     >>> # String arguments refer to fields; the following two examples are equivalent:
    
  59.     >>> When(account_type=Client.GOLD, then='name')
    
  60.     >>> When(account_type=Client.GOLD, then=F('name'))
    
  61.     >>> # You can use field lookups in the condition
    
  62.     >>> from datetime import date
    
  63.     >>> When(registered_on__gt=date(2014, 1, 1),
    
  64.     ...      registered_on__lt=date(2015, 1, 1),
    
  65.     ...      then='account_type')
    
  66.     >>> # Complex conditions can be created using Q objects
    
  67.     >>> When(Q(name__startswith="John") | Q(name__startswith="Paul"),
    
  68.     ...      then='name')
    
  69.     >>> # Condition can be created using boolean expressions.
    
  70.     >>> from django.db.models import Exists, OuterRef
    
  71.     >>> non_unique_account_type = Client.objects.filter(
    
  72.     ...     account_type=OuterRef('account_type'),
    
  73.     ... ).exclude(pk=OuterRef('pk')).values('pk')
    
  74.     >>> When(Exists(non_unique_account_type), then=Value('non unique'))
    
  75.     >>> # Condition can be created using lookup expressions.
    
  76.     >>> from django.db.models.lookups import GreaterThan, LessThan
    
  77.     >>> When(
    
  78.     ...     GreaterThan(F('registered_on'), date(2014, 1, 1)) &
    
  79.     ...     LessThan(F('registered_on'), date(2015, 1, 1)),
    
  80.     ...     then='account_type',
    
  81.     ... )
    
  82. 
    
  83. Keep in mind that each of these values can be an expression.
    
  84. 
    
  85. .. note::
    
  86. 
    
  87.     Since the ``then`` keyword argument is reserved for the result of the
    
  88.     ``When()``, there is a potential conflict if a
    
  89.     :class:`~django.db.models.Model` has a field named ``then``. This can be
    
  90.     resolved in two ways::
    
  91. 
    
  92.         >>> When(then__exact=0, then=1)
    
  93.         >>> When(Q(then=0), then=1)
    
  94. 
    
  95. ``Case``
    
  96. --------
    
  97. 
    
  98. .. class:: Case(*cases, **extra)
    
  99. 
    
  100. A ``Case()`` expression is like the :keyword:`if` ... :keyword:`elif` ...
    
  101. :keyword:`else` statement in ``Python``. Each ``condition`` in the provided
    
  102. ``When()`` objects is evaluated in order, until one evaluates to a
    
  103. truthful value. The ``result`` expression from the matching ``When()`` object
    
  104. is returned.
    
  105. 
    
  106. An example::
    
  107. 
    
  108.     >>>
    
  109.     >>> from datetime import date, timedelta
    
  110.     >>> from django.db.models import Case, Value, When
    
  111.     >>> Client.objects.create(
    
  112.     ...     name='Jane Doe',
    
  113.     ...     account_type=Client.REGULAR,
    
  114.     ...     registered_on=date.today() - timedelta(days=36))
    
  115.     >>> Client.objects.create(
    
  116.     ...     name='James Smith',
    
  117.     ...     account_type=Client.GOLD,
    
  118.     ...     registered_on=date.today() - timedelta(days=5))
    
  119.     >>> Client.objects.create(
    
  120.     ...     name='Jack Black',
    
  121.     ...     account_type=Client.PLATINUM,
    
  122.     ...     registered_on=date.today() - timedelta(days=10 * 365))
    
  123.     >>> # Get the discount for each Client based on the account type
    
  124.     >>> Client.objects.annotate(
    
  125.     ...     discount=Case(
    
  126.     ...         When(account_type=Client.GOLD, then=Value('5%')),
    
  127.     ...         When(account_type=Client.PLATINUM, then=Value('10%')),
    
  128.     ...         default=Value('0%'),
    
  129.     ...     ),
    
  130.     ... ).values_list('name', 'discount')
    
  131.     <QuerySet [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]>
    
  132. 
    
  133. ``Case()`` accepts any number of ``When()`` objects as individual arguments.
    
  134. Other options are provided using keyword arguments. If none of the conditions
    
  135. evaluate to ``TRUE``, then the expression given with the ``default`` keyword
    
  136. argument is returned. If a ``default`` argument isn't provided, ``None`` is
    
  137. used.
    
  138. 
    
  139. If we wanted to change our previous query to get the discount based on how long
    
  140. the ``Client`` has been with us, we could do so using lookups::
    
  141. 
    
  142.     >>> a_month_ago = date.today() - timedelta(days=30)
    
  143.     >>> a_year_ago = date.today() - timedelta(days=365)
    
  144.     >>> # Get the discount for each Client based on the registration date
    
  145.     >>> Client.objects.annotate(
    
  146.     ...     discount=Case(
    
  147.     ...         When(registered_on__lte=a_year_ago, then=Value('10%')),
    
  148.     ...         When(registered_on__lte=a_month_ago, then=Value('5%')),
    
  149.     ...         default=Value('0%'),
    
  150.     ...     )
    
  151.     ... ).values_list('name', 'discount')
    
  152.     <QuerySet [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]>
    
  153. 
    
  154. .. note::
    
  155. 
    
  156.     Remember that the conditions are evaluated in order, so in the above
    
  157.     example we get the correct result even though the second condition matches
    
  158.     both Jane Doe and Jack Black. This works just like an :keyword:`if` ...
    
  159.     :keyword:`elif` ... :keyword:`else` statement in ``Python``.
    
  160. 
    
  161. ``Case()`` also works in a ``filter()`` clause. For example, to find gold
    
  162. clients that registered more than a month ago and platinum clients that
    
  163. registered more than a year ago::
    
  164. 
    
  165.     >>> a_month_ago = date.today() - timedelta(days=30)
    
  166.     >>> a_year_ago = date.today() - timedelta(days=365)
    
  167.     >>> Client.objects.filter(
    
  168.     ...     registered_on__lte=Case(
    
  169.     ...         When(account_type=Client.GOLD, then=a_month_ago),
    
  170.     ...         When(account_type=Client.PLATINUM, then=a_year_ago),
    
  171.     ...     ),
    
  172.     ... ).values_list('name', 'account_type')
    
  173.     <QuerySet [('Jack Black', 'P')]>
    
  174. 
    
  175. Advanced queries
    
  176. ================
    
  177. 
    
  178. Conditional expressions can be used in annotations, aggregations, filters,
    
  179. lookups, and updates. They can also be combined and nested with other
    
  180. expressions. This allows you to make powerful conditional queries.
    
  181. 
    
  182. Conditional update
    
  183. ------------------
    
  184. 
    
  185. Let's say we want to change the ``account_type`` for our clients to match
    
  186. their registration dates. We can do this using a conditional expression and the
    
  187. :meth:`~django.db.models.query.QuerySet.update` method::
    
  188. 
    
  189.     >>> a_month_ago = date.today() - timedelta(days=30)
    
  190.     >>> a_year_ago = date.today() - timedelta(days=365)
    
  191.     >>> # Update the account_type for each Client from the registration date
    
  192.     >>> Client.objects.update(
    
  193.     ...     account_type=Case(
    
  194.     ...         When(registered_on__lte=a_year_ago,
    
  195.     ...              then=Value(Client.PLATINUM)),
    
  196.     ...         When(registered_on__lte=a_month_ago,
    
  197.     ...              then=Value(Client.GOLD)),
    
  198.     ...         default=Value(Client.REGULAR)
    
  199.     ...     ),
    
  200.     ... )
    
  201.     >>> Client.objects.values_list('name', 'account_type')
    
  202.     <QuerySet [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]>
    
  203. 
    
  204. .. _conditional-aggregation:
    
  205. 
    
  206. Conditional aggregation
    
  207. -----------------------
    
  208. 
    
  209. What if we want to find out how many clients there are for each
    
  210. ``account_type``? We can use the ``filter`` argument of :ref:`aggregate
    
  211. functions <aggregation-functions>` to achieve this::
    
  212. 
    
  213.     >>> # Create some more Clients first so we can have something to count
    
  214.     >>> Client.objects.create(
    
  215.     ...     name='Jean Grey',
    
  216.     ...     account_type=Client.REGULAR,
    
  217.     ...     registered_on=date.today())
    
  218.     >>> Client.objects.create(
    
  219.     ...     name='James Bond',
    
  220.     ...     account_type=Client.PLATINUM,
    
  221.     ...     registered_on=date.today())
    
  222.     >>> Client.objects.create(
    
  223.     ...     name='Jane Porter',
    
  224.     ...     account_type=Client.PLATINUM,
    
  225.     ...     registered_on=date.today())
    
  226.     >>> # Get counts for each value of account_type
    
  227.     >>> from django.db.models import Count
    
  228.     >>> Client.objects.aggregate(
    
  229.     ...     regular=Count('pk', filter=Q(account_type=Client.REGULAR)),
    
  230.     ...     gold=Count('pk', filter=Q(account_type=Client.GOLD)),
    
  231.     ...     platinum=Count('pk', filter=Q(account_type=Client.PLATINUM)),
    
  232.     ... )
    
  233.     {'regular': 2, 'gold': 1, 'platinum': 3}
    
  234. 
    
  235. This aggregate produces a query with the SQL 2003 ``FILTER WHERE`` syntax
    
  236. on databases that support it:
    
  237. 
    
  238. .. code-block:: sql
    
  239. 
    
  240.     SELECT count('id') FILTER (WHERE account_type=1) as regular,
    
  241.            count('id') FILTER (WHERE account_type=2) as gold,
    
  242.            count('id') FILTER (WHERE account_type=3) as platinum
    
  243.     FROM clients;
    
  244. 
    
  245. On other databases, this is emulated using a ``CASE`` statement:
    
  246. 
    
  247. .. code-block:: sql
    
  248. 
    
  249.     SELECT count(CASE WHEN account_type=1 THEN id ELSE null) as regular,
    
  250.            count(CASE WHEN account_type=2 THEN id ELSE null) as gold,
    
  251.            count(CASE WHEN account_type=3 THEN id ELSE null) as platinum
    
  252.     FROM clients;
    
  253. 
    
  254. The two SQL statements are functionally equivalent but the more explicit
    
  255. ``FILTER`` may perform better.
    
  256. 
    
  257. Conditional filter
    
  258. ------------------
    
  259. 
    
  260. When a conditional expression returns a boolean value, it is possible to use it
    
  261. directly in filters. This means that it will not be added to the ``SELECT``
    
  262. columns, but you can still use it to filter results::
    
  263. 
    
  264.     >>> non_unique_account_type = Client.objects.filter(
    
  265.     ...     account_type=OuterRef('account_type'),
    
  266.     ... ).exclude(pk=OuterRef('pk')).values('pk')
    
  267.     >>> Client.objects.filter(~Exists(non_unique_account_type))
    
  268. 
    
  269. In SQL terms, that evaluates to:
    
  270. 
    
  271. .. code-block:: sql
    
  272. 
    
  273.     SELECT ...
    
  274.     FROM client c0
    
  275.     WHERE NOT EXISTS (
    
  276.       SELECT c1.id
    
  277.       FROM client c1
    
  278.       WHERE c1.account_type = c0.account_type AND NOT c1.id = c0.id
    
  279.     )