更新嵌套映射DynamoDB

33

我有一个包含嵌套映射的dynamodb表,并且我想更新通过过滤表达式筛选出的特定库存项目,该过滤表达式结果为来自此映射的单个项目的属性。

如何编写更新表达式以将名称=opel,标签包括“x1”(可能还包括f3)的项目的位置更新为“第三个位置”? 这应该只更新第一个列表元素的位置属性。

  {
    "inventory": [
    {
      "location": "in place one",      # I want to update this
      "name": "opel",
      "tags": [
        "x1",
        "f3"
      ]
    },
    {
      "location": "in place two",
      "name": "abc",
      "tags": [
        "a3",
        "f5"
      ]
    }],
    "User" :"test" 
  } 

请包含完整的物品模式。 - cementblocks
5个回答

64

更新的回答 - 基于更新后的问题陈述

您可以使用更新表达式在嵌套映射中更新属性,以便仅更新项目的一部分(即DynamoDB将对项目应用等效的补丁),但由于DynamoDB是文档数据库,所有操作(Put、Get、Update、Delete等)都作用于整个项目。

因此,在您的示例中,假设User是分区键并且没有排序键(我没有看到任何可能成为排序键的属性),则Update请求可能如下所示:

table.update_item(
  Key={
    'User': 'test'
  },
  UpdateExpression="SET #inv[0].#loc = :locVal",
  ExpressionAttributeNames={
    '#inv': 'inventory',
    '#loc': 'location'
  },
  ExpressionAttributeValues={
    ':locVal': 'in place three',
  },
)

话虽如此,您确实需要知道项目模式的外观以及应更新哪些项目属性。

DynamoDB没有一种操作子项的方法。也就是说,无法告诉Dynamo执行类似于 "update item, set 'location' property of elements of the 'inventory' array that have a property of 'name' equal to 'opel'" 的操作。

这可能不是您希望得到的答案,但这是今天可用的方法。您可以通过稍微更改模式来更接近您想要的结果。

如果您需要按名称引用子项,可以存储类似于以下内容:

{
  "inventory": {
    "opel": {
       "location": "in place one",      # I want to update this
       "tags": [ "x1", "f3" ]
    },
    "abc": {
       "location": "in place two",
       "tags": [ "a3", "f5" ]
    }
  },
  "User" :"test" 
} 

那么你的查询将会是:

table.update_item(
  Key={
    'User': 'test'
  },
  UpdateExpression="SET #inv.#brand.#loc = :locVal",
  ExpressionAttributeNames={
    '#inv': 'inventory',
    '#loc': 'location',
    '#brand': 'opel'
  },
  ExpressionAttributeValues={
    ':locVal': 'in place three',
  },
)

但因人而异,即使如此也有限制,因为您只能通过名称识别库存项目(即仍然无法说“使用标签'x1'更新库存”)。

最终,您应该仔细考虑为什么需要Dynamo来执行这些复杂操作,而不是明确指定要更新的内容。


谢谢,但那正是我的问题,我想避免更新整个项目,我只想更新列表中的单个元素并避免额外读取项目内容。 - Nico Müller
1
你不一定需要读取该项。如果你有该项的分区键和范围键,那么你可以发出更新请求,仅修改嵌套属性。如果你更新你的问题以明确你想要更新的内容,我可以帮助你查询。 - Mike Dinescu
1
很高兴能帮上忙。确保根据最关键/最频繁的访问模式考虑最合理的架构。 - Mike Dinescu
惊人的解决方案。我有一个场景,我的索引是可变的,不像这个场景SET #inv[0].#loc = :locVal",我使用了类似于SET #inv[index].#loc =:locVal",我得到了错误Invalid UpdateExpression: Syntax error; token: \"index\", near: \"[index]\""任何帮助感谢。 - Aravind Reddy
1
工作案例 'set someitem['+index+'].somevalue = :reply_content' - Aravind Reddy
显示剩余3条评论

4
您可以按照以下方式更新嵌套地图:
  1. First create and empty item attribute of type map. In the example graph is the empty item attribute.

    dynamoTable = dynamodb.Table('abc')
    dynamoTable.put_item(
        Item={
            'email': email_add,
            'graph': {},
        }
    
  2. Update nested map as follow:

    brand_name = 'opel'
    DynamoTable = dynamodb.Table('abc')
    
    dynamoTable.update_item(
        Key={
            'email': email_add,
        },
        UpdateExpression="set #Graph.#brand= :name, ",
        ExpressionAttributeNames={
            '#Graph': 'inventory',
            '#brand': str(brand_name),
        },
        ExpressionAttributeValues = {
            ':name': {
                "location": "in place two",
                'tag': {
                    'graph_type':'a3',
                    'graph_title': 'f5'
                } 
            }
    

空格缩进是有意为之的吗? - cyrf
1
@cyrf 这是一个 JSON 对象。空格不会影响任何内容,只是为了让它更易读而进行的样式设置。 - Shreya Rajput
我认为你有过多的空格。如果你把 dynamoTable.update_item 往左移动,你的答案会更容易阅读。这是 Python 吗? - cyrf

2

更新Mike的回答,因为那种方式现在不再适用(至少对我来说是这样)。

现在是这样工作的(注意UpdateExpressionExpressionAttributeNames):

table.update_item(
  Key={
    'User': 'test'
  },
  UpdateExpression="SET inv.#brand.loc = :locVal",
  ExpressionAttributeNames={
    '#brand': 'opel'
  },
  ExpressionAttributeValues={
    ':locVal': 'in place three',
  },
)

无论什么内容放在 Key={} 中,它总是分区键(如果有的话还包括排序键)。

编辑: 似乎这种方式只适用于两层嵌套属性。在这种情况下,您只需要为“中间”属性使用“ExpressionAttributeNames”(在此示例中,那将是 #brandinv.#brand.loc)。我还不确定现在的真实规则是什么。


0

我有同样的需求。 希望这段代码能够帮到你。你只需要调用compose_update_expression_attr_name_values并传入包含新值的字典即可。

def compose_update_expression_attr_name_values(data: dict) -> (str, dict, dict):
    """ Constructs UpdateExpression, ExpressionAttributeNames, and ExpressionAttributeValues for updating an entry of a DynamoDB table.

    :param data: the dictionary of attribute_values to be updated
    :return: a tuple (UpdateExpression: str, ExpressionAttributeNames: dict(str: str), ExpressionAttributeValues: dict(str: str))
    """
    # prepare recursion input
    expression_list = []
    value_map = {}
    name_map = {}

    # navigate the dict and fill expressions and dictionaries
    _rec_update_expression_attr_name_values(data, "", expression_list, name_map, value_map)

    # compose update expression from single paths
    expression = "SET " + ", ".join(expression_list)

    return expression, name_map, value_map


def _rec_update_expression_attr_name_values(data: dict, path: str, expressions: list, attribute_names: dict,
                                        attribute_values: dict):
    """ Recursively navigates the input and inject contents into expressions, names, and attribute_values.

    :param data: the data dictionary with updated data
    :param path: the navigation path in the original data dictionary to this recursive call
    :param expressions: the list of update expressions constructed so far
    :param attribute_names: a map associating "expression attribute name identifiers" to their actual names in ``data``
    :param attribute_values: a map associating "expression attribute value identifiers" to their actual values in ``data``
    :return: None, since ``expressions``, ``attribute_names``, and ``attribute_values`` get updated during the recursion
    """
    for k in data.keys():
        # generate non-ambiguous identifiers
        rdm = random.randrange(0, 1000)
        attr_name = f"#k_{rdm}_{k}"
        while attr_name in attribute_names.keys():
        rdm = random.randrange(0, 1000)
        attr_name = f"#k_{rdm}_{k}"

        attribute_names[attr_name] = k
        _path = f"{path}.{attr_name}"

        # recursion
        if isinstance(data[k], dict):
            # recursive case
            _rec_update_expression_attr_name_values(data[k], _path, expressions, attribute_names, attribute_values)

        else:
            # base case
            attr_val = f":v_{rdm}_{k}"
            attribute_values[attr_val] = data[k]
            expression = f"{_path} = {attr_val}"
            # remove the initial "."
            expressions.append(expression[1:])

0

DynamoDB的UpdateExpression与SQL不同,它不会在数据库中搜索匹配条件的所有项目进行更新(例如,在SQL中,您可以更新所有符合某些条件的项目)。为了更新项目,您首先需要识别它并获取主键或复合键,如果有很多符合您条件的项目,则需要逐个更新。

然后,更新嵌套对象的问题是定义UpdateExpression、ExpressionAttributeValues和ExpressionAttributeNames以传递给Dynamo Update Api。

我使用递归函数来更新DynamoDB上的嵌套对象。你要求Python,但我使用JavaScript,我认为这段代码很容易看懂,并且可以在Python上实现: https://gist.github.com/crsepulv/4b4a44ccbd165b0abc2b91f76117baa5

/**
 * Recursive function to get UpdateExpression,ExpressionAttributeValues & ExpressionAttributeNames to update a nested object on dynamoDB
 * All levels of the nested object must exist previously on dynamoDB, this only update the value, does not create the branch.
 * Only works with objects of objects, not tested with Arrays.
 * @param obj , the object to update.
 * @param k , the seed is any value, takes sense on the last iteration.
 */
function getDynamoExpression(obj, k) {

    const key = Object.keys(obj);

    let UpdateExpression = 'SET ';
    let ExpressionAttributeValues = {};
    let ExpressionAttributeNames = {};
    let response = {
        UpdateExpression: ' ',
        ExpressionAttributeNames: {},
        ExpressionAttributeValues: {}
    };

    //https://dev59.com/S2oy5IYBdhLWcg3wfeMR#16608074

    /**
     * true when input is object, this means on all levels except the last one.
     */
    if (((!!obj) && (obj.constructor === Object))) {

        response = getDynamoExpression(obj[key[0]], key);
        UpdateExpression = 'SET #' + key + '.' + response['UpdateExpression'].substring(4); //substring deletes 'SET ' for the mid level values.
        ExpressionAttributeNames = {['#' + key]: key[0], ...response['ExpressionAttributeNames']};
        ExpressionAttributeValues = response['ExpressionAttributeValues'];

    } else {
        UpdateExpression = 'SET   = :' + k;
        ExpressionAttributeValues = {
            [':' + k]: obj
        }
    }

    //removes trailing dot on the last level
    if (UpdateExpression.indexOf(". ")) {
        UpdateExpression = UpdateExpression.replace(". ", "");
    }

    return {UpdateExpression, ExpressionAttributeValues, ExpressionAttributeNames};
}

//you can try many levels.
const obj = {
    level1: {
        level2: {
            level3: {
                level4: 'value'
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接