Atomically reorder items in nested array of MongoDB document - mongodb

I have a document with nested array of ordered items and I need to atomically reorder items in that array.
are the server-side code is a right solution for this task?
are the server-side code is an only solution for this task?
at witch scope this code will blocking:
single document
single collection
whole server ?
this is the code that will be executed at server-side:
var reorder = function (
catalog_id,
parent_id,
item_id,
new_pos) {
var old_pos;
var collection = db.catalogs;
var catalog = collection.findOne({catalog_id:catalog_id});
var result = [];
for(i in catalog.list) {
var item = catalog.list[i];
if(item.id == item_id) {
old_pos = item.order;
result.push({old_pos:old_pos});
break;
}
}
if(old_pos == new_pos)
return result;
var up = new_pos < old_pos;
catalog.list.forEach(function(item){
if( item.parent == parent_id &&
(up ?
(item.order <= old_pos && item.order >= new_pos) :
(item.order <= old_pos && item.order >= new_pos))){
if(item.id != item_id) {
item.order++;
result.push({down:item});
}
else {
item.order = new_pos;
result.push({up:item});
}
collection.update(
{catalog_id:catalog_id, 'list.id':item.id},
{$set:{'list.$.order':item.order}});
}
});
return result;
};
reorder('diagnostic_graph', 'n1', 'n5', 1);
this is a sample data:
{
"_id" : ObjectId("4efc939094f4a115d80c8543"),
"catalog_id" : "diagnostic_graph",
"list" : [{
"id" : "n1",
"order" : 0
}, {
"id" : "n2",
"parent" : "n1",
"order" : 0
}, {
"id" : "n3",
"parent" : "n1",
"order" : 1
}, {
"id" : "n4",
"parent" : "n1",
"order" : 2
}, {
"id" : "n5",
"parent" : "n1",
"order" : 3
}]
}
PS. sorry if something is unclear - english is not my best skill

Q 1. are the server-side code is a right solution for this task?
A 1. It depends. If the array needs to be reordered whenever you use it, than you should reorder it whenever you add an item to the array. You can call server side .js code whenever you push an item to array, or do it in your application.
Q 2. are the server-side code is an only solution for this task?
A 2. As I stated in the answer of the first question, it is not the only solution.
Q 3. at witch scope this code will blocking:
A 3. The answer is single document (AFAIK).

Related

Mongodb - Query embedded object with variable key

I have a Mongo DB with the following object:
"clients" : {
"x" : {
"clientId" : "x1",
"mainInfo" : {
...
},
"events" :
{
"58a176bbc3588410cd5450c6" : {
"clientType" : "5001",
"location" : "60001"
}
"58a176bbc3588410cd5450c8" : {
"clientType" : "5001",
"location" : "60002"
}
....}
I cannot seem to figure out how to query where 'clients.x.events.(variable id).clientType' = 50001. Is there a way to drill down inside the events embedded object to get all records matching "clientType" : "5001"?
Thank you
You need to create the key value pair object like below.
var variable_id = <your variable id>;
db.collection.find({ [ 'clients.x.events.'+variable_id+'.clientType' ]:5001 });
Read more about this here
Maybe you can use $where operator.It works for me.
Use the $where operator to pass either a string containing a
JavaScript expression or a full JavaScript function to the query
system.
db.collection.find({"$where" : function(){
for( var c in this ){
if( c == "x" ){
for(var i in this[c]){
for(var j in this[c][i]){
if(j == 'clientType' && this[c][i][j] == '5001'){
return true;
}
}
}
};
}
return false;
}});
Hope this helps.

MongoDB: Update a field of an item in array with matching another field of that item

I have a data structure like this:
We have some centers. A center has some switches. A switch has some ports.
{
"_id" : ObjectId("561ad881755a021904c00fb5"),
"Name" : "center1",
"Switches" : [
{
"Ports" : [
{
"PortNumber" : 2,
"Status" : "Empty"
},
{
"PortNumber" : 5,
"Status" : "Used"
},
{
"PortNumber" : 7,
"Status" : "Used"
}
]
}
]
}
All I want is to write an Update query to change the Status of the port that it's PortNumber is 5 to "Empty".
I can update it when I know the array index of the port (here array index is 1) with this query:
db.colection.update(
// query
{
_id: ObjectId("561ad881755a021904c00fb5")
},
// update
{
$set : { "Switches.0.Ports.1.Status" : "Empty" }
}
);
But I don't know the array index of that Port.
Thanks for help.
You would normally do this using the positional operator $, as described in the answer to this question:
Update field in exact element array in MongoDB
Unfortunately, right now the positional operator only supports one array level deep of matching.
There is a JIRA ticket for the sort of behavior that you want: https://jira.mongodb.org/browse/SERVER-831
In case you can make Switches into an object instead, you could do something like this:
db.colection.update(
{
_id: ObjectId("561ad881755a021904c00fb5"),
"Switch.Ports.PortNumber": 5
},
{
$set: {
"Switch.Ports.$.Status": "Empty"
}
}
)
Since you don't know the array index of the Port, I would suggest you dynamically create the $set conditions on the fly i.e. something which would help you get the indexes for the objects and then modify accordingly, then consider using MapReduce.
Currently this seems to be not possible using the aggregation framework. There is an unresolved open JIRA issue linked to it. However, a workaround is possible with MapReduce. The basic idea with MapReduce is that it uses JavaScript as its query language but this tends to be fairly slower than the aggregation framework and should not be used for real-time data analysis.
In your MapReduce operation, you need to define a couple of steps i.e. the mapping step (which maps an operation into every document in the collection, and the operation can either do nothing or emit some object with keys and projected values) and reducing step (which takes the list of emitted values and reduces it to a single element).
For the map step, you ideally would want to get for every document in the collection, the index for each Switches and Ports array fields and another key that contains the $set keys.
Your reduce step would be a function (which does nothing) simply defined as var reduce = function() {};
The final step in your MapReduce operation will then create a separate collection Switches that contains the emitted Switches array object along with a field with the $set conditions. This collection can be updated periodically when you run the MapReduce operation on the original collection.
Altogether, this MapReduce method would look like:
var map = function(){
for(var i = 0; i < this.Switches.length; i++){
for(var j = 0; j < this.Switches[i].Ports.length; j++){
emit(
{
"_id": this._id,
"switch_index": i,
"port_index": j
},
{
"index": j,
"Switches": this.Switches[i],
"Port": this.Switches[i].Ports[j],
"update": {
"PortNumber": "Switches." + i.toString() + ".Ports." + j.toString() + ".PortNumber",
"Status": "Switches." + i.toString() + ".Ports." + j.toString() + ".Status"
}
}
);
}
}
};
var reduce = function(){};
db.centers.mapReduce(
map,
reduce,
{
"out": {
"replace": "switches"
}
}
);
Querying the output collection Switches from the MapReduce operation will typically give you the result:
db.switches.findOne()
Sample Output:
{
"_id" : {
"_id" : ObjectId("561ad881755a021904c00fb5"),
"switch_index" : 0,
"port_index" : 1
},
"value" : {
"index" : 1,
"Switches" : {
"Ports" : [
{
"PortNumber" : 2,
"Status" : "Empty"
},
{
"PortNumber" : 5,
"Status" : "Used"
},
{
"PortNumber" : 7,
"Status" : "Used"
}
]
},
"Port" : {
"PortNumber" : 5,
"Status" : "Used"
},
"update" : {
"PortNumber" : "Switches.0.Ports.1.PortNumber",
"Status" : "Switches.0.Ports.1.Status"
}
}
}
You can then use the cursor from the db.switches.find() method to iterate over and update your collection accordingly:
var newStatus = "Empty";
var cur = db.switches.find({ "value.Port.PortNumber": 5 });
// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
var doc = cur.next();
var update = { "$set": {} };
// set the update query object
update["$set"][doc.value.update.Status] = newStatus;
db.centers.update(
{
"_id": doc._id._id,
"Switches.Ports.PortNumber": 5
},
update
);
};

Mongodb update subdocument inside array field of a collection

I have a mongodb collection like
{
"_id" : ObjectId("5375ef2153bb790b20d8a660"),
"association" : [
{
"count" : 3,
"name" : "hayatdediğin"
},
{
"count" : 2,
"name" : "sadecesenolsan"
},
{
"count" : 2,
"name" : "üslupnamustur"
}
],
"tag_count" : 4,
"tag_name" : "vazgeçilmezolan",
"variation" : [
{
"count" : 4,
"name" : "VazgeçilmezOlan"
}
]
}
Each collection consists of tag_name, tag_count, array field association and array field variation. For each name inside association, there exists a different document same as this document. I need to add new field "total_count" inside each association dictionary whose value equals the tag_count of the name by querying the database.
I tried this code but its not working
db.hashtag.find().forEach(function (doc) {
if (doc.association.length != 0 ) {
doc.association.forEach(function (assoc) {
db.hashtag.find({'tag_name': assoc.name}).forEach(function(tag){
assoc.total_count=tag.tag_count;
})
});
}
});
After modifying each doc you need to call save on the collection to commit the change.
Assuming you're doing this in the shell:
db.hashtag.find().forEach(function (doc) {
if (doc.association.length != 0 ) {
doc.association.forEach(function (assoc) {
db.hashtag.find({'tag_name': assoc.name}).forEach(function(tag){
assoc.total_count=tag.tag_count;
});
});
// Save the changed doc back to the collection
db.hashtag.save(doc);
}
});
To update doc in database you have to use db.hashtag.update, not db.hashtag.find. Find only retrieves document from db.
I changed the previous method of looping using forEach and then saved the doc at last and the code worked.
db.hashtag.find().forEach(function (doc) {
var array = doc.association;
if (array != undefined){
for(var i=0;i<array.length;i++)
{
var obj = db.hashtag.findOne({'name':array[i].name});
var count = obj.count;
doc.association[i].total_count = count;
db.hashtag.save(doc);
}
}
});

Update entry in an array or add to array if item doesn't exist [duplicate]

This question already has an answer here:
'upsert' in an embedded document
(1 answer)
Closed 8 years ago.
I have a collection that has a structure that looks like the following
{ "_id" : "MHBk8q96vpuRYrAdn",
"circles" : {
"guests" : 3,
"properties" : [
{
"position" : { "x" : 146, "y" : 70.5207970},
"name" : "circle-1"
},
{
"position" : { "x" : 200, "y" : 85},
"name" : "circle-2"
}
],
"tables" : 1
}
}
I need to be able to either update the position of circles.properties.position if it exists by name, or add a new entry if it does not. For example, update the position of "circle-1" since it exists, but add a new array item for "circle-3" with a name and position. Is it possible to achieve this? So far I have only been able to push onto the array using $push, and I have messed around with the $(query) operator with no success. Thanks.
Since MongoDB doesn't support upserts to arrays it can be tricky. You can try something like below:
var query = {};
new_circle = { "position" : { "x" : -1, "y" : -1}, "name" : "circle-1" };
db.foo.find(query).forEach(
function(doc) {
// Find index of 'circle-1'
i = doc.circles.properties.map(
function(el) { if (el.name == 'circle-1') return 1; else return -1;}
).indexOf(1);
// Update if circle-1 in circles-properties
if (i != -1) {
doc.circles.properties[i] = new_circle;
}
// If not push new
else {
doc.circles.properties.push(new_circle);
}
db.foo.save(doc);
}
)
Edit
If you cannot use save and update with upsert option replacing if-else block posted above with something like this should do the trick:
if (i != -1) {
db.foo.update(
{"_id" : doc._id, "circles.properties.name": "circle-1"},
{$set: {"circles.properties.$.position": new_circle.position}}
}
else {
db.foo.update(
{"_id" : doc._id},
{$push: {"properties": new_circle }}
)
}

Ordering for a todo list with MongoDB

I'm attempting to create my own todo list using Javascript, Python and MongoDB. I'm getting stuck on how to handle the task ordering.
My current idea is to have an order field in each task document and when the order changes on the client I would grab the task list from the db and reorder each task individually/sequentially. This seems awkward because large todo lists would mean large amount of queries. Is there a way to update a field in multiple documents sequentially?
I'm also looking for advice as to whether this is the best way to do this. I want to be able to maintain the todo list order but maybe I'm going about it the wrong way.
{
"_id" : ObjectId("50a658f2cace55034c68ce95"),
"order" : 1,
"title" : "task1",
"complete" : 0
}
{
"_id" : ObjectId("50a658fecace55034c68ce96"),
"order" : 2,
"title" : "task2",
"complete" : 1
}
{
"_id" : ObjectId("50a65907cace55034c68ce97"),
"order" : 3,
"title" : "task3",
"complete" : 1
}
{
"_id" : ObjectId("50a65911cace55034c68ce98"),
"order" : 4,
"title" : "task4",
"complete" : 0
}
{
"_id" : ObjectId("50a65919cace55034c68ce99"),
"order" : 5,
"title" : "task5",
"complete" : 0
}
Mongo is very very fast with queries, you should not be as concerned with performance as if you were using a full featured relational database. If you want to be prudent, just create a todo list of 1k items and try it out, it should be pretty instant.
for (var i = 0; i < orderedListOfIds.length; i++)
{
db.collection.update({ '_id': orderedListOfIds[i] }, { $set: { order:i } })
}
then
db.collection.find( { } ).sort( { order: 1 } )
Yes, mongo allows for updating multiple documents. Just use a modifier operation and multi=True. For example, this increments order by one for all documents with order greater than five:
todos.update({'order':{'$gt':5}}, {'$inc':{'order':1}}, multi=True)
As to the best way, usually it's better to use a "natural" ordering (by name, date, priority etc) rather than create a fake field just for that.
I'm doing something similar. I added a field ind to my list items. Here's how I move a list item to a new location:
moveItem: function (sourceIndex, targetIndex) {
var id = Items.findOne({ind:sourceIndex})._id;
var movinUp = targetIndex > sourceIndex;
shift = movinUp ? -1 : 1;
lowerIndex = Math.min(sourceIndex, targetIndex);
lowerIndex += movinUp ? 1 : 0;
upperIndex = Math.max(sourceIndex, targetIndex);
upperIndex -= movinUp ? 0 : 1;
console.log("Shifting items from "+lowerIndex+" to "+upperIndex+" by "+shift+".");
Items.update({ind: {$gte: lowerIndex,$lte: upperIndex}}, {$inc: {ind:shift}},{multi:true});
Items.update(id, {$set: {ind:targetIndex}});
}
if you're using native promises (es6) in mongoose mongoose.Promise = global.Promise you can do the following to batch:
function batchUpdate(res, req, next){
let ids = req.body.ids
let items = []
for(let i = 0; i < ids.length; i++)
items.push(db.collection.findOneAndUpdate({ _id:ids[i] }, { $set: { order:i } }))
Promise.all(items)
.then(() => res.status(200).send())
.catch(next)
}