Sort Embedded Documents in Array and fetch specifics - node.js

I have a mongoose model like this:
var activityItem = mongoose.Schema({
timestampValue: Number,
xabc: String,
full: Boolean,
comp: Boolean
});
var ABC = mongoose.Schema({
activity: [activityItem],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
username: String
});
I want to get the activityItem array elements that have a timestampValue less than a specific value. Also, I want to sort the activity array first according to the timestampValue
This is the code that I currently have. And it doesn't work.
UserActivity.findOne({
'user': current_user,
'activity' : {
$all: [
{
"$elemMatch": {
timestampValue: {
$lte: time
}
}
}
]
}
},
function(err, user){
})
Sample Document structure:
{
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"user" : ObjectId("56bf225342e662f4277ded73"),
"notifications" : [],
"completed" : [],
"activity" : [
{
"timestampValue": 1456902600000,
"xabc": "Some value",
"full": true,
"comp": false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d2")
},
{
"timestampValue": 1456702600000,
"xabc": "Some other value",
"full": true,
"comp": false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d3")
}
],
"__v" : 1
}
The POST call has the following params
hash: "2e74aaaf42aa5ea733be963cb61fc5ff"
time: 1457202600000
hash comes into the picture once i have the docs from mongo
time is a unix timestamp value.
Instead of returning only the elements that are less than the time value, it is returning all the array elements. I tried the aggregation framework to sort the array before querying, but couldn't get the hang of it.
Any help would be greatly appreciated.

Please try to do it through aggregation as below
ABS.aggregate([
// filter the document by current_user
{$match: {user: ObjectId(current_user)}},
// unwind the activity array
{$unwind: '$activity'},
// filter the timestampValue less than time
{$match: {'activity.timestampValue': {$lte: time}}},
// sort activity by timestampValue in ascending order
{$sort: {'activity.timestampValue': 1}},
// group by _id, and assemble the activity array.
{$group: {_id: '$_id', user: {$first: '$user'},activity: {$push: '$activity'}}}
], function(err, results){
if (err)
throw err;
// populate user to get details of user information if needed
//ABS.populate( results, { "path": "user" }, function(err, rets) {
//
//});
});

Well, it seems little bit tricky with MongoDb aggregation pipeline unless you have MongoDB 3.2, but you can definitely
achieve your result with help of map-reduce.
e.g.
MongoDB version < 3.2
var findActivities = function (time) {
db.col.mapReduce(function () {
var item = Object.assign({}, this);
delete item.activity;
item.activity = [];
for (var i = 0; i < this.activity.length; i++) {
if (this.activity[i].timestampValue <= time) {
item.activity.push(this.activity[i]);
}
}
emit(item._id, item);
}, function (k, v) {
return {items: v};
}, {
out: {"inline": true},
scope: {time: time}
}).results.forEach(function (o) {
printjson(o); // Or perform action as appropriate
});
};
Based your sample data when called findActivities(1456802600000), it will find and return only those documents matching criteria.
{
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"value" : {
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"user" : ObjectId("56bf225342e662f4277ded73"),
"notifications" : [
],
"completed" : [
],
"__v" : NumberInt(1),
"activity" : [
{
"timestampValue" : NumberLong(1456702600000),
"xabc" : "Some other value",
"full" : true,
"comp" : false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d3")
}
]
}
}
MongoDB version 3.2+
db.col.aggregate([
{$project:{user:1, notifications:1, completed:1, activity:{
$filter:{input: "$activity", as: "activity", cond:{
$lte: ["$$activity.timestampValue", 1456802600000]}}}}}
])
Both solutions will have same output.

Related

NodeJs : $nin array query in $geoNear in MongoDb doesn't work

I don't understand why my query doesn't work with MongoDb $geoNear ?
I checked this issue without success : mongodb geoNear command with filter
My environment :
NodeJs : V6.6.0
Mongoose : 4.8.6
this works
db.collection.geoNear(
coords,
{
query : { status: "1" } }, // I have the good filtered results
...
I already tested
...
query : { _id: { $in: itemIds } }, //no error and empty result
query : { "_id": "5639ce8c01f7d527089d2a74" }, //no error and empty result
query : { "_id" : 'ObjectId("5649e9f35f0b360300cad022")' }, //no error and empty result
query : { "_id" : ObjectId("5649e9f35f0b360300cad022") }, //error ObjectId not defined
...
I want this
db.collection.geoNear(
coords,
{
query : { _id: { $nin: poiIds } }, // no error but I have not the good results because I obtain all results geolocalized
...
Thank you ;-)
For this issue:
query : { "_id" : ObjectId("5649e9f35f0b360300cad022") }, //error ObjectId not defined
Try:
const ObjectID = require('mongodb').ObjectID
In my case, I did it like this:
Add index to coords in your model, then request with query.
modelSchema.index({ coords: '2dsphere' });
query = {
_id: {
$nin: poiIds
},
coords: {
$near: {
$geometry: { type: "Point", coordinates: position }
}
}
}
where position is an array [Lat, Lon].
Hope this helps. ;)

Mongoose Aggregation match an array of objectIds

I have a schema that Looks like this
var Post = new mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
created: {
type: Date,
Default: Date.now
})
I have a User Table as well. I Have a array of user ids and i am trying to search the post table based on an array of user ids
For Example
var userIds = ["575e96652473d2ab0ac51c1e","575e96652473d2ab0ac51c1d"] .... and so on
I want to return all posts created by these users. And posts should be sorted by their creation date. Is there a way to group this post based on the user ids provided, basically match the posts for an individual user?
The result I am trying to attain is something like this:
[{
userAId : "56656.....",
post : [postA, postB],
},{
userBId :"12345...",
post : [postA, postB]
}]
How do I write this query?
This is what I have so far
Post.aggregate([{
// {"$unwind" : ""},
// "$group": {
// _id: "$author",
// "created" : {"$sum" : 1 }
// }
"$match" : { author : id}
}]).exec(function(error, data) {
if(error){
return console.log(error);
}else{
return console.log(data)
}
})
{
"_id" : ObjectId("575e95bc2473d2ab0ac51c1b"),
"lastMod" : ISODate("2016-06-13T11:15:08.950Z"),
"author" : ObjectId("575dac62ec13010678fe41cd"),
"created" : ISODate("2016-06-13T11:15:08.947Z"),
"type" : "photo",
"end" : null,
"commentCount" : 0,
"viewCount" : 0,
"likes" : 0,
"tags" : [],
"title" : "Today is a good day",
"__v" : 0
}
To return all posts created by users depicted in a list of ids, use the $in operator in your query and then chain the sort() method to the query to order the results by the created date field:
Post.find({ "author": { "$in": userIds } })
.sort("-created") // or .sort({ field: 'asc', created: -1 });
.exec(function (err, data){
if(err){
return console.log(err);
} else {
return console.log(data);
}
});
To get a result where you have the post id's grouped per user, you need to run the following aggregation operation:
Post.aggregate([
{ "$match" : { "author": { "$in": userIds } } },
{ "$sort": { "created": -1 } },
{
"$group" : {
"_id" : "$author",
"posts" : { "$push": "$_id" }
}
},
{
"$project": {
"_id": 0,
"userId": "$_id",
"posts": 1
}
}
]).exec(function (err, result){
if(err){
return console.log(err);
} else {
return console.log(result);
}
});
Or with the fluent API:
Post.aggregate()
.match({ "author": { "$in": userIds } })
.sort("-created")
.group({
"_id" : "$author",
"posts" : { "$push": "$_id" }
})
.project({
"_id" : 0,
"userId" : "$_id",
"posts": 1
})
.exec(function (err, result){
if(err){
return console.log(err);
} else {
return console.log(result);
}
});
This should be possible without aggregation.
Post
.find({ author: { $in: userIds } })
.sort({ created: -1 })
If you get CastError: Cast to ObjectId failed, make sure to map your userIds array from an array of strings to an array of mongoose id's.
userIds = userIds.map(userId => new mongoose.Types.ObjectId(userId))

Create GeoJson from coordinates array in the same Mongodb collection

I want to modify my mongodb collection from 2d to 2dsphere.
I have this structure in my db.users:
{
"_id" : "1c6930387123e960eecd65e8ade28488",
"interests" : [
{
"_id" : ObjectId("56a8b2a72f2c2a4c0250e896"),
"coordinates" : [-3.6833, 40.4]
}
],
}
I would like to have something like this:
{
"_id" : "1c6930387123e960eecd65e8ade28488",
"interests" : [
{
"_id" : ObjectId("56a8b2a72f2c2a4c0250e896"),
"loc":{
"type":"Point",
"coordinates" : [-3.6833, 40.4]
}
}
],
}
I tried this:
db.users.update( {interests:{$elemMatch:{coordinates : { $exists: true } }}}, { $set: { 'interests.$.place':{"type":"Point", "coordinates":'interests.$.coordinates'} } }, { upsert: false, multi: false });
Obviously, it insert literally "'interests.$.coordinates'
And tried this in Node:
users.find({interests:{$elemMatch:
{coordinates :
{ $exists: true } }}} ,
{"interests.coordinates":1,"interests._id":1,"_id":0 }).forEach( function( doc ){
users.update( {interests:
{$elemMatch:
{_id : doc.interests[0]['_id'] }}},
{ $set: { 'interests.$.loc':{"type":"Point", "coordinates":doc.interests[0]['coordinates']} } },
{ upsert: false, multi: false },function(err, numberAffected, rawResponse) {
var modified=numberAffected.result.nModified;
console.log(modified)
});
});
But coordinates were inserted with mixed values.
Thoughts?
Thanks!!
I didn't test this piece of code but I think this would shed light on this issue.
users.find(
{interests:{ $elemMatch: {coordinates : { $exists: true } }}}).forEach( function( doc ){
for(var c = 0; c < doc.interests.length; c++) {
var orig = doc.interests[c].coordinates;
doc.interests[c].loc: { type:"Point", coordinates: orig };
//delete doc.interests[c].coordinates;
};
users.update({ _id: doc._id }, doc);
});
Try to iterate over all documents in your collection modifying the whole document and then update it in a single line.
Warning: If you have a huge quantity of documents in your collection that match the 'find' condition, you should use pagination (skip/limit) to avoid performance and memory issues in the first load.

How do I query an Id in an array? Mongoose

How do I query a specific _id in an array using mongoose
Here's the code
var userSchema = new mongoose.Schema({
resume: {
educations: [{
school: String,
year: String
}]
}
});
How do I specifically query a educations id?
My attempt
User.findById({ 'resume.educations._id': req.body.education_id}, function(err, foundUser){
console.log(foundUser) --> Keep returning Undefined
});
The data that has been stored
console.log(req.body.education_id) --> 5644318364acb9f1eb25c939
You can use $elementMatch with findOne like so
User.findOne({'resume.educations': {$elemMatch: {_id: '564497eb366f59b004e9d17e'}}}, function(err, foundUser){
console.log(foundUser);
// output
// [ { resume: { educations: [ [Object] ] },
// __v: 0,
// _id: 564497eb366f59b004e9d17d } ]
});
this is a document i tried it on and it worked
{
"_id" : ObjectId("564497eb366f59b004e9d17d"),
"resume" : {
"educations" : [
{
"school" : "school name",
"year" : "1999",
"_id" : ObjectId("564497eb366f59b004e9d17e")
}
]
},
"__v" : 0
}

How to build query for relating parent document's field to embedded array document's field. Using MongoDB aggregation Operators

Consider the following document in the collection named 'CityAssociation'
{
"_id" : "MY_ID",
"ThisCityID" : "001",
"CityIDs" : [{
"CityID" : "001",
"CityName" : "Bangalore"
}, {
"CityID" : "002",
"CityName" : "Mysore"
}],
"CityUserDetails": {
"User" : "ABCD"
}
}
Now I have User value i.e. in above case I have value ABCD and want to find it with only city where the first level's field ThisCityID matches to the embedded array documnet's field CityID. Finally I need to project as follows (for the above case):
{
'UserName': 'ABCD',
'HomeTown':'Bangalore'
}
In Node.js + MongoDB native drive, I wrote a aggregation query as follows which is not working as expected.
collection.aggregate([
{ $match: { 'CityUserDetails.User': 'ABCD', 'CityIDs': { $elemMatch: { CityID: ThisCityID}}} },
{ $unwind: "$CityIDs" },
{ $group: {
_id: '$_id',
CityUserDetails: { $first: "$CityUserDetails" },
CityIDs: { $first: "$CityIDs" }
}
},
{ $project: {
_id: 0,
"UserName": "$CityUserDetails.User",
"HomeTown": "$CityIDs.CityName"
}
}
], function (err, doc) {
if (err) return console.error(err);
console.dir(doc);
}
);
Can anyone tell me how this can be done with query.
Note: On MongoDB schema we don't have control to change it.
You can use the $eq operator to check if the first level's field ThisCityID matches embedded array document's field CityID.
db.city.aggregate([
{ $match : { "CityUserDetails.User" : "ABCD" }},
{ $unwind : "$CityIDs" },
{ $project : {
matches : { $eq: ["$CityIDs.CityID","$ThisCityID"]},
UserName : "$CityUserDetails.User",
HomeTown : "$CityIDs.CityName"
}},
{ $match : { matches : true }},
{ $project : {
_id : 0,
UserName : 1,
HomeTown : 1
}},
])
And the result is:
{
"result" : [
{
"UserName" : "ABCD",
"HomeTown" : "Bangalore"
}
],
"ok" : 1
}

Resources