Reputation: 383
I am having trouble using AWS Boto3 to query DynamoDB with a hash key and a range key at the same time using the recommend KeyConditionExpression. I have attached an example query:
import boto3
from boto3 import dynamodb
from boto3.session import Session
dynamodb_session = Session(aws_access_key_id=AWS_KEY,
aws_secret_access_key=AWS_PASS,
region_name=DYNAMODB_REGION)
dynamodb = dynamodb_session.resource('dynamodb')
table=dynamodb.Table(TABLE_NAME)
request = {
'ExpressionAttributeNames': {
'#n0': 'hash_key',
'#n1': 'range_key'
},
'ExpressionAttributeValues': {
':v0': {'S': MY_HASH_KEY},
':v1': {'N': GT_RANGE_KEY}
},
'KeyConditionExpression': '(#n0 = :v0) AND (#n1 > :v1)',
'TableName': TABLE_NAME
}
response = table.query(**request)
When I run this against a table with the following scheme:
Table Name: TABLE_NAME
Primary Hash Key: hash_key (String)
Primary Range Key: range_key (Number)
I get the following error and I cannot understand why:
ClientError: An error occurred (ValidationException) when calling the Query operation: Invalid KeyConditionExpression: Incorrect operand type for operator or function; operator or function: >, operand type: M
From my understanding the type M would be a map or dictionary type and I am using a type N which is a number type and matches my table scheme for the range key. If someone could explain why this error is happening or I am also open to a different way of accomplishing the same query even if you cannot explain why this error exists.
Upvotes: 38
Views: 67669
Reputation: 1908
For anyone looking at using boto3
to query Dynamodb with composite keys in 2023 this is the first result from google.
To return a single record, the easiest way is to just pass the hash_key and range_key as a dictionary to the key
argument on the get_item
method.
e.g.
from boto3 import resource
dynamodb_table = resource("dynamodb", region_name="eu-west-1").Table("your_table_name")
dynamodb_table.get_item(Key={"hash_key_name": "value", "range_key_name": "value"})
For returning more than one object, I recommend using the query method and using the KeyConditionExpression
argument. Then using the arguments from the boto3.dynamodb.conditions
class to build your query. The example below giving the same results as the example above.
For example
from boto3 import resource
from boto3.dynamodb.conditions import Key
dynamodb_table = resource("dynamodb", region_name="eu-west-1").Table("your_table_name")
dynamodb_table.query(
KeyConditionExpression=Key("hash_key_name").eq("value")
& Key("range_key_name").eq("value")
)
The key difference is get_item
, results are stored in "Item" key, while query
results are in Items
and are stored as a list
Upvotes: 2
Reputation: 101
Adding this solution as the accepted answer did not address why the query used did not work.
TLDR: Using query on a Table
resource in boto3 has subtle differences as opposed to using client.query(...)
and requires a different syntax.
The syntax is valid for a query on a client, but not on a Table. The ExpressionAttributeValues
on a table do not require you to specify the data type. Also if you are executing a query on a Table
resource you do not have to specify the TableName
again.
Working solution:
from boto3.session import Session
dynamodb_session = Session(aws_access_key_id=AWS_KEY,aws_secret_access_key=AWS_PASS,region_name=DYNAMODB_REGION)
dynamodb = dynamodb_session.resource('dynamodb')
table = dynamodb.Table(TABLE_NAME)
request = {
'ExpressionAttributeNames': {
'#n0': 'hash_key',
'#n1': 'range_key'
},
'ExpressionAttributeValues': {
':v0': MY_HASH_KEY,
':v1': GT_RANGE_KEY
},
'KeyConditionExpression': '(#n0 = :v0) AND (#n1 > :v1)',
}
response = table.query(**request)
I am the author of a package called botoful which might be useful to avoid dealing with these complexities. The code using botoful will be as follows:
import boto3
from botoful import Query
client = boto3.Session(
aws_access_key_id=AWS_KEY,
aws_secret_access_key=AWS_PASS,
region_name=DYNAMODB_REGION
).client('dynamodb')
results = (
Query(TABLE_NAME)
.key(hash_key=MY_HASH_KEY, range_key__gt=GT_RANGE_KEY)
.execute(client)
)
print(results.items)
Upvotes: 6
Reputation: 2912
The Boto 3 SDK constructs a Condition Expression for you when you use the Key and Attr functions imported from boto3.dynamodb.conditions:
response = table.query(
KeyConditionExpression=Key('hash_key').eq(hash_value) & Key('range_key').eq(range_key_value)
)
Reference: Step 4: Query and Scan the Data
Hope it helps
Upvotes: 73