【问题标题】:node.js AWS dynamodb updateItemnode.js AWS dynamodb updateItem
【发布时间】:2021-09-22 06:18:37
【问题描述】:

有没有办法用updateItem实现以下几点: 1.如果DynamoDB中不存在属性,则添加属性 2. 如果 DynamoDB 中存在属性,则更新属性 3. 如果参数中不包含属性,则保留这些属性。

这是一个例子: 这是 DynamoDB 中的对象:

{
    id: "1234",
    variable1: "hello",
    variable2: "world"
}

这是我要更新的输入:

{
    id: "1234",
    variable1: "hello2",
    variable23: "dog"  // the variable name "variable23" could be anything
}

这是我想要实现的 DynamoDB 中的更新项:

{
    id: "1234",
    variable1: "hello2",
    variable2: "world",
    variable23: "dog"
}

“variable23”可以是任何变量名作为输入。

请帮忙!我使用 node.js,如果有人能告诉我一些如何实现这一点的代码,我真的很感激。

谢谢!

【问题讨论】:

    标签: node.js amazon-web-services amazon-dynamodb


    【解决方案1】:

    这正是 AWS.DynamoDB.DocumentClient 的 update 方法所做的。

    已经有一个示例代码介绍了如何在 Node.js 中为 AWS SDK for JavaScript 使用 update 方法 here

    例如:

    'use strict';
    
    const aws = require('aws-sdk');
    
    // It is recommended that we instantiate AWS clients outside the scope of the handler 
    // to take advantage of connection re-use.
    const docClient = new aws.DynamoDB.DocumentClient();
    
    exports.handler = (event, context, callback) => {
        const params = {
            TableName: "MYTABLE",
            Key: {
                "id": "1"
            },
            UpdateExpression: "set variable1 = :x, #MyVariable = :y",
            ExpressionAttributeNames: {
                "#MyVariable": "variable23"
            },
            ExpressionAttributeValues: {
                ":x": "hello2",
                ":y": "dog"
            }
        };
    
        docClient.update(params, function(err, data) {
            if (err) console.log(err);
            else console.log(data);
        });
    };
    

    【讨论】:

    • 你能告诉我一些关于我的例子的代码吗?我看过那个例子,但仍然感到困惑。如果我有 30 个属性,那么我需要为 30 个属性编写表达式吗?如果我有新属性怎么办?谢谢!
    • 您需要指定只希望更新的属性。因此,如果您需要更新 30 个属性,则必须为所有 30 个属性编写更新表达式。请参阅我更新的代码示例答案。
    • 所以我应该遍历输入对象,将属性名称添加到“ExpressionAttributeNames”,将相应的值添加到“ExpressionAttributeValues”,然后生成一个表达式字符串放入UpdateExpression?
    • 如果我是你,并且我应该像你在 DynamoDB 中那样存储不可预测的数据,我会将它按原样作为单个 JSON 对象使用一个固定的属性名称,并让后端应用程序在检索时解析 JSON 对象。
    • 对我来说似乎不优雅。对于一个简单的情况,我们不能只传递一个对象并以类似于 putItem 方法的方式更新参数吗?!对于其他情况,UpdateExpression、ExpressionAttributes 等将是相关的
    【解决方案2】:

    您可以动态更新属性。见下面的代码。

    export const update = (item) => {
      console.log(item)
      const Item = {
        note: "dynamic",
        totalChild: "totalChild",
        totalGuests: "totalGuests"
      };
      let updateExpression='set';
      let ExpressionAttributeNames={};
      let ExpressionAttributeValues = {};
      for (const property in Item) {
        updateExpression += ` #${property} = :${property} ,`;
        ExpressionAttributeNames['#'+property] = property ;
        ExpressionAttributeValues[':'+property]=Item[property];
      }
    
      
      console.log(ExpressionAttributeNames);
    
    
      updateExpression= updateExpression.slice(0, -1);
      
      
       const params = {
         TableName: TABLE_NAME,
         Key: {
          booking_attempt_id: item.booking_attempt_id,
         },
         UpdateExpression: updateExpression,
         ExpressionAttributeNames: ExpressionAttributeNames,
         ExpressionAttributeValues: ExpressionAttributeValues
       };
    
       return dynamo.update(params).promise().then(result => {
           return result;
       })
       
    }
    

    【讨论】:

    • updateExpression的最后一个子句以逗号结尾可以吗?
    • 我已经测试并确定挂逗号是不行的,但我现在也看到你的解决方案切掉了挂逗号
    • 我不建议使用“扫描”功能。随着数据库的增长,这将增加您的成本。您应该创建索引并使用查询索引。
    • 谁说过扫描?
    【解决方案3】:

    这里有一个更安全和最新的功能来实现这一点:

    const {
      DynamoDBClient, UpdateItemCommand,
    } = require('@aws-sdk/client-dynamodb');
    const { marshall, unmarshall } = require('@aws-sdk/util-dynamodb');
    
    const client = new DynamoDBClient({});
    
    /**
     * Update item in DynamoDB table
     * @param {string} tableName // Name of the target table
     * @param {object} key // Object containing target item key(s)
     * @param {object} item // Object containing updates for target item
     */
    const update = async (tableName, key, item) => {
      const itemKeys = Object.keys(item);
    
      // When we do updates we need to tell DynamoDB what fields we want updated.
      // If that's not annoying enough, we also need to be careful as some field names
      // are reserved - so DynamoDB won't like them in the UpdateExpressions list.
      // To avoid passing reserved words we prefix each field with "#field" and provide the correct
      // field mapping in ExpressionAttributeNames. The same has to be done with the actual
      // value as well. They are prefixed with ":value" and mapped in ExpressionAttributeValues
      // along witht heir actual value
      const { Attributes } = await client.send(new UpdateItemCommand({
        TableName: tableName,
        Key: marshall(key),
        ReturnValues: 'ALL_NEW',
        UpdateExpression: `SET ${itemKeys.map((k, index) => `#field${index} = :value${index}`).join(', ')}`,
        ExpressionAttributeNames: itemKeys.reduce((accumulator, k, index) => ({ ...accumulator, [`#field${index}`]: k }), {}),
        ExpressionAttributeValues: marshall(itemKeys.reduce((accumulator, k, index) => ({ ...accumulator, [`:value${index}`]: item[k] }), {})),
      }));
    
      return unmarshall(Attributes);
    };
    

    【讨论】:

    • 为什么更安全?
    • @thedanotto 它采取了第一步通过混淆密钥来防止注入
    • 谢谢!我不确定在做什么,但它有效:D
    • AWS 开发人员应该为这个 API 感到羞耻。谢谢,成功了!
    【解决方案4】:

    这是一个实用的方法:

    update: async (tableName, item, idAttributeName) => {
    
        var params = {
            TableName: tableName,
            Key: {},
            ExpressionAttributeValues: {},
            ExpressionAttributeNames: {},
            UpdateExpression: "",
            ReturnValues: "UPDATED_NEW"
        };
    
        params["Key"][idAttributeName] = item[idAttributeName];
    
        let prefix = "set ";
        let attributes = Object.keys(item);
        for (let i=0; i<attributes.length; i++) {
            let attribute = attributes[i];
            if (attribute != idAttributeName) {
                params["UpdateExpression"] += prefix + "#" + attribute + " = :" + attribute;
                params["ExpressionAttributeValues"][":" + attribute] = item[attribute];
                params["ExpressionAttributeNames"]["#" + attribute] = attribute;
                prefix = ", ";
            }
        }
    
        return await documentClient.update(params).promise();
    }
    

    【讨论】:

      【解决方案5】:

      我认为其中一些示例有点令人困惑。如果我有下表列

      ID  | Name | Age
      

      我想更新Name 属性并保持Age 属性不变。

      const updateName = async () => {
        const aws = require('aws-sdk');
        const docClient = new aws.DynamoDB.DocumentClient();
      
        const newName = 'Bob';
      
        const params = {
          TableName: 'myTable',
          Key: {
            ID: 'myId',
          },
          UpdateExpression: 'set Name = :r',
          ExpressionAttributeValues: {
            ':r': newName,
          },
        };
      
        await docClient.update(params).promise();
      }
      
      updateName();
      

      这似乎更简单了。

      【讨论】:

      • 这个例子让我终于明白了语法。谢谢。
      • 谢谢,通俗易懂
      • 赞成这个名字,而且答案很好:)
      【解决方案6】:

      这是我使用的批量更新功能,重点是可读性。

      const documentClient = new AWS.DynamoDB.DocumentClient(options);
      
      const update = async ({  tableName,  primaryKeyName,  primaryKeyValue,  updates }) => {
          const keys = Object.keys(updates)
          const keyNameExpressions = keys.map(name => `#${name}`)
          const keyValueExpressions = keys.map(value => `:${value}`)
          const UpdateExpression = "set " + keyNameExpressions
              .map((nameExpr, idx) => `${nameExpr} = ${keyValueExpressions[idx]}`)
              .join(", "),
          const ExpressionAttributeNames = keyNameExpressions
              .reduce((exprs, nameExpr, idx) => ({ ...exprs, [nameExpr]: keys[idx] }), {})
          const ExpressionAttributeValues = keyValueExpressions
              .reduce((exprs, valueExpr, idx) => ({ ...exprs, [valueExpr]: updates[keys[idx]] }), {})
      
          const params = {
              TableName: tableName,
              Key: { [primaryKeyName]: primaryKeyValue },
              UpdateExpression,
              ExpressionAttributeNames,
              ExpressionAttributeValues
          };
          return documentClient.update(params).promise();
      }
      
      // USAGE
      let { ID, ...fields} = {
          ID: "1234",
          field1: "hello",
          field2: "world"
      }
      
      update('tableName', 'ID', ID, fields) 
      

      【讨论】:

      • 第 9 行有逗号而不是分号(我无法对其进行编辑,只需稍作改动),但一切都像一个魅力:D
      【解决方案7】:

      我使用 dynamo DB 客户端制作了这个:

      updateItem(item: { [key: string]: any }) {
        const marshaledItem = marshall(item, { removeUndefinedValues: true, });
        const marshaledItemKeys = Object.entries(marshaledItem);
      
        const params: UpdateItemInput = {
          TableName: this.tableName,
          UpdateExpression: 'set',
          ExpressionAttributeNames: {},
          ExpressionAttributeValues: {},
          Key: marshall({ pk: item.pk, sk: item.sk })
        };
      
        marshaledItemKeys.forEach(([key, value] ) => {
          if (key === 'sk' || key === 'pk') return;
          params.UpdateExpression += ` #${key} = :${key},`;
          params.ExpressionAttributeNames[`#${key}`] = key;
          params.ExpressionAttributeValues[`:${key}`] = value;
        })
      
        params.UpdateExpression = params.UpdateExpression.slice(0, -1);
        console.log('REVEAL YOURSELF, YOU MIGHTY BUG: ', params);
      
        return this.dynamoDbClient.send(new UpdateItemCommand(params));
      }
      

      这对我来说非常有效。 Marshall 和 unmarshall 属于:

      import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
      

      如果我传递 undefined 的值,它将从查询中删除这些值。如果我保留它们null,它将用null覆盖它们

      这是我如何使用它的示例:

      async updatePatient(data: PutPatientData): Promise<DBPatient> {
          const {
            pk,
            sk,
            databaseId,
            betterPatientId,
            clinicientPatientId,
            latestClinicientCaseId,
            firstName,
            lastName,
            email,
            birthday,
            gender,
            phone,
          } = data;
      
          if (!pk && !databaseId) throw Error('Please provide PK or databaseId');
          if (!sk && !betterPatientId) throw Error('Please provide SK or betterPatientId');
      
          const patientRequestData = {
            pk: pk || `DATABASE#${databaseId}`,
            sk: sk || `PATIENT#${betterPatientId}`,
            itemType: 'Patient',
            lastUpdatedAt: DateTime.now().toString(),
            latestClinicientCaseId: latestClinicientCaseId || undefined,
            clinicientPatientId: clinicientPatientId || undefined,
            firstName: firstName || undefined,
            lastName: lastName || undefined,
            email: email || undefined,
            birthday: birthday || undefined,
            gender: gender || undefined,
            phone: phone || undefined,
            betterPatientId: betterPatientId || undefined,
          } as DBPatient;
          // Composite key
          if (email && birthday) patientRequestData['itemId'] = `PATIENT#${email}#${birthday}`;
              console.log('PATIENT UPDATE', patientRequestData)
          return this.updateItem(patientRequestData).then(() => patientRequestData);
      }
      

      【讨论】:

        【解决方案8】:

        对于这里的解决方案的速度模板版本的任何人,他们在他们的文档中记录了一些东西,我花了一段时间才找到,所以如果它可以帮助其他人,这里是一个链接

        https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html#id4 在“选项 2”下

        【讨论】:

        • 亲爱的 AWS,请简化一下。
        【解决方案9】:

        很抱歉迟到了,但这是 google 上的顶级 [非 aws-documentation] 结果并且没有回答我的用例 - 使用 DocumentClient 而没有 [un] marshalling 并且有一个动态项目。所以我想在这里放下我的 2 美分,并尝试通过合并来自 @khalid-t 的批准答案和来自 @Arno 的答案来提供帮助。

        'use strict';
        const aws = require('aws-sdk');
        const docClient = new aws.DynamoDB.DocumentClient();
        const updateItem = async (pk, item) => await docClient.update({
            TableName,
            Key: {pk},
            UpdateExpression: 'set ' + Object.keys(item).map(k => `#${k} = :${k}`).join(', '),
            ExpressionAttributeNames: Object.entries(item).reduce((acc, cur) => ({...acc, [`#${cur[0]}`]: cur[0]}), {}),
            ExpressionAttributeValues: Object.entries(item).reduce((acc, cur) => ({...acc, [`:${cur[0]}`]: cur[1]}), {}),
        }).promise();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-24
          相关资源
          最近更新 更多