A sort key is the optional second attribute of a DynamoDB primary key. The first attribute is the partition key. Together, their values must uniquely identify an item, but either value can appear in multiple items. DynamoDB keeps sort keys ordered within each partition-key value. section 12.2.2 section 12.7
Tasks for each user
A to-do application stores tasks with a user ID (`uid`) as the partition key and a task ID (`tid`) as the sort key. Several tasks can share a `uid`, so the application can list one user’s tasks with a query. Because `uid` and `tid` must be unique together, using a millisecond timestamp for `tid` limits each user to one task per timestamp. section 12.2.2
$ aws dynamodb create-table --table-name todo-task \ --attribute-definitions AttributeName=uid,AttributeType=S \ #1 AttributeName=tid,AttributeType=N \ --key-schema AttributeName=uid,KeyType=HASH \ AttributeName=tid,KeyType=RANGE \ #2 --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 #1 At least two attributes are needed for a partition key and sort key. #2 The tid attribute is the sort key.
Create the task table with `uid` declared as the `HASH` key and numeric `tid` as the `RANGE` key. Wait for the table to become `ACTIVE` before using it. section 12.2.2
Use `getItem` with both key values to retrieve one task. Use `query` with the partition key to retrieve multiple tasks for a user; add a sort-key condition to narrow the results. A partition key requires an exact match, while sort-key conditions include comparisons and `BETWEEN`. You cannot query by sort key alone. section 12.4.1 section 12.7.2
Listing 12.11 nodetodo: Retrieving tasks (index.js)
if (input['task-ls'] === true) { const yyyymmdd = moment().format('YYYYMMDD'); const params = { KeyConditionExpression: 'uid = :uid', #1 ExpressionAttributeValues: { ':uid': {S: input['<uid>']} #2 }, TableName: 'todo-task', Limit: input['--limit'] }; if (input['--next'] !== null) { params.KeyConditionExpression += ' AND tid > :next'; params.ExpressionAttributeValues[':next'] = {N: input['--next']}; } if (input['--overdue'] === true) { params.FilterExpression = 'due < :yyyymmdd'; #3 params.ExpressionAttributeValues [':yyyymmdd'] = {N: yyyymmdd}; #4 } else if (input['--due'] === true) { params.FilterExpression = 'due = :yyyymmdd'; params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd}; } else if (input['--withoutdue'] === true) { params.FilterExpression = 'attribute_not_exists(due)'; #5 } else if (input['--futuredue'] === true) { params.FilterExpression = 'due > :yyyymmdd'; params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd}; } else if (input['--dueafter'] !== null) { params.FilterExpression = 'due > :yyyymmdd'; params.ExpressionAttributeValues[':yyyymmdd'] = {N: input['--dueafter']}; } else if (input['--duebefore'] !== null) { params.FilterExpression = 'due < :yyyymmdd'; params.ExpressionAttributeValues[':yyyymmdd'] = {N: input['--duebefore']}; } if (input['<category>'] !== null) { if (params.FilterExpression === undefined) { params.FilterExpression = ''; } else { params.FilterExpression += ' AND '; #6 } params.FilterExpression += 'category = :category'; params.ExpressionAttributeValues[':category'] = S: input['<category>']}; } db.query(params, (err, data) => { #7 if (err) { console.error('error', err); } else { console.log('tasks', data.Items.map(mapTaskItem)); if (data.LastEvaluatedKey !== undefined) { console.log('more tasks available with --next=' + data.LastEvaluatedKey.tid.N); } } }); } #1 Primary key query. The task table uses a partition and sort key. Only the partition key is defined in the query, so all tasks belonging to a user are returned. #2 Query attributes must be passed this way. #3 Filtering uses no index; it’s applied over all elements returned from the primary key query. #4 Filter attributes must be passed this way. #5 attribute_not_exists(due) is true when the attribute is missing (opposite of attribute_exists). #6 Multiple filters can be combined with logical operators. #7 Invokes the query operation on DynamoDB
The task-list command queries by `uid` and, when a starting point is supplied, adds `tid > :next` to its key condition. Its due-date checks are filters rather than sort-key conditions. section 12.4.2
Choose the sort key around the queries the application needs. A timestamp sort key, for example, lets a message query find one user’s newer or older messages in timestamp order. Filtering on a non-key attribute is less efficient because the filter inspects items returned by the key condition. section 12.7.2 section 12.4.2
If another access pattern needs different keys, a global secondary index can use its own partition and sort keys. A category-and-task-ID index supports task queries across users, but adds storage and write-capacity costs and is updated asynchronously. section 12.4.3 section 12.4.4
