How to query a table by using IN conditions in DynamoDB - node.js

Struggling to find an example of how to query a table to return rows with ids from a given list.
The query below throws on the inclusion of IN
var params = {
id: '7deb3df9-552b-47a4-aef3-ad601f141d50'
};
var p = {
TableName: 'players',
KeyConditionExpression: 'id IN (:id)',
ExpressionAttributeValues: buildQuery(params)
};

You can't use "IN" operator with KeyConditionExpression, please see details in this SO question
You may want to use batchGetItem instead of query, which is not so efficient though.
Here is how your params could look like:
var params = {
RequestItems: {
'players': {
Keys: [{
id: "7deb3df9-552b-47a4-aef3-ad601f141d50",
rangeKey: "<range key 1>" // <--- if your table has a range key, you must specify its value here
}, {
id: "<ANOTHER ID 2>",
rangeKey: "<range key 2>"
}, {
id: "<ANOTHER ID 3>",
rangeKey: "<range key 3>"
}]
}
}
};
dynamodbDoc.batchGet(params, function(err, data) {
});

Related

Retrieving a StringSet value using DynamoDB.DocumentClient in Node

The DynamoDB.DocumentClient automatically marshals & unmarshals values between JavaScript types DynamoDB's more descriptive AttributeMap type. However, when working with an Item that has a StringSet attribute, it does not seem to do the conversion automatically.
When adding a StringSet attribute to the table using DocumentClient, I use the createSet(...) method to convert the array to a Set. When retrieving the value back, what is the inverse of createSet(...)? Is the best practice to just access the Set's .values directly? And if so, is that documented somewhere?
Here's sample code adding an Item with a StringSet attribute, then retrieving that item:
const docClient = new DocumentClient();
const TableName = "StringSets-Example";
const PK = "Names";
const Values = ["Peter", "Paul", "Mary"];
const putParams = {
TableName,
Item: {
PK,
names: docClient.createSet(Values)
}
}
await docClient.put(putParams).promise();
// ... some time later, I can retrieve the value with ...
const getParams = {
TableName,
Key: { PK }
}
const result = await docClient.get(getParams).promise();
The result.Item there is a Set object, whereas I would expect it to be the same array I passed into createSet(...).
If interested in seeing this live, this repo has a fully-functioning example. Clone it, npm install, and run index.js and you'll see something like:
$ ./index.js
Running On: darwin 19.6.0
Node version: v12.20.0
AWS SDK version: 2.799.0
-------------------------
Creating table "StringSets-Example"
Waiting for "StringSets-Example" status to be "ACTIVE"
Table status is: CREATING
Table status is: ACTIVE
Put String Set "["Peter, "Paul, "Mary"]" into "StringSets-Example" with key "Names" and attribute "names"
Retrieved Item with key "Names" from "StringSets-Example"
The raw Item: {
PK: 'Names',
names: Set {
wrapperName: 'Set',
values: [ 'Mary', 'Paul', 'Peter' ],
type: 'String'
}
}
The raw Item.names.values: [ 'Mary', 'Paul', 'Peter' ]
-------------------------
Done. To clean up, run:
./src/deleteTable.js
The best solution I have here is to avoid the DocumentClient and the createSet(...) method. Here's a sample using AWS SDK V3:
const key = { PK: `SampleNames`, SK: `SampleNames` };
const names = new Set([`Peter`, `Paul`, `Mary`]);
const item = { ...key, names };
const marshalledItem = marshall(item);
console.log(`Raw item: ${inspect(item)}`)
console.log(`Marshalled item to PUT: ${inspect(marshalledItem, { depth: 4 })}`)
const client = new DynamoDBClient({});
await client.send(new PutItemCommand({
TableName: tableName,
Item: marshalledItem,
}));
const { Item } = await client.send(new GetItemCommand({
TableName: tableName,
Key: marshall(key),
}));
console.log(`Returned item: ${inspect(Item, { depth: 4 })}`);
console.log(`Unmarshalled returned item: ${inspect(unmarshall(Item))}`);
The console output from there is:
Raw item: {
PK: 'SampleNames',
SK: 'SampleNames',
names: Set { 'Peter', 'Paul', 'Mary' }
}
Marshalled item to PUT: {
PK: { S: 'SampleNames' },
SK: { S: 'SampleNames' },
names: { SS: [ 'Peter', 'Paul', 'Mary' ] }
}
Returned item: {
PK: { S: 'SampleNames' },
SK: { S: 'SampleNames' },
names: { SS: [ 'Mary', 'Paul', 'Peter' ] }
}
Unmarshalled returned item: {
PK: 'SampleNames',
SK: 'SampleNames',
names: Set { 'Mary', 'Paul', 'Peter' }
}
... which makes a lot more sense to me. I expect using the marshall/unmarshall methods from AWS SDK V2's Converter module would work similarly.

Remove JSON Element if it doesn't contain a specific value

I'm trying to return only values where john is found from a DynamoDB database.
I'm able to return values where it contains name: john from a mapped list, however the problem am having is that it appears to also be returning other values as well.
Running select: 'count' returns 1 match which is correct but it doesn't return anything when used.
I'm assuming that count just returns a number and not a specific select where john is matched.
I'm writing this in NodeJS; am hoping someone can help me figure this out.
I know that the value I only want shown are json elements where name: john, anything else I want omitted from being shown.
Here's my result as of right now:
{
"Department": [
{
"employees": [
{
"name": "john"
},
{
"name": "sally"
}
]
}
],
"Count": 1
}
My code:
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies
const dc = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context, callback) => {
// Construct the params for filtering data through dynamodb
const params = {
FilterExpression: "contains(#department, :employees)",
ExpressionAttributeNames: {
"#department": "employees",
},
ExpressionAttributeValues: {
":employees": {
"name":"john"
}
},
ProjectionExpression: "#department",
TableName: 'mytable',
//Select: 'COUNT'
};
const resultDC = await dc.scan(params).promise();
var items = resultDC.Items;
var count = resultDC.Count;
var returned_list = {
'Department' : items,
'Count' : count,
};
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(returned_list),
};
callback(null, response);
};
I suggest you to use a Local Secondary Index.
Take a look here.

AWS DynamoDB Transact Write using IN operator on Array

I am working on one query where I need to use TrasactWrite of dynamoDB and perform update query on one of my table.
Scenario is I am having array of unique IDs and I need to perform same update operation on each ID in array and change one flag in table without using Loop or map.
But I am having difficulty in finding example or docs related to how do I use IN operator on IDs which are primary keys in a Key parameter of transactWrite.
Here is my sample code:
let IDs = await find('table', { type: 'some_type' }, 'type-index}
let params = {
TransactItems: [{
Update: {
TableName: 'table',
Key: '#_id IN :IDs,
UpdateExpression: 'set #flag = :flag',
ExpressionAttributeNames: {
'#flag': 'flag',
'#_id': '_id'
},
ExpressionAttributeValues: {
':flag': false,
":IDs": IDs
}
}
}]
}
Already tried: Answer Its not same question as mine
I will answer if anyone has any question. Any help will be really helpful. Thank you
You can't use IN operator in this case - Update. With update operator you have to put key value to Update object. A key includes partitionKey and sortKey, I think in your case, you just set partitionKey.
To update more than one item by ID, you can put many Update object to TransactItems.
let IDs = await find('table', { type: 'some_type' }, 'type-index');
let params = {
TransactItems: [], // init empty array
}
IDs.forEach((ID) => {// loop though IDs array
params.TransactItems.push({
Update: {
TableName: 'table',
Key: {
'_id': ID, // ID value
},
UpdateExpression: 'set #flag = :flag',
ExpressionAttributeNames: {
'#flag': 'flag',
// '#_id': '_id' // remove this line
},
ExpressionAttributeValues: {
':flag': false,
// ":IDs": IDs // remove this line
}
}
})
});
// await client.transactWrite(params).promise()

How to write the parameter to update a particular property's value in an object from an array?

How to update quantity value based on title in the movies array and Item id (123)
I only manage to update value at the first layer like name (David), but don't know how to update the second layer with additional filter for the array (movies).
From:
Item:
{
id: 123,
name: 'David',
movies: [
{
id: 1,
title: 'The lord of the ring',
quantity: 1
},
{
id: 2,
title: 'Star Wars',
quantity: 1
}
]
}
To:
Item:
{
id: 123,
Name: 'David',
movies: [
{
id: 1,
title: 'The lord of the ring',
quantity: 2
},
{
id: 2,
title: 'Star Wars',
quantity: 1
}
]
}
By the way, I'm using aws DynamoDB document client in node.js, it will be nice if you can share me how you do it in your update parameter.
There is no way to update an object inside of a list without replacing it.
You probably want to restructure your table to emulate a relational data model. AWS has some documentation on this.
As an example, create your table like this:
aws dynamodb create-table \
--table-name movie-table \
--attribute-definitions AttributeName=rId,AttributeType=N AttributeName=rKey,AttributeType=S \
--key-schema AttributeName=rId,KeyType=HASH AttributeName=rKey,KeyType=RANGE
The table will have generically named hash and range keys. This script demonstrates how to structure the data and add to the "count":
const { DynamoDB } = require('aws-sdk');
const client = new DynamoDB.DocumentClient({ region: 'us-east-1' });
const addItem = (rId, rKey, attributes) => {
const item = { rId, rKey };
Object.assign(item, attributes);
return client.put({ TableName: 'movie-table', Item: item }).promise();
};
// NOTE: this is where the count attribute gets iterated
const addToCount = (rId, rKey) => client.update({
TableName: 'movie-table',
Key: { rId, rKey },
UpdateExpression: 'ADD #count :n',
ExpressionAttributeNames: { '#count': 'count' },
ExpressionAttributeValues: { ':n': 1 },
}).promise();
const run = async () => {
await addItem(123, 'USER|123', { name: 'David' });
await addItem(1, 'MOVIE|1', { title: 'The lord of the ring' });
await addItem(2, 'MOVIE|2', { title: 'Star Wars' });
await addItem(123, 'COUNT|1', { count: 1 });
await addItem(123, 'COUNT|2', { count: 1 });
await addToCount(123, 'COUNT|1');
};
run();
This is what the table looks like after the script runs:
I know this is a bit old but there is a way. Using the document client SDK, you can reference object properties and array elements in the UpdateExpression. However, you can't run any logic so you have to know/assume/expect that the element indexes are enough.
For example, you can do something like this:
let params = {
TableName: 'your-table-name',
Key: { id: 123 },
UpdateExpression: 'set movies[0].quantity = :x',
ExpressionAttributeValues: { ':x': 5 }
};
const client = AWS.DynamoDB.DocumentClient();
client.update(params);
NOTE: You cannot make the index an Expression Attribute Value. You would have to dynamically build that update expression based on the index you know has to be updated. It's not a perfect solution but it could get the job done.
For reference, I derived this from the base (non-DocumentClient) example from here: Adding Nested Map Attributes

Update array item in dynamoDB

In my dynamoDB, I have a table that structure as like as given below:
var params = {
"TableName" : "pdfs",
"Item" : {
pdfid: // Partition key
html: [] // array attribute
}
};
I can insert new array data, just like as given code:
Insert array data
var params = {
"TableName" : "pdfs",
"Item" : {
pdfid: pdfid,
html: [event.html] // here "html" is an array, I just insert first array data
}
};
dc.put(params, function(err, data) {
............................
});
How can I update array in dynamoDB ?
var params = {
TableName: "pdfs",
Key: {
pdfid: event.pdfid
},
UpdateExpression: "SET html = :html",
ExpressionAttributeValues: {
":html": ??????
},
ReturnValues: "ALL_NEW"
};
Use the following UpdateExpression to append values to a list: SET html = list_append(html, :html). The ExpressionAttributeValues would just be a mapping from :html to the string that you want to add.
Use the String Set attribute to update the list.
ExpressionAttributeValues: {
":html": {
SS: aws.StringSlice(your_html_array)
}

Resources