summaryrefslogtreecommitdiff
path: root/docs/topics
diff options
context:
space:
mode:
Diffstat (limited to 'docs/topics')
-rw-r--r--docs/topics/db/queries.txt230
1 files changed, 230 insertions, 0 deletions
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index c0d0f2b3e3..c0e141ea8d 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -798,6 +798,236 @@ being evaluated and therefore populate the cache::
Simply printing the queryset will not populate the cache. This is because
the call to ``__repr__()`` only returns a slice of the entire queryset.
+.. _querying-jsonfield:
+
+Querying ``JSONField``
+======================
+
+Lookups implementation is different in :class:`~django.db.models.JSONField`,
+mainly due to the existence of key transformations. To demonstrate, we will use
+the following example model::
+
+ from django.db import models
+
+ class Dog(models.Model):
+ name = models.CharField(max_length=200)
+ data = models.JSONField(null=True)
+
+ def __str__(self):
+ return self.name
+
+Storing and querying for ``None``
+---------------------------------
+
+As with other fields, storing ``None`` as the field's value will store it as
+SQL ``NULL``. While not recommended, it is possible to store JSON scalar
+``null`` instead of SQL ``NULL`` by using :class:`Value('null')
+<django.db.models.Value>`.
+
+Whichever of the values is stored, when retrieved from the database, the Python
+representation of the JSON scalar ``null`` is the same as SQL ``NULL``, i.e.
+``None``. Therefore, it can be hard to distinguish between them.
+
+This only applies to ``None`` as the top-level value of the field. If ``None``
+is inside a :py:class:`list` or :py:class:`dict`, it will always be interpreted
+as JSON ``null``.
+
+When querying, ``None`` value will always be interpreted as JSON ``null``. To
+query for SQL ``NULL``, use :lookup:`isnull`::
+
+ >>> Dog.objects.create(name='Max', data=None) # SQL NULL.
+ <Dog: Max>
+ >>> Dog.objects.create(name='Archie', data=Value('null')) # JSON null.
+ <Dog: Archie>
+ >>> Dog.objects.filter(data=None)
+ <QuerySet [<Dog: Archie>]>
+ >>> Dog.objects.filter(data=Value('null'))
+ <QuerySet [<Dog: Archie>]>
+ >>> Dog.objects.filter(data__isnull=True)
+ <QuerySet [<Dog: Max>]>
+ >>> Dog.objects.filter(data__isnull=False)
+ <QuerySet [<Dog: Archie>]>
+
+Unless you are sure you wish to work with SQL ``NULL`` values, consider setting
+``null=False`` and providing a suitable default for empty values, such as
+``default=dict``.
+
+.. note::
+
+ Storing JSON scalar ``null`` does not violate :attr:`null=False
+ <django.db.models.Field.null>`.
+
+.. fieldlookup:: jsonfield.key
+
+Key, index, and path transforms
+-------------------------------
+
+To query based on a given dictionary key, use that key as the lookup name::
+
+ >>> Dog.objects.create(name='Rufus', data={
+ ... 'breed': 'labrador',
+ ... 'owner': {
+ ... 'name': 'Bob',
+ ... 'other_pets': [{
+ ... 'name': 'Fishy',
+ ... }],
+ ... },
+ ... })
+ <Dog: Rufus>
+ >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
+ <Dog: Meg>
+ >>> Dog.objects.filter(data__breed='collie')
+ <QuerySet [<Dog: Meg>]>
+
+Multiple keys can be chained together to form a path lookup::
+
+ >>> Dog.objects.filter(data__owner__name='Bob')
+ <QuerySet [<Dog: Rufus>]>
+
+If the key is an integer, it will be interpreted as an index transform in an
+array::
+
+ >>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
+ <QuerySet [<Dog: Rufus>]>
+
+If the key you wish to query by clashes with the name of another lookup, use
+the :lookup:`contains <jsonfield.contains>` lookup instead.
+
+To query for missing keys, use the ``isnull`` lookup::
+
+ >>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
+ <Dog: Shep>
+ >>> Dog.objects.filter(data__owner__isnull=True)
+ <QuerySet [<Dog: Shep>]>
+
+.. note::
+
+ The lookup examples given above implicitly use the :lookup:`exact` lookup.
+ Key, index, and path transforms can also be chained with:
+ :lookup:`contains`, :lookup:`icontains`, :lookup:`endswith`,
+ :lookup:`iendswith`, :lookup:`iexact`, :lookup:`regex`, :lookup:`iregex`,
+ :lookup:`startswith`, :lookup:`istartswith`, :lookup:`lt`, :lookup:`lte`,
+ :lookup:`gt`, and :lookup:`gte` lookups.
+
+.. warning::
+
+ Since any string could be a key in a JSON object, any lookup other than
+ those listed below will be interpreted as a key lookup. No errors are
+ raised. Be extra careful for typing mistakes, and always check your queries
+ work as you intend.
+
+.. admonition:: MariaDB and Oracle users
+
+ Using :meth:`~django.db.models.query.QuerySet.order_by` on key, index, or
+ path transforms will sort the objects using the string representation of
+ the values. This is because MariaDB and Oracle Database do not provide a
+ function that converts JSON values into their equivalent SQL values.
+
+.. admonition:: Oracle users
+
+ On Oracle Database, using ``None`` as the lookup value in an
+ :meth:`~django.db.models.query.QuerySet.exclude` query will return objects
+ that do not have ``null`` as the value at the given path, including objects
+ that do not have the path. On other database backends, the query will
+ return objects that have the path and the value is not ``null``.
+
+.. admonition:: PostgreSQL users
+
+ On PostgreSQL, if only one key or index is used, the SQL operator ``->`` is
+ used. If multiple operators are used then the ``#>`` operator is used.
+
+Containment and key operations
+------------------------------
+
+.. fieldlookup:: jsonfield.contains
+
+``contains``
+~~~~~~~~~~~~
+
+The :lookup:`contains` lookup is overridden on ``JSONField``. The returned
+objects are those where the given ``dict`` of key-value pairs are all
+contained in the top-level of the field. For example::
+
+ >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
+ <Dog: Rufus>
+ >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ <Dog: Meg>
+ >>> Dog.objects.create(name='Fred', data={})
+ <Dog: Fred>
+ >>> Dog.objects.filter(data__contains={'owner': 'Bob'})
+ <QuerySet [<Dog: Rufus>, <Dog: Meg>]>
+ >>> Dog.objects.filter(data__contains={'breed': 'collie'})
+ <QuerySet [<Dog: Meg>]>
+
+.. fieldlookup:: jsonfield.contained_by
+
+``contained_by``
+~~~~~~~~~~~~~~~~
+
+This is the inverse of the :lookup:`contains <jsonfield.contains>` lookup - the
+objects returned will be those where the key-value pairs on the object are a
+subset of those in the value passed. For example::
+
+ >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
+ <Dog: Rufus>
+ >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ <Dog: Meg>
+ >>> Dog.objects.create(name='Fred', data={})
+ <Dog: Fred>
+ >>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
+ <QuerySet [<Dog: Meg>, <Dog: Fred>]>
+ >>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
+ <QuerySet [<Dog: Fred>]>
+
+.. admonition:: Oracle
+
+ ``contained_by`` is not supported on Oracle.
+
+.. fieldlookup:: jsonfield.has_key
+
+``has_key``
+~~~~~~~~~~~
+
+Returns objects where the given key is in the top-level of the data. For
+example::
+
+ >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+ <Dog: Rufus>
+ >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ <Dog: Meg>
+ >>> Dog.objects.filter(data__has_key='owner')
+ <QuerySet [<Dog: Meg>]>
+
+.. fieldlookup:: jsonfield.has_any_keys
+
+``has_keys``
+~~~~~~~~~~~~
+
+Returns objects where all of the given keys are in the top-level of the data.
+For example::
+
+ >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+ <Dog: Rufus>
+ >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ <Dog: Meg>
+ >>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
+ <QuerySet [<Dog: Meg>]>
+
+.. fieldlookup:: jsonfield.has_keys
+
+``has_any_keys``
+~~~~~~~~~~~~~~~~
+
+Returns objects where any of the given keys are in the top-level of the data.
+For example::
+
+ >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+ <Dog: Rufus>
+ >>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
+ <Dog: Meg>
+ >>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
+ <QuerySet [<Dog: Rufus>, <Dog: Meg>]>
+
.. _complex-lookups-with-q:
Complex lookups with ``Q`` objects