conditional-expressions.txt 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. =======================
  2. Conditional Expressions
  3. =======================
  4. .. currentmodule:: django.db.models.expressions
  5. .. versionadded:: 1.8
  6. Conditional expressions let you use :keyword:`if` ... :keyword:`elif` ...
  7. :keyword:`else` logic within filters, annotations, aggregations, and updates. A
  8. conditional expression evaluates a series of conditions for each row of a
  9. table and returns the matching result expression. Conditional expressions can
  10. also be combined and nested like other :doc:`expressions <expressions>`.
  11. The conditional expression classes
  12. ==================================
  13. We'll be using the following model in the subsequent examples::
  14. from django.db import models
  15. class Client(models.Model):
  16. REGULAR = 'R'
  17. GOLD = 'G'
  18. PLATINUM = 'P'
  19. ACCOUNT_TYPE_CHOICES = (
  20. (REGULAR, 'Regular'),
  21. (GOLD, 'Gold'),
  22. (PLATINUM, 'Platinum'),
  23. )
  24. name = models.CharField(max_length=50)
  25. registered_on = models.DateField()
  26. account_type = models.CharField(
  27. max_length=1,
  28. choices=ACCOUNT_TYPE_CHOICES,
  29. default=REGULAR,
  30. )
  31. When
  32. ----
  33. .. class:: When(condition=None, then=None, **lookups)
  34. A ``When()`` object is used to encapsulate a condition and its result for use
  35. in the conditional expression. Using a ``When()`` object is similar to using
  36. the :meth:`~django.db.models.query.QuerySet.filter` method. The condition can
  37. be specified using :ref:`field lookups <field-lookups>` or
  38. :class:`~django.db.models.Q` objects. The result is provided using the ``then``
  39. keyword.
  40. Some examples::
  41. >>> from django.db.models import When, F, Q
  42. >>> # String arguments refer to fields; the following two examples are equivalent:
  43. >>> When(account_type=Client.GOLD, then='name')
  44. >>> When(account_type=Client.GOLD, then=F('name'))
  45. >>> # You can use field lookups in the condition
  46. >>> from datetime import date
  47. >>> When(registered_on__gt=date(2014, 1, 1),
  48. ... registered_on__lt=date(2015, 1, 1),
  49. ... then='account_type')
  50. >>> # Complex conditions can be created using Q objects
  51. >>> When(Q(name__startswith="John") | Q(name__startswith="Paul"),
  52. ... then='name')
  53. Keep in mind that each of these values can be an expression.
  54. .. note::
  55. Since the ``then`` keyword argument is reserved for the result of the
  56. ``When()``, there is a potential conflict if a
  57. :class:`~django.db.models.Model` has a field named ``then``. This can be
  58. resolved in two ways::
  59. >>> from django.db.models import Value
  60. >>> When(then__exact=0, then=1)
  61. >>> When(Q(then=0), then=1)
  62. Case
  63. ----
  64. .. class:: Case(*cases, **extra)
  65. A ``Case()`` expression is like the :keyword:`if` ... :keyword:`elif` ...
  66. :keyword:`else` statement in ``Python``. Each ``condition`` in the provided
  67. ``When()`` objects is evaluated in order, until one evaluates to a
  68. truthful value. The ``result`` expression from the matching ``When()`` object
  69. is returned.
  70. A simple example::
  71. >>>
  72. >>> from datetime import date, timedelta
  73. >>> from django.db.models import CharField, Case, Value, When
  74. >>> Client.objects.create(
  75. ... name='Jane Doe',
  76. ... account_type=Client.REGULAR,
  77. ... registered_on=date.today() - timedelta(days=36))
  78. >>> Client.objects.create(
  79. ... name='James Smith',
  80. ... account_type=Client.GOLD,
  81. ... registered_on=date.today() - timedelta(days=5))
  82. >>> Client.objects.create(
  83. ... name='Jack Black',
  84. ... account_type=Client.PLATINUM,
  85. ... registered_on=date.today() - timedelta(days=10 * 365))
  86. >>> # Get the discount for each Client based on the account type
  87. >>> Client.objects.annotate(
  88. ... discount=Case(
  89. ... When(account_type=Client.GOLD, then=Value('5%')),
  90. ... When(account_type=Client.PLATINUM, then=Value('10%')),
  91. ... default=Value('0%'),
  92. ... output_field=CharField(),
  93. ... ),
  94. ... ).values_list('name', 'discount')
  95. [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]
  96. ``Case()`` accepts any number of ``When()`` objects as individual arguments.
  97. Other options are provided using keyword arguments. If none of the conditions
  98. evaluate to ``TRUE``, then the expression given with the ``default`` keyword
  99. argument is returned. If no ``default`` argument is provided, ``Value(None)``
  100. is used.
  101. If we wanted to change our previous query to get the discount based on how long
  102. the ``Client`` has been with us, we could do so using lookups::
  103. >>> a_month_ago = date.today() - timedelta(days=30)
  104. >>> a_year_ago = date.today() - timedelta(days=365)
  105. >>> # Get the discount for each Client based on the registration date
  106. >>> Client.objects.annotate(
  107. ... discount=Case(
  108. ... When(registered_on__lte=a_year_ago, then=Value('10%')),
  109. ... When(registered_on__lte=a_month_ago, then=Value('5%')),
  110. ... default=Value('0%'),
  111. ... output_field=CharField(),
  112. ... )
  113. ... ).values_list('name', 'discount')
  114. [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]
  115. .. note::
  116. Remember that the conditions are evaluated in order, so in the above
  117. example we get the correct result even though the second condition matches
  118. both Jane Doe and Jack Black. This works just like an :keyword:`if` ...
  119. :keyword:`elif` ... :keyword:`else` statement in ``Python``.
  120. Advanced queries
  121. ================
  122. Conditional expressions can be used in annotations, aggregations, lookups, and
  123. updates. They can also be combined and nested with other expressions. This
  124. allows you to make powerful conditional queries.
  125. Conditional update
  126. ------------------
  127. Let's say we want to change the ``account_type`` for our clients to match
  128. their registration dates. We can do this using a conditional expression and the
  129. :meth:`~django.db.models.query.QuerySet.update` method::
  130. >>> a_month_ago = date.today() - timedelta(days=30)
  131. >>> a_year_ago = date.today() - timedelta(days=365)
  132. >>> # Update the account_type for each Client from the registration date
  133. >>> Client.objects.update(
  134. ... account_type=Case(
  135. ... When(registered_on__lte=a_year_ago,
  136. ... then=Value(Client.PLATINUM)),
  137. ... When(registered_on__lte=a_month_ago,
  138. ... then=Value(Client.GOLD)),
  139. ... default=Value(Client.REGULAR)
  140. ... ),
  141. ... )
  142. >>> Client.objects.values_list('name', 'account_type')
  143. [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]
  144. Conditional aggregation
  145. -----------------------
  146. What if we want to find out how many clients there are for each
  147. ``account_type``? We can nest conditional expression within
  148. :ref:`aggregate functions <aggregation-functions>` to achieve this::
  149. >>> # Create some more Clients first so we can have something to count
  150. >>> Client.objects.create(
  151. ... name='Jean Grey',
  152. ... account_type=Client.REGULAR,
  153. ... registered_on=date.today())
  154. >>> Client.objects.create(
  155. ... name='James Bond',
  156. ... account_type=Client.PLATINUM,
  157. ... registered_on=date.today())
  158. >>> Client.objects.create(
  159. ... name='Jane Porter',
  160. ... account_type=Client.PLATINUM,
  161. ... registered_on=date.today())
  162. >>> # Get counts for each value of account_type
  163. >>> from django.db.models import IntegerField, Sum
  164. >>> Client.objects.aggregate(
  165. ... regular=Sum(
  166. ... Case(When(account_type=Client.REGULAR, then=1),
  167. ... output_field=IntegerField())
  168. ... ),
  169. ... gold=Sum(
  170. ... Case(When(account_type=Client.GOLD, then=1),
  171. ... output_field=IntegerField())
  172. ... ),
  173. ... platinum=Sum(
  174. ... Case(When(account_type=Client.PLATINUM, then=1),
  175. ... output_field=IntegerField())
  176. ... )
  177. ... )
  178. {'regular': 2, 'gold': 1, 'platinum': 3}