Skip to content

Query Operations

Query and Scan operations for retrieving multiple items.


POST /api/v1/databases/{databaseId}/items/query

Section titled “POST /api/v1/databases/{databaseId}/items/query”

Queries items by partition key with optional sort key conditions.

Request:

{
"partitionKey": "user#123",
"sortKeyCondition": "begins_with:order#2024",
"filterExpression": null,
"limit": 20,
"cursor": null,
"scanForward": true
}
FieldTypeRequiredDescription
partitionKeystringYesPartition key value to query
sortKeyConditionstringNoSort key condition (see operators below)
filterExpressionobjectNoAdditional filter after query
limitintegerNoMax items to return (default: 20)
cursorstringNoPagination cursor from previous response
scanForwardbooleanNoSort direction, true = ascending (default: true)

Response (200 OK):

{
"items": [
{
"pk": "user#123",
"sk": "order#2024-001",
"data": { "total": 99.99 }
}
],
"nextCursor": "eyJsYXN0S2V5Ijo...",
"count": 1,
"scannedCount": 1
}

OperatorFormatExampleDescription
Equals=:value=:profileExact match
Less than<:value<:2024-01-01Before value
Less than or equals<=:value<=:2024-01-01At or before value
Greater than>:value>:2024-01-01After value
Greater than or equals>=:value>=:2024-01-01At or after value
Begins withbegins_with:prefixbegins_with:order#Prefix match
Betweenbetween:low:highbetween:2024-01-01:2024-12-31Range inclusive
// Get all orders for a user
{
"partitionKey": "user#123",
"sortKeyCondition": "begins_with:order#"
}
// Get orders from 2024
{
"partitionKey": "user#123",
"sortKeyCondition": "between:order#2024-01-01:order#2024-12-31"
}
// Get the user's profile
{
"partitionKey": "user#123",
"sortKeyCondition": "=:profile"
}

POST /api/v1/databases/{databaseId}/items/scan

Section titled “POST /api/v1/databases/{databaseId}/items/scan”

Scans all items in the database. Use with caution on large datasets.

Request:

{
"filterExpression": null,
"limit": 20,
"cursor": null
}

Response: Same format as query response.


Both Query and Scan support cursor-based pagination:

// First request
{
"partitionKey": "user#123",
"limit": 10
}
// Response
{
"items": [...],
"nextCursor": "eyJsYXN0S2V5Ijo...",
"count": 10
}
// Next page
{
"partitionKey": "user#123",
"limit": 10,
"cursor": "eyJsYXN0S2V5Ijo..."
}

When nextCursor is null, there are no more items.


Control the order of results with scanForward:

// Ascending order (oldest first)
{
"partitionKey": "user#123",
"sortKeyCondition": "begins_with:order#",
"scanForward": true
}
// Descending order (newest first)
{
"partitionKey": "user#123",
"sortKeyCondition": "begins_with:order#",
"scanForward": false
}

// Query with sort key condition
var filter = new QueryFilter
{
PartitionKey = "user#123",
SortKeyCondition = SortKeyCondition.BeginsWith("order#")
};
var options = new QueryOptions
{
Limit = 50,
ScanForward = false // Descending order
};
var result = await client.QueryAsync(filter, options);
if (result.IsSuccess)
{
foreach (var item in result.Value.Items)
{
Console.WriteLine($"Order: {item.SortKey}");
}
// Handle pagination
if (result.Value.HasMore)
{
var nextPage = await client.QueryAsync(filter, new QueryOptions
{
NextToken = result.Value.NextToken
});
}
}
// Scan all items
var scanResult = await client.ScanAsync(new PaginationOptions
{
Limit = 100
});

Queries are efficient because they target a specific partition. Scans read the entire database.

// Good: Query by partition key
{ "partitionKey": "user#123" }
// Avoid: Scan the whole database
{}

Structure your keys to support your query patterns:

// Access pattern: Get all orders for a user
{ "pk": "user#123", "sk": "order#2024-001" }
// Access pattern: Get orders by date
{ "pk": "orders#2024-01", "sk": "user#123#order#001" }

Always specify a reasonable limit to avoid fetching too much data:

{
"partitionKey": "user#123",
"limit": 50
}