merge aggregation and find results in MongoDB - node.js

I have this model:
const recordSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'Person',
required: true
},
date: {
type: Date,
default: Date.now()
},
time: {
type: Date,
default: Date.now()
}
});
So, when I make a HTTP Get request I receive an array of records:
[{/*record1*/}, {/*record2*/}, ...]
The point is that I'm using aggregation to get the number of records of each user (I got that cover), but I would like merge this with the find results to receive something like this:
{
"records": [{/*record1*/}, {/*record2*/}, ...],
"stats": [
{
"_id" : ObjectId("5b6393f2a1d3de31d9547f63"),
"count" : 3.0
},
{
"_id" : ObjectId("5b5d22d8b6195d1b6a5d2574"),
"count" : 17.0
}
]
}
So, how do I get this?
Note: I'm using this for data for some charts, should I handle this on node or on the front-end?

This can be done in the back-end and if you're using MongoDB Server version 3.4.4 or greater in your backend, $facet aggregate pipeline should cover your needs.
Consider the following example which runs two aggregate queries in the same aggregate pipeline using $facet: one returns the records for today and the other returns the counts for each user in the collection:
let start = new Date();
start.setHours(0,0,0,0);
let end = new Date();
end.setHours(23,59,59,999);
Record.aggregate([
{ '$facet': {
'records': [
{ '$match': {
'date': {
'$gte': start,
'$lte': end
}
} }
],
'stats': [
{ '$group': {
'_id': '$user',
'count': { '$sum': 1 }
} }
]
} }
]).exec((err, results) => {
if (err) {
console.error(err);
throw new Error(err);
}
const data = results[0];
console.log(JSON.stringify(data, null, 4));
})
For MongoDB 3.2 and below
1. Using Promises
const recordsQuery = Record.find({ 'date': {
'$gte': start, // date object representing start of today
'$lte': end // date object representing end of today
} }).lean().exec();
const statsQuery = Record.aggregate([
{ '$group': {
'_id': '$user',
'count': { '$sum': 1 }
} }
]).exec();
Promise.all([ recordsQuery, statsQuery ]).then(([ recordsData, statsData ]) => {
const records = recordsData[0];
const stats = statsData[0];
const data = { records, stats };
console.log(JSON.stringify(data, null, 4));
}).catch(err => console.error(err));
2. Using async/await
(async () => {
try {
const recordsQuery = await Record.find({ 'date': {
'$gte': start, // date object representing start of today
'$lte': end // date object representing end of today
} }).lean().exec();
const statsQuery = await Record.aggregate([
{ '$group': {
'_id': '$user',
'count': { '$sum': 1 }
} }
]).exec();
const records = recordsQuery[0];
const stats = statsQuery[0];
const data = { records, stats };
console.log(JSON.stringify(data, null, 4));
} catch (err) {
console.error(err);
}
})();

You can use the $lookup operator to link the original records for the users by Id
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}

Related

How to export the value from the promise

I am fairly new to node and mongoose and I am having some issues with module.exports. While trying to make my code a bit more clean I have been trying to use modules to take the mongoDB queries since they take too much space and it does not that easy to read, but I got an error.
Here I am merging 2 collections to get a result but the console on index.js tells me undefined. When I do a console.log on operations.js and then execute on index, the console from inside return the array but not the one outside.
After a bit of reading I see that is because the value i want is inside of the promise, but how can I get it back then?
operations.js
const express = require('express')
const mongoose = require('mongoose')
const CheckOutOrder = require('../models/CheckOutOrder')
function ordersQuery(){
console.log('lol')
CheckOutOrder.aggregate([
{
$match:{
'is_open':1
}
},
{
$addFields:{
'itemToMatch':{
$toObjectId:'$idItem'
}
}
},
{
$lookup:{
from: 'e_menu_items',
localField: 'itemToMatch',
foreignField: '_id',
as: 'itemsReceipt'
}
},
{
$unwind:'$itemsReceipt'
}
]).then(info => {
console.log(info)
return info
}).catch(e => {
console.log('e')
return e
})
}
module.exports.totOrders = ordersQuery
index.js
const mongoQueries = require('../mongo/operations')
if(ActionDOM=='test'){
let orders = []
orders = mongoQueries.totOrders()
console.log('------------------------------------------------------')
console.log(orders)
}
console
lol
------------------------------------------------------
undefined
[
{
_id: new ObjectId("61da4036bc966b2a284e8075"),
idReceipt: 4,
idUser: '61d1606420f281d51d52e2e1',
idItem: '61d7f9205bda9c3be60b14ed',
is_open: 1,
date_added: 2022-01-09T01:53:58.017Z,
__v: 0,
itemToMatch: new ObjectId("61d7f9205bda9c3be60b14ed"),
itemsReceipt: {
_id: new ObjectId("61d7f9205bda9c3be60b14ed"),
menuItem: 'Molletes',
description: 'Frijoles, queso y salsa',
price: 18,
id_cat: 1,
is_active: 1,
date_added: 2022-01-07T08:26:08.990Z,
date_modified: 2022-01-07T08:26:08.990Z,
__v: 0
}
},
{
_id: new ObjectId("61da4036bc966b2a284e8079"),
idReceipt: 4,
idUser: '61d1606420f281d51d52e2e1',
idItem: '61d7f9205bda9c3be60b14ed',
is_open: 1,
date_added: 2022-01-09T01:53:58.128Z,
__v: 0,
itemToMatch: new ObjectId("61d7f9205bda9c3be60b14ed"),
itemsReceipt: {
_id: new ObjectId("61d7f9205bda9c3be60b14ed"),
menuItem: 'Molletes',
description: 'Frijoles, queso y salsa',
price: 18,
id_cat: 1,
is_active: 1,
date_added: 2022-01-07T08:26:08.990Z,
date_modified: 2022-01-07T08:26:08.990Z,
__v: 0
}
}
]
What am I doing wrong?
You can use async/await to make the code more "linear":
async function ordersQuery() {
console.log('lol');
try {
const info = await CheckOutOrder.aggregate([
{
$match: {
is_open: 1,
},
},
{
$addFields: {
itemToMatch: {
$toObjectId: '$idItem',
},
},
},
{
$lookup: {
from: 'e_menu_items',
localField: 'itemToMatch',
foreignField: '_id',
as: 'itemsReceipt',
},
},
{
$unwind: '$itemsReceipt',
},
]);
console.log(info);
return info;
} catch (e) {
console.log(e);
return e;
}
}
and then call it with await too (the calling function must be declared async):
async function someFunc(/*....*/) {
// ...
if (ActionDOM == 'test') {
let orders = [];
orders = await mongoQueries.totOrders();
console.log('------------------------------------------------------');
console.log(orders);
}
// ...
}

check an array of string value with array of object in mongodb

I have array of strings like this
let fromHour = ['2.5','3','3.5']
let toHour = ['2.5','3','3.5']
I have an array of object saved in mongoDB
timeRange = [
{
from:'2.5',
to:'3'
},
{
from:'3',
to:'3.5'
}
]
I want to check if any of my array of string value exist in that object value
I have tried this but it give me this error ( Unrecognized expression '$match' )
checkAppoint = await Appointment.aggregate([
{
$project: {
date: myScheduleFinal[k].date,
status: { $in: ['pending', 'on-going'] },
timeRange: {
'$match': {
'from': { $in: fromHolder },
'to': { $in: toHolder },
},
},
},
},
]);
also I have tried this solution and it work for me but it take to much time so I am trying this with aggregate
checkAppoint = await Appointment.findOne({
date: myScheduleFinal[k].date,
status: { $in: ['pending', 'on-going'] },
timeRange:{$elemMatch:{
from:{$in:fromHolder},
to:{$in:toHolder}
}}
});
So anyone have a solution for that
Just try $elemMatch and $in operators,
using find() method
checkAppoint = await Appointment.find({
timeRange: {
$elemMatch: {
from: { $in: fromHour },
to: { $in: toHour }
}
}
})
Playground
using aggregate() method
checkAppoint = await Appointment.aggregate([
{
$match: {
timeRange: {
$elemMatch: {
from: { $in: fromHour },
to: { $in: toHour }
}
}
}
}
])
Playground
So I have found a way around to solve this problem and I will share the solution I used
First I want to minimize my request to mongodb so I am now making just one request that bring all the appointment with the required date
and I want to make it this way because my fromHour and toHour array will change many time through single request
helperArray => contains all the day I want to check it's range
let checkAppoint = await Appointment.find({
date: { $in: helperArray },
status: { $in: ['pending', 'on-going'] },
});
now inside my for loop I will go through that data
checkAppoint.filter((singleAppoint) => {
if (singleAppoint._doc.date === myScheduleFinal[k].date) {
singleAppoint._doc.timeRange.map((singleTime) => {
if (fromHolder.includes(singleTime.from)) {
busy = true;
}
});
}
});

MongoDB: Set field value to 10 if less than 10, otherwise increment by one

Consider the following code:
findAndModify({id: id}, undefined, {$inc: {counter: 1}, {$max: {counter: 10})
This fails with an error because both $inc and $max try to update the same field.
But what if I want to set the counter to 10 if its value is less than 10 and if not, increment its value by 1? How do I do that in one atomic operation?
Is there a way to apply the updates sequentially in a single operation?
I don't think that can be done in a single operation. However, you can create an aggregate query with the conditional statements that you then use in your update.
(async () => {
try {
const results = await Model.aggregate([
{ '$match': { 'id': id } },
{ '$project': {
'counter': {
'$cond': [
{ '$lt': ['$counter', 10 ] },
10,
{ '$add': ['$counter', 1 ] }
]
}
} }
]).exec();
const data = results[0];
const { counter } = data;
const doc = await Model.findOneAndUpdate({ id },
{ '$set': { counter } },
{ new: true }
).exec();
console.log(doc);
}
catch (err) {
console.error(err);
}
})()
For an atomic update, include a query that restricts the increment for documents that only have counter less than the given ceiling value of 10:
findAndModify(
{
'id': id,
'counter': { '$lt': 10 }
}, undefined,
{ '$inc': { 'counter': 1 } },
callback
)

Mongoose returning Object not Array

I've struggling trying to make my mongoose query return an array, since I need to:
Find 0..N docs from one collection;
Save this found array in another nested doc collection;
My code:
CarIndex.find({ '_id': { $in: ids } }, 'carID carBrand carModel location')
.then(collections => {
const out = []
collections.map( (doc) => {
const {carID ,carBrand ,carModel ,location} = doc
const car = {carID ,carBrand ,carModel ,location};
out.push(car)
})
console.log(out)
console.log(out.length)
console.log(typeof out)
return CarCollection.findOneAndUpdate({ '_id': collectionId }, { $addToSet: { carCollection: { $each: { out } } } })
});
The output error:
[04/01/2018 11:04:48.980] [LOG]
[ { carID: 'd82e41b0-f14f-11e7-b845-990cb852c2b3',
carBrand: 'Peugeot',
carModel: '207 Sed. Passion XR Sport 1.4 Flex 8V 4p',
location: [-23.539727799999998,-46.5111749] },
{ carID: 'd82f2c10-f14f-11e7-b845-990cb852c2b3',
carBrand: 'Nissan',
carModel: 'Sentra 2.0/ 2.0 Flex Fuel 16V Mec.',
location: [-23.607240972099525,-46.72912079051677] } ]
[04/01/2018 11:04:48.982] [LOG] 2
[04/01/2018 11:04:48.983] [LOG] object
[04/01/2018 11:04:48.997] [ERROR] MongoError: The argument to $each in
$addToSet must be an array but it was of type object
you can do like this
CarIndex.find({ '_id': { $in: ids } }, 'carID carBrand carModel location')
.then(collections => {
const out = []
collections.map( (doc) => {
const {carID ,carBrand ,carModel ,location} = doc
const car = {carID ,carBrand ,carModel ,location};
out.push(car)
})
console.log(out)
console.log(out.length)
console.log(typeof out)
return CarCollection.findOneAndUpdate({ '_id': collectionId }, { $addToSet: { carCollection: { $each: out } } })
});

mongodb get sum of value within time period

I am trying to find some records that has created within specific time period.
Then I want to calculate the sum of value of these record.
{"member_id":"3755","value":184607,"create_time":"2017-8-11 10:36:58"
{"member_id":"3234","value":74582,"create_time":"2017-8-11 10:36:58",
{"member_id":"4857","value":36776,"create_time":"2017-8-11 10:36:58",
{"member_id":"2042","value":15753,"create_time":"2017-8-11 10:36:58",
{"member_id":"1374","value":655103,"create_time":"2017-8-11 10:36:59"
{"member_id":"3777","value":595437,"create_time":"2017-8-11 10:36:59"
{"member_id":"5271","value":306364,"create_time":"2017-8-11 10:36:59"
{"member_id":"2143","value":164831,"create_time":"2017-8-11 10:36:59"
{"member_id":"1374","value":655103,"create_time":"2017-8-11 10:36:59"
{"member_id":"3777","value":595437,"create_time":"2017-8-11 10:36:59"
{"member_id":"5271","value":306364,"create_time":"2017-8-12 10:36:59"
{"member_id":"2143","value":164831,"create_time":"2017-8-12 11:28:59"
{"member_id":"3777","value":595437,"create_time":"2017-8-12 14:46:59"
{"member_id":"5271","value":306364,"create_time":"2017-8-13 11:36:59"
{"member_id":"2143","value":164831,"create_time":"2017-8-13 13:36:59"
...
Here are the code for getting the sum of value, how can I get the sum of value between 2017-8-11 10:36:00 and 2017-8-12 14:00:00
connection.aggregate([{
$match: match
},
{
$group: {
_id: null,
total: {
$sum: "$value"
}
}
}
], function(err, result) {
if (err) throw (err);
result = {
member_id: member_id,
total: result[0].total,
}
cb(result);
});
Don't store date as string. Store it with date format and Try below query
db.connection.aggregate([{
"$match" : { "create_time" : { "$gt" : new ISODate("2017-08-11T10:36:00.000Z"), "$lt" : new ISODate("2017-08-12T14:00:00.000Z") }},
"$group" : { "_id": "$member_id", "total": { "$sum": 1 }}
}])
let todayDate = new Date();
let beforeDate = new Date();
beforeDate.setDate(beforeDate.getDate() - 15); // 15 is days
db.collections.aggregate([
{
"$match":
{
"insertDate":
{
"$lte": todayDate,
"$gte": beforeDate
}
}
}
])
.exec()
.then((result) => {
//result
})
.catch((err) => {
// error
});

Resources