MongoDB Mongoose aggregation / math operations - javascript

I am having a problem with understanding how the $aggregation method works in Mongoose. To be honest, I couldn't found any code examples in mongoose docs (I haven't found even [search] option on their site [google site:mongoosejs.com helps me])
So, I hope someone will help me to explain this and answer my question.
For example, I have various documents in my collection, with fields:
{ "_id": 1, "item":47139, "total_price": 560000, "quantity": 56, "lastModified" : 1491748073000 }
{ "_id": 3, "item":47140, "total_price": 1750000, "quantity": 150, "lastModified" : 1491748073000 }
and I would like to $sum all the "quantity" for documents with id: 47139 and timestamp: 1491748073000. As for now this code works fine and provides me the necessary data:
var server = mongoose.model('Name_of_my_Schema', Schema);
server.find({ lastModified : 1491748073000, item : 47139 }, null, {sort: 'buyout'},function (err, res) {
total = 0;
for (i = 0; i < res.length; i++) { //sorry, map.reduce, but not this time
total += res[i].quantity;
}
console.log(total); //in this case total = 256
});
but is it possible to do this operation via mongoose? According to mongo docs I should use this code for matching the necessary array of docs, like these:
server.aggregate().match({
lastModified : 1491748073000,
item : 47139
}).exec(function (err, result){
console.log(result);
console.log(err);
});
and then use the $group to group the necessary data and { $sum: "quantity" }, but what I should do next? Why should I use a $group, if I just want to receive a sum of all quantity?
Could someone give me a clue what am I missing?

As you have rightly said, you are missing the $group pipeline step to do the sum aggregate. Complete the pipeline as:
server.aggregate()
.match({
"lastModified": 1491748073000,
"item": 47139
})
.group({
"_id": null,
"total": { "$sum": "$quantity" }
})
.exec(function (err, result){
console.log(result);
console.log(err);
});
or as an array of operators:
server.aggregate([
{ "$match": { "lastModified": 1491748073000, "item": 47139 } },
{ "$group": { "_id": null, "total": { "$sum": "$quantity" } } }
]).exec(function (err, result){
console.log(result);
console.log(err);
});

Related

Build multiple arrays using forEach looping based on results of sequentially run MySQL SELECT queries, then INSERT a final all-inclusive array

I am trying to do sequential forEach looping to build 2 consecutive arrays and then do a MySQL INSERT on the final array.
The first query selects a "pacesetterID" from table_1 and the results are declared in array1 (pacesetterList). The second query selects a "studentID" from table_2 and then pushes the results into array2 for each "pacesetterID" in array1.
At this point, array2 looks something like this (FYI, classID is constant at every point throughout the function):
[{classID: "251", pacesetterID: "40"}, {classID: "251", pacesetterID: "41"}, [{studentID: "138"}, {studentID: "151"}]]
Then, the second query... at this point I get more confused than a chameleon in a bag of skittles.
In any case, what I am trying to achieve is a final array like this that I can insert in a MySQL table:
[
{ classID: "15", pacesetterID: "201", studentID: "87" },
{ classID: "15", pacesetterID: "201", studentID: "88" },
{ classID: "15", pacesetterID: "201", studentID: "89" },
{ classID: "15", pacesetterID: "202", studentID: "87" },
{ classID: "15", pacesetterID: "202", studentID: "88" },
{ classID: "15", pacesetterID: "202", studentID: "89" },
];
The problem is that there could be 1 or many studentIDs as well as 1 or many pacesetterIDs. That gives us, in effect, 4 different combinations to cater for:
single studentID & single pacesetterID
single studentID & many pacesetterID
many studentID & single pacesetterID
many studentID & many pacesetterID
Here is the function itself:
db.query(
"SELECT pacesetterID FROM pacesetterIndex WHERE classID = ?;",
[classID],
(err, results) => {
if (err) {
res.status(500).send("Something went wrong...");
} else {
let parentArr = [
{
pacesetterList: results,
},
];
db.query(
"SELECT studentID FROM classStudent WHERE classID = ?;",
[classID],
(err, results) => {
if (err) {
res.status(500).send("Something went wrong");
} else {
if (results) {
let newArr = [];
parentArr.forEach((parent) => {
parent.pacesetterList.forEach((child) => {
newArr.push({
pacesetterID: child.pacesetterID,
classID: classID,
studentList: results,
});
});
});
let finalArr = [];
newArr.forEach((parent) => {
parent.studentList.forEach((child) => {
finalArr.push({
pacesetterID: parent.pacesetterID,
classID: parent.classID,
studentID: child.studentID,
});
});
});
console.log(finalArr);
finalArr.forEach((data) => {
db.query(
"INSERT INTO pacesetterEntries SET pacesetterID = ?, classID = ?, studentID = ?;",
[data.pacesetterID, data.classID, data.studentID]
);
});
}
}
}
);
}
}
);
So far the function above works about 20% of the time. I need a way to cater for all 4 functions. I have found that the issue comes in at the first query, it does not always return results. Apart from that, the function is solid. Whenever pacesetterIDs are returned, the function executes as expected.
I would greatly appreciate any assistance/guidance. Please let me know if I can supply any more code.

prevent duplicated values mongodb find query

(javascript)
hello, i have a mongodb collection who have this schema:
{
_id: "any",
ids: {
user: "some value who can repeat" // and more keys but i will use this key here
},
time: 400 // can vary
}
I need to get some documents from this collection, filter to "time less than 700" and dont repeat the key "user"
I tried to use js tools for this, but in only 1 find query i get +900 documents
const ids = [];
const query = (await Personagens.find({ time: { $lt: 700 }}).sort({ time: 1 }))
.filter(x => {
if (!ids.includes(x.ids.user)) {
ids.push(x.ids.user);
return true;
}
}).slice(0, 50)
the output who shows +900 documents in 1 query
so i want to know if has some mongo db operator to filter repeated keys (the key ids.user) and get only 50 documents (obs: i use mongoose)
db.collection.aggregate([
{
"$match": {
time: {
"$gt": 700
}
}
},
{
"$group": {
"_id": "$ids.user",
"doc": {
"$push": "$$ROOT"
}
}
},
{
"$set": {
"doc": {
"$first": "$doc"
}
}
},
{
"$replaceWith": "$doc"
}
])
mongoplayground

Find Index of Object in array with Aggregate

Is there a way to get index in aggregate pipeline, I have a result from long aggreagte query
[
{
"_id": "59ed949227ec482044b2671e",
"points": 300,
"fan_detail": [
{
"_id": "59ed949227ec482044b2671e",
"name": "mila ",
"email": "mila#gmail.com ",
"password": "$2a$10$J0.KfwVnZkaimxj/BiqGW.D40qXhvrDA952VV8x.xdefjNADaxnSW",
"username": "mila 0321",
"updated_at": "2017-10-23T07:04:50.004Z",
"created_at": "2017-10-23T07:04:50.004Z",
"celebrity_request_status": 0,
"push_notification": [],
"fan_array": [],
"fanLength": 0,
"celeb_bio": null,
"is_admin": 0,
"is_blocked": 2,
"notification_setting": [
1,
2,
3,
4,
5,
6,
7
],
"total_stars": 0,
"total_points": 134800,
"user_type": 2,
"poster_pic": null,
"profile_pic": "1508742289662.jpg",
"facebook_id": "alistnvU79vcc81PLW9o",
"is_user_active": 1,
"is_username_selected": "false",
"__v": 0
}
]
}
],
so I want to find the index of _id in aggregate query and above array can contain 100s of object in it.
Depending on the available version of MongoDB you have there are different approaches:
$indexOfArray - MongoDB 3.4
The best operator for this is simply $indexOfArray where you have it available. The name says it all really:
Model.aggregate([
{ "$match": { "fan_detail._id": mongoose.Types.ObjectId("59ed949227ec482044b2671e") } },
{ "$addFields": {
"fanIndex": {
"$indexOfArray": [
"$fan_detail._id",
mongoose.Types.ObjectId("59ed949227ec482044b2671e")
]
}
}}
])
$unwind with includeArrayIndex - MongoDB 3.2
Going back a version in releases, you can get the index from the array from the syntax of $unwind. But this does require you to $unwind the array:
Model.aggregate([
{ "$match": { "fan_detail._id": mongoose.Types.ObjectId("59ed949227ec482044b2671e") } },
{ "$unwind": { "path": "$fan_detail", "includeArrayIndex": true } },
{ "$match": { "fan_detail._id": mongoose.Types.ObjectId("59ed949227ec482044b2671e") } }
])
mapReduce - Earlier versions
Earlier versions of MongoDB to 3.2 don't have a way of returning an array index in an aggregation pipeline. So if you want the matched index instead of all the data, then you use mapReduce instead:
Model.mapReduce({
map: function() {
emit(
this._id,
this['fan_detail']
.map( f => f._id.valueOf() )
.indexOf("59ed949227ec482044b2671e")
)
},
reduce: function() {},
query: { "fan_detail._id": mongoose.Types.ObjectId("59ed949227ec482044b2671e") }
})
In all cases we essentially "query" for the presence of the element "somewhere" in the array beforehand. The "indexOf" variants would return -1 where nothing was found otherwise.
Also $addFields is here just for example. If it's your real intent to not return the array of 100's of items, then you're probably using $project or other output anyway.

node.js find date in mongodb [duplicate]

I am using nodejs with the node-mongodb-native driver (http://mongodb.github.io/node-mongodb-native/).
I have documents with a date property stored as ISODate type.
Through nodejs, I am using this query:
db.collection("log").find({
localHitDate: {
'$gte': '2013-12-12T16:00:00.000Z',
'$lt': '2013-12-12T18:00:00.000Z'
}
})
It returns nothing. To make it work I need to do the following instead:
db.collection("log").find({
localHitDate: {
'$gte': ISODate('2013-12-12T16:00:00.000Z'),
'$lt': ISODate('2013-12-12T18:00:00.000Z')
}
})
But ISODate is not recognized in my nodejs code.
So how can I make a query against mongo date fields through my nodejs program?
Thank you
You can use new Date('2013-12-12T16:00:00.000Z') in node.js;
new is a must, because Date() is already use to return date string.
ISODate is concepted in mongodb, you can use it in mongodb console, but it can be different for different programming language.
You can use this, for me worked perfectly
//lets require/import the mongodb native drivers.
var mongodb = require('mongodb');
//We need to work with "MongoClient" interface in order to connect to a mongodb server.
var MongoClient = mongodb.MongoClient;
// Connection URL. This is where your mongodb server is running.
var url = 'mongodb://localhost/klevin';
// Use connect method to connect to the Server
MongoClient.connect(url, function (err, db) {
if (err) {
console.log('Unable to connect to the mongoDB server. Error:', err);
} else {
//HURRAY!! We are connected. :)
console.log('Connection established to', url);
// Get the documents collection
var collection = db.collection('frames');
//We have a cursor now with our find criteria
var cursor = collection.find({
tv: 'tematv',
date_created: {"$gte": new Date("2015-10-01T00:00:00.000Z") , "$lt": new Date("2017-03-13T16:17:36.470Z") }});
//We need to sort by age descending
cursor.sort({_id: -1});
//Limit to max 10 records
cursor.limit(50);
//Skip specified records. 0 for skipping 0 records.
cursor.skip(0);
//Lets iterate on the result
cursor.each(function (err, doc) {
if (err) {
console.log(err);
} else {
console.log('Fetched:', doc);
if(doc !== null){
}
}
});
}
});
we need to use new Date() is best option to get the data.
db.getCollection('orders').aggregate([
{
'$match': {
$and: [
{
status: 'UNASSIGNED'
},
{
plannedDeliveryDate: {
'$eq': new Date('2017-10-09')
}
}
]
}
},
{
$lookup: {
from: "servicelocations",
localField: "serviceLocationId",
foreignField: "serviceLocationId",
as: "locations"
}
},
{
$unwind: "$locations"
},
{
"$project": {
"accountId": 1,
"orderId": 1,
"serviceLocationId": 1,
"orderDate": 1,
"description": 1,
"serviceType": 1,
"orderSource": 1,
"takenBy": 1,
"plannedDeliveryDate": 1,
"plannedDeliveryTime": 1,
"actualDeliveryDate": 1,
"actualDeliveryTime": 1,
"deliveredBy": 1,
"size1": 1,
"size2": 1,
"size3": 1,
"jobPriority": 1,
"cancelReason": 1,
"cancelDate": 1,
"cancelBy": 1,
"reasonCode": 1,
"reasonText": 1,
"status": 1,
"lineItems": 1,
"locations": {
"lng": "$locations.location.lng",
"lat": "$locations.location.lat"
}
}
}
])
You can go through my answer, provided in other asked question.
Their instead of $eq use $gte:"yyyy-mm-dd", $lte:"yyyy-mm-dd"
NodeJS MongoDB: Querying ISO Date
Hope this will help you or somebody else!

Mongoose - Increment a value inside an array of objects

I am having hard time figuring out how to increment a value in an object within an array
For instance I have this document based on Poll schema:
{
"_id": "584b2cc6817758118e9557d8",
"title": "Number of Skittles",
"description": "Test1",
"date": "Dec 9, 2016",
"__v": 0,
"labelOptions": [
{
"Bob": 112
},
{
"Billy": 32
},
{
"Joe": 45
}
]
}
Using express, I am able to get this far:
app.put('/polls/:id', function(req, res){
let id = req.params.id;
let labelOption = req.query.labelOption;
Poll.findOneAndUpdate(
{'_id' : id},
{$inc: {`labelOptions.$.${labelOption}`: 1 }},
function(err){
console.log(err)
})
where labelOption is the one that I would like to increment its value
To be more concise, I am having trouble transversing inside the document.
It is not possible to directly increment the value in the .find query if labelOptions is an Array of Object. To make it easier, you should change the labelOptions type from Array of Objects to Object:
"labelOptions": {
"Bob": 112,
"Billy": 32,
"Joe": 45
};
Also consider using .findByIdAndUpdate instead of .findOneAndUpdate if you are querying by the document's _id. And then, you can achieve what you want by:
Poll.findByIdAndUpdate(
id,
{$inc: {`labelOptions.${labelOption}`: 1 }},
function(err, document) {
console.log(err);
});
UPDATE: If you are persistent on using Array of Objects for labelOptions, there is a workaround:
Poll.findById(
id,
function (err, _poll) {
/** Temporarily store labelOptions in a new variable because we cannot directly modify the document */
let _updatedLabelOptions = _poll.labelOptions;
/** We need to iterate over the labelOptions array to check where Bob is */
_updatedLabelOptions.forEach(function (_label) {
/** Iterate over key,value of the current object */
for (let _name in _label) {
/** Make sure that the object really has a property _name */
if (_label.hasOwnProperty(_name)) {
/** If name matches the person we want to increment, update it's value */
if (_name === labelOption) ++_label._name;
}
}
});
/** Update the documents labelOptions property with the temporary one we've created */
_poll.update({labelOptions: _updatedLabelOptions}, function (err) {
console.log(err);
});
});
There is another way to do this which allows a more flexible document model. If you add a field to your object like:
{
"_id": "584b2cc6817758118e9557d8",
"title": "Number of Skittles",
"description": "Test1",
"date": "Dec 9, 2016",
"__v": 0,
"labelOptions": [
{
"name": "Bob",
"number": 112
},
{
"name": "Billy",
"number": 32
},
{
"name": "Joe"
"number": 45
}
]
}
Then you can do:
app.put('/polls/:id', function(req, res){
let id = req.params.id;
let labelOption = req.query.labelOption;
Poll.findOneAndUpdate(
{
'_id' : id,
'labelOptions.name
},
{$inc: {
`labelOptions.$.number`: 1
}},
function(err){
console.log(err)
})

Categories