I have a product collection in MongoDb which sole fields like _id, category, user_id.
I want to check and count the sum number of each category in collection given the matching the user_id and then sum up all the count again at the end.
my solution is :
return Product.aggregate([
{ $match: { "user_id": "id if user that added the product" } },
{ "$unwind": "$category" },
{
"$group": {
"_id": {
'category': '$category',
},
"count": { "$sum": 1 }
}
},
{ "$sort": { "_id.category": 1 } },
{
"$group": {
"_id": "$_id.category",
"count": { "$first": "$count" }
}
}
])
the code gives me the count of each category without matching the condition of user_id. But when I add the $match it fails.
Product Schema:
const ProductSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
quantity: {
type: Number,
default: -1
},
category:
{
type: String,
required: true
},
manufactured_by: {
type: String,
required: true
},
user_id: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
})
my result if I dont add the condition:
[
{
"_id": "A Tables",
"count": 1
},
{
"_id": "C Tables",
"count": 4
},
{
"_id": "B Tables",
"count": 2
}
]
Not sure what you are trying to achieve from the last stage in your pipeline.
But the following should give you desired output (without any complications that you added)
async getSellerStatistics(seller_id) {
return await Product.aggregate([
{ $match: { user_id: seller_id } }
{ $unwind: "$category" },
{
$group: {
_id: "$category",
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
}
Related
This is a new requirement I need to implement in my personal project. I successfully joined two collections using aggregate (previous question). Now, I have a new collection that I need to join. Please see codes below:
Student model:
{
fullname: { type: String, required: true },
email: { type: String, required: true },
}
Exams model:
{
test: { type: String, required: false },
top10: [
type: {
studentId: { type: String, required: true },
score: { type: Number, required: false },
}
]
}
Aggregated collections:
db.exams.aggregate([
{ $unwind: "$top10" },
{
$lookup: {
from: "students",
localField: "top10.studentId",
foreignField: "_id",
as: "students"
}
},
{ $addFields: { students: { $arrayElemAt: ["$students", 0] } } },
{
$group: {
_id: "$_id",
test: { $first: "$test" },
students: {
$push: {
studentId: "$top10.studentId",
score: "$top10.score",
fullname: "$students.fullname"
}
}
}
}
])
Now, the 3rd collection is called Teacher.
Teacher model:
{
fullName: { type: String, required: false },
studentId: { type: String, required: false }
}
Expected output should be:
{
"test": "Sample Test #1",
"students": [
{
"studentId": "5f22ef443f17d8235332bbbe",
"fullname": "John Smith",
"score": 11,
"teacher": "Dr. Hofstadter"
},
{
"studentId": "5f281ad0838c6885856b6c01",
"fullname": "Erlanie Jones",
"score": 9,
"teacher": "Mr. Roberts"
},
{
"studentId": "5f64add93dc79c0d534a51d0",
"fullname": "Krishna Kumar",
"score": 5,
"teacher": "Ms. Jamestown"
}
]
}
Appreciate kind of help. Thanks!
You can add these 2 more pipelines before the $group pipeline,
$lookup join collection with teacher
$addFields to get convert array to object
$group to add teacher name in students array
{
$lookup: {
from: "teacher",
localField: "top10.studentId",
foreignField: "studentId",
as: "students.teacher"
}
},
{
$addFields: {
"students.teacher": { $arrayElemAt: ["$students.teacher", 0] }
}
},
{
$group: {
_id: "$_id",
test: { $first: "$test" },
students: {
$push: {
studentId: "$top10.studentId",
score: "$top10.score",
fullname: "$students.fullname",
teacher: { $ifNull: ["$students.teacher.fullname", ""] }
}
}
}
}
Playground
I am getting this output:
Sorting on the inner array is not working. I have the two tables as shown below.
The pages schema is this:
const PageSchema = new Schema({
name: {
type: String,
required: true
},
created: {
type: Date
},
position: {
type: Number,
default: 0
}
});
module.exports = mongoose.model('pages', PageSchema);
The container schema is this:
const ContainerSchema = new Schema({
filename: {
type: String,
required: true
},pageId: {
type: Schema.Types.ObjectId,
ref: 'pages'
},
created: {
type: Date
}
});
For sorting the data I used this code:
Container.aggregate(match, {
"$group": {
"_id": {
"pageId": "$pageId",
"id": "$_id",
"filename": "$filename",
"position": "$position"
},
"containerCount": {
"$sum": 1
}
}
}, {
"$group": {
"_id": "$_id.pageId",
"container": {
"$push": {
"_id": "$_id.id",
"filename": "$_id.filename",
},
},
"position": {
"$first": "$_id.pageId.position"
}
"count": {
"$sum": "$containerCount"
}
}
}, {
"$project": {
"container": 1,
"count": 1
}
}, {
"$sort": {
"position": 1
}
}).exec()
I want the data sort according to the position field in the pages but it's not working.
You have forgotten to add position in $project.
Once you add in $project then its available in $sort
{
"$project": {
"position" :1,
"container": 1,
"count": 1
}
}
I'm trying to get a list of sorted comments by createdAt from a Post doc where an aggregate pipeline would be used to populate the owner of a comment in comments field with displayName and profilePhoto fields.
Post Schema:
{
_owner: { type: Schema.Types.ObjectId, ref: 'User', required: true },
...
comments: [
{
_owner: { type: Schema.Types.ObjectId, ref: 'User' },
createdAt: Number,
body: { type: String, maxlength: 200 }
}
]
}
User schema:
{
_id: '123abc'
profilePhoto: String,
displayName: String,
...
}
What I want to return:
[
{
"_id": "5bb5e99e040bf10b884b9653",
"_owner": {
"_id": "5bb51a97fb250722d4f5d5e1",
"profilePhoto": "https://...",
"displayName": "displayname"
},
"createdAt": 1538648478544,
"body": "Another comment"
},
{
"_id": "5bb5e96686f1973274c03880",
"_owner": {
"_id": "5bb51a97fb250722d4f5d5e1",
"profilePhoto": "https://...",
"displayName": "displayname"
},
"createdAt": 1538648422471,
"body": "A new comment"
}
]
I have some working code that goes from aggregate to get sorted comments first, then I populate separately but I want to be able to get this query just by using aggregate pipeline.
Current solution looks like this:
const postComments = await Post.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(postId) } },
{ $unwind: '$comments' },
{ $limit: 50 },
{ $skip: 50 * page },
{ $sort: { 'comments.createdAt': -1 } },
{$replaceRoot: {newRoot: '$comments'}},
{
$project: {
_owner: 1,
createdAt: 1,
body: 1
}
}
]);
await Post.populate(postComments, {path: 'comments._owner', select: 'profilePhoto displayName' } )
You can try below aggregation
const postComments = await Post.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(postId) } },
{ "$unwind": "$comments" },
{ "$lookup": {
"from": "users",
"localField": "comments._owner",
"foreignField": "_id",
"as": "comments._owner"
}},
{ "$unwind": "$comments._owner" },
{ "$replaceRoot": { "newRoot": "$comments" }},
{ "$sort": { "createdAt": -1 } }
{ "$limit": 50 }
])
I want to find out duplicate entries on the basis of field " name " and in the result, I want to fetch " name " and " _id ".
I have tried using aggregate function and it is returning all the result from database as null:
here is my code:
ModelRestaurant.aggregate((
// { "$group": { "_id": "$name", "count": { "$sum": 1 } } },
// { "$match": { "_id": { "$ne": null }, "count": { "$gt": 1 } } },
// { "$project": { "name": "$_id", "_id": 0 } }
{ $group: { "_id": "$name", "name": { $first: "$name" }, "count": { $sum: 1 } } },
{ $match: { "count": { $gt: 1 } } },
{ $project: { "name": 1, "_id": 0 } },
{ $group: { "_id": null, "duplicateNames": { $push: "$name" } } },
{ $project: { "_id": 0, "duplicateNames": 1 } }
), function (err, result) {
if (result) {
console.log(result)
}
})
from above commented code it is giving me all id but now it is giving me null values and all the result json of 6000 with null value.
here is the field of data in collection:
My Schema is :
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var autoIncrement = require('mongoose-auto-increment');
var ModelRestaurant = new mongoose.Schema({
name: String,
ownerName: String, // to be removed by checking code
ownerID: Number,
ownerEmail: String, // to be removed by checking code
city: String,
zipCode: String,
image: [String],
restaurantType: String,
address: String,
landmark: String,
isAddedByCustomer: {
type: Boolean,
default: false
},
location: {
lat: String,
long: String
}
},
{
timestamps: true
});
ModelRestaurant.plugin(autoIncrement.plugin, 'Restaurants');
ModelRestaurant.plugin(uniqueValidator);
mongoose.model('Restaurants', ModelRestaurant);
The following code should print all the duplicates:
db.ModelRestaurant.aggregate([
{$group:{"_id":"$name","name":{$first:"$name"},"count":{$sum:1}}},
{$match:{"count":{$gt:1}}}
])
I'm trying to get a sum of all values of $revenue, and a count of only where $user is equal to the user param I pass when calling this function.
this.aggregate([
{ $match: { createdAt: { $gte: start, $lte: end }, 'status.verified': true } },
{
$group: {
_id: null,
balance: {
$sum: "$revenue"
},
count: {
$cond: {
if: { $eq: [ "$user", user ] },
then: { $sum: 1 },
else: { $sum: 0 }
}
}
}
}
], next);
I'm expecting the data to look like this:
[ { _id: null, balance: 1287, count: 10 ] }
Where balance is the sum of all revenue fields in the match query, and count is the count of that users contributions to the data.
It works fine if I sum the count unconditionally (e.g. like this)
this.aggregate([
{ $match: { createdAt: { $gte: start, $lte: end }, 'status.verified': true } },
{
$group: {
_id: null,
balance: {
$sum: "$revenue"
},
count: { $sum: 1 }
}
}
], next);
Which suggests the error is with my conditional sum. The error thrown by MongoDB is
TypeError: Cannot read property '0' of undefined
at redacted:10:20
at redacted/node_modules/mongoose/lib/aggregate.js:529:13
My schema is
var schema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
status: {
verified: { type: Boolean, default: false },
completed: { type: Boolean, default: false },
canceled: { type: Boolean, default: false },
refused: { type: Boolean, default: false }
},
meta: { type: Schema.Types.Mixed, default: {} },
revenue: { type: Number, default: 0 }
});
Note: the createdAt value used in $match is inserted automatically by a plugin.
The $cond operator should essentially be an expression of the $sum operator, like the following:
this.aggregate([
{ "$match": {
"createdAt": { "$gte": start, "$lte": end },
"status.verified": true
} },
{
"$group": {
"_id": null,
"balance": { "$sum": "$revenue" },
"count": {
"$sum": {
"$cond": [
{ "$eq": [ "$user", user ] },
1, 0
]
}
}
}
}
], next);