Index Queries ====================== DynamoDB supports two types of indexes: global secondary indexes, and local secondary indexes. Indexes can make accessing your data more efficient, and should be used when appropriate. See `the documentation for more information `__. Index Settings ^^^^^^^^^^^^^^ The ``Meta`` class is required with at least the ``projection`` class attribute to specify the projection type. For Global secondary indexes, the ``read_capacity_units`` and ``write_capacity_units`` also need to be provided. By default, PynamoDB will use the class attribute name that you provide on the model as the ``index_name`` used when making requests to the DynamoDB API. You can override the default name by providing the ``index_name`` class attribute in the ``Meta`` class of the index. Global Secondary Indexes ^^^^^^^^^^^^^^^^^^^^^^^^ Indexes are defined as classes, just like models. Here is a simple index class: .. code-block:: python from pynamodb.indexes import GlobalSecondaryIndex, AllProjection from pynamodb.attributes import NumberAttribute class ViewIndex(GlobalSecondaryIndex): """ This class represents a global secondary index """ class Meta: # index_name is optional, but can be provided to override the default name index_name = 'foo-index' read_capacity_units = 2 write_capacity_units = 1 # All attributes are projected projection = AllProjection() # This attribute is the hash key for the index # Note that this attribute must also exist # in the model view = NumberAttribute(default=0, hash_key=True) Global indexes require you to specify the read and write capacity, as we have done in this example. Indexes are said to *project* attributes from the main table into the index. As such, there are three styles of projection in DynamoDB, and PynamoDB provides three corresponding projection classes. * :py:class:`AllProjection `: All attributes are projected. * :py:class:`KeysOnlyProjection `: Only the index and primary keys are projected. * :py:class:`IncludeProjection(attributes) `: Only the specified ``attributes`` are projected. We still need to attach the index to the model in order for us to use it. You define it as a class attribute on the model, as in this example: .. code-block:: python from pynamodb.models import Model from pynamodb.attributes import UnicodeAttribute class TestModel(Model): """ A test model that uses a global secondary index """ class Meta: table_name = 'TestModel' forum = UnicodeAttribute(hash_key=True) thread = UnicodeAttribute(range_key=True) view_index = ViewIndex() view = NumberAttribute(default=0) Local Secondary Indexes ^^^^^^^^^^^^^^^^^^^^^^^ Local secondary indexes are defined just like global ones, but they inherit from ``LocalSecondaryIndex`` instead: .. code-block:: python from pynamodb.indexes import LocalSecondaryIndex, AllProjection from pynamodb.attributes import NumberAttribute class ViewIndex(LocalSecondaryIndex): """ This class represents a local secondary index """ class Meta: # All attributes are projected projection = AllProjection() forum = UnicodeAttribute(hash_key=True) view = NumberAttribute(range_key=True) Every local secondary index must meet the following conditions: - The partition key (hash key) is the same as that of its base table. - The sort key (range key) consists of exactly one scalar attribute. The range key can be any attribute. - The sort key (range key) of the base table is projected into the index, where it acts as a non-key attribute. Querying an index ^^^^^^^^^^^^^^^^^^ Index queries use the same syntax as model queries. Continuing our example, we can query the ``view_index`` global secondary index simply by calling ``query``: .. code-block:: python for item in TestModel.view_index.query(1): print("Item queried from index: {0}".format(item)) This example queries items from the table using the global secondary index, called ``view_index``, using a hash key value of 1 for the index. This would return all ``TestModel`` items that have a ``view`` attribute of value 1. Local secondary index queries have a similar syntax. They require a hash key, and can include conditions on the range key of the index. Here is an example that queries the index for values of ``view`` greater than zero: .. code-block:: python for item in TestModel.view_index.query('foo', TestModel.view > 0): print("Item queried from index: {0}".format(item.view)) Pagination and last evaluated key ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The query returns a ``ResultIterator`` object that transparently paginates through results. To stop iterating and allow the caller to continue later on, use the ``last_evaluated_key`` property of the iterator: .. code-block:: python def iterate_over_page(last_evaluated_key = None): results = TestModel.view_index.query('foo', TestModel.view > 0, limit=10, last_evaluated_key=last_evaluated_key) for item in results: ... return results.last_evaluated_key The ``last_evaluated_key`` is effectively the key attributes of the last iterated item; the next returned items will be the items following it. For index queries, the returned ``last_evaluated_key`` will contain both the table's hash/range keys and the indexes hash/range keys. This is due to the fact that DynamoDB indexes have no uniqueness constraint, i.e. the same hash/range pair can map to multiple items. For the example above, the ``last_evaluated_key`` will look like: .. code-block:: python { "forum": {"S": "..."}, "thread": {"S": "..."}, "view": {"N": "..."} }