Nested attributes query on DynamoDB - amazon-web-services

I have something like this on Dynamo:
{
"mail": "user#anonymous.com",
"data": {
"type": 1
}
}
I have an index on "mail" attribute and I'm trying to query over all data found with an specified mail filtering the attribute "data". Something like this:
const params = {
TableName: 'tableName',
IndexName: "mail_index",
KeyConditionExpression: "#mail = :mail",
FilterExpression: '#status = :val',
ExpressionAttributeNames: {
'#mail': 'mail',
'#status': 'data.type'
},
ExpressionAttributeValues: {
':mail': 'user#anonymous.com',
':val': {N: 5}
}
};
dynamoDoc.query(params, (err, data) => {
console.log(data);
});
But I'm always getting an empty result. What am I doing wrong?

Try this:
const params = {
TableName: 'tableName',
IndexName: "mail_index",
KeyConditionExpression: "#mail = :mail",
FilterExpression: '#data.#type = :val',
ExpressionAttributeNames: {
'#mail': 'mail',
'#data': 'data',
'#type': 'type'
},
ExpressionAttributeValues: {
':mail': 'user#anonymous.com',
':val': {N: 5}
}
};
Because both data and type are reserved words, DynamoDB needs both of them to be escaped.

Related

Querying with FilterExpression CONTAINS method

I want to query a specific row from a table based on a track's genre and release year.
Task: Query rows with release_year=2018 AND genre="pop"
Tracks Table:
trackId----------------release_year----------------genres----------------count
trackId: "7sT7kZEYd1MrmzLLIRVZas", release_year: 2018, genres: ["pop","rap", "hip-hop"], count: 7
Below code is how I am attempting to make this query as shown in FilterExpression. Currently I'm checking if the Table's attribute: genres (Array), contains :genre, which is a String sent from the client in the request. How would one set up a GSI in a way to make this query?
let queryParams = {
TableName: tableName,
IndexName: req.query.index,
KeyConditionExpression: 'release_year = :release_year and genres = :genre',
ExpressionAttributeValues: { ':release_year': parseInt(req.query.year), ':genre': req.query.genre},
FilterExpression : "contains (genres, :genre)",
ScanIndexForward: false,
Limit: 50
}
dynamodb.query(queryParams, (err, data) => {
if (err) {
res.statusCode = 500;
res.json({error: 'Could not load items: ' + err});
} else {
res.json(data.Items);
}
});
GSI should be created with release_year as the PK. Generes is part of the FilterExpression only.
let queryParams = {
TableName: tableName,
IndexName: req.query.index,
KeyConditionExpression: 'release_year = :release_year',
ExpressionAttributeValues: { ':release_year': parseInt(req.query.year), ':genre': req.query.genre},
FilterExpression : "contains (genres, :genre)",
ScanIndexForward: false,
Limit: 50
}
dynamodb.query(queryParams, (err, data) => {
if (err) {
res.statusCode = 500;
res.json({error: 'Could not load items: ' + err});
} else {
res.json(data.Items);
}
});

How can I update a nested field in DynamoDB?

I defined a DynamoDB table and it has a nested field site. I'd like to update the field site.enable in below code. But when I run the update command I got this error:
ValidationException: The document path provided in the update expression is invalid for update`
What should I do in order to fix the issue?
{
TableName: 'MyTable',
Key: {
id: '4b7020d2-2d19-4aeb-7f27e49d5bec',
type: '80422149-c97d-4a1a-7bf20ef57056',
},
UpdateExpression: 'set #site.#siteenable= :siteenable',
ExpressionAttributeValues: {
':siteenable': true,
},
ExpressionAttributeNames: {
'#siteenable': 'enable',
'#site': 'site',
}
}
You don't mention a programming language, so I'm going to assume what I'm used to: Python.
In Python there are two ways you can do this:
The lower level client API, which requires you to format the data the way DynamoDB would
def enable_site_with_client():
ddb = boto3.client("dynamodb")
ddb.update_item(
TableName=TABLE_NAME,
Key={
"PK": {"S": "SITE_ENTRY"}
},
UpdateExpression="SET #site.#enabled = :update_value",
ExpressionAttributeNames={
"#site": "site",
"#enabled": "enabled"
},
ExpressionAttributeValues={
":update_value": {"BOOL": True}
}
)
The higher level resource API, which allows you to use the language native data structures
def enable_site_with_resource():
ddb = boto3.resource("dynamodb")
ddb.Table(TABLE_NAME).update_item(
Key={
"PK": "SITE_ENTRY"
},
UpdateExpression="SET #site.#enabled = :update_value",
ExpressionAttributeNames={
"#site": "site",
"#enabled": "enabled"
},
ExpressionAttributeValues={
":update_value": True
}
)
I have tested both of these and they work.
Given code works fine if the map site exists already, seeing the error message, it looks like the path site doesn't exist.
We could create empty map during the create of the document, then update it easily OR
We could create the map during the update "set #site = :siteValue"
Here is slightly modified query which creates the map.
const dynamodb = new AWS.DynamoDB();
let docClient = new AWS.DynamoDB.DocumentClient();
docClient.update(
{
TableName: "MyTable",
Key: {
id: "4b7020d2-2d19-4aeb-7f27e49d5bec",
type: "80422149-c97d-4a1a-7bf20ef57056",
},
UpdateExpression: "set #site = :siteValue",
ExpressionAttributeValues: {
":siteValue": { enable: true },
},
ExpressionAttributeNames: {
"#site": "site",
},
},
function (error, result) {
console.log("error", error, "result", result);
}
);
Here is an example for Java SDK V2 that will update root->nested->someValue
Map<String, AttributeValue> attributeValues = new HashMap<>();
attributeValues.put(":myValue", AttributeValue.builder().s(jsonString).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder()
.tableName("my_table_name")
.key(keyToUpdate)
.updateExpression("SET nested.someValue= :myValue")
.expressionAttributeValues(attributeValues)
.build();
client.updateItem(updateRequest);

DynamoDB update list of list items

I need your help to update/add item in a list of list of DynamoDB.
I want to add and update a list that is composed by a list.
How can i do that? Thanks for your help
Here my example:
DATABASE
Item{1}
idProject:"15azeze-55ze"
dateCreationProjectString: 08/01/2018 14:6:32
environnements: List[1]
0 MAP {3}
idEnvironnement: "11-aa",
name:"Exemple Environnement"
tasks[0]
NodeJS code :
let newTask = {
authorTask : "Toto",
dateCreationTask: "01/01/1960",
idTask: "154-141-aa41",
nameTask: "Task name ..."
};
dynamodbdc.update({
TableName: "projects",
Key: { idProject: "15azeze-55ze", idEnvironnement:"11-aa" },
ReturnValues: 'ALL_NEW',
UpdateExpression: 'set #environnements.#tasks = list_append(if_not_exists(#environnements.#tasks, :empty_list), :newTask)',
ExpressionAttributeNames: {
'#environnements' : 'environnements',
'#tasks' : 'tasks',
},
ExpressionAttributeValues: {
':newTask': [newTask],
':empty_list': []
}
}, function(error, stdout) {
if(error){
console.log("error==", error)
else {
console.log("Nice thank you !!")
}
});
I would suggest to keep the tasks attribute as SET ('SS') which will simplify to NOT add the duplicate values. Otherwise, it would be difficult to fulfil the scenario.
Also, you may the index of the environments attribute list to form the update expression correctly. There is no options in the dynamodb api to workout the index automatically.
The below code should work if you define the tasks attribute as SET.
Insert item code:-
var params = {
TableName : table,
Item : {
"idProject" : "15azeze-55ze",
"dateCreationProjectString" : Date(),
"environnements" : [{
"idEnvironnement" : "11-aa",
"name" : "Example Environments",
"tasks" : docClient.createSet(["1"])
}]
}
};
console.log("Adding a new item...");
docClient.put(params, function(err, data) {
if (err) {
console.error("Unable to add item. Error JSON:", JSON.stringify(err,
null, 2));
} else {
console.log("Added item:", JSON.stringify(data, null, 2));
}
});
Sample update item working code:-
The ADD operation will ensure that it doesn't add duplicates to the SET
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "projects",
Key: {
"idProject" : "15azeze-55ze"
},
UpdateExpression: 'ADD environnements['+0+'].tasks :tasksVal',
ExpressionAttributeValues: {
":tasksVal": docClient.createSet(["2"])
},
ReturnValues: "UPDATED_NEW"
};
console.log("Updating the item...");
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));
}
});

Dynamo DB - Is is possible to update/insert nested field

I have a document in the dynamo DB in the following format.
{
key1: "value1",
key2: {sKeya: "A", sKeyB: "B" }
}
Is it possible to update (via JavaScript) directly nested field in the dynamo DB document? BCOZ I don't want to update key2 with all values again.
Example: I tried the below query, but did not work. It rather created one more field key2.sKeya: "ABC"
UpdateExpression: "set #field = :value",
ExpressionAttributeNames: {
"#field": "key2.sKeya"
},
ExpressionAttributeValues: {
":value": "ABC",
}
Has any one faced similar issue?
You need to define the object key as separate Attribute names.
So one alias for "key2" and one alias for "sKeya".
const AWS = require("aws-sdk");
const DOCCLIENT = new AWS.DynamoDB.DocumentClient();
exports.handler = (event, context, callback) => {
let entry = {
TableName: "test",
Key: {
"key1": "value1",
},
UpdateExpression: "set #parent.#child = :value",
ExpressionAttributeNames: {
"#parent": "key2",
"#child": "sKeya"
},
ExpressionAttributeValues: {
":value": "ABC",
},
ReturnValues: "UPDATED_NEW"
};
DOCCLIENT.update(entry, function(err, data) {
callback(null, 'done');
});
};

Append to or create StringSet if it doesn't exist

So this should be simple...
I want to append a string to a StringSet in a DynamoDB if it exists, or create the StringSet property if it doesn't and set the value. If we could initialize the StringSet on creation with an empty array, it would be fine, but alas we can not.
Here's what I have so far:
const companiesTable = 'companies';
dynamodb.updateItem({
TableName: companiesTable,
Key: {
id: {
S: company.id
}
},
UpdateExpression: 'ADD socialAccounts = list_append(socialAccount, :socialAccountId)',
ExpressionAttributeValues: {
':socialAccountId': {
'S': [socialAccountId]
}
},
ReturnValues: "ALL_NEW"
}, function(err, companyData) {
if (err) return cb({ error: true, message: err });
const response = {
error: false,
message: 'Social account created',
socialAccountData
};
cb(response);
});
I've also tried...
UpdateExpression: 'SET socialAccounts = list_append(socialAccounts, :socialAccountId)',
ExpressionAttributeValues: {
':socialAccountId': {
S: socialAccountId
}
},
and...
UpdateExpression: 'ADD socialAccounts = :socialAccountId',
ExpressionAttributeValues: {
':socialAccountId': {
S: socialAccountId
}
},
and...
UpdateExpression: 'SET socialAccounts = [:socialAccountId]',
ExpressionAttributeValues: {
':socialAccountId': socialAccountId
},
and...
UpdateExpression: 'ADD socialAccounts = :socialAccountId',
ExpressionAttributeValues: {
':socialAccountId': socialAccountId
},
Among about every other variation of the above. Am I dumb? Is DynamoDB not capable of simple write/updates to an array type field? Do I REALLY have to lookup the item first to see if it has that field before I try to either add or set that field, because I can't instantiate that field with an empty array?
The ADD action handles the create/update logic, but only supports numbers and sets. You are trying to add a string type 'S'. You need to wrap this string in an array and pass it as a string set 'SS'. You also don't need the equals sign.
Your UpdateExpression and ExpressionAttributeValues should look like this:
UpdateExpression: 'ADD socialAccounts :socialAccountId',
ExpressionAttributeValues: {
':socialAccountId': {
'SS': [socialAccountId]
}
},
More information about updating items can be found here