72

I am running an AWS Lambda service written in Node.js that interacts with a DynamoDB database. One of my methods performs an update (AWS.DynamoDB.DocumentClient().update) on DynamoDB to update a specific item. My problem is, however, that when I try to update an item and that item does not exist, then the update method adds this new item to my table. This behavior is the default described in the documentation too.

I do not want my method to create a new item if it does not yet exist. I want this to be an update only if the record exists. If it doesn't exist it must do nothing. How do I achieve this? I expect I should be able to use a ConditionalExpression to achieve this, but the attempts I've made at doing this have not been successful.

An example of current parameters I'm sending to the update function is given below. In this example I want to update the user that has userId 123 and I want to set the isActive field to 'false' for this user (but I only want to do this if user 123 actually exists!)

const params = {
    TableName: 'users',
    Key: {
        userId: '123',
    },
    ExpressionAttributeValues: {
        ':isActive': 'false'
    },
    UpdateExpression: 'SET isActive = :isActive',
    ReturnValues: 'ALL_NEW',
};
Renato Gama
  • 16,431
  • 12
  • 58
  • 92
Stanley
  • 5,261
  • 9
  • 38
  • 55
  • 5
    If anyone's wondering the opposite: "How can I create new item with UpdateItem if key doesn't exist?", then just use what OP has posted – denvercoder9 Mar 29 '18 at 12:58

3 Answers3

77

You can use ConditionExpression for this. The update will happen if the key (i.e. userId) is present and update attribute (i.e. isActive) is not equal to the new value that you are trying to update.

ConditionExpression: "userId = :userIdVal and isActive <> :isActiveVal",
ExpressionAttributeValues: {
    ':isActive': 'false',
    ':userIdVal': '123'
},

EDIT:-

This should work. It should be ConditionExpression (not ConditionalExpression).

var params = {
    TableName: 'users',
    Key: {
        'userId': '123'
    },
    UpdateExpression: 'SET isActive = :isActiveVal',
    ConditionExpression: 'userId = :userIdVal and isActive <> :isActiveVal',
    ExpressionAttributeValues: {
        ':userIdVal': '123',
        ':isActiveVal': 'false'
    },
    ReturnValues: "ALL_NEW"
};

Update, 2020:

You can use attribute_exists in ConditionExpression to check if the item exists or not. (Sources: 1, 2)

const params = {
    TableName: 'users',
    Key: {
        'userId': '123'
    },
    UpdateExpression: 'SET isActive = :isActiveVal',
    ConditionExpression: 'attribute_exists(userId)',
    ExpressionAttributeValues: {
        ':isActiveVal': 'false'
    },
    ReturnValues: "ALL_NEW"
};
imedadel
  • 185
  • 2
  • 8
notionquest
  • 37,595
  • 6
  • 111
  • 105
  • This is one of the options I've actually tried. When I do this I get an exception, ValidationException: Value provided in ExpressionAttributeValues unused in expressions: keys: {:userIdVal} – Stanley Jan 26 '17 at 13:56
  • Looks like you have not provided the userIdVal in the expression. Have you included this part "userId = :userIdVal" in ConditionExpression? – notionquest Jan 26 '17 at 13:59
  • Indeed I have, and checked spelling and syntax. – Stanley Jan 26 '17 at 13:59
  • can you please provide the whole params? I will look at it. – notionquest Jan 26 '17 at 14:00
  • We can discuss in chat if needed ...http://chat.stackoverflow.com/rooms/134084/room-for-notionquest-and-stanley – notionquest Jan 26 '17 at 14:05
  • 1
    The code as it's written after your edit does indeed work. It looks like the big problem was that our expression attribute values should have a distinctly different name from our attributes in the table. So if the table attribute is called isActive, one shouldn't have an ExpressionAttributeValue of :isActive (but if you change it to :isActiveVal it works). – Stanley Jan 26 '17 at 15:38
44

ConditionExpression is the way to go, but you can also use the attribute_exists function :

ConditionExpression: 'attribute_exists(userId)'

It will only update the line with the correct key and throw a ConditionalCheckFailedException if requested key doesn't exist.

The goal is more obvious and you can skip the extra userId binding.

ref : https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html

Gabriel Bleu
  • 9,703
  • 2
  • 30
  • 43
2

I bumped into a somehow similar problem using attribute_exists, the only difference is, I need to query from the table and get only the record that does have the attribute I need. Using ConditionExpression did not work for me. Thought of sharing it here in case someone is in need.

Here, if the subscriberKey is equal to given param and the attribute_exists (mobileNumber) within the record, it returns true, false otherwise.

var params = {
    TableName : table_name,
    IndexName: "subsciberKey-index",
    KeyConditionExpression: "subscriberKey = :subscriberKey",
    FilterExpression: "attribute_exists(mobileNumber)",
    ExpressionAttributeValues: {
        ":subscriberKey": subscriberNo
    }
};
Alex
  • 1,457
  • 1
  • 13
  • 26
sinbad
  • 21
  • 5