Group data in mongoDB - node.js

I am trying to group the data that I get from mongoDB by repo Id
my collection structure is:
{
"id":"8820624457",
"type":"CreateEvent",
"actor":{
"id":27394937,
"login":"Johnson0608",
"display_login":"Johnson0608",
"gravatar_id":"",
"url":"https://api.github.com/users/Johnson0608",
"avatar_url":"https://avatars.githubusercontent.com/u/27394937?"
},
"repo":{
"id":163744671,
"name":"Johnson0608/test",
"url":"https://api.github.com/repos/Johnson0608/test"
},
"payload":{
"ref":"master",
"ref_type":"branch",
"master_branch":"master",
"description":null,
"pusher_type":"user"
},
"public":true,
"created_at":"2019-01-01T15:00:00Z"
}
my code is :
collection.find({}).project({ 'repo.id': 1, 'actor.login': 1, 'type': 1 }).toArray(function (err, docs) {
assert.equal(err, null);
console.log("Found the following records");
res.status(200).json({ docs });
callback(docs);
});
I am trying to group by repo id but i don't know how(I am new to mongoDB)

Go to this MongoPlayAround
db.collection.aggregate([
{
$group: {
_id: "$repo.id",
Actors: {
$push: {
"login": "$actor.login"
}
}
}
}
])

You can use the aggregation function.
Groups input documents by the specified _id expression and for each distinct grouping, outputs a document. The _id field of each output document contains the unique group by value.
collection
.find({})
.aggregate({
$group: {
_id : 'repo.id' // The thing you want to group by
// ... Other arguments
}
})
.project({'repo.id': 1, 'actor.login': 1, 'type': 1})
.toArray(function (err, docs) {
assert.equal(err, null);
console.log("Found the following records");
res.status(200).json({ docs });
callback(docs);
});

Related

Mongodb findone document in array by object id

Can I get only 1 photo by objectid? I only need to get 1 Image detail from 1 post by photo but what i get is all photo of post.
this is my db structure
and this is my code looks like:
Post.findOne({
$and: [
{ photo: { $elemMatch: { _id: id } } } ]
}).exec((err, post) => {
if (err) {
return res.status(400).json({ error: err });
}
req.post = post;
console.log(req.post);
next();
});
what i get in req.post is only [].
Thanks in advance
The $elemMatch projection operator provides a way to alter the returned documents, in here coupled with find utilizing second parameter which is projection will achieve that.
Post.find(
{},
{
_id: 0,
photo: { $elemMatch: { _id: id } }
}
);
This will provide leaned documents with the promise: .lean().exec():
Post.find(
{},
{
_id: 0,
photo: { $elemMatch: { _id: id } }
}
)
.lean()
.exec();
NOTE: $elemMatch behaviour is to return the first document where photo's id matches.
You can try with aggregate instead of findOne:
https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
Post.aggregate([
{ $match: { 'photo._id': id } },
{ $unwind: "$photo" },
{ $match: { 'photo._id': id } },
]);
Maybe not the best, but single photo data is achievable.

How can I sort then limit an array of results in mongoose?

I have a user collection with tons of properties for each user. One property is an array ('guestlist_submitted') and I am trying to return the most recent 3 items in that array according to the date field.
Using Mongoose, I am able to target that specific property on the user object, but I can't seem to limit or sort them.
I've tried using slice and and a date calculation to return the most recent 3 entires but I am struggling to get it right and not sure whats wrong.
I've tried using Sort/Limit on query builder but I've learned that these dont apply to the array inside the property I am targeting despite me saying " select('guestlist_submitted') as part ofthe query builder.
When I return the result after just targeting the property on the user object, it comes as an array with an object inside and another array inside that object. I am having problems traversing down it to target that inner array to use the slice and date sort.
First try
Users.
find({ fbid: req.query.fbid}).
select('guestlist_submitted -_id').
sort({"guestlist_submitted.date": 'desc'}).
limit(3).
exec((err, result) => {
if (err) throw (err));
if (result) {
res.send(result);
}
Second try:
Users.
find({ fbid: req.query.fbid}).
select('guestlist_submitted -_id').
exec((err, result) => {
if (err) res.send(JSON.stringify(err));
if (result) {
const slicedArr = result.guestlist_submitted.slice(0,1);
const sortedResultArr = slicedArr.sort(function(a,b){return new Date(b.date) - new Date(a.date); });
const newResultArr = sortedResultArr.slice(0, 2);
res.send(newResultArr);
}
The best I can get is 'guestlist_submitted' and the entire array, not sorted and its inside an object inside an array.
I expected the output to be the 3 most recent results from the guestlist_submitted array inside a new array that I can display on the client.
Thank you in advance for your help
EDIT - FIGURED OUT A SOLUTION
I ended up figuring out how to get what I wanted using aggregate()
Users.aggregate([
{
$match: {fbid: req.query.fbid}
},
{
$unwind: "$guestlist_submitted"
},
{
$sort: {'guestlist_submitted.date': -1}
},
{
$limit: 2
},
{
$group: {_id: '$_id', guestlist_submitted: {$push: '$guestlist_submitted'}}
},
{
$project: { guestlist_submitted: 1, _id: 0 }
}
]).exec((err, result) => {
if (err) res.send(JSON.stringify(err));
if (result) {
console.log(result);
res.send(result);
}
});
I ended up figuring out how to get what I wanted using aggregate()
Users.aggregate([
{
$match: {fbid: req.query.fbid}
},
{
$unwind: "$guestlist_submitted"
},
{
$sort: {'guestlist_submitted.date': -1}
},
{
$limit: 2
},
{
$group: {_id: '$_id', guestlist_submitted: {$push: '$guestlist_submitted'}}
},
{
$project: { guestlist_submitted: 1, _id: 0 }
}
]).exec((err, result) => {
if (err) res.send(JSON.stringify(err));
if (result) {
console.log(result);
res.send(result);
}
});

Mongoose/MongoDB: Get second to last document

How would I be able to get the 2nd to latest or previous day's document collection using Mongoose?
my code to get the latest data goes as follows:
data.findOne()
.sort({"_id": -1})
.exec(function(err, data) {
if (err) {
console.log('Error getting data..');
}
if (data) {
res.json(data);
}
else {
console.log('No data found!');
}
});
This only returns the LATEST document in the collection. I instead need the one prior to the latest one, so one from a day before this one, how would I be able to do this?
Then you'd have to use aggregation:
db.getCollection('data').aggregate([
{ $sort: {"date": -1}}, // sort by latest date (descending)
{ $limit: 2}, // limit to the first two results
{ $sort: {"date": 1}}, // sort these two ascending
{ $limit: 1} // get first document
])
This pipeline is translated in mongoose like that (I think):
data.aggregate([
{ $sort: {"date": -1}},
{ $limit: 2},
{ $sort: {"date": 1}},
{ $limit: 1}
], function (err, result) {
if (err) {
next(err);
} else {
res.json(result);
}
});
Also by sorting without adding a $match pipeline, your first $sort would sort all the documents in your collection. So if your collection is big you might want to restrict them with some query parameters that you can add to the $match pipeline. You could add the match before the first $sort
E.g.
db.getCollection('data').aggregate([
{ $match: {...}},
{ $sort: {"date": -1}},
{ $limit: 2},
{ $sort: {"date": 1}},
{ $limit: 1}
])
To get the 2nd before the last
data.find().sort({ _id: -1 }).limit(1).skip(2).exec(function(err, data) {
if (err) {
console.log('Error getting data..');
}
if (data) {
res.json(data);
}
else {
console.log('No data found!');
}
});
you could do like this:
data
.find()
.limit(2)
.sort({ "_id": -1 })
.exec(function(err, data) {
if (err) return res.json(err);
return res.json(data);
});
this will find on all documents sorted by id: -1, and then limit the result to two, this should give you the result that you want.
This is the fix I used to make it work for what I wanted, hope it helps others:
I decided to import MomentJS, from there it displays the current date, subtracting 1 from it, to display the previous date's value, hence giving me the proper collection from the day before.
Code:
const moment = require('moment');
...
data.find({"updated": moment().subtract(1, 'days').format('L')}) ...

How to update a array value in Mongoose

I want to update a array value but i am not sure about the proper method to do it ,so for i tried following method but didnt worked for me.
My model,
The children field in my model
childrens: {
type: Array,
default: ''
}
My query,
Employeehierarchy.update({ _id: employeeparent._id} ,{ $set: {"$push": { "childrens": employee._id }} })
.exec(function (err, managerparent) {});
Can anyone please provide me help.Thanks.
You can't use both $set and $push in the same update expression as nested operators.
The correct syntax for using the update operators follows:
{
<operator1>: { <field1>: <value1>, ... },
<operator2>: { <field2>: <value2>, ... },
...
}
where <operator1>, <operator2> can be from any of the update operators list specified here.
For adding a new element to the array, a single $push operator will suffice e.g. you can use the findByIdAndUpdate update method to return the modified document as
Employeehierarchy.findByIdAndUpdate(employeeparent._id,
{ "$push": { "childrens": employee._id } },
{ "new": true, "upsert": true },
function (err, managerparent) {
if (err) throw err;
console.log(managerparent);
}
);
Using your original update() method, the syntax is
Employeehierarchy.update(
{ "_id": employeeparent._id},
{ "$push": { "childrens": employee._id } },
function (err, raw) {
if (err) return handleError(err);
console.log('The raw response from Mongo was ', raw);
}
);
in which the callback function receives the arguments (err, raw) where
err is the error if any occurred
raw is the full response from Mongo
Since you want to check the modified document, I'd suggest you use the findByIdAndUpdate function since the update() method won't give you the modified document, just the full write result from mongo.
If you want to update a field in the document and add an element to an array at the same time then you can do
Employeehierarchy.findByIdAndUpdate(employeeparent._id,
{
"$set": { "name": "foo" },
"$push": { "childrens": employee._id }
}
{ "new": true, "upsert": true },
function (err, managerparent) {
if (err) throw err;
console.log(managerparent);
}
);
The above will update the name field to "foo" and add the employee id to the childrens array.
can follow this
if childrens contains string values then model can be like:
childrens: [{
type : String
}]
if childrens contains ObjectId values of another collection _id and want populate then model can be like:
childrens: [{
type : mongoose.Schema.Types.ObjectId,
ref: 'refModelName'
}]
no need to use $set just use $push to insert value in childrens array. so query can be like:
Employeehierarchy.update(
{ _id: employeeparent._id},
{"$push": { "childrens": employee._id } }
).exec(function (err, managerparent) {
//
});
This will help I guess
Employeehierarchy.findOneAndUpdate(
{ _id:employeeparent._id },
{ $set: { "childrens": employee._id }}
)

Mongoose select $ne in array

I am wondering how you would do a query where array._id != 'someid'.
Here is an example of why I would need to do this. A user wants to update their account email address. I need these to be unique as they use this to login. When they update the account I need to make sure the new email doesn't exist on another account, but don't give an error if it exists on their account already (they didn't change their email, just something else in their profile).
Below is the code I have tried using. It doesn't give any errors, but it always returns a count of 0 so an error is never created even if it should.
Schemas.Client.count({ _id: client._id, 'customers.email': email, 'customers._id': { $ne: customerID } }, function (err, count) {
if (err) { return next(err); }
if (count) {
// it exists
}
});
I'm guessing it should use either $ne, or $not, but I can't find any examples for this online with an ObjectId.
Example client data:
{
_id: ObjectId,
customers: [{
_id: ObjectId,
email: String
}]
}
With your existing query, the customers.email and customers._id parts of your query are evaluated over all elements of customers as a group, so it won't match a doc that has any element with customerID, regardless of its email. However, you can use $elemMatch to change this behavior so the two parts operate in tandem on each element at a time:
Schemas.Client.count({
_id: client._id,
customers: { $elemMatch: { email: email, _id: { $ne: customerID } } }
}, function (err, count) {
if (err) { return next(err); }
if (count) {
// it exists
}
});
I was able to do this using aggregate.
Why this didn't work the way I had it: When looking for $ne: customerID it will never return a result because that _id does in fact exist. It can't combine cutomers.email and customers._id the way I wanted it to.
Here is how it looks:
Schemas.Client.aggregate([
{ $match: { _id: client._id } },
{ $unwind: '$customers' },
{ $match: {
'customers._id': { $ne: customerID },
'customers.email': req.body.email
}},
{ $group: {
_id: '$_id',
customers: { $push: '$customers' }
}}
], function (err, results) {
if (err) { return next(err); }
if (results.length && results[0].customers && results[0].customers.length) {
// exists
}
});
);

Resources