Reputation: 3752
I'm using DynamoDB UpdateItem to update records in my DB. A basic function like this is working for me.
var user = {
userID: '123213',
name: 'John Doe',
age: 12,
type: 'creator'
};
var params = {
TableName:table,
Key:{
"UserID": user.userID
},
UpdateExpression: "set Name = :r, Age=:p, Type=:a",
ExpressionAttributeValues:{
":r":user.name,
":p":user.age,
":a":user.type
},
ReturnValues:"UPDATED_NEW"
};
docClient.update(params, function(err, data) {
if (err) {
console.error("Unable to update item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
}
});
But...
If I'd like to only update one attribute, the name, like this:
var user = {
userID: '123213',
name: 'John Smith'
};
var params = {
TableName:table,
Key:{
"UserID": user.userID
},
UpdateExpression: "set Name = :r, Age=:p, Type=:a",
ExpressionAttributeValues:{
":r":user.name,
":p":user.age,
":a":user.type
},
ReturnValues:"UPDATED_NEW"
};
It gives me the error that
ExpressionAttributeValues cannot be NULL.
I know that I could dynamically produce the UpdateExpression
String by checking for values in user, like this:
for (var key in user) {
if (user.hasOwnProperty(key)) {
...add to DynamicUpdateExpression..
}
}
but is there a way that I can tell updateItem to ignore the null values and only update the name
?
Upvotes: 8
Views: 19577
Reputation: 45
In TypeScript I was working this way: (Have worked with only first level attributes)
async function _genericUpdateItem(table: string, id: string, data: any, attributes: string[], timestamp: number): Promise<any> {
const existingItem = _getItemById(table, id);
const expressionAttributeNames={};
const expressionAttributeValues={};
let updateExpression = 'SET ';
attributes.forEach(key => {
if (!data[key]) {
data[key] = existingItem[key];
}
expressionAttributeNames[`#${key}`] = key;
expressionAttributeValues[`:${key}`] = data[key];
updateExpression += `#${key} = :${key}, `;
});
const params = {
TableName: TicketTableName,
Key: { id },
ExpressionAttributeNames: expressionAttributeNames,
ExpressionAttributeValues: expressionAttributeValues,
UpdateExpression: updateExpression.slice(0, -2),
ReturnValues: "ALL_NEW",
};
try {
const result: any = await dynamodb.update(params).promise();
return {
statusCode: 200,
body: result.Attributes
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
body: error
};
}
}
Upvotes: 0
Reputation: 117
I have found another method inspired by @David White's answer above. Also, the OP was very close to getting it right with writing the UpdateExpression and ExpressionAttributeValues dynamically. Here's the code for how to make the dynamic update expression, no matter what properties you're given.
const create = (req, context, callback) => {
const data = JSON.parse(req.body)
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: { title: data.title },
};
params.UpdateExpression = "SET latestUpdate = :updateTime"
params.ExpressionAttributeValues = {}
for (let k in data) {
if (k !== 'title') {
// you don't want to update whatever you've set as your primary key for the table, so you have to ignore this
params.UpdateExpression += `, ${k} = :${k}`
params.ExpressionAttributeValues[`:${k}`] = data[k]
}
}
params.ExpressionAttributeValues[':updateTime'] = Date.now()
dynamodb.update(params)
Upvotes: 2
Reputation: 41
I managed to get the desired result by passing an additional option to my documentClient
constructor.
const documentClient = new AWS.DynamoDB.DocumentClient({convertEmptyValues: true});
This converts empty strings to true
(aka NULL
in dynamodb world) and fields absent in my http request json object do not affect the database or cause the request to fail.
Upvotes: 1
Reputation: 656
This is a much simpler answer.
It works when you consider ExpressionAttributeValues an object.
Here's the code:
params.TableName = ddbTable;
params.UpdateExpression = "set LastPostedDateTime = :l" ;
if (req.body.AttachmentDescription) { params.UpdateExpression += ", AttachmentDescription = :d"; }
if (req.body.AttachmentURL) { params.UpdateExpression += ", AttachmentURL = :a"; }
so first we build up the expression if values are available to be passed, using a simple concatenation technique.
Then we supply the values:
params.ExpressionAttributeValues = {};
params.ExpressionAttributeValues[':l'] = formattedDate ;
if (req.body.AttachmentDescription) { params.ExpressionAttributeValues[':d']= req.body.AttachmentDescription ; }
if (req.body.AttachmentURL) { params.ExpressionAttributeValues[':a']= req.body.AttachmentURL ; }
The difficulty is with ExpressionAttributeValues which here, we treat as an object and we can add to the object if we first define it as an object, hence the {}.
Then if the object does not already have the property name it adds it and then adds the value.
The net result is you can have very wide flat records as your records can be extended with variable field names. I.e. this application lists a URL and descriptor. With variable field names, I could add more URLs and descriptors to the same record. There is ultimately a memory limit, yet this type of application, for a few variable fields, would be sufficient for my application.
Upvotes: 6
Reputation: 1499
I was asking the same question...In Java there's the SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES but I couldn't find anything like that in aws-sdk for nodejs.
You could use AttributeUpdates instead of UpdateExpression to make a cleaner workaround:
const AWS = require(aws-sdk);
const bluebird = require('bluebird');
const _ = require('lodash');
AWS.config.setPromisesDependency(bluebird);
const dynamodb = new AWS.DynamoDB.DocumentClient();
var skipNullAttributes = (attributes) => {
return _.omitBy(attributes, (attr) => {
return _.isNil(attr.Value);
});
}
var update = (id, attributes) => {
var params = {
TableName : 'MyTableName',
Key : { id: id },
AttributeUpdates: skipNullAttributes(attributes)
};
return dynamodb.update(params).promise();
}
exports.handler = (event, context, callback) => {
var body = JSON.parse(event.body);
var userId = event.pathParameters.id;
var attributes = {
firstName: { Action: 'PUT', Value: body.firstName },
lastName : { Action: 'PUT', Value: body.lastName }
};
update(userId, attributes)
.then((result) => console.log(result) )
.catch((error) => console.error(error) );
callback(null, {statusCode: 200, body: JSON.stringify({message: 'done!'})});
}
Upvotes: 4