DynamoDB Query Examples in CLI, Python and Node

Working Query examples for the AWS CLI, Boto3 and the Node SDK: sort key conditions, GSI queries, pagination, projections and fixes for empty result sets.

Orlando Adeyemi 12 min read

DynamoDB’s Query operation is one of the most powerful and cost-effective ways to retrieve data from your tables. Unlike the Scan operation, which reads every item in a table, Query allows you to efficiently retrieve items by primary key values, enabling fast and economical access to your data.

This guide provides a comprehensive collection of practical DynamoDB Query examples across multiple interfaces (AWS CLI, Python Boto3, and Node.js), covering both basic usage and advanced techniques to help you master this essential operation.

When to Use Query vs Scan

Before diving into examples, let’s clarify when to use Query:

  • Use Query when: You need to find items based on primary key values (partition key and optional sort key conditions)
  • Use Scan when: You need to search across all items without knowing their key values (though this should be avoided for large tables)

Query operations are typically more efficient because:

  1. They consume fewer read capacity units (RCUs)
  2. They return results faster, especially in large tables
  3. They reduce your DynamoDB costs

Note: A well-designed DynamoDB table should support your common access patterns through Query operations rather than Scans. If you find yourself needing frequent Scans, consider reviewing your table design.

Basic Query Syntax

At its core, a Query operation requires:

  1. The table name
  2. A partition key value (equal condition only)
  3. Optional sort key conditions (equal, less than, greater than, begins with, etc.)

Let’s explore how to implement queries across different interfaces.

AWS CLI Query Examples

The AWS Command Line Interface (CLI) provides a straightforward way to query DynamoDB tables.

Basic Query by Partition Key

Assume we have a table named Orders with a partition key CustomerID:

Shell
aws dynamodb query \    --table-name Orders \    --key-condition-expression "CustomerID = :customerId" \    --expression-attribute-values '{":customerId": {"S": "CUSTOMER123"}}'

This query retrieves all orders for customer “CUSTOMER123”.

Query with Sort Key Condition

If our table has a composite key (partition key + sort key), we can add conditions on the sort key:

Shell
aws dynamodb query \    --table-name Orders \    --key-condition-expression "CustomerID = :customerId AND OrderDate > :date" \    --expression-attribute-values '{        ":customerId": {"S": "CUSTOMER123"},        ":date": {"S": "2025-01-01"}    }'

This retrieves all orders for CUSTOMER123 placed after January 1, 2025.

Using Different Sort Key Comparisons

DynamoDB supports various comparison operators for sort keys:

Shell
# "begins_with" example - find orders with tracking numbers starting with "TX"aws dynamodb query \    --table-name Orders \    --key-condition-expression "CustomerID = :customerId AND begins_with(TrackingNumber, :prefix)" \    --expression-attribute-values '{        ":customerId": {"S": "CUSTOMER123"},        ":prefix": {"S": "TX"}    }'# "between" example - find orders in a date rangeaws dynamodb query \    --table-name Orders \    --key-condition-expression "CustomerID = :customerId AND OrderDate BETWEEN :start AND :end" \    --expression-attribute-values '{        ":customerId": {"S": "CUSTOMER123"},        ":start": {"S": "2025-01-01"},        ":end": {"S": "2025-03-31"}    }'

Python Boto3 Query Examples

Python’s Boto3 library provides a more programmatic way to interact with DynamoDB.

Basic Query

Python
import boto3from boto3.dynamodb.conditions import Key# Initialize DynamoDB resourcedynamodb = boto3.resource('dynamodb')table = dynamodb.Table('Orders')# Query for a specific customerresponse = table.query(    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123'))# Print the items returnedfor item in response['Items']:    print(item)

Query with Sort Key Conditions

Python
from boto3.dynamodb.conditions import Key, Attr# Query with a date range on the sort keyresponse = table.query(    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123') &                         Key('OrderDate').between('2025-01-01', '2025-03-31'))# Query with begins_with on sort keyresponse = table.query(    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123') &                         Key('OrderDate').begins_with('2025-03'))

Filtering Query Results

While Query operations filter by key conditions, you can further refine results with a FilterExpression:

Python
# Query for a customer's orders, but only return completed onesresponse = table.query(    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123'),    FilterExpression=Attr('OrderStatus').eq('COMPLETED'))

Important: FilterExpression is applied after DynamoDB retrieves items matching the KeyConditionExpression. This means you’re still charged for reading all items that match the key conditions, even if they’re filtered out afterward. For this reason, it’s best to design your data model to minimize the need for filtering.

Node.js Query Examples

For JavaScript developers, the AWS SDK for JavaScript provides similar capabilities.

Basic Query

JavaScript
const AWS = require('aws-sdk');// Configure AWS SDKAWS.config.update({ region: 'us-east-1' });const docClient = new AWS.DynamoDB.DocumentClient();const params = {  TableName: 'Orders',  KeyConditionExpression: 'CustomerID = :customerId',  ExpressionAttributeValues: {    ':customerId': 'CUSTOMER123'  }};docClient.query(params, (err, data) => {  if (err) {    console.error('Error:', err);  } else {    console.log('Query results:', data.Items);  }});

Using Promises and Async/Await

A more modern approach using async/await:

JavaScript
async function queryCustomerOrders(customerId, startDate, endDate) {  const params = {    TableName: 'Orders',    KeyConditionExpression: 'CustomerID = :customerId AND OrderDate BETWEEN :start AND :end',    ExpressionAttributeValues: {      ':customerId': customerId,      ':start': startDate,      ':end': endDate    }  };  try {    const data = await docClient.query(params).promise();    return data.Items;  } catch (err) {    console.error('Error querying orders:', err);    throw err;  }}// UsagequeryCustomerOrders('CUSTOMER123', '2025-01-01', '2025-03-31')  .then(orders => console.log('Orders:', orders))  .catch(err => console.error('Failed:', err));

Querying Secondary Indexes

One of DynamoDB’s most powerful features is the ability to create secondary indexes that enable efficient queries on non-key attributes.

Querying a Global Secondary Index (GSI)

Assume we have a GSI named ProductIndex with partition key ProductID:

AWS CLI

Shell
aws dynamodb query \    --table-name Orders \    --index-name ProductIndex \    --key-condition-expression "ProductID = :productId" \    --expression-attribute-values '{":productId": {"S": "PRODUCT456"}}'

Python

Python
response = table.query(    IndexName='ProductIndex',    KeyConditionExpression=Key('ProductID').eq('PRODUCT456'))

Node.js

JavaScript
const params = {  TableName: 'Orders',  IndexName: 'ProductIndex',  KeyConditionExpression: 'ProductID = :productId',  ExpressionAttributeValues: {    ':productId': 'PRODUCT456'  }};docClient.query(params, (err, data) => {  if (err) console.error(err);  else console.log(data.Items);});

Handling Pagination

DynamoDB limits the response size of a single Query operation to 1MB. For queries that return more data, you’ll need to handle pagination.

Using LastEvaluatedKey for Pagination

Python

Python
import boto3from boto3.dynamodb.conditions import Keydynamodb = boto3.resource('dynamodb')table = dynamodb.Table('Orders')def query_all_customer_orders(customer_id):    items = []    last_key = None    while True:        if last_key:            response = table.query(                KeyConditionExpression=Key('CustomerID').eq(customer_id),                ExclusiveStartKey=last_key            )        else:            response = table.query(                KeyConditionExpression=Key('CustomerID').eq(customer_id)            )        items.extend(response['Items'])        last_key = response.get('LastEvaluatedKey')        if not last_key:            break    return items# Get all orders for a customer, handling pagination automaticallyall_orders = query_all_customer_orders('CUSTOMER123')print(f"Retrieved {len(all_orders)} orders")

Node.js

JavaScript
async function queryAllItems(params) {  let items = [];  let lastEvaluatedKey = undefined;  do {    // Add the ExclusiveStartKey if we have a LastEvaluatedKey from a previous query    if (lastEvaluatedKey) {      params.ExclusiveStartKey = lastEvaluatedKey;    }    const response = await docClient.query(params).promise();    // Add the items from this page to our collection    items = items.concat(response.Items);    // Get the LastEvaluatedKey for the next query, if any    lastEvaluatedKey = response.LastEvaluatedKey;  } while (lastEvaluatedKey);  return items;}// Usage exampleconst params = {  TableName: 'Orders',  KeyConditionExpression: 'CustomerID = :customerId',  ExpressionAttributeValues: {    ':customerId': 'CUSTOMER123'  }};queryAllItems(params)  .then(allItems => console.log(`Retrieved ${allItems.length} items`))  .catch(err => console.error('Query failed:', err));

Limiting Results and Projecting Attributes

You can optimize your queries by requesting only the attributes you need and limiting the number of results.

AWS CLI with Projection and Limit

Shell
aws dynamodb query \    --table-name Orders \    --key-condition-expression "CustomerID = :customerId" \    --expression-attribute-values '{":customerId": {"S": "CUSTOMER123"}}' \    --projection-expression "OrderID, OrderDate, TotalAmount" \    --limit 10

Python with Projection and Limit

Python
response = table.query(    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123'),    ProjectionExpression='OrderID, OrderDate, TotalAmount',    Limit=10)

Node.js with Projection and Limit

JavaScript
const params = {  TableName: 'Orders',  KeyConditionExpression: 'CustomerID = :customerId',  ExpressionAttributeValues: {    ':customerId': 'CUSTOMER123'  },  ProjectionExpression: 'OrderID, OrderDate, TotalAmount',  Limit: 10};docClient.query(params, (err, data) => {  if (err) console.error(err);  else console.log(data.Items);});

Query With Consistent Reads

By default, DynamoDB Query operations use eventually consistent reads. For applications requiring the most up-to-date data, you can use strongly consistent reads at the cost of additional read capacity consumption.

AWS CLI

Shell
aws dynamodb query \    --table-name Orders \    --key-condition-expression "CustomerID = :customerId" \    --expression-attribute-values '{":customerId": {"S": "CUSTOMER123"}}' \    --consistent-read

Python

Python
response = table.query(    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123'),    ConsistentRead=True)

Node.js

JavaScript
const params = {  TableName: 'Orders',  KeyConditionExpression: 'CustomerID = :customerId',  ExpressionAttributeValues: {    ':customerId': 'CUSTOMER123'  },  ConsistentRead: true};docClient.query(params, (err, data) => {  if (err) console.error(err);  else console.log(data.Items);});

Query Best Practices

To get the most out of DynamoDB Query operations:

  1. Design for your access patterns: Structure your table and indexes to support the queries your application needs
  2. Minimize attribute projections: Only request the attributes you need to shrink the response and network transfer (read capacity is still charged on the full item size)
  3. Use sparse indexes: If you’re frequently querying for a subset of items, consider a sparse GSI where only relevant items have the indexed attribute
  4. Monitor consumption: Keep an eye on consumed capacity using the ReturnConsumedCapacity parameter
  5. Prefer Query over Scan: Whenever possible, design your data model to use Query instead of Scan operations
  6. Be careful with FilterExpressions: Remember that filtering happens after items are read, so you’re still charged for the read capacity

Common Query Issues and Solutions

Issue: Query returns no items

  • Verify the partition key value is correct
  • Check if you’re querying the correct table or index
  • Ensure your sort key condition isn’t overly restrictive
  • Verify the attribute names in your expressions match your table schema

Issue: Query is too slow

  • Consider adding a Global Secondary Index to better support your query pattern
  • Reduce the amount of data retrieved by using projections
  • Ensure you’re not using a FilterExpression when you could use a better index

Issue: Query is expensive (high consumed capacity)

  • Check if you’re using a strongly consistent read when eventual consistency would suffice
  • Keep items small: read capacity is charged on the full size of each item read, even if you request only some attributes
  • Consider using a GSI with specific projected attributes

Issue: 1MB result limit is being hit

  • Implement pagination using the LastEvaluatedKey mechanism
  • Consider more selective key conditions

Conclusion

DynamoDB’s Query operation provides a powerful way to efficiently retrieve data from your tables. By following the examples and best practices in this guide, you can optimize your queries for performance, cost, and scalability.

Remember that effective DynamoDB queries start with proper table design. Always consider your access patterns when designing your schema and indexes to ensure you can retrieve data efficiently.

For more complex DynamoDB operations, consider using Dynomate, which offers an intuitive interface for constructing, testing, and optimizing your queries without having to write code. Dynomate makes it easier to visualize your data, understand query performance, and refine your DynamoDB implementation for maximum efficiency.

Need to learn more about DynamoDB? Check out our other guides:

All posts

DynamoDB PutItem vs UpdateItem: Which to Use

PutItem replaces the entire item. UpdateItem changes only the attributes you name. When each is correct, and the silent data loss PutItem can cause.

Reading time: 10 min

DynamoDB Free Tier: What You Get and What You Pay

25GB of storage plus 25 read and 25 write capacity units, free every month. The exact limits, the charges that catch people out, and how to stay inside.

Reading time: 10 min

DynamoDB vs Azure Cosmos DB: Which to Choose

Cosmos DB sells multi-model APIs and tunable consistency. DynamoDB sells simplicity and AWS-native scale. Compared on pricing, APIs and latency.

Reading time: 12 min