pkaeding
pkaeding

Reputation: 37663

Is it possible to upsert nested fields in DynamoDB?

I would like to 'upsert' a document in DynamoDB. That is, I would like to specify a key, and a set of field/value pairs. If no document exists with that key, I want one created with that key and the key/value pairs I specified. If a document exists with that key, I want the fields I specified to be set to the values specified (if those fields did not exist before, then they should be added). Any other, unspecified fields on the existing document should be left alone.

It seems I can do this pretty well with the UpdateItem call, when the field/value pairs I am setting are all top-level fields. If I have nested structures, UpdateItem will work to set the nested fields, as long as the structure exists. In other words, if my existing document has "foo": {}, then I can set "foo.bar": 42 successfully.

However, I don't seem to be able to set "foo.bar": 42 if there is no foo object already (like in the case where there is no document with the specified field at all, and my 'upsert' is behaving as an 'insert'.

I found a discussion on the AWS forums from a few years ago which seems to imply that what I want to do cannot be done, but I'm hoping this has changed recently, or maybe someone knows of a way to do it?

Upvotes: 21

Views: 10472

Answers (3)

Stan Kurdziel
Stan Kurdziel

Reputation: 5724

I found this UpdateItem limitation (top level vs nested attributes) frustrating as well. Eventually I came across this answer and was able to work around the problem: https://stackoverflow.com/a/43136029/431296

It requires two UpdateItem calls (possibly more depending on level of nesting?). I only needed a single level, so this is how I did it:

  1. Update the item using an attribute_exists condition to create the top level attribute as an empty map if it doesn't already exist. This will work if the entire item is missing or if it exists and has other pre-existing attributes you don't want to lose.

  2. Then do the 2nd level update item to update the nested value. As long as the parent exists (ex: an empty map in my case) it works great.

I got the impression you weren't using python, but here's the python code to accomplish the upsert of a nested attribute in an item like this:

{
  "partition_key": "key",
  "top_level_attribute": {
    "nested_attribute": "value"
  }
}

python boto3 code:

def upsert_nested_item(self, partition_key, top_level_attribute_name, nested_attribute_name, nested_item_value):
    try:
        self.table.update_item(
            Key={'partition_key': partition_key},
            ExpressionAttributeNames={f'#{top_level_attribute_name}': top_level_attribute_name},
            ExpressionAttributeValues={':empty': {}},
            ConditionExpression=f'attribute_not_exists(#{top_level_attribute_name})',
            UpdateExpression=f'SET #{top_level_attribute_name} = :empty',
        )
    except self.DYNAMODB.meta.client.exceptions.ConditionalCheckFailedException:
        pass
    self.table.update_item(
        Key={'partition_key': partition_key},
        ExpressionAttributeNames={
            f'#{top_level_attribute_name}': top_level_attribute_name,
            f'#{nested_attribute_name}': nested_attribute_name
        },
        ExpressionAttributeValues={f':{top_level_attribute_name}': nested_item_value},
        UpdateExpression=f'SET #{top_level_attribute_name}.#{nested_attribute_name} = :{top_level_attribute_name}',
    )

Upvotes: 1

manojpt
manojpt

Reputation: 1269

That ("foo.bar": 42) can be achieved using the below query:

 table.update_item(Key = {'Id' : id},
              UpdateExpression = 'SET foo = :value1',
              ExpressionAttributeValues = {':value1': {'bar' : 42}}
              )

Hope this helps :)

Upvotes: 1

Neil
Neil

Reputation: 8634

UpdateItem behaves like an "upsert" operation: The item is updated if it exists in the table, but if not, a new item is added (inserted). http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SQLtoNoSQL.UpdateData.html

Upvotes: 1

Related Questions