I need some help with Mongo, Mongoose and Node.js.
In the code below, I'd like to join carrinho and produtos collection to retrieve produtos _id, price and description in the same array/object.
My Carrinho Schema
const Carrinho = new mongoose.Schema(
{
title: {
type: String,
},
produtos: [{
price: Number,
produto: { type: mongoose.Schema.Types.ObjectId, ref:
"Produtos" }
}
],
total: {
type: Number,
},
},
{
timestamps: true
})
My Produtos Schema
const Produtos = new mongoose.Schema(
{
description: {
type: String,
required: true,
},
gtin: {
type: String,
required: true,
unique: true,
},
thumbnail: {
type: String,
},
price: {
type: Number,
}
},
{
timestamps: true
}
)
After reading aggregate documentation this is the best I've got:
Carrinho.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.id) } },
{
"$lookup": {
"from": "produtos",
"localField": "produtos._id",
"foreignField": "_id",
"as": "produtosnocarrinho"
}
},
{
"$addFields": {
"total": {
"$reduce": {
"input": "$produtos",
"initialValue": 0,
"in": { "$add": ["$$value", "$$this.price"] }
}
}
}
}
]).exec((err, data) => {
if (err) res.json(err)
res.json(data)
});
And this is the result:
[
{
"_id": "5cb76d7d99c3f4062f512537",
"title": "Carrinho do Lucas",
"produtos": [
{
"_id": "5cafead2bc648978100d7698",
"price": 20.1
},
{
"_id": "5cae911adf75ac4d3ca4bcb6",
"price": 20.1
},
{
"_id": "5cb0f0adc5fb29105d271499",
"price": 20.1
}
],
"createdAt": "2019-04-17T18:16:29.833Z",
"updatedAt": "2019-04-19T00:50:43.316Z",
"__v": 3,
"produtosnocarrinho": [
{
"_id": "5cae911adf75ac4d3ca4bcb6",
"description": "AÇÚCAR REFINADO UNIÃO 1KGS",
"gtin": "7891910000197",
"thumbnail": "7891910000197",
"createdAt": "2019-04-11T00:58:02.296Z",
"updatedAt": "2019-04-11T00:58:02.296Z",
"__v": 0
},
{
"_id": "5cafead2bc648978100d7698",
"description": "HASBRO MR. POTATO HEAD MALETA DE PEÇAS",
"gtin": "5010994598815",
"thumbnail": "pecas_300x300-PU3435f_1.jpg",
"createdAt": "2019-04-12T01:33:06.628Z",
"updatedAt": "2019-04-12T01:33:06.628Z",
"__v": 0
},
{
"_id": "5cb0f0adc5fb29105d271499",
"description": "REPELENTE EXPOSIS INFANTIL SPRAY",
"gtin": "7898392800055",
"thumbnail": "PU28bb9_1.jpg",
"createdAt": "2019-04-12T20:10:21.363Z",
"updatedAt": "2019-04-12T20:10:21.363Z",
"__v": 0
}
],
"total": 60.300000000000004
}
]
The following Query will be help:
models.Carrinho.aggregate(
[
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.id) } },
{
"$lookup": {
"from": "produtos",
"localField": "produtos._id",
"foreignField": "_id",
"as": "produtosnocarrinho"
}
},
{
"$addFields": {
"total": {
"$reduce": {
"input": "$produtos",
"initialValue": 0,
"in": { "$add": ["$$value", "$$this.price"] }
}
}
}
},
{$unwind : '$produtos'},
{$unwind : '$produtosnocarrinho'},
{$redact: { $cond: [{
$eq: [
"$produtos._id",
"$produtosnocarrinho._id"
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{ $project: {
_id : 1,
title : 1,
produtosData : {
_id : "$produtos._id",
price : "$produtos.price",
description : "$produtosnocarrinho.description"
},
total : 1,
createdAt: 1,
updatedAt : 1
}
},
{
$group : {
_id : {
_id : '$_id',
title : '$title',
total : '$total',
createdAt : '$createdAt',
updatedAt : '$updatedAt'
},
produtosData: {$push: "$produtosData" }
}
},
{ $project: {
_id : '$_id._id',
title : '$_id.title',
total : '$_id.total',
createdAt : '$_id.createdAt',
updatedAt : '$_id.updatedAt',
produtosData: '$produtosData'
}
}
]).exec((err, data) => {
if (err) res.json(err)
res.json(data)
});
Output :
[{
"_id": "5cbc42c24502a7318952d7b2",
"title": "Carrinho do Lucas",
"total": 60.300000000000004,
"createdAt": "2019-04-21T10:15:30.629Z",
"updatedAt": "2019-04-21T10:15:30.629Z",
"produtosData": [{
"_id": "5cafead2bc648978100d7698",
"price": 20.1,
"description": "HASBRO MR. POTATO HEAD MALETA DE PEÇAS"
}, {
"_id": "5cae911adf75ac4d3ca4bcb6",
"price": 20.1,
"description": "AÇÚCAR REFINADO UNIÃO 1KGS"
}, {
"_id": "5cb0f0adc5fb29105d271499",
"price": 20.1,
"description": "REPELENTE EXPOSIS INFANTIL SPRAY"
}]
}]
performance depends on produtos matching data from Lookup Query As we are doing double Unwind.
Related
I have the following MongoDB schema:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, 'Email is required.']
},
transactions: [
{
categoryName: {
type: String,
required: [true, 'Category name in transaction is required.']
},
categoryType: {
type: String,
required: [true, 'Category type in transaction is required.']
},
amount: {
type: Number,
required: [true, 'Transaction amount is required.']
}
}
]})
transactions.categoryType can only be Income or Expense. Now per queried _id, I want to return the ratio/percentage of transactions.CategoryName per Income and Expense. Meaning if I have the following data:
{
"_id": 000001,
"email": "asdasd#email.com"
"transactions": [
{
"categoryName": "Food",
"categoryType": "Expense",
"amount": 200
},
{
"categoryName": "Rent",
"categoryType": "Expense",
"amount": 1000
},
{
"categoryName": "Salary",
"categoryType": "Income",
"amount": 15000
}
]
}
the result that I would want is:
{ "email": "asdasd#email.com",
"Income": [["Salary", 100]],
"Expense": [["Food", 16.67],["Rent",83.33]],
}
Now, I have the following query:
return User.aggregate([
{ $match: { _id : ObjectId(request.params.id) } },
{ $unwind : "$transactions"},
{ $group : { _id : { type: "$transactions.categoryType" },
total: {$sum : "$transactions.amount"},
transactionsArray: { $push: "$transactions"}
}
},
{ $project: {
_id: 0,
transactionsArray:1,
type: "$_id.type",
total:1
}
}
])
which returns a data like this:
[
{
"total": 1200,
"transactions": [
{
"categoryName": "Food",
"categoryType": "Expense",
"amount": 200,
},
{
"categoryName": "Rent",
"categoryType": "Expense",
"amount": 1000,
}
],
"type": "Expense"
},
{
"total": 15000,
"transactions": [
{
"categoryName": "Salary",
"categoryType": "Income",
"amount": 15000,
}
],
"type": "Income"
}
]
Now, I do not know how am I going to further process the result set to divide the transactions.amount by the total to get the result that I want.
You may go with multiple steps in aggregations
$unwind to deconstruct the array
$group- first group to group by _id and $categoryType. So we can get the total amount and an amount for particular transaction. This helps to calculate the ratio.
$map helps to loop over the array and calculate the ratio
$reduce- You need comma separated string array of objects. So loop it and get the structure.
$group to group by _id only so we can get the key value pair of category type and Income/Expense when we push
$replaceRoot to make the $grp object as root which should be merged with already existing fields ($mergeObjects)
$project for remove unwanted fields
Here is the code
db.collection.aggregate([
{ "$unwind": "$transactions" },
{
"$group": {
"_id": { id: "$_id", catType: "$transactions.categoryType" },
"email": { "$first": "$email" },
"amount": { "$sum": "$transactions.amount" },
"category": {
$push: { k: "$transactions.categoryName", v: "$transactions.amount" }
}
}
},
{
$addFields: {
category: {
$map: {
input: "$category",
in: {
k: "$$this.k",
v: {
"$multiply": [
{ "$divide": [ "$$this.v","$amount" ]},
100
]
}
}
}
}
}
},
{
"$addFields": {
category: {
"$reduce": {
"input": "$category",
"initialValue": [],
"in": {
"$concatArrays": [
[
[ "$$this.k", { $toString: "$$this.v" } ]
],
"$$value"
]
}
}
}
}
},
{
"$group": {
"_id": "$_id.id",
"email": { "$first": "$email" },
"grp": { "$push": { k: "$_id.catType", v: "$category" } }
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ { "$arrayToObject": "$grp" }, "$$ROOT" ]
}
}
},
{ "$project": { grp: 0 } }
])
Working Mongo playground
I have 3 collections
const userSchema = new mongoose.Schema ({
username : {
type : String,
trim: true
},
name : {
type : String,
trim: true
},
avatar : {
type : String
}
last_seen: {
type: Date,
default: Date.now
},
status: {
type : Boolean,
default: true
}
})
const hsVideoSchema = new mongoose.Schema ({
name : {
type : String,
trim: true,
required : true
},
url : {
type : String,
trim: true,
required : true
},
uploadedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
status: {
type : Boolean,
default: true
}
})
const fsVideoSchema = new mongoose.Schema ({
name : {
type : String,
trim: true,
required : true
},
url : {
type : String,
trim: true,
required : true
},
uploadedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
status: {
type : Boolean,
default: true
}
})
Now, to keep user's action history, i created History Model as below :
const historySchema = new mongoose.Schema ({
user_id : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
hs_videoId : {
type: mongoose.Schema.Types.ObjectId,
ref: 'HsVideo',
default: null
},
fs_videoId : {
type: mongoose.Schema.Types.ObjectId,
ref: 'FsVideo',
default: null
},
action : {
type : String,
trim: true,
enum:['downloaded','viewed','liked','reported']
}
})
So, i will add new record in history collection when user will perform any action. At a time, out of 2 fields (hs_videId & fs_videoId) one field will be null and another will have id of ref document. In history collection, there can be same hs_videId/fs_videId with different action ('downloaded','viewed','liked','reported').
I am looking for query to get user's history by passing user_id and get all video history array with 2 sub arrays : HsVideos and FsVideos. Both sub array should have action's sub-array, which will have complete details of video (name, url,uploadedBy(UserArray),status).
What query i should write to get desire result ?
Query i tried already :
User.aggregate([
{ $match: {_id : ObjectId('5f3a90110132e115db700201')} },
{
$lookup: {
from: "histories",
as: "history",
pipeline: [
{
$match: {
user_id : ObjectId('5f3a90110132e115db700201')
}
}
]
},
},
]).exec(function(err, results){
if(err) console.log(err);
return res.status(200).json(results);
})
Please help ! Any help will be appreciated. Thanks.
EDIT : 1
I am expecting below result :
[
{
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "John",
"status": true,
"username": "jony"
"FsVideos": [
{
Viewed :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
Downloaded :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
Liked :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
Reported :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
}
],
"HsVideos": [
{
Viewed :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
Downloaded :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
Liked :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
},
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
Reported :[
{
"_id": ObjectId("5a934e000102030405000001"),
"url": "http://example.com/video.mp4",
"uploadedBy": {
"_id": ObjectId("5f3a90110132e115db700201"),
"avatar": "default.png",
"last_seen": ISODate("1970-01-01T00:00:00Z"),
"name": "A",
"status": true,
"username": "A"
},
}
],
}
]
}
]
This is totally a bad structure of schema, you really need to update it, because this will cause memory usage and execution time of the query,
You need to use lookup with pipeline and nested lookup,
$match your conditions for user
$lookup with histories collection and result will be in HsVideos
$match get histories of user_id
$lookup for hsVideo collection and result will be in hs_videoId
$match get hsVideo details
$lookup with user collection for updatedBy and result will be in updatedBy
$unwind deconstruct result updatedBy and it will be object
$unwind deconstruct hs_videoId because its array and we need object
$group by action and push required fields in v array
$project to show and hide required fields
User.aggregate([
{ $match: { _id: ObjectId("5f3a90110132e115db700201") } },
{
$lookup: {
from: "histories",
let: { user_id: "$_id" },
as: "HsVideos",
pipeline: [
{ $match: { $expr: { $eq: ["$user_id", "$$user_id"] } } },
{
$lookup: {
from: "hsVideo",
let: { hs_videoId: "$hs_videoId" },
as: "hs_videoId",
pipeline: [
{ $match: { $expr: { $eq: ["$$hs_videoId", "$_id"] } } },
{
$lookup: {
from: "user",
localField: "uploadedBy",
foreignField: "_id",
as: "uploadedBy"
}
},
{ $unwind: "$uploadedBy" }
]
}
},
{ $unwind: "$hs_videoId" },
{
$group: {
_id: "$action",
v: {
$push: {
_id: "$_id",
url: "$hs_videoId.url",
name: "$hs_videoId.name",
uploadedBy: "$hs_videoId.uploadedBy"
}
}
}
},
{ $project: { _id: 0, k: "$_id", v: 1 } }
]
}
},
i am repeating the above lines of explanation, repeat the same flow of HsVideos in FsVideos
{
$lookup: {
from: "histories",
let: { user_id: "$_id" },
as: "FsVideos",
pipeline: [
{ $match: { $expr: { $eq: ["$user_id", "$$user_id"] } } },
{
$lookup: {
from: "fsVideo",
let: { fs_videoId: "$fs_videoId" },
as: "fs_videoId",
pipeline: [
{ $match: { $expr: { $eq: ["$$fs_videoId", "$_id"] } } },
{
$lookup: {
from: "user",
localField: "uploadedBy",
foreignField: "_id",
as: "uploadedBy"
}
},
{ $unwind: "$uploadedBy" }
]
}
},
{ $unwind: "$fs_videoId" },
{
$group: {
_id: "$action",
v: {
$push: {
_id: "$_id",
url: "$fs_videoId.url",
name: "$fs_videoId.name",
uploadedBy: "$fs_videoId.uploadedBy"
}
}
}
},
{ $project: { _id: 0, k: "$_id", v: 1 } }
]
}
},
$addFields to convert FsVideos and HsVideos array to object, action as key and vlaue
{
$addFields: {
FsVideos: { $arrayToObject: "$FsVideos" },
HsVideos: { $arrayToObject: "$HsVideos" }
}
}
])
.exec(function(err, results){
if(err) console.log(err);
return res.status(200).json(results);
})
Playground
I am trying to sort by the task._id & date in desc order. I am able to sort by task._id but sortibg bydate doesnt work, I tried changing the order in aggregate still no luck. I get the response but just the order by usertasks were added in collection and not by the usertask.date
User(name, address, etc)
Task(name, icon, assignee)
UserTask(User.ObjectId, Task.ObjectId, date)
User Collection:
{
"users": [
{
"name": "Bill",
"phone": "345"
},
{
"name": "Steve",
"phone": "123"
},
{
"name": "Elon",
"phone": "567"
}
]
}
Task collection:
{
"tasks": [
{
"name": "Run 100m",
"icon": "run",
"assignee": "Elon"
},
{
"name": "Walk 1 hour",
"icon": "walk",
"assignee": "Bill"
},
{
"name": "Jog 30 minutes",
"icon": "jog",
"assignee": "Steve"
}
]
}
UserTasks:
{
"_id": "5e72fec..",
"user": "5e72fa4..",
"task": "5e72fbac..",
"date": "2020-03-03T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:37.027Z",
"updatedAt": "2020-03-19T05:10:37.027Z",
"__v": 0
},
{
"_id": "5e72fed3..",
"user": "5e72fa4e..",
"task": "5e72fbac..",
"date": "2020-03-12T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:43.296Z",
"updatedAt": "2020-03-19T05:10:43.296Z",
"__v": 0
},
{
"_id": "5e72fed6..",
"user": "5e72fa..",
"task": "5e72fb..",
"date": "2020-03-15T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:46.057Z",
"updatedAt": "2020-03-19T05:10:46.057Z",
"__v": 0
},
{
"_id": "5e72feda...",
"user": "5e72fa4..",
"task": "5e72fb..",
"date": "2020-03-07T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:50.785Z",
"updatedAt": "2020-03-19T05:10:50.785Z",
"__v": 0
}
This is the Aggregate that needs changing
UserTask.aggregate([
{
$lookup: {
from: "tasks",
localField: "task",
foreignField: "_id",
as: "matchedTask"
}
},
{
$unwind: "$matchedTask"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "matchedUser"
}
},
{
$unwind: "$matchedUser"
},
{
$group: {
_id: "$matchedTask._id",
name: {$first: "$matchedTask.name"},
icon: {$first: "$matchedTask.icon"},
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
date: "$date"
}
}
}
},
{
$sort: { _id: 1, "userdata.date": -1 }
}
])
.exec()
.then(doc => res.status(200).json(doc))
.catch(err => res.status(400).json("Error: " + err));
The response is shown below, please note the usertask.date. it is NOT sorted
{
"_id": "5e...",
"name": "Run 100m",
"icon": "run",
"assignee": "Elon",
"userdata": [
{
"name": "Elon",
"date": "2020-03-21T20:02:38.143Z"
},
{
"name": "Bill",
"date": "2020-03-11T20:02:38.000Z"
},
{
"name": "Steve",
"date": "2020-03-19T20:02:38.000Z"
}
]
}
As you can see the it is not sorted by date - desc order. The result should be like shown below
"userdata": [
{
"name": "Elon",
"date": "2020-03-21T20:02:38.143Z"
},
{
"name": "Steve",
"date": "2020-03-19T20:02:38.000Z"
},
{
"name": "Bill",
"date": "2020-03-11T20:02:38.000Z"
}
]
$sort will use the last object which comes out from the aggregate pipe and theres no field "date" in this object:
{
$group: {
_id: "$matchedTask._id",
name: {$first: "$matchedTask.name"},
icon: {$first: "$matchedTask.icon"},
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
execDate: "$date"
}
}
}
},
your group has to returned a field named date in order to be able to sort it
{
$group: {
_id: "$matchedTask._id",
name: ....,
date: ....
}
}
{ $sort: {date: -1}}
if the value you want to sort is indide another object you must specify it on sort:
{$sort: {"userdata.date": -1}}
I fixed it, had to use sort two times, now I am able to get the result as I want
Solution provided below
UserTask.aggregate([
{
$lookup: {
from: "tasks",
localField: "task",
foreignField: "_id",
as: "matchedTask"
}
},
{
$unwind: "$matchedTask"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "matchedUser"
}
},
{
$unwind: "$matchedUser"
},
{
$sort: { date: -1 }
},
{
$group: {
_id: "$matchedTask._id",
name: { $first: "$matchedTask.name" },
icon: { $first: "$matchedTask.icon" },
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
execDate: "$date"
}
}
}
},
{
$sort: { _id: 1 }
}
])
.exec()
.then(doc => res.status(200).json(doc))
.catch(err => res.status(400).json("Error: " + err));
I have the following structure in my collection (you don't have to mind the status) :
{
"_id": {
"$oid": "5e6355e71b14ee00175698cb"
},
"finance": {
"expenditure": [
{
"status": true,
"_id": { "$oid": "5e63562d1b14ee00175698df" },
"amount": { "$numberInt": "100" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e6356491b14ee00175698e0" },
"amount": { "$numberInt": "200" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e63565b1b14ee00175698e1" },
"amount": { "$numberInt": "50" },
"category": "Outdoor"
},
{
"status": true,
"_id": { "$oid": "5e63566d1b14ee00175698e2" },
"amount": { "$numberInt": "400" },
"category": "Outdoor"
}
]
}
}
My previos command was this:
User.aggregate([
{ $match: {_id: req.user._id} },
{ $unwind: '$finance.expenditure' },
{ $match: {'finance.expenditure.status': true} },
{ $sort: {'finance.expenditure.currentdate': -1} },
{
$group: {
_id: '$_id',
expenditure: { $push: '$finance.expenditure' }
}
}
])
With this I just get every single expenditure back.
But now I want to group the expenditures by their category and sum up the amount of every single expenditure for their group.
So it should look like this:
{ "amount": 300 }, "category": "Sport" },
{ "amount": 450 }, "category": "Outdoor" }
Thanks for your help
Instead of grouping on _id field group on category field & sum amount field:
db.collection.aggregate([
{ $match: {_id: req.user._id}},
{
$unwind: "$finance.expenditure"
},
{
$match: {
"finance.expenditure.status": true
}
},
{
$sort: {
"finance.expenditure.currentdate": -1
}
},
{
$group: {
_id: "$finance.expenditure.category",
amount: {
$sum: "$finance.expenditure.amount"
}
}
},
{
$project: {
_id: 0,
category: "$_id",
amount: 1
}
}
])
Test : MongoDB-Playground
i want to aggregate the ratings so i could get the total of feedbacks. But the thing is, it's referenced. Here's my schema
User
username: String,
fullname: String,
email: {
type: String,
lowercase: true,
unique: true
},
address: String,
password: String,
feedback: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Feedback'
}]
Feedback
var FeedbackSchema = new mongoose.Schema({
postname: String,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
message: String,
feedbacktype: String,
thumbsup: Boolean,
rating: {
communication: Number,
timeliness: Number,
delivery: Number
}
});
So what i want to achieve is, i will find the User by id then populate the feedbacks field, then i will aggregate the ratings on the feedback field so i would get a total of number of ratings for communication, for delivery and for timeliness. (The ratings are 1-5 stars)
Do you know how to aggregate and populate? thank you
**update
So i've run the aggregation to the user schema, now im getting 0 results from all ratings
User.aggregate([
{ "$match": { "_id": ObjectId('593150f6ac4d9b0410d2aac0') } },
{ "$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "feedback"
}},
{ "$project": {
"username": 1,
"fullname": 1,
"email": 1,
"password": 1,
"rating": {
"communication": { "$sum": "$feedback.rating.communication" },
"timeliness": { "$sum": "$feedback.rating.timeliness" },
"delivery": { "$sum": "$feedback.rating.delivery" }
}
}}
]).exec(function(err, a){
console.log(a)
})
result rating: { communication: 0, timeliness: 0, delivery: 0 } } ]
also tried it with other users, all of them 0 result rating
Simple Listing to Follow
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/rating');
var userSchema = new Schema({
username: String,
feedback: [{ type: Schema.Types.ObjectId, ref: 'Feedback' }]
});
var feedbackSchema = new Schema({
rating: {
communication: Number,
timeliness: Number,
delivery: Number
}
});
var User = mongoose.model('User', userSchema);
var Feedback = mongoose.model('Feedback', feedbackSchema);
async.series(
[
(callback) => {
async.each([User,Feedback],(model,callback) => {
model.remove({},callback);
},callback);
},
(callback) => {
async.waterfall(
[
(callback) => {
async.map(
[
{ "rating": {
"communication": 1, "timeliness": 2, "delivery": 3
}},
{ "rating": {
"communication": 2, "timeliness": 3, "delivery": 4
}}
],
(item,callback) => {
Feedback.create(item,callback)
},
callback
);
},
(feedback, callback) => {
User.create({ "username": "Bill", "feedback": feedback },callback);
},
(user, callback) => {
User.aggregate([
{ "$match": { "_id": user._id } },
{ "$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "feedback"
}},
{ "$project": {
"username": 1,
"rating": {
"communication": { "$sum": "$feedback.rating.communication" },
"timeliness": { "$sum": "$feedback.rating.timeliness" },
"delivery": { "$sum": "$feedback.rating.delivery" }
}
}}
],(err,results) => {
console.log(JSON.stringify(results, undefined, 2));
callback(err);
});
}
],
callback
)
}
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
);
This will create two collections as User
{
"_id" : ObjectId("593548455198ab3c09cf736b"),
"username" : "Bill",
"feedback" : [
ObjectId("593548455198ab3c09cf7369"),
ObjectId("593548455198ab3c09cf736a")
],
"__v" : 0
}
And feedbacks:
{
"_id" : ObjectId("593548455198ab3c09cf7369"),
"rating" : {
"communication" : 1,
"timeliness" : 2,
"delivery" : 3
},
"__v" : 0
}
{
"_id" : ObjectId("593548455198ab3c09cf736a"),
"rating" : {
"communication" : 2,
"timeliness" : 3,
"delivery" : 4
},
"__v" : 0
}
Program Output Shows the aggregation:
[
{
"_id": "5935494a159c633c1b34807b",
"username": "Bill",
"rating": {
"communication": 3,
"timeliness": 5,
"delivery": 7
}
}
]
Also package.json if the two dependencies are not clear enough:
{
"name": "ratings",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"async": "^2.4.1",
"mongoose": "^4.10.4"
}
}
Original Answer
Personally I would work this from the "Feedback" since you have the user already recorded there, and it is actually the way this scales better.
Instead of using population, we can instead use $lookup with a MongoDB server version of at least 3.2:
Feedback.aggregate([
{ "$match": { "user": userId } },
{ "$group": {
"_id": "$user",
"communication": { "$sum": "$rating.communication" },
"timeliness": { "$sum": "$rating.timeliness" },
"delivery": { "$sum": "$rating.delivery" }
}},
{ "$lookup": {
"from": "users",
"localField": "_id",
"foreignField": "_id",
"as": "user"
}},
{ "$unwind": "$user" }
])
If you do not have a server version that supports $lookupthen you can still "manually join" the User details with something like this:
Feedback.aggregate([
{ "$match": { "user": userId } },
{ "$group": {
"_id": "$user",
"communication": { "$sum": "$rating.communication" },
"timeliness": { "$sum": "$rating.timeliness" },
"delivery": { "$sum": "$rating.delivery" }
}}
],function(err, results) {
result = results[0];
User.findById(userId).lean().exec(function(err, user) {
result.user = user; // swap the _id for the Object
// Then output result
});
})
Which is basically what .populate() does, but we are doing it manually and efficiently for the result returned.
You can work the other way around from the User model, but it's likely more efficient to simply work this way around.
User.aggregate([
{ "$match": { "_id": userid } },
{ "$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "feedback"
}},
{ "$project": {
"username": 1,
"fullname": 1,
"email": 1,
"password": 1,
"rating": {
"communication": { "$sum": "$feedback.rating.communication" },
"timeliness": { "$sum": "$feedback.rating.timeliness" },
"delivery": { "$sum": "$feedback.rating.delivery" }
}
}}
])