javascriptamazon-dynamodbdynamodb-queriesdocumentclient

DynamoDB update fails when nested path not exists


My table structure is below where time is the unique key and that "01" was the id of the count, the item of a month will create right after any id get the count result of that month.

{
  "details": {
    "01": {
      "a": {
        "count": [10, 5]
      },
      "b": {
        "count": [10, 5]
      }
    }
  },
  "time": "2020_06"
}

And I use update function to save the count result:

var params = {
  TableName: tableName,
  Key: { time },
  UpdateExpression: `ADD details.#id.#type.count[0] :count0, details.#id.#type.count[1] :count1`,
  ExpressionAttributeNames: {
    '#id': id,
    '#type': type
  },
  ExpressionAttributeValues: {
    ':count0': count[0],
    ':count1': count[1]
  },
  ReturnValues: 'ALL_NEW'
}

If the result of the id already exists then that's fine. But if it doesn't, I'll get an error says that

ValidationException: The document path provided in the update expression is invalid for update

How to let the map be constructed before updating data? I had tried to use SET if_not_exists() but it'll overlap the path what I really want to update (the count array)


Solution

  • In short you cannot, there are 2 options I can think on.

    1. When creating the item create an empty map for details. This will allow you to update nested values for details without raising an exception about an invalid path.

    2. Add some exception handling which will update the entire map should it throw and exception on the first execution:

    import boto3
    from botocore.exceptions import ClientError
    
    table = boto3.resource('dynamodb', region_name='eu-west-1').Table('test1')
    
    try:
        table.update_item(
            Key={
                'pk': '1'
            },
            UpdateExpression="SET #buyer.#value = :val1, #buyer.#label = :val2",
            ExpressionAttributeNames={
                '#label': 'label',
                '#value': 'value',
                '#buyer': 'buyer',
            },
            ExpressionAttributeValues={
                ':val1': 'id1',
                ':val2': 'Test'
            }
        )
    except ClientError as e:
           if e.response['Error']['Code'] == 'ValidationException':
                # Creating new top level attribute `buyer` (with nested props)
                # if the previous query failed
                response = table.update_item(
                    Key={
                        'pk': '1'
                    },
                    UpdateExpression="set #buyer = :val1",
                    ExpressionAttributeNames={
                        '#buyer': 'buyer'
                    },
                    ExpressionAttributeValues={
                        ':val1': {
                            'value': 'id1',
                            'label': 'Test'
                        }
                    }
                )
            else:
                raise