【问题标题】:Example of update_item in dynamodb boto3dynamodb boto3 中的 update_item 示例
【发布时间】:2021-12-23 18:34:34
【问题描述】:

the documentation 之后,我正在尝试创建一个更新语句,如果 dynamodb 表中不存在一个属性,则该语句将更新或添加。

我正在尝试这个

response = table.update_item(
    Key={'ReleaseNumber': '1.0.179'},
    UpdateExpression='SET',
    ConditionExpression='Attr(\'ReleaseNumber\').eq(\'1.0.179\')',
    ExpressionAttributeNames={'attr1': 'val1'},
    ExpressionAttributeValues={'val1': 'false'}
)

我得到的错误是:

botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem operation: ExpressionAttributeNames contains invalid key: Syntax error; key: "attr1"

如果有人做过类似我想要实现的事情,请分享示例。

【问题讨论】:

  • 我知道这是一个非常古老的问题,但我想知道....如果您的项目已经存在,“put_item”将覆盖它。如果我不能更改任何 index_key ,为什么要使用 update_item ?
  • @Claudiu 我想更新不是所有字段

标签: python amazon-dynamodb boto3 botocore


【解决方案1】:

找到工作示例here,将表的所有索引作为键列出非常重要,这将需要在更新之前进行额外查询,但它可以工作。

response = table.update_item(
    Key={
        'ReleaseNumber': releaseNumber,
        'Timestamp': result[0]['Timestamp']
    },
    UpdateExpression="set Sanity = :r",
    ExpressionAttributeValues={
        ':r': 'false',
    },
    ReturnValues="UPDATED_NEW"
)

【讨论】:

  • 应该注意的是,您的原始错误指的是 ExpressionAttributeNames 已从此答案中提供的示例中排除...虽然您必须包含键的所有值才能更新项目这不是您最初遇到的错误。
  • 值得一提,Key 这里应该有index 的所有键,无论是Local Secondary Index 还是Global Secondary Index。和这里一样,ReleaseNumberprimary keyTimestamp 默认是 sort key
  • 此解决方案对我不起作用 - 我必须在更新项目时添加 ExpressionAttributeNamesExpressionAttributeValues
  • 我尝试了上述方法,但没有奏效。尝试使用ExpressionAttributeNames,但仍然无效。得到错误botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem operation: The provided key element does not match the schema
  • 这个接受的答案不起作用。此答案的编辑队列已满,因此无法编辑。下面的另一个答案应标记为已接受,它使用“ExpressionAttributeNames”。 stackoverflow.com/a/53201316/5600195
【解决方案2】:

使用 boto3 进行 dynamodb 更新的详细信息在网上似乎非常稀少,所以我希望这些替代解决方案有用。

获取/放置

import boto3

table = boto3.resource('dynamodb').Table('my_table')

# get item
response = table.get_item(Key={'pkey': 'asdf12345'})
item = response['Item']

# update
item['status'] = 'complete'

# put (idempotent)
table.put_item(Item=item)

实际更新

import boto3

table = boto3.resource('dynamodb').Table('my_table')

table.update_item(
    Key={'pkey': 'asdf12345'},
    AttributeUpdates={
        'status': 'complete',
    },
)

【讨论】:

  • 虽然像这样使用 AttributeUpdates 是正确的并且应该仍然有效(而且我更喜欢语法而不是 UpdateExpression),但文档中提到此方法是遗留的(因此在某个时间点可能不再有效)。来源:boto3.readthedocs.io/en/latest/reference/services/…
  • AttributeUpdates 是 boto3 中的遗留参数,因此在上面 Dmitry R 的回答中使用 UpdateExpression 可能更好
  • AttributeUpdates 似乎是一个更干净的 API。 :(
  • @AndyHayden AttributeUpdates 更简单,但表达式更强大,因为它们可以被参数化以处理保留字冲突、代码注入和其他问题。
【解决方案3】:

原始代码示例:

response = table.update_item(
    Key={'ReleaseNumber': '1.0.179'},
    UpdateExpression='SET',
    ConditionExpression='Attr(\'ReleaseNumber\').eq(\'1.0.179\')',
    ExpressionAttributeNames={'attr1': 'val1'},
    ExpressionAttributeValues={'val1': 'false'}
)

固定:

response = table.update_item(
    Key={'ReleaseNumber': '1.0.179'},
    UpdateExpression='SET #attr1 = :val1',
    ConditionExpression=Attr('ReleaseNumber').eq('1.0.179'),
    ExpressionAttributeNames={'#attr1': 'val1'},
    ExpressionAttributeValues={':val1': 'false'}
)

在标记的答案中还显示有一个范围键,因此它也应该包含在Key 中。 update_item 方法必须寻找要更新的确切记录,没有批量更新,并且您不能更新过滤到某个条件的一系列值以获取单个记录。 ConditionExpression 有助于使更新具有幂等性;即如果它已经是那个值,就不要更新它。它不像 sql where 子句。

关于看到的具体错误。

ExpressionAttributeNames 是在 UpdateExpression 中使用的键占位符列表,如果键是保留字,则很有用。

来自文档,“表达式属性名称必须以 # 开头,后跟一个或多个字母数字字符”。错误是因为代码没有使用以# 开头的ExpressionAttributeName,也没有在UpdateExpression 中使用它。

ExpressionAttributeValues 是您要更新到的值的占位符,它们必须以 : 开头

【讨论】:

  • 我已经尝试了其他帖子中提到的所有其他选项,但如果我们不提及ExpressionAttributeNames,它们都不起作用 - 谢谢!
【解决方案4】:

如果您不想逐个检查参数以进行更新,我编写了一个很酷的函数,该函数将返回所需的参数以使用 boto3 执行 update_item 方法。

def get_update_params(body):
    """Given a dictionary we generate an update expression and a dict of values
    to update a dynamodb table.

    Params:
        body (dict): Parameters to use for formatting.

    Returns:
        update expression, dict of values.
    """
    update_expression = ["set "]
    update_values = dict()

    for key, val in body.items():
        update_expression.append(f" {key} = :{key},")
        update_values[f":{key}"] = val

    return "".join(update_expression)[:-1], update_values

这是一个简单的例子:

def update(body):
    a, v = get_update_params(body)
    response = table.update_item(
        Key={'uuid':str(uuid)},
        UpdateExpression=a,
        ExpressionAttributeValues=dict(v)
        )
    return response

【讨论】:

  • +1,因为这很棒,但请注意它有两个错误:1. 它不处理保留关键字(如名称) 2. 它不处理以 __ 等符号开头的属性。这两个问题都可以通过使用 ExpressionAttributeNames 来解决
  • 为什么要排除 join 表达式中的最后一项?
  • @MarkRansom 删除尾随逗号
  • 啊,我没有注意到切片在最后一个字符串而不是列表上。现在明白了,谢谢。
【解决方案5】:

基于official example,这是一个简单而完整的解决方案,可用于手动更新(我不推荐)terraform S3 后端使用的表。

假设这是 AWS CLI 显示的表数据:

$ aws dynamodb scan --table-name terraform_lock --region us-east-1
{
    "Items": [
        {
            "Digest": {
                "S": "2f58b12ae16dfb5b037560a217ebd752"
            },
            "LockID": {
                "S": "tf-aws.tfstate-md5"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

您可以将其更新为新的摘要(例如您回滚了状态),如下所示:

import boto3

dynamodb = boto3.resource('dynamodb', 'us-east-1')


try:
    table = dynamodb.Table('terraform_lock')
    response = table.update_item(
        Key={
            "LockID": "tf-aws.tfstate-md5"
        },
        UpdateExpression="set Digest=:newDigest",
        ExpressionAttributeValues={
            ":newDigest": "50a488ee9bac09a50340c02b33beb24b"
        },
        ReturnValues="UPDATED_NEW"
    )
except Exception as msg:
    print(f"Oops, could not update: {msg}")

注意":newDigest": "50a488ee9bac09a50340c02b33beb24b" 开头的:,它们很容易错过或忘记。

【讨论】:

    【解决方案6】:

    更新任意数量的dict 属性的示例,并跟踪更新的数量。使用保留字(即name)。

    不应使用以下属性名称,因为我们将覆盖值:_inc_start

    from typing import Dict
    from boto3 import Session
    
    
    def getDynamoDBSession(region: str = "eu-west-1"):
        """Connect to DynamoDB resource from boto3."""
        return Session().resource("dynamodb", region_name=region)
    
    
    DYNAMODB = getDynamoDBSession()
    
    
    def updateItemAndCounter(db_table: str, item_key: Dict, attributes: Dict) -> Dict:
        """
        Update item or create new. If the item already exists, return the previous value and
        increase the counter: update_counter.
        """
        table = DYNAMODB.Table(db_table)
    
        # Init update-expression
        update_expression = "SET"
    
        # Build expression-attribute-names, expression-attribute-values, and the update-expression
        expression_attribute_names = {}
        expression_attribute_values = {}
        for key, value in attributes.items():
            update_expression += f' #{key} = :{key},'  # Notice the "#" to solve issue with reserved keywords
            expression_attribute_names[f'#{key}'] = key
            expression_attribute_values[f':{key}'] = value
    
        # Add counter start and increment attributes
        expression_attribute_values[':_start'] = 0
        expression_attribute_values[':_inc'] = 1
    
        # Finish update-expression with our counter
        update_expression += " update_counter = if_not_exists(update_counter, :_start) + :_inc"
    
        return table.update_item(
            Key=item_key,
            UpdateExpression=update_expression,
            ExpressionAttributeNames=expression_attribute_names,
            ExpressionAttributeValues=expression_attribute_values,
            ReturnValues="ALL_OLD"
        )
    

    希望它对某人有用!

    【讨论】:

      【解决方案7】:

      使用 eltbus 以前的答案,它对我有用,除了小错误,

      您必须使用 update_expression[:-1] 删除多余的逗号

      【讨论】:

      • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
      猜你喜欢
      • 1970-01-01
      • 2022-01-23
      • 2022-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多