DynamoDB Boto3中update_item的示例

75
根据文档,我试图创建一个更新语句,在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: 在调用UpdateItem操作时发生ValidationException错误:ExpressionAttributeNames包含无效键:语法错误;键:"attr1"

如果有人做过类似于我尝试实现的内容,请分享例子。


我知道这是一个很老的问题,但我在想...如果你的项已经存在,"put_item"会覆盖它。 如果索引键无法更改,为什么要使用"update_item"? - Claudiu
@Claudiu 我猜只需要更新 不是全部 的字段。 - mccc
10个回答

86

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

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

1
需要注意的是,您最初的错误是指ExpressionAttributeNames,该名称已从本答案中提供的示例中排除...虽然您必须包含所有键的值以更新项目,但这并不是您最初遇到的直接错误。 - James
1
值得一提的是,这里的“Key”应该包含该“索引”的所有键,无论是“本地二级索引”还是“全局二级索引”。 在这里,“ReleaseNumber”是“主键”,而“Timestamp”默认为“排序键”。 - Abhishake Gupta
1
这个解决方案对我没有起作用 - 我必须在更新项目时添加ExpressionAttributeNamesExpressionAttributeValues - AADProgramming
我尝试了上述方法,但没有成功。尝试过使用 ExpressionAttributeNames 但仍然没用。出现了错误 botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem operation: The provided key element does not match the schema - Harsh Math
这个被接受的答案不起作用。编辑队列已满,因此无法进行编辑。应该将下面的另一个答案标记为被接受的答案,该答案使用“ExpressionAttributeNames”。https://dev59.com/DFsW5IYBdhLWcg3w7KxL#53201316 - Utkarsh

51

在网上,使用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',
    },
)

9
尽管像这样使用AttributeUpdates是正确的,并且仍然有效(我更喜欢这种语法而不是UpdateExpression),但文档提到这种方法已经过时(因此在某个时间点可能不再起作用)。来源:https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Table.update_item - Aart Goossens
AttributeUpdates是boto3中的遗留参数,因此最好使用Dmitry R上面提供的UpdateExpression。 - gimbel0893
20
AttributeUpdates 看起来是一个更清晰的 API。:( - Andy Hayden
@AndyHayden AttributeUpdates更简单,但Expressions更强大,因为它们可以被参数化以处理保留字冲突、代码注入和其他问题。 - Davos
2
他们取消AttributeUpdates这一事实让我非常生气。 - Captain Jack Sparrow
你不知道第一个get/put方法对我有多大用处! - ERJAN

28

如果您不想逐个检查更新参数,我编写了一个很棒的函数,可以返回执行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来解决。 - orcaman
为什么你在join表达式中排除了最后一个项目? - Mark Ransom
@MarkRansom 删除结尾逗号 - Jam M. Hernandez Quiceno
1
啊,我没注意到切片是在最后一个字符串上而不是列表上。现在有意义了,谢谢。 - Mark Ransom
','.join()会用逗号将它们连接起来,但不会在最后加上一个逗号。 - UnderSampled

22

原始代码示例:

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是您要更新为的值的占位符,它们必须以:开头。


1
我已经尝试了其他帖子中提到的所有选项,但如果我们不提及“ExpressionAttributeNames”,它们都无法正常工作 - 谢谢! - AADProgramming
文档建议将“Key”等值指定为    Key = {'ReleaseNumber': {'S': '1.0.179'}} - MikeW

5
基于官方示例,这里提供了一个简单而完整的解决方案,可用于手动更新(虽然不建议这样做)由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" 开头的 : 很容易被忽略或遗忘。

4

对Jam M. Hernandez Quiceno的回答进行了小更新,包括ExpressionAttributeNames以避免出现以下错误:

"errorMessage": "An error occurred (ValidationException) when calling the UpdateItem operation: 
Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: timestamp",

def get_update_params(body):
    """
    Given a dictionary of key-value pairs to update an item with in DynamoDB,
    generate three objects to be passed to UpdateExpression, ExpressionAttributeValues, 
    and ExpressionAttributeNames respectively.
    """
    update_expression = []
    attribute_values = dict()
    attribute_names = dict()

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

    return "set " + ", ".join(update_expression), attribute_values, attribute_names

示例用法:

update_expression, attribute_values, attribute_names = get_update_params(
    {"Status": "declined", "DeclinedBy": "username"}
)

response = table.update_item(
    Key={"uuid": "12345"},
    UpdateExpression=update_expression,
    ExpressionAttributeValues=attribute_values,
    ExpressionAttributeNames=attribute_names,
    ReturnValues="UPDATED_NEW"
)
print(response)

3

多字段的简单示例:

import boto3
dynamodb_client = boto3.client('dynamodb')

dynamodb_client.update_item(
    TableName=table_name,
    Key={
        'PK1': {'S': 'PRIMARY_KEY_VALUE'},
        'SK1': {'S': 'SECONDARY_KEY_VALUE'}
    }
    UpdateExpression='SET #field1 = :field1, #field2 = :field2',
    ExpressionAttributeNames={
        '#field1': 'FIELD_1_NAME',
        '#field2': 'FIELD_2_NAME',
    },
    ExpressionAttributeValues={
        ':field1': {'S': 'FIELD_1_VALUE'},
        ':field2': {'S': 'FIELD_2_VALUE'},
    }
)

1
以简单的方式,您可以使用下面的代码将项目值更新为新值:
    response = table.update_item(
       Key={"my_id_name": "my_id_value"}, # to get record
            
       UpdateExpression="set item_key_name=:item_key_value", # Operation action (set)
       ExpressionAttributeValues={":value": "new_value"}, # item that you need to update
       
       ReturnValues="UPDATED_NEW" # optional for declarative message
       )

1

以下是一个例子,可以更新任何以 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"
    )

希望对某些人有所帮助!

-1

使用eltbus之前的答案,对我很有用,除了一个小错误。

你需要使用update_expression[:-1]删除额外的逗号。


1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

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