Does $elemMatch not work with a new mongodb 2.2? - node.js

In a new version on MongoDB we can use an $elemMatch projection operator to limit the response of a query to a single matching element of an array. http://docs.mongodb.org/manual/reference/projection/elemMatch/
But it seems doesn't work yet in mongoose 3 here is the example:
{
_id: ObjectId(5),
items: [1,2,3,45,4,67,9,4]
}
Folder.findOne({_id: Object(5)}, {$elemMatch: {$in: [1,67,9]}})
.exec(function (err, doc) {
});
I'm expected to get the follows doc:
{
_id: ObjectId(5),
items: [1,67,9]
}
But unfortunately what I'm getting is document with all items:
{
_id: ObjectId(5),
items: [1,2,3,45,4,67,9,4]
}

The mongodb docs here are misleading, we'll get them updated.
What its saying is that you can now use $elemMatch in your projection, that is, your field selection:
https://gist.github.com/3640687
See also: https://github.com/learnboost/mongoose/issues/1085
[Edit] pull request for docs sent: https://github.com/mongodb/docs/pull/185

Firstly, you are missing the items field name in front of the $elemMatch operator. Your query should read
Folder.findOne({_id: Object(5)}, {items: {$elemMatch: {$in: [1,67,9]}}})
.exec(function (err, doc) { });
But this would still not return the desired result, because as stated in the documentation:
The $elemMatch projection will only match one array element per source
document.
So you would only get back something like:
{
_id: ObjectId(5),
items: [1]
}

I haven't got mongoose set up to do this with node, but you can also get the result you want using the new aggregation framework in 2.2 - here's an example that gets you the result you wanted. First, my sample doc looks like this:
> db.foo.findOne()
{
"_id" : ObjectId("50472eb566caf6af6108de02"),
"items" : [
1,
2,
3,
45,
4,
67,
9,
4
]
}
To get to what you want I did this:
> db.foo.aggregate(
{$match : {"_id": ObjectId("50472eb566caf6af6108de02")}},
{$unwind : "$items"},
{$match : {"items": {$in : [1, 67, 9]}}},
{$group : {_id : "$_id", items : { $push : "$items"}}},
{$project : {_id : 0, items : 1}}
)
{
"result" : [
{
"_id" : ObjectId("50472eb566caf6af6108de02"),
"items" : [
1,
67,
9
]
}
],
"ok" : 1
}
To explain, in detail I will take it line by line:
{$match : {"_id": ObjectId("50472eb566caf6af6108de02")}}
This is fairly obvious - it is basically the equivalent to the find criteria on a regular query, the results are passed to the next step in the pipeline to be processed. This is the piece that can use indexes etc.
{$unwind : "$items"}
This will explode the array, creating a stream of documents, one for each element of the array.
{$match : {"items": {$in : [1, 67, 9]}}}
This second match will return only the documents in the list, basically reducing the stream of docs to a result set of three.
{$group : {_id : "$_id", items : { $push : "$items"}}}
We want our output to be an array, so we have to undo the unwind above now that we have selected the items we want, using the _id as the key to group. Note: this will have repeating values if there is more than one match, if you wanted a unique list you would use $addToSet instead of $push
{$project : {_id : 1, items : 1}}
Then finally, this projection is not really needed, but I included it to illustrate the functionality - you could choose to not return the _id if you wished etc.

Neither $elemMatch nor MongoDB in general will filter data from an array. $elemMatch can be used to match a document but it won't affect the data to be returned. You can only include/exclude fields from a documented by using the filter parameter (second parameter of a find() findOne() call) but you can not filter the result based on some query input.

Related

sum of all fields under collection on specific post

My Dataset is like this
{
"_id" : ObjectId("6a1464430b4215046c768y66"),
"a" : 5,
"b" : 4,
"c" : 2,
"d" : 14,
"e" : 7,
"f" : 25,
"g" : 85,
}
Now I want to do sum of all fields on this id only.
My query is
db.collection.aggregate([
{$match:{_id: req.body.id}},
{$project: {total: { $sum: [ "$a", "$b", "$c", "$d","$e","$f","$g" ]}}}]);
Using $match, it returns Blank Array[]. When Not use $match then it will return the sum, but for all not for this id.
You can use $add as well.
db.collection.aggregate([
{$match:{_id: ObjectId("6a1464430b4215046c768y66")}},
{$project: { total: {$add: ["$a", "$b", "$c"]}}} // etc.
])
But $sum should work too. Could it be that the $match pipeline doesn't return any results?
I don't which driver you use, but doing it in your code it could interpret your id. But if you search directly on your database you should go with a specific _id
Try to execute only the match and find out:
db.collection.aggregate([
{$match:{_id: ObjectId("6a1464430b4215046c768y66")}}
])
If the $match executed in your database returns a result but not through your node.js application then try this:
ObjectId = require('mongodb').ObjectID;
db.collection.aggregate([
{$match:{_id: new ObjectId(req.body.id})}},
{$project: { total: {$add: ["$a", "$b", "$c"]}}} // etc. you could also use $sum
])
Its Done. Problem is not in Query. Actually Object Id problem. So convert id to object id with the help of some mongoose logic.
First call Mongoose i.e
var mongoose = require('mongoose');
After That Under Function Just add
var id = mongoose.Types.ObjectId(req.body.id);
Now Problem is solved. It convert String to Object.
Your query is absolutely fine , the problem is that what you are passing the "_id" field in the query is a string but type of your "_id" if the data record is ObjectId. So you will have to first convert the string to ObjectId and then pass into "_id" field in the $match field in the query.
var docId = mongoose.Types.ObjectId(req.body.id);
db.collection.aggregate([
{$match:{_id: docIid}},
{$project: {total: { $sum: [ "$a", "$b", "$c", "$d","$e","$f","$g" ]}}}]);
Use {$match:{_id:ObjectId("SomeId")}}
db.collection_name.aggregate([
{$match:{_id:ObjectId("5a153c6d4828c9f51d3adb53")}},
{$project: {total: { $sum: [ "$a", "$b", "$c", "$d","$e","$f","$g" ]}}}
])
Use mongoose.Types.ObjectId(id) to convert an id to ObjectId
output :
{
"_id" : ObjectId("5a153c6d4828c9f51d3adb53"),
"total" : 142 }

Insert an array item after a specific value in mongodb

I have db like;
{"_id" : 1 , children: ["2", "123", "5"]}
I want to insert an element into children array. It is easy when you know the index. $position can be used for this purpose. Like;
db.module_menu.update({"_id" : "1"} ,
{$push : {"children" : {
$each : ["12"] , $position : 1}}});
But lets say I don't know the position index, I know the value of element that I want to insert after.
I have value: 12 and value that I want to insert after. insertAfter: 2
As a result it should be;
{"_id" : 1, text: "" , children: ["2","12", "123", "5"]}
How can I do it?
Go through each element in the children array find the index of value and by using $position, insert the value. Is it the only way to do it?
I'm afraid it's not possible. There's an ancient issue to use $function in update but you're only way is to read, modify and persist the array.
On the other hand if you an array of embedded documents then you can use the $ positional operator.
The $ can be used to $set a value in you simple array but to $push.
> db.test.find()
{ "_id" : ObjectId("56d9570d09e01f20e254d0f3"), "a" : [ 1, 2, 3 ] }
> db.test.update({"_id": ObjectId("56d9570d09e01f20e254d0f3"), "a": 2},
{$push: {a: {$each: [2], $position: "$"}}})
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 2,
"errmsg" : "The value for $position must be a positive numeric value not a String"
}
})
There is no function to get position and update at one go,
so in you problem you need to get position before update and pass it to query - so that means we are operating on 1:1 bassis.
Other approach is to retrieve data - perform in-place update and push full array set back to mongo

Mongoose count by subobjects

I am trying to count the number of models in a collection based on a property:
I have an upvote model, that has: post (objectId) and a few other properties.
First, is this good design? Posts could get many upvotes, so I didn’t want to store them in the Post model.
Regardless, I want to count the number of upvotes on posts with a specific property with the following and it’s not working. Any suggestions?
upvote.count({‘post.specialProperty’: mongoose.Types.ObjectId(“id”), function (err, count) {
console.log(count);
});
Post Schema Design
In regards to design. I would design the posts collection for documents to be structured as such:
{
"_id" : ObjectId(),
"proprerty1" : "some value",
"property2" : "some value",
"voteCount" : 1,
"votes": [
{
"voter": ObjectId()// voter Id,
other properties...
}
]
}
You will have an array that will hold objects that can contain info such as voter id and other properties.
Updating
When a posts is updated you could simply increment or decrement the voteCountaccordingly. You can increment by 1 like this:
db.posts.update(
{"_id" : postId},
{
$inc: { voteCount: 1},
$push : {
"votes" : {"voter":ObjectId, "otherproperty": "some value"}
}
}
)
The $inc modifier can be used to change the value for an existing key or to create a new key if it does not already exist. Its very useful for updating votes.
Totaling votes of particular Post Criteria
If you want to total the amount for posts fitting a certain criteria, you must use the Aggregation Framework.
You can get the total like this:
db.posts.aggregate(
[
{
$match : {property1: "some value"}
},
{
$group : {
_id : null,
totalNumberOfVotes : {$sum : "$voteCount" }
}
}
]
)

mongoose $match wont return document

I use two ways to retrieve documents from my collection, the first one:
db.comments.find({"nid" : "req.body.data"});
returns many doc like:
{
"nid" : 20404,
"_id" : ObjectId("5638ba331294943d3d0a092b"),
"uid" : 1937,
"posted" : ISODate("2015-11-03T13:44:19.811Z"),
"text" : "txt",
"title" : "Test nid 2",
"stars" : 3,
"__v" : 0
}
,
And for another query I need to use aggregate and the query:
var pipleline = [
{$match: {nid:req.body.data}}
];
Comments.aggregate(pipleline, function(err, rank){
if(err) {
res.send("Error", String(err));
}
res.send(rank);
});
Returns [] - empty array.
Any ideas?
You can use the built in function chaining mongoose provides. Aside from match, it also has sort, project, group, and few others I don't know off the top of my head. More info here
Comments.aggregate().match({nid:req.body.data})
.exec(function(err,rank){
if(err) {
res.send("Error", String(err));
}
res.send(rank);
});

Can't get to work $match & $group MongoDB Aggregation

This is my code:
this.aggregate(
{
$match: {
measurer_code : parseInt(_measurer_code),
user_code :parseInt(_user_code),
time : {$gte : iDate, $lte : eDate}
}
},
{
$group:{
sum: {$sum: "$value"},
cant: {$sum: 1}
}
},cb);
The collection has the following structure:
user_code : Number,
measurer_code : Number,
value : Number,
time : Date
Returns undefined. But if i run only $match or only $group returns documents.
Someone can help me?
Thanks!
When you group you have to specify how you want to group the data.
If you want count and sum across all the documents, you have to add "_id":null or "_id":1 (any constant) in the $group document. If you want to group by field "foo" you have to add "_id":"$foo" to the document.

Resources