Here are my use cases: I have a Dynamo table with a hash + range key. When I put new items in the table, I want to do a uniqueness check. Sometimes I want to guarantee th
You can use AND operation if your table has hash and range
'ConditionExpression' => 'attribute_not_exists(hash) AND attribute_not_exists(range)'
This version of explanation taken from amazon aws forum says that a search will look an item that matches a provided hash key and then only checks if the attribute exists in that record. It should works the same if you have a hash and a range keys, I suppose.
If a request try to find an existing item with hash key "b825501b-60d3-4e53-b737-02645d27c2ae". If this is first time this id is being used there will be no existing item and "attribute_not_exists(email)" will evaluate to true, Put request will go through.
If this id is already used there will be an existing item. Then condition expression will look for an existing email attribute in the existing item, if there is an email attribute the Put request will fail, if there is no email attribute the Put request will go through.
Either way it's not comparing the value of "email" attribute and it's not checking if other items in the table used the same "email" value.
If email was the hash key, then request will try to find an existing item with hash key "tielur@example.me".
If there is another item with same email value an existing item will be found. Since email is the hash key it has to be present in the existing item and "attribute_not_exists(email)" will evaluate to false and Put request will fail.
If "email" value is not used before existing item will not be found and "attribute_not_exists(email)" will evaluate to true hence Put request will go through.
Careful with reserved keywords.
attribute_not_exists
will not work as expected if the provided attributeName matches a word from the reserved list. hash
and range
are both reserved and thus require the need to work around that issue by using ExpressionAttributeNames
.
The following example allows for duplicate partition keys and only fails if there's already an Item in the Table with the provided partition AND sort key.
$client->putItem(array(
'TableName' => 'test',
'Item' => array(
'hash' => array('S' => 'abcdefg'),
'range' => array('S' => 'some other value'),
'whatever' => array('N' => 233)
),
'ConditionExpression' => 'attribute_not_exists(#h) AND attribute_not_exists(#r)',
'ExpressionAttributeNames' => array('#h' => 'hash', '#r' => 'range')
));
And this one would make sure the partition key named hash
is unique.
$client->putItem(
'TableName' => 'test',
'Item' => array(
'hash' => array('S' => 'abcdefg'),
'range' => array('S' => 'some other value'),
'whatever' => array('N' => 233)
),
'ConditionExpression' => 'attribute_not_exists(#h)',
'ExpressionAttributeNames' => array('#h' => 'hash')
));
You can't. All items in DynamoDB are indexed by either their hash
or hash
+range
(depending on your table).
A sort of summary of what is going on so far:
hash
and a range
keyPutItem
request and must provide both the hash
and range
ConditionExpression
with attribute_not_exists
on either the hash
or range
attribute nameattribute_not_exists
condition is merely checking if an attribute with that name exists, it doesn't care about the valueLet's walk through an example. Let's start with a hash
+range
key table with this data:
hash=A,range=1
hash=A,range=2
There are four possible cases:
If you try to put an item with hash=A,range=3
and attribute_not_exists(hash)
, the PutItem
will succeed because attribute_not_exists(hash)
evaluates to true
. No item exists with key hash=A,range=3
that satisfies the condition of attribute_not_exists(hash)
.
If you try to put an item with hash=A,range=3
and attribute_not_exists(range)
, the PutItem
will succeed because attribute_not_exists(range)
evaluates to true
. No item exists with key hash=A,range=3
that satisfies the condition of attribute_not_exists(range)
.
If you try to put an item with hash=A,range=1
and attribute_not_exists(hash)
, the PutItem
will fail because attribute_not_exists(hash)
evaluates to false
. An item exists with key hash=A,range=1
that does not satisfy the condition of attribute_not_exists(hash)
.
If you try to put an item with hash=A,range=1
and attribute_not_exists(range)
, the PutItem
will fail because attribute_not_exists(range)
evaluates to false
. An item exists with key hash=A,range=1
that does not satisfy the condition of attribute_not_exists(range)
.
This means that one of two things will happen:
hash
+range
pair exists in the database.
attribute_not_exists(hash)
must be true
attribute_not_exists(range)
must be true
hash
+range
pair does not exist in the database.
attribute_not_exists(hash)
must be false
attribute_not_exists(range)
must be false
In both cases, you get the same result regardless of whether you put it on the hash or the range key. The hash
+range
key identifies a single item in the entire table, and your condition is being evaluated on that item.
You are effectively performing a "put this item if an item with this hash
+range
key does not already exist".
For Googlers:
attribute_not_exists
checks whether an item with same primary key as the to-be-inserted item existsattribute_not_exists
with primary key (or partition key, or range key), since the key must exist, check (b) will always pass, only check (a) will be in effectReasoning:
attribute_not_exists
suggests that it checks whether an attribute exists on an itemNote: To prevent a new item from replacing an existing item, use a conditional expression that contains the attribute_not_exists function with the name of the attribute being used as the partition key for the table. Since every record must contain that attribute, the attribute_not_exists function will only succeed if no matching item exists.
Link
Following Jun711's answer, here is what I did to implement putItem only if hash doesn't exist
in Kotlin. DocId
is the hash key of my DynamoDB table.
val item = Item().withPrimaryKey(...).withString(...)
val putItemSpec = PutItemSpec().withItem(item)
.withConditionExpression("attribute_not_exists(DocId)")
table.putItem(putItemSpec)
If using DynamoDBMapper annotations, here is an example.
@DynamoDBTable(tableName = "Docs")
class Docs {
// Partition key
@get:DynamoDBHashKey(attributeName = "DocId")
var docId: String? = null
}
val doc = Docs().apply {
docId = "myId"
}
val mapper = DynamoDBMapper(AmazonDynamoDBClientBuilder.defaultClient())
// As suggested by http://rrevo.github.io/2018/03/09/dynamo-no-update/
val ifDocIdNotExists = DynamoDBSaveExpression().apply {
expected = mapOf("DocId" to ExpectedAttributeValue().apply {
isExists = false
})
}
mapper.save(doc, ifDocIdNotExists)
Catch com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException
to handle the case where the hash key already exists.