I have a dynamodb table that backs a shopping cart. The schema is CartKey then a List of Maps that contain a CartItemId. Is there a way to update a cart item, which is nested in the list of maps, based on the CartKey and a CartItemId.
Thanks
I'm in search for a solution to the same issue. Unfortunately I don't think one is available.
In mature document-based DBs (such as MongoDB) you should be able to specify a queried index (see https://docs.mongodb.org/manual/reference/operator/update/positional/#up.S), but DynamoDB doesn't support that.
The next best thing is to query the Cart document with the entire CartItems array, iterate it to find the index of your CartItem and do a conditional write. (For example: update the document and set CartItems[7].Quantity to 4 only if CartItems[7].ProductId is "WSK-1234")
Yes you need to do a read before a write and perform some client-side searching, but at least you can be sure you aren't updating the wrong item.
I would change your data model from a list of maps, to a map of maps where the keys are CartItemId's.
Example document:
{
CartKey : 'Cart-123',
items : : {
CartItemId1 : { quantity : 1, productId: "pid-123" },
CartItemId2 : { quantity : 4, productId: "pid-987" }
}
}
Then you can perform update expressions to specific CartItems.
UpdateExpression: "set items.CartItemId1.quantity = 2"
I did something similar with a map of maps and it worked for me. Hopefully this will be helpful.
$RegID = "abracadabra";
$tableName="DefaultDelivery";
$marshaler = new Marshaler();
$requested_delivery = '{"Packet0":{"PacketNo":"2","Quantity":"1000ml","Type":"Toned Milk"},"Packet2":{"PacketNo":"4","Quantity":"250ml","Type":"Toned Milk"}}';
$eav = $marshaler->marshalJson('
{
":RequestedDelivery" : '.$requested_delivery.'
}
');
$key = $marshaler->marshalJson('
{
"RegistrationID" : "'.$RegID.'"
}
');
$params = [
'TableName' => "$tableName",
'Key' => $key,
'ExpressionAttributeValues' => $eav,
'UpdateExpression' => 'SET RequestedDelivery = :RequestedDelivery',
'ReturnValues' => 'UPDATED_NEW'
];
try {
$result = $client->updateItem($params);
echo "SUCCESS";
}
catch (DynamoDbException $e){
echo "Unable to update Item : \n";
}
Related
{ _id: 1, results: [ "tokyo", "japan" ] }
{ _id: 2, results: [ "sydney", "australia" ] }
db.scores.find(
{ results: { $elemMatch: { $regex: *some regex* } } }
)
How do you convert this simple elemMatch example using spring mongodb data Query Criteria?
If the array contains object I can do it this way
Criteria criteria =
Criteria.where("results").
elemMatch(
Criteria.where("field").is("tokyo")
);
But in my question, I dont have the "field"
Update:
I thought the Veeram's answer was going to work after trying it out
Criteria criteria =
Criteria.where("results").
elemMatch(
new Criteria().is("tokyo")
);
It does not return anything. Am I missing something?
When i inspect the query object, it states the following:
Query: { "setOfKeys" : { "$elemMatch" : { }}}, Fields: null, Sort: null
On the other hand, If i modify the criteria using Criteria.where("field") like above,
Query: { "setOfKeys" : { "$elemMatch" : { "field" : "tokyo"}}}, Fields: null, Sort: null
I'm getting something but that's not how my data was structured, results is an array of strings not objects.
I actually need to use regex, for simplicity , the above example is using .is
You can try below query.
Criteria criteria = Criteria.where("results").elemMatch(new Criteria().gte(80).lt(85));
Try this
Criteria criteria = Criteria.where("results").regex(".*tokyo.*","i");
I have a scenario where I want to create an item if it doesn't exist, or update an item - incrementing a total, if it already exists.
I was running into problems splitting the two operations, so I am now trying to do both using UpdateItem in a single command.
I've tried 3 different approaches none work, and they have different errors listed below, the problem it seems is creating the map and trying to update it in a single command - what should my update params look like?
Attempt one:
{
TableName: TableName,
Key: {
'key': key
},
UpdateExpression: `
ADD #total :change
, mapname.#type.#total :one
`,
ExpressionAttributeValues: {
':change': change,
':one': 1
},
ExpressionAttributeNames: {
'#type': 'dynamicstring',
'#total': 'total'
}
};
With an error of: ValidationException: The document path provided in the update expression is invalid for update
Attempt two:
{
TableName: TableName,
Key: {
"key": key
},
UpdateExpression: `
SET custommap = if_not_exists(custommap, :emptyMap)
SET #total = #total + :change,
custommap.#type.#total = custommap.#type.#total + :one
`,
ExpressionAttributeValues: {
':change': change,
':one': 1,
':emptyMap': {
'M': {
'dynamicstring': {
'M': {
'total': {
'N': 0
}
}
}
}
}
},
ExpressionAttributeNames: {
'#type': 'dynamicstring',
'#total': 'total'
}
}
With an error of: ValidationException: Invalid UpdateExpression: The "SET" section can only be used once in an update expression;
So when I use UpdateItem to create or update (increment) a map within an Item, what syntax is correct?
Thanks
SET will only stop you overwriting an attribute, not an item.
They way to achieve this is:
Use GetItem with your key to see if the item already exists
If the item exists, then do an UpdateItem and increment the counter
If the item does not exist, then use PutItem
I've tried for get data in web services at http://api.rajaongkir.com/dokumentasi/starter. And I was success show data in view browser. When I implementation for insert data with a lot of into database something any wrong. I don't know why.
This code for get data in web service at http://api.rajaongkir.com/dokumentasi/starter. And put in controllers/TestController.php
public function actionGetProvince($id=0)
{
$client = new client();
$addUrl = ($id>0)?'id='.$id:'';
$response = $client->createRequest()
->setFormat(Client::FORMAT_JSON)
->setMethod('get')
->setUrl('http://api.rajaongkir.com/starter/province?'.$addUrl)
->addHeaders(['key' => 'example'])
->send();
if ($response->isOk) {
$content = \Yii\helpers\Json::decode($response->content);
//$content['rajaongkir']['query']
//$content['rajaongkir']['status']
$results = $content['rajaongkir']['results'];
if ($id > 0) {
if (count($results)>0) {
echo $results['province_id'] . ' - ';
echo $results['province'] . '<br>';
}
else {
echo "blank";
}
} else {
foreach ($results as $provinces) {
echo $provinces['province_id']." - ".$provinces['province']."<br>";
}
}
} else {
$content = \Yii\helpers\Json::decode($response->content);
echo $content['rajaongkir']['status']['description'];
}
}
And this code for insert data with a lot of in database, and I put in file same.
Yii::$app->db->createCommand()->batchInsert('province', [
'id_province' => $provinces['province_id'], 'name' => $provinces['province']
])->execute();
And the result error is :
PHP Warning – yii\base\ErrorException : Missing argument 3 for yii\db\Command::batchInsert(), called in C:\wamp\www\basic_yii2\controllers\TestController.php on line 60 and defined
You are not calling batchInsert() properly.
See it in documentation.
public $this batchInsert ( $table, $columns, $rows )
$table string The table that new rows will be inserted into.
$columns array The column names
$rows array The rows to be batch inserted into the table
Example:
$connection->createCommand()->batchInsert('user', ['name', 'age'], [
['Tom', 30],
['Jane', 20],
['Linda', 25],
])->execute();
I have a list of maps as one field of a DynamoDB table. How can I update a specific element (or, rather element field ?)
Trying something like
rc = table.update_item(Key={ 'username' : user },
UpdateExpression="set list[:i].field = :nd",
ExpressionAttributeValues={
':i' : itemnum,
':nd': data,
},
ReturnValues="UPDATED_NEW"
);
But I am getting an error:
Invalid UpdateExpression: Syntax error; token: ":i", near: "[:i]"
Any ideas how can I reference list element with variable number. Thanks.
Use a literal instead:
rc = table.update_item(Key={ 'username' : user },
UpdateExpression="set list[" + itemnum + "].field = :nd",
ExpressionAttributeValues={
':nd': data,
},
ReturnValues="UPDATED_NEW"
);
I'm adding an answer to #Snedden27 "Does this have any security risk if itemnum is send by the end user?"
Yes, in theory a user could inject something here in certain circumstances (e.g. if you are updating a list and the list items are also lists then the user could input "3][2" which would make a valid expression that updates a nested list and screws up your data structure.
Something like parseInt on the input should prevent this injection:
rc = table.update_item(Key={ 'username' : user },
UpdateExpression="set parent_list[" + parseInt(itemnum) + "] = :nd",
ExpressionAttributeValues={
':nd': child_list,
},
ReturnValues="UPDATED_NEW"
);
I have movie database with different fields. the Genre field contains a comma separated string like :
{genre: 'Action, Adventure, Sci-Fi'}
I know I can use regular expression to find the matches. I also tried:
{'genre': {'$in': genre}}
the problem is the running time. it take lot of time to return a query result. the database has about 300K documents and I have done normal indexing over 'genre' field.
Would say use Map-Reduce to create a separate collection that stores the genre as an array with values coming from the split comma separated string, which you can then run the Map-Reduce job and administer queries on the output collection.
For example, I've created some sample documents to the foo collection:
db.foo.insert([
{genre: 'Action, Adventure, Sci-Fi'},
{genre: 'Thriller, Romantic'},
{genre: 'Comedy, Action'}
])
The following map/reduce operation will then produce the collection from which you can apply performant queries:
map = function() {
var array = this.genre.split(/\s*,\s*/);
emit(this._id, array);
}
reduce = function(key, values) {
return values;
}
result = db.runCommand({
"mapreduce" : "foo",
"map" : map,
"reduce" : reduce,
"out" : "foo_result"
});
Querying would be straightforward, leveraging the queries with an multi-key index on the value field:
db.foo_result.createIndex({"value": 1});
var genre = ['Action', 'Adventure'];
db.foo_result.find({'value': {'$in': genre}})
Output:
/* 0 */
{
"_id" : ObjectId("55842af93cab061ff5c618ce"),
"value" : [
"Action",
"Adventure",
"Sci-Fi"
]
}
/* 1 */
{
"_id" : ObjectId("55842af93cab061ff5c618d0"),
"value" : [
"Comedy",
"Action"
]
}
Well you cannot really do this efficiently so I'm glad you used the tag "performance" on your question.
If you want to do this with the "comma separated" data in a string in place you need to do this:
Either with a regex in general if it suits:
db.collection.find({ "genre": { "$regex": "Sci-Fi" } })
But not really efficient.
Or by JavaScript evaluation via $where:
db.collection.find(function() {
return (
this.genre.split(",")
.map(function(el) {
return el.replace(/^\s+/,"")
})
.indexOf("Sci-Fi") != -1;
)
})
Not really efficient and probably equal to above.
Or better yet and something that can use an index, the separate to an array and use a basic query:
{
"genre": [ "Action", "Adventure", "Sci-Fi" ]
}
With an index:
db.collection.ensureIndex({ "genre": 1 })
Then query:
db.collection.find({ "genre": "Sci-Fi" })
Which is when you do it that way it's that simple. And really efficient.
You make the choice.