node.js AWS dynamodb updateItem

53

使用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

9个回答

84

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

在 AWS SDK for JavaScript in Node.js 中,已经有一个使用 update 方法的示例代码在这里

例如:

'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);
    });
};

6
你能为我演示一些例子吗?我看过了那个例子,但仍感到困惑。如果我有30个属性,那么我是否需要为这30个属性编写表达式?如果我有新的属性怎么办?谢谢! - Pano
1
您需要指定您只想要更新的属性。因此,如果您需要更新30个属性,则必须为所有30个属性编写更新表达式。请参阅我的更新答案以获取代码示例。 - Khalid T.
2
那么我应该循环遍历输入对象,将属性名称添加到“ExpressionAttributeNames”,将相应的值添加到“ExpressionAttributeValues”,然后生成一个表达式字符串放入UpdateExpression中? - Pano
3
如果我是你,并且需要在 DynamoDB 中存储不可预测的数据,我会将其作为单个 JSON 对象以一个固定的属性名称放置,并让后端应用程序在检索时解析 JSON 对象。 - Khalid T.
6
对我来说似乎不够优雅。对于简单的情况,我们能否只传递一个对象,并以与putItem方法类似的方式更新参数?而对于其他情况,UpdateExpression、ExpressionAttributes等将是相关的。 - darkace
显示剩余2条评论

31

我认为一些例子有点令人困惑。如果我有以下表格列:

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();

这似乎更简单一些。


3
这个例子让我最终明白了语法。谢谢。 - MondQ
谢谢,易于理解。 - kubs
1
因为名字好听,回答也很好,我点了个赞 :) - Daniel Otto
这是一个很好的例子。通过使用ExpressionAttributeNames而不是直接在格式字符串中使用表属性,语法变得过于复杂。在大多数情况下,仅使用ExpressionAttributeValues就足够了。 - h0r53

26

您可以动态更新属性。请见下方代码。

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;
   })
   
}

更新表达式的最后一句以逗号结尾,这样可以吗? - ORcoder
我已经测试并确定挂起逗号不是一个好的解决方案,但我现在也看到你的解决方案会切掉挂起逗号。 - ORcoder
我不建议使用“扫描”功能。随着数据库的增长,它会增加你的成本。你应该创建索引并使用查询索引。 - Cemil Birinci
谁说要扫描了? - ORcoder

17

这里是一个更安全、更实时的实现方法:

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
@thedanotto 通过混淆密钥,它采取了第一步来保护免受注入攻击。 - Arno
谢谢!我不确定它在做什么,但它能正常工作 :D - Mascarpone
5
AWS 的开发者应该为这个 API 感到惭愧。谢谢,它起作用了! - Mathias Gheno Azzolini
这是一个很棒的解决方案,但是天哪,这简直就是做一个部分更新要跳过一些非常高级的技巧啊。 - Martin Devillers

7
这里有一个实用的方法来完成这个任务:
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();
}

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 - jonzee

3
我使用 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);
}

2

2
亲爱的AWS,能否让这更简单些。 - webjay

0

对不起,晚来了一步,但这是谷歌上排名前列的一个非AWS文档结果,没有回答我的用例——在不需要[解]封装的情况下使用DocumentClient并具有动态项目。所以我想把我的意见融合到@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();

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