当地图尚不存在时,如何设置DynamoDB Map属性值

时间:2016-02-13 08:26:01

标签: amazon-dynamodb

你如何" upsert" DynamoDB行的属性。例如。某个项目的SET address.state = "MA",当address尚不存在时?

我觉得我有鸡蛋问题,因为DynamoDB不能让你提前定义一个草率的架构。

如果该address类型M已存在SET #address.#state = :value(对于地图),则互联网会告诉我可以发出类似以下内容的UpdateExpression:

#address

#state:valueaddress分别适当地映射到stateMAaddress

但如果address.state属性已存在,则会出错:

''' ValidationException:更新表达式中提供的文档路径对于更新无效 '''

所以..看起来我要么:

  1. 找出一种方法来" upsert" SET address = {}; SET address.state = 'MA'(例如,SET address = {};在一个命令中)
    1. 我尝试三次(!!!)往返,public int getID(String resourceName,Context context){ Resources resources = context.getResources(); final int resourceId = resources.getIdentifier(resourceName, "drawable", context.getPackageName()); return resourceId; } 失败,然后再试一次。
    2. 如果是后者......我该如何设置空白地图?!?

      呃..我喜欢迪纳摩,但除非我错过了一些明显的东西,否则这有点疯狂..

5 个答案:

答案 0 :(得分:4)

如果父文档不存在,则无法设置嵌套属性。由于address不存在,因此您无法在其中设置属性province。如果在创建项目时将address设置为空地图,则可以实现目标。然后,您可以使用以下参数来调整尚未存在的属性address.province的更新。

var params = {
    TableName: 'Image',
    Key: {
        Id: 'dynamodb.png'
    },
    UpdateExpression: 'SET address.province = :ma',
    ConditionExpression: 'attribute_not_exists(address.province)',
    ExpressionAttributeValues: {
        ':ma': 'MA'
    },
    ReturnValues: 'ALL_NEW'
};
docClient.update(params, function(err, data) {
    if (err) ppJson(err); // an error occurred
    else ppJson(data); // successful response
});

顺便说一句,我不得不用省来取代州,因为州是一个保留词。

答案 1 :(得分:2)

你可以通过两次往返来完成,第一次有条件地设置address的空地图(如果它还不存在),第二次设置state

db.update({
    UpdateExpression: 'SET #a = {}',
    ConditionExpression: 'attribute_not_exists(#a)',
    AttributeExpressionNames: {
        '#a': 'address'
    }
}, ...);

然后:

db.update({
    UpdateExpression: 'SET #a.#b = :v',
    AttributeExpressionNames: {
        '#a': 'address',
        '#b': 'state'
    },
    AttributeExpressionValues: {
        ':v': 'whatever'
    }
}, ...);

答案 2 :(得分:0)

另一种完全不同的方法是在创建父文档时简单地创建address节点。例如,假设您的散列键为id,您可以执行以下操作:

db.put({
    Item: {
        id: 42,
        address: {}
    }
}, ...);

这样您就可以设置address.state值,因为address地图已经存在:

db.update({
    UpdateExpression: 'SET #a.#b = :v',
    AttributeExpressionNames: {
        '#a': 'address',
        '#b': 'state'
    },
    AttributeExpressionValues: {
        ':v': 'whatever'
    }
}, ...);

答案 3 :(得分:0)

一些kotlin代码以递归方式执行此操作,而不管其深度如何。它将父路径的存在设置为条件,如果条件检查失败,则首先递归创建这些路径。它必须位于库的程序包中,以便它可以访问那些程序包的私有字段/类。

package com.amazonaws.services.dynamodbv2.xspec

import com.amazonaws.services.dynamodbv2.document.Table
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException
import com.amazonaws.services.dynamodbv2.xspec.ExpressionSpecBuilder.attribute_exists

fun Table.updateItemByPaths(hashKeyName: String, hashKeyValue: Any, updateActions: List<UpdateAction>) {
    val parentPaths = updateActions.map { it.pathOperand.path.parent() }
            .filter { it.isNotEmpty() }
            .toSet()    // to remove duplicates
    try {
        val builder = ExpressionSpecBuilder()
        updateActions.forEach { builder.addUpdate(it) }
        if (parentPaths.isNotEmpty()) {
            var condition: Condition = ComparatorCondition("=", LiteralOperand(true), LiteralOperand(true))
            parentPaths.forEach { condition = condition.and(attribute_exists<Any>(it)) }
            builder.withCondition(condition)
        }
        this.updateItem(hashKeyName, hashKeyValue, builder.buildForUpdate())
    } catch (e: ConditionalCheckFailedException) {
        this.updateItemByPaths(hashKeyName, hashKeyValue, parentPaths.map { M(it).set(mapOf<String, Any>()) })
        this.updateItemByPaths(hashKeyName, hashKeyValue, updateActions)
    }
}

private fun String.parent() = this.substringBeforeLast('.', "")


答案 4 :(得分:0)

这是我在 Typescript 中编写的一个辅助函数,它使用递归方法进行单层嵌套。

我将顶级属性称为列。

//usage
await setKeyInColumn('customerA', 'address', 'state', "MA")
  // Updates a map value to hold a new key value pair. It will create a top-level address if it doesn't exist.
    
    static async setKeyInColumn(primaryKeyValue: string, colName: string, key: string, value: any, _doNotCreateColumn?:boolean) {

        const obj = {};
        obj[key] = value; // creates a nested value like {address:value}


        // Some conditions depending on whether the column already exists or not

        const ConditionExpression = _doNotCreateColumn ? undefined:`attribute_not_exists(${colName})`
        const AttributeValue = _doNotCreateColumn? value : obj;
        const UpdateExpression = _doNotCreateColumn? `SET ${colName}.${key} = :keyval `: `SET ${colName} = :keyval ` ;


        try{
            const updateParams = {
                TableName: TABLE_NAME,
                Key: {key:primaryKeyValue},
                UpdateExpression,
                ExpressionAttributeValues: {
                    ":keyval": AttributeValue
                },
                ConditionExpression,
                ReturnValues: "ALL_NEW",
            }
            const resp = await docClient.update(updateParams).promise()

            if (resp && resp[colName]) {
                return resp[colName];
            }
            
        }catch(ex){
            //if the column already exists, then rerun and do not create it
            if(ex.code === 'ConditionalCheckFailedException'){
               return this.setKeyInColumn(primaryKeyValue,colName,key, value, true)
            }
            console.log("Failed to Update Column in DynamoDB")
            console.log(ex);
            return undefined
        }
    
    }