Home Blog Best Practices DynamoDB Query Examples: A Comprehensive Guide for Developers in 2025
Best Practices

DynamoDB Query Examples: A Comprehensive Guide for Developers in 2025

March 9, 2025 By Alex Mitchell 12 min read
DynamoDB Query Examples: A Comprehensive Guide for Developers in 2025

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.

Dynomate: Modern DynamoDB GUI Client

Built for real developer workflows with AWS profile integration, multi-session support, and team collaboration.

AWS SSO support & multi-region browsing
Script-like operations with data chaining
Git-friendly local storage for team sharing

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:

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:

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:

# "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 range
aws 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

import boto3
from boto3.dynamodb.conditions import Key

# Initialize DynamoDB resource
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Orders')

# Query for a specific customer
response = table.query(
    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123')
)

# Print the items returned
for item in response['Items']:
    print(item)

Query with Sort Key Conditions

from boto3.dynamodb.conditions import Key, Attr

# Query with a date range on the sort key
response = table.query(
    KeyConditionExpression=Key('CustomerID').eq('CUSTOMER123') &
                         Key('OrderDate').between('2025-01-01', '2025-03-31')
)

# Query with begins_with on sort key
response = 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:

# Query for a customer's orders, but only return completed ones
response = 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

const AWS = require('aws-sdk');
// Configure AWS SDK
AWS.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:

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;
  }
}

// Usage
queryCustomerOrders('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

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

Python

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

Node.js

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

import boto3
from boto3.dynamodb.conditions import Key

dynamodb = 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 automatically
all_orders = query_all_customer_orders('CUSTOMER123')
print(f"Retrieved {len(all_orders)} orders")

Node.js

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 example
const 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));

Familiar with these Dynamodb Challenges ?

  • Writing one‑off scripts for simple DynamoDB operations
  • Constantly switching between AWS profiles and regions
  • Sharing and managing database operations with your team

You should try Dynomate GUI Client for DynamoDB

  • Create collections of operations that work together like scripts
  • Seamless integration with AWS SSO and profile switching
  • Local‑first design with Git‑friendly sharing for team collaboration

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

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

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

Node.js with Projection and Limit

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

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

Python

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

Node.js

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 reduce consumed read capacity
  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
  • Ensure you’re only requesting attributes you need
  • 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

Switching from Dynobase? Try Dynomate

Developers are switching to Dynomate for these key advantages:

Better Multi-Profile Support

  • Native AWS SSO integration
  • Seamless profile switching
  • Multiple accounts in a single view

Developer-Focused Workflow

  • Script-like operation collections
  • Chain data between operations
  • Full AWS API logging for debugging

Team Collaboration

  • Git-friendly collection sharing
  • No account required for installation
  • Local-first data storage for privacy

Privacy & Security

  • No account creation required
  • 100% local data storage
  • No telemetry or usage tracking

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:

Share this article:

Related Articles