how to use mongoose aggregate to search an array within a collection - node.js

I am trying to group by products in my sales collection and add their totals to know which are the best selling products of my app.
MONGOOSE MODEL
const mongoose = require('mongoose');
const DHCustomerinvoiceSchema = mongoose.Schema({
Saledetail: {
type: Array,
required: true
},
date:{
type: Date,
required: true
},
total:{
type: Number,
required: true
},
pay:{
type: Number,
required: true,
default: 0
},
topay:{
type: Number,
required: true
},
user:{
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'UserDH'
},
customer:{
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'DHcontacto'
},
state:{
type: String,
default: "OWED"
},
created:{
type: Date,
default: Date.now()
},
});
module.exports = mongoose.model('DHCustomerinvoice', DHCustomerinvoiceSchema);
COLLECTION EXAMPLE
{
"id": "5ef6*****",
"Saledetail": [
{
"id": "5ebf*****",
"quantity": 9,
"price": 2000,
"totalline": 18000
}
],
"datesale": "1593129600000",
"grandtotal": 18000,
"user": "5eb9ab******",
"customer": {
"name": "isabella"
},
"state": "PAID"
},
RESOLVER:
mostSellingProducts: async (_,{},ctx)=>{
const Products = await invoice.aggregate([
{ $unwind: "$Saledetail" },
{ $match: { "state" : 'PAID'}},
{ $group: {
_id : "$Saledetail.id",
total: { $sum: '$Saledetail.totalline' }
}},
{
$lookup: {
from: 'dhproducts',
localField: '_id',
foreignField: "_id",
as: "producto"
}
},
{
$limit: 4
},
{
$sort : {total: -1}
}
]);
console.log(Products);
return Products;
},
I have used many methods that actually did not give me this result, but nevertheless I have achieved a positive result in terms of finding my best clients who actually develop it with aggregate, match and group also apply sort and limit ...
but with this example I have not been able to achieve success, and I imagine that it is because the architecture of the collection is distinguished due to the arrangement of the purchase detail

I don't have enough reputation to comment on your question. So I am sharing this as an answer.
I think you can use $elemMatch to search for the item in an array.
const Productos = await Factura.aggregate([{ detlle: { $elemMatch: { $gte: 80, $lt: 85 } } }])
For more detailed info elemMatch

below the answer to my question
mostSellingProducts: async (_,{},ctx)=>{
const Products = await Invoice.aggregate([
{ $unwind: "$Saledetail" },
{ $match: { "state" : 'PAY'}},
{ $group: {
_id : { $toObjectId: "$Saledetail.id" },
total: { $sum: '$Saledetail.totalline' }
}},
{
$lookup: {
from: 'dhproducts',
localField: '_id',
foreignField: "_id",
as: "products"
}
},
{
$limit: 4
},
{
$sort : {total: -1}
}
]);
return Products;
},

Related

How to find sum in Mongo aggregate method with match in different collection in an array of object?

I am trying to find the sum by matching array of object using mongoose. I have 2 collection such as
const accountSchema = new mongoose.Schema({
groupId: {
type: Number,
required: true
},
account_no: {
type: String,
required: true
},
account_name: {
type: String,
required: true
},
opening_balance: {
type: Number,
default: 0
}
})
And second collection as:
const mongoose = require('mongoose')
const AutoIncrement = require('mongoose-sequence')(mongoose);
const accountJournalSchema = new mongoose.Schema({
journal_no: {
type: Number
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users',
required: [true, 'User ID is required.'],
},
groupId: {
type: Number,
required: true
},
date: {
type: Date,
required: true
},
receipt: [
{
account_no: {
type: mongoose.Schema.ObjectId,
ref: 'Accounts',
required: true
},
debit: {
type: Number,
default: 0
},
credit: {
type: Number,
default: 0
},
}
]
})
And my aggregate method is:
await Accounts.aggregate([
{
$match: {
$and: [
{ groupId: {$eq: parseInt(req.params.group_id)} },
{ 'Account_jour.groupId': { $eq: parseInt(req.params.group_id) } }
]
}
},
{ unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])
I am getting error from the above statement:
Arguments must be aggregate pipeline operators
And after solving the issue I also want to find the sum of debit and credit.
Thank you!!
Try this:
await Accounts.aggregate([
{
$match: {
$and: [{ groupId: { $eq: parseInt(req.params.group_id) } }, { "Account_jour.groupId": { $eq: parseInt(req.params.group_id) } }]
}
},
{ $unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])

Mongoose calculating average from another collection

I am new to MongoDB/Mongoose. I am trying to do a search based on a key string for a 'Resource' which will return a list of resources based on average of ratings for that resource. I am having a hard time calculating and returning the average. This is my schema.
Resource Schema:
const ResourceSchema = mongoose.Schema({
title: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
createdDate: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Resource", ResourceSchema);
Rating Schema:
const RatingSchema = mongoose.Schema({
resourceId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Resource",
},
createdDate: {
type: Date,
default: Date.now,
},
rating: {
type: Number,
required: true,
min: 1,
max: 5,
},
review: {
type: String,
required: true,
},
});
module.exports = mongoose.model("Rating", RatingSchema);
Each Resource will have multiple Ratings. I am trying to calculate the average of ratings in my list of fetched Resources and add it to the search results.
This is what I have for my search:
Resource.find({
$or: [
{ title: { $regex: req.params.searchStr.toLowerCase(), $options: "i" } },
{ url: { $regex: req.params.searchStr.toLowerCase(), $options: "i" } },
],
})
Here's one way you could do it.
db.resources.aggregate([
{ // filter resources
"$match": {
"title": {
"$regex": "passenger",
"$options": "i"
},
"url": {
"$regex": "https",
"$options": "i"
}
}
},
{ // get ratings for resource
"$lookup": {
"from": "ratings",
"localField": "_id",
"foreignField": "resourceId",
"pipeline": [
{
"$project": {
"_id": 0,
"rating": 1
}
}
],
"as": "ratings"
}
},
{ // calculate average
"$set": {
"avgRating": { "$avg": "$ratings.rating" }
}
},
{ // don't need ratings array anymore
"$unset": "ratings"
}
])
Try it on mongoplayground.net.

How to aggregate with many conditions on MongoDB. Double $lookup etc

How to display "hardest category" based on in which "study" size of notLearnedWords was the highest. MongoDB Aggregation
I have these 3 models:
Study
WordSet
Category
Study model has reference into WordSet, then WordSet has reference into Category.
And based on Studies i'm displaying statistics.
How i can display "The hardest category" based on size of "notLearnedWords" was the highest?
I don't know on which place i should start with that querying.
For now i display "hardestCategory" as element that is most used.
I think that condition would look something like this:
{ $max: { $size: '$notLearnedWords' } } // size of the study with most notLearnedWords
I would achieve a response like this:
"stats": [
{
"_id": null,
"numberOfStudies": 4,
"averageStudyTime": 82.5,
"allStudyTime": 330,
"longestStudy": 120,
"allLearnedWords": 8
"hardestCategory": "Work" // only this field is missing
}
]
I've tried to do it like this:
const stats = await Study.aggregate([
{ $match: { user: new ObjectID(currentUserId) } },
{
$lookup: {
from: 'users',
localField: 'user',
foreignField: '_id',
as: 'currentUser',
},
},
{
$lookup: {
from: 'wordsets',
let: { wordSetId: '$learnedWordSet' },
pipeline: [
{ $match: { $expr: { $eq: ['$_id', '$$wordSetId'] } } },
{
$project: {
_id: 0,
category: 1,
},
},
{ $unwind: '$category' },
{
$group: {
_id: '$category',
count: { $sum: 1 },
},
},
{ $sort: { count: -1 } },
{ $limit: 1 },
{
$lookup: {
from: 'categories',
localField: '_id',
foreignField: '_id',
as: 'category',
},
},
{
$project: {
_id: 0,
category: { $arrayElemAt: ['$category.name', 0] },
},
},
],
as: 'wordSet',
},
},
{
$group: {
_id: null,
numberOfStudies: { $sum: 1 },
averageStudyTime: { $avg: '$studyTime' },
allStudyTime: { $sum: '$studyTime' },
longestStudy: { $max: '$studyTime' },
allLearnedWords: {
$sum: { $size: '$learnedWords' },
},
hardestCategory: {
$first: {
$first: '$wordSet.category',
},
},
studyWithMostNotLearnedWords: { $max: { $size: '$notLearnedWords' } },
},
},
]);
Study
const studySchema = new mongoose.Schema({
name: {
type: String,
},
studyTime: {
type: Number,
},
learnedWords: [String],
notLearnedWords: [String],
learnedWordSet: {
type: mongoose.Schema.Types.ObjectId,
ref: 'WordSet',
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
});
WordSet
const wordSetSchema = new mongoose.Schema({
name: {
type: String,
},
category: {
type: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true,
},
],
},
});
Category
const categorySchema = new mongoose.Schema({
name: {
type: String,
},
});

Get the average value with mongoose query

I'm getting a list of 'spot' with mongoose filtered by location and other stuff, with the code below which works fine.
But I want the value of 'rate' to be a $avg (average) of all reviews and not the list of the reviews. It's an aggregation of another collection.
this is what I get:
{
"_id":"5f0ade7d1f84460434524d3d",
"name":"Fly",
...
"rate":[
{"_id":"5f0bfca64ca1cc02ffe48faf","spot_id":"5f0ade7d1f84460434524d3d","rate":3},
{"_id":"5f0bfdb44ca1cc02ffe48fb0","spot_id":"5f0ade7d1f84460434524d3d","rate":2},
{"_id":"5f0bfdb44ca1cc02ffe48fb1","spot_id":"5f0ade7d1f84460434524d3d","rate":1}
]
},
but I would like this kind of result:
{
"_id":"5f0ade7d1f84460434524d3d",
"name":"Fly",
...
"rate": 2
},
I tried many different things, and I guess I need to use $group but can't figure out how to get the right output.
the reviews schema:
const reviewsSchema = new mongoose.Schema({
_id: {
type: ObjectId,
required: true,
},
spot_id: {
type: ObjectId,
required: true,
},
rate: {
type: Number,
required: true,
},
})
the spot Schema
const spotsSchema = new mongoose.Schema({
_id: {
type: ObjectId,
required: true,
},
name: {
type: String,
required: true,
},
...
})
The code:
Spots.aggregate
([
{
$geoNear: {
near: { type: "Point", coordinates: [ parseFloat(longitude), parseFloat(latitude) ] },
distanceField: "location",
maxDistance: parseInt(distance) * 1000,
query: {
$and: [
{ $or : filter },
{ $or : closed },
{ published: true }
]
}
}
},
{ $match: {} },
{
$lookup:{
from: 'reviews',
localField: '_id',
foreignField: 'spot_id',
as: 'rate'
}
},
])
You're really close, you just have to actually calculate the avg value which can be done using $map and $avg like so:
{
$addFields: {
rate: {
$avg: {
$map: {
input: "$rate",
as: "datum",
in: "$$datum.rate"
}
}
}
}
}
MongoPlayground

Mongoose $group & $lookup aggregation returns empty array with $unwind

I have 2 schemas TravelRoute & Flights. I am trying to find the $min of Flight.fare.total_price and return that result with some details from TravelRoute which is ref-ed in the Flights schema. I am using Mongo 3.4.1 and Mongoose 4.8.5
const TravelRoute = new mongoose.Schema({
departureAirport: {
type: String,
required: true,
trim: true,
},
arrivalAirport: {
type: String,
required: true,
trim: true,
},
durationDays: {
type: Number
},
fromNow: {
type: Number
},
isActive: {
type: Boolean
},
class: {
type: String,
enum: ['economy', 'business', 'first', 'any']
}
}
const Flights = new mongoose.Schema({
route: {
type: mongoose.Schema.Types.ObjectId, ref: 'TravelRoute'
},
departure_date: {
type: Date
},
return_date: {
type: Date
},
fare: {
total_price: {
type: Number
}
}
}
I have the following code:
Flights.aggregate(
{$group: {
_id: {
route: '$route',
departure_date: '$departure_date',
return_date: '$return_date'
},
total_price: {$min: '$fare.total_price'}
}
},
{$lookup: {
from: 'TravelRoute',
localField: '_id.route',
foreignField: '_id',
as: 'routes'
}},
{$unwind: '$routes'},
{$project: {
'routes.departureAirport': 1,
'routes.destinationAirport': 1,
'departure_date': 1,
'return_date': 1,
'total_price': 1,
'_id': 1
}},
{$sort: {'total_price': 1}},
function(err, cheapFlights){
if (err) {
log.error(err)
return next(new errors.InvalidContentError(err.errors.name.message))
}
res.send(cheapFlights)
next()
})
The above returns an empty array [] when I use $unwind. When I comment out the $unwind stage, it returns the following:
[
{
"_id": {
"route": "58b30cac0efb1c7ebcd14e0a",
"departure_date": "2017-04-03T00:00:00.000Z",
"return_date": "2017-04-08T00:00:00.000Z"
},
"total_price": 385.6,
"routes": []
},
{
"_id": {
"route": "58ae47ddc30b175150d94eef",
"departure_date": "2017-04-03T00:00:00.000Z",
"return_date": "2017-04-10T00:00:00.000Z"
},
"total_price": 823.68,
"routes": []
}
...
I'm reasonably new to Mongo and Mongoose. I am confounded by the the fact that it returns "routes" even though I don't project that. And it doesn't return departure_date (etc) even though I ask for it? I am not sure I need $unwind as there will only be one TravelRoute per Flight.
Thanks...
Edit
Here was the final solution:
{$lookup: {
from: 'travelroutes', //<-- this is the collection name in the DB
localField: '_id.route',
foreignField: '_id',
as: 'routes'
}},
{$unwind: '$routes'},
{$project: {
departureAirport: '$routes.departureAirport',
arrivalAirport: '$routes.arrivalAirport',
departureDate: '$_id.departure_date',
returnDate: '$_id.return_date',
'total_price': 1,
'routeID': '$_id.route',
'_id': 0
}},
{$sort: {'total_price': 1}},

Resources