How to find a certain element in the embedded document - mongodb

Is there any way that will only return the field of the store that has grade B?
I tried: db.restaurant.find( {"Result.Grade" : "B"} )
But it will return back all document content with grade B.
Thanks!!

First argument in the find query is for filter. Use second argument and pass the fields you want to retrieve.
db.restaurant.find(
{ "Result.Grade" : "B" }, //filter
{ "Name": 1, "Number": 1 } //projection
)
With the mongodb node driver use .project() cursor method
db.restaurant.find(
{ "Result.Grade" : "B" },
).project({ "Name": 1, "Number": 1 })

Related

Mongodb partial index on one of the indexed field

I want to create partial index on one of the indexed field
but I am failing miserably
db.Comment.createIndex(
{ "siteId": 1,
{ { "parent": 1} ,{partialFilterExpression:{parent:{$exists: true}}}},
"updatedDate": 1,
"label": 1 }
);
how to do that?
the field "parent" is the one I want to index partially
In roboMongo I get the error
Error: Line 3: Unexpected token {
You pass the partialFilterExpression object as a second parameter to createIndex. See the documentation.
db.Comment.createIndex(
{ "siteId": 1, "parent": 1, "updatedDate": 1, "label": 1 },
{ partialFilterExpression: { parent: { $exists: true } }
);
So don't think of it as partially indexing a field; your partial filter expression defines which documents to include in your index.

Concurrent update of array elements which are embedded documents in MongoDB

I have documents like this one at collection x at MongoDB:
{
"_id" : ...
"attrs" : [
{
"key": "A1",
"type" : "T1",
"value" : "13"
},
{
"key": "A2",
"type" : "T2",
"value" : "14"
}
]
}
The A1 and A2 elements above are just examples: the attrs field may hold any number of array elements.
I'd need to access concurrently to the attrs array from several independent clients accessing to MongoDB. For example, considers two clients, one wanting to change the value of the element identified by key equal to "A1" to "80" and other wanting to change the value of the element identified by key equal to "A2" to "20". Is there any compact way of doing it using MongoDB operations?
It is important to note that:
Clients doesn't know the position of each element in the attr array, only the key of the element which value has to be modified.
Reading the whole attrs array in client space, searching the element to modify at client space, then updating attrs with the new array (in which the element to modify has been changed) would involve race conditions.
Clients also may add and remove elements in the array. Thus, doing a first search at MongoDB to locate the position of the element to modify, then update it using that particular position doesn't work in general, as elements could have been added/removed thus altering of the position previously found.
The process here is really quite simple, it only varies in where you want to "find or create" the elements in the array.
First, assuming the elements for each key are in place already, then the simple case is to query for the element and update with the index returned via the positional $ operator:
db.collection.update(
{
"_id": docId,
"attrs": { "$elemMatch": { "key": "A1", "type": "T1" } }
}
{ "$set": { "attrs.$.value": "20" }
)
That will only modify the element that is matched without affecting others.
In the second case where "find or create" is required and the particular key may not exist, then you use "two" update statements. But the Bulk Operations API allows you to do this in a single request to the server with a single response:
var bulk = db.collection.initializeOrderedBulkOp();
// Try to update where exists
bulk.find({
"_id": docId,
"attrs": { "$elemMatch": { "key": "A1", "type": "T2" } }
}).updateOne({
"$set": { "attrs.$.value": "30" }
});
// Try to add where does noes not exist
bulk.find({
"_id": docId,
"attrs": { "$not": { "$elemMatch": { "key": "A1", "type": "T2" } } }
}).updateOne({
"$push": { "attrs": { "key": "A1", "type": "T2", "value": "30" } }
});
bulk.execute();
The basic logic being that first the update attempt is made to match an element with the required values just as done before. The other condition tests for where the element is not found at all by reversing the match logic with $not.
In the case where the array element was not found then a new one is valid for addition via $push.
I should really add that since we are specifically looking for negative matches here it is always a good idea to match the "document" that you intend to update by some unique identifier such as the _id key. While possible with "multi" updates, you need to be careful about what you are doing.
So in the case of running the "find or create" process then element that was not matched is added to the array correctly, without interferring with other elements, also the previous update for an expected match is applied in the same way:
{
"_id" : ObjectId("55b570f339db998cde23369d"),
"attrs" : [
{
"key" : "A1",
"type" : "T1",
"value" : "20"
},
{
"key" : "A2",
"type" : "T2",
"value" : "14"
},
{
"key" : "A1",
"type" : "T2",
"value" : "30"
}
]
}
This is a simple pattern to follow, and of course the Bulk Operations here remove any overhead involved by sending and receiving multiple requests to and from the server. All of this hapily works without interferring with other elements that may or may not exist.
Aside from that, there are the extra benefits of keeping the data in an array for easy query and analysis as supported by the standard operators without the need to revert to JavaScript server processing in order to traverse the elements.

Recursive search in MongoDB

I have a Mongo collection that contains data sets with a pmID and a replyID, where the replyID can be null or a pmID. I'm trying to figure out, if I select by a pmID, how I can also get the PM where the pmID = replyID, and if that set's replyID isn't null, the object that matches that, etc.
{
pmID: 1,
replyID: null
},
{
pmID: 2,
replyID: null
},
{
pmID: 3,
replyID: 1
},
{
pmID: 4,
replyID: 3
}
So if I selected on pmID = 4, I also wanna get 3 and 1. I'm debating just doing a one by one select, but I'm hoping there's an easier answer.
Not really sure of your use case here, but from a glance I would personally model to store the "ancestors" in order with the object like so:
{
"pmId": 1,
"replyIds": [ ]
},
{
"pmId": 3,
"replyIds": [ 1 ]
},
{
"pmId": 4,
"replyIds": [ 3, 1 ]
}
In either that order or in reverse depending on how you implement the ancestor history.
The point being that when issuing a "reply" to an existing object, you should already have the data loaded that tells you if there are any existing replies. The new object then pushes the id of the the object it is being issued in reply to onto the the existing "replyIds" array when that new object is created:
// data is "pmId": 3 object
data.replyIds.unshift( data.pmId );
data.pmId = getNewPmId(); // whatever
db.collection.insert(data);
It's a fairly simple pattern which obviates the need to "recursively walk" through queries on read requests, and also allows for simple writes.
Retrieving all ancestors is just a simple matter of passing the array of replies to $in:
var data = db.collection.findOne({ "pmId": 4 });
db.collection.find({ "pmId": { "$in": data.replyIds } })
In addition to Neil's suggestion to use ancestor arrays, if you're modelling conversations you can group together conversations with a conversation_id and recall all messages connected to a given one by conversation_id:
{
"pmId": 1,
"conversationId" : 0
},
{
"pmId": 3,
"conversationId" : 0
},
{
"pmId": 4,
"conversationId" : 0
}
> var pm = db.test.findOne({ "pmId" : 3 })
> db.test.find({ "conversationId" : pm.conversationId })

MongoDB update all fields of array error

Im tring to set 0 the items.qty of a document obtains by a id query.
db.warehouses.update(
// query
{
_id:ObjectId('5322f07e139cdd7e31178b78')
},
// update
{
$set:{"items.$.qty":0}
},
// options
{
"multi" : true, // update only one document
"upsert" : true // insert a new document, if no existing document match the query
}
);
Return:
Cannot apply the positional operator without a corresponding query field containing an array.
This is the document that i want to set all items.qty to 0
{
"_id": { "$oid" : "5322f07e139cdd7e31178b78" },
"items": [
{
"_id": { "$oid" : "531ed4cae604d3d30df8e2ca" },
"brand": "BJFE",
"color": "GDRNCCD",
"hand": 1,
"model": 0,
"price": 500,
"qty": 0,
"type": 0
},
{
"brand": "BJFE",
"color": "GDRNCCD",
"hand": 1,
"id": "23",
"model": 0,
"price": 500,
"qty": 4,
"type": 0
},
{
"brand": "BJFE",
"color": "GDRNCCD",
"hand": 1,
"id": "3344",
"model": 0,
"price": 500,
"qty": 6,
"type": 0
}
],
"name": "a"
}
EDIT
The detail missing from the question was that the required field to update was actually in a sub-document. This changes the answer considerably:
This is a constraint of what you can possibly do with updating array elements. And this is clearly explained in the documentation. Mostly in this paragraph:
The positional $ operator acts as a placeholder for the first element that matches the query document
So here is the thing. Trying to update all of the array elements in a single statement like this will not work. In order to do this you must to the following.
db.warehouses.find({ "items.qty": { "$gt": 0 } }).forEach(function(doc) {
doc.items.forEach(function(item) {
item.qty = 0;
});
db.warehouses.update({ "_id": doc._id }, doc );
})
Which is basically the way to update every array element.
The multi setting in .update() means across multiple "documents". It cannot be applied to multiple elements of an array. So presently the best option is to replace the whole thing. Or in this case we may just as well replace the whole document since we need to do that anyway.
For real bulk data, use db.eval(). But please read the documentation first:
db.eval(function() {
db.warehouses.find({ "items.qty": { "$gt": 0 } }).forEach(function(doc) {
doc.items.forEach(function(item) {
item.qty = 0;
});
db.warehouses.update({ "_id": doc._id }, doc );
});
})
Updating all the elements in an array across the whole collection is not simple.
Original
Pretty much exactly what the error says. In order to use a positional operator you need to match something first. As in:
db.warehouses.update(
// query
{
_id:ObjectId('5322f07e139cdd7e31178b78'),
"items.qty": { "$gt": 0 }
},
// update
{
$set:{"items.$.qty":0}
},
// options
{
"multi" : true,
"upsert" : true
}
);
So where the match condition fins the position of the items that are less than 0 then that index is passed to the positional operator.
P.S : When muti is true it means it updates every document. Leave it false if you only mean one. Which is the default.
You can use the $ positional operator only when you specify an array in the first argument (i.e., the query part used to identify the document you want to update).
The positional $ operator identifies an element in an array field to update without explicitly specifying the position of the element in the array.

Updating Value of Array Element in MongoDB

I'd like to know how to update the "value" field of one of the elements identified by the "name" field in the array "array_of_stuff". For example, I want to update the value for "name_of_thing_1" to "new_value_of_thing_1". How can I do this ONLY using the second parameter (i.e. the update parameter) to the update command. I am re-using a class library written in-house I don't have control over the first argument to the update command (i.e. the query parameter). Is this possible?
{
"array_of_stuff": [
{
"name": "name_of_thing_1",
"value": "value_of_thing_1",
},
{
"name": "name_of_thing_2",
"value": "value_of_thing_2",
}
]
}
Thanks for your help!
You can update the value of a single item in an array (if you know its index) like this:
db.stuff.update(/* query ... */, {$set:{"arrayname.<index>":new_value}})
If your array contains documents, you can update a particular field of a document at that index like this:
db.stuff.update(/* query ... */, {$set:{"array_of_stuff.0.value":"new_value_of_thing_1"}})
// If you could use the query parameter and knew something
// about the value in the array you wanted to change:
db.stuff.update({"array_of_stuff.value":"value_of_thing_1"}, {$set:{"array_of_stuff.$.value":"new_value_of_thing_1"}})
See if this example help you:
db.bruno.insert({"array": [{"name": "Hello", "value": "World"}, {"name": "Joker", "value": "Batman"}]})
db.bruno.update({"array.name": "Hello"}, {$set: {"array.$.value": "Change"}})
db.bruno.find().pretty()
output:
db.bruno.find().pretty()
{
"_id" : ObjectId("52389faaafd72821e7b25a73"),
"array" : [
{
"name" : "Hello",
"value" : "Change"
},
{
"name" : "Joker",
"value" : "Batman"
}
]
}
I don't think it is possible. In order to update field of one of the elements in array, you should use positional $ operator, e.g.:
update({'array_of_stuff.name':'name_of_thing_1'},
{ $set: {'array_of_stuff.$.value':'new_value_of_thing_1'}})
But according to documentation: positional $ operator acts as a placeholder for the first element that matches query document, and the array field must appear as part of the query document.