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:
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.
AllProjection
: All attributes are projected.KeysOnlyProjection
: Only the index and primary keys are projected.IncludeProjection(attributes)
: Only the specifiedattributes
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:
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:
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
:
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:
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:
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:
{
"forum": {"S": "..."},
"thread": {"S": "..."},
"view": {"N": "..."}
}