how do i simplify aggregation pipeline result - javascript

i am trying to return aggregation result based on user's role and active status
sample doc
{
"email": "doe#gmail.com",
"firstName": "doe",
"lastName": "bghh",
"phone": "+919016703350",
"password":"$.5GIV3q9JqzRqY/lP2",
"status": "verified",
"role": "admin",
"isActive": true
}
const user = await User.aggregate([
{ $project: { isActive: 1, role: 1 } },
{ $group: { _id: { role: '$role', isActive: '$isActive' }, all: { $sum: 1 } } },
])
above query result below
{
"user": [
{
"_id": {
"role": "user",
"isActive": false
},
"all": 1
},
{
"_id": {
"role": "vendor",
"isActive": true
},
"all": 1
},
{
"_id": {
"role": "user",
"isActive": true
},
"all": 2
},
{
"_id": {
"role": "vendor",
"isActive": false
},
"all": 1
},
{
"_id": {
"role": "admin",
"isActive": true
},
"all": 1
}
]
}
expecting result like, help to write query to achieve below result
{ role:{user:{all:3, active:2 }, admin:{all:1, active:1}, vendor:{all:2, active:2}}}

Try this one:
db.collection.aggregate([
{ $project: { isActive: 1, role: 1 } },
{
$group: {
_id: '$role',
all: { $sum: 1 },
active: { $sum: { $cond: ['$isActive', 1, 0] } }
}
},
{
$group: {
_id: null,
role: { $push: "$$ROOT" }
}
},
{
$set: {
role: {
$map: {
input: "$role",
in: { k: "$$this._id", v: { all: "$$this.all", active: "$$this.active" } }
}
}
}
},
{ $set: { role: { $arrayToObject: "$role" } } },
{ $replaceWith: { role: "$role" } }
])
Mongo Playground

Related

Exclude specific fields in all nested document in mongoose query

I have a ride Collection with trips as a field, trips is a map where the keys are different years. I want to query the collection but exclude the passengers field in each trip
const ride = new Schema(
{
boat_operator: {
type: Schema.Types.ObjectId,
required: true,
ref: 'User'
},
trips: {
type: Map,
of: {
passengers: [{ type: Schema.Types.ObjectId, ref: 'User' }],
available_seats: { type: Number, required: true }
},
default: new Map()
}
}
)
I tried this
const rides = await Ride.find({ status: 'waiting' }).select("-trips.*.passengers")
I tried to select all the items in value then remove the corresponding passengers field in each
It had no effect
this is what the response looks like
[
{
"_id": "632a1669279c86f4ab3a4bf5",
"boat_operator": "6328c434a98212a7f57c4edc",
"trips": {
"2019": {
"passengers": [],
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bfe"
},
"2020": {
"passengers": [],
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bfc"
},
"2021": {
"passengers": [],
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bfa"
},
"2022": {
"passengers": [],
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bf8"
}
}
}
]
I want to exclude the passengers field in the returned document
This would solve it
const rides = await Ride.aggregate([
{ "$match": { "status": "waiting" } },
{ $project: { "trips": { $objectToArray: '$trips' } } },
{ $project: { 'trips.v.passengers': 0 } },
{ $project: { "trips": { $arrayToObject: '$trips' } } }
]);
nodejs javascript mongodb mongoose
Here's the returned document
{
"_id": "632a1669279c86f4ab3a4bf5",
"trips": {
"2019": {
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bfe"
},
"2020": {
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bfc"
},
"2021": {
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bfa"
},
"2022": {
"available_seats": 5,
"_id": "632a1669279c86f4ab3a4bf8"
}
}
}

How to merge duplicates in an array of objects and sum a specific property in MongoDB?

I am building an URL shortener. So I want to track datewise click count when a URL is visited using the short URL. For example: on 30th January if the URL was visited 3 times by using the short URL it will show the click count 5 but my document looks like this -
{
"_id":{"$oid":"61f6322cd3e3484d97a25e7c"},
"dayWiseClicks":
[{"date":"01/30/2022","dailyClicks":1,"_id":{"$oid":"61f66ff95fadc3e9f01b0d34"}},
{"date":"01/30/2022","dailyClicks":1,"_id":{"$oid":"61f66ffd5fadc3e9f01b0d38"}},
{"date":"01/30/2022","dailyClicks":1,"_id":{"$oid":"61f66fff5fadc3e9f01b0d3c"}}]
}
I want my document to look like this:
{
"_id":{"$oid":"61f6322cd3e3484d97a25e7c"},
"dayWiseClicks":
[{"date":"01/30/2022","dailyClicks":3,"_id":{"$oid":"61f66ff95fadc3e9f01b0d34"}}]
}
How can I achieve this using the MongoDB aggregation pipeline?
UPDATE
Here's the full schema:
{
merchant_name: String,
store_id: String,
original_url: String,
url_hash: String,
subdomain: String,
// password: String,
totalClicks: {
type: Number,
default: 0,
},
igClicks: {
type: Number,
default: 0,
},
fbClicks: {
type: Number,
default: 0,
},
directClicks: {
type: Number,
default: 0,
},
dayWiseClicks: [
{
date: {
type: String,
default: moment(new Date()).format("L"),
},
dailyClicks: {
type: Number,
default: 0,
},
},
],
desktopClicks: {
device: { type: String, default: "Desktop" },
clicks: { type: Number, default: 0 },
},
mobileClicks: {
device: { type: String, default: "Mobile" },
clicks: { type: Number, default: 0 },
},
},
{ timestamps: true }
After the aggregation is done I want my document to look like this:
{
"_id": {
"$oid": "61f7a3b83dcebb77b05bd180"
},
"desktopClicks": {
"device": "Desktop",
"clicks": 5
},
"mobileClicks": {
"device": "Mobile",
"clicks": 0
},
"totalClicks": 5,
"igClicks": 0,
"fbClicks": 0,
"directClicks": 5,
"dayWiseClicks": [
{
"date": "01/31/2022",
"dailyClicks": 5,
"_id": {
"$oid": "61f7a3fe5bd4f779cc53f697"
}
}
],
"merchant_name": "Akash DTH",
"store_id": "3333",
"url_hash": "OGZhYTI",
"subdomain": "",
"original_url": "https://akashdth.com/",
"createdAt": {
"$date": "2022-01-31T08:54:16.472Z"
},
"updatedAt": {
"$date": "2022-01-31T09:03:55.925Z"
},
"__v": 0
}
try
https://mongoplayground.net/p/osWlBuYy0NZ
db.collection.aggregate([
{
$unwind: {
"path": "$dayWiseClicks"
}
},
{
$group: {
_id: {
"oid": "$_id",
"date": "$dayWiseClicks.date"
},
"dailyClicks": {
$sum: "$dayWiseClicks.dailyClicks"
},
"originalDayWiseClicks": {
$push: "$dayWiseClicks"
}
}
},
{
"$addFields": {
"dayWiseClicks": [
{
"date": "$_id.date",
"dailyClicks": "$dailyClicks",
"_id": {
"$first": "$originalDayWiseClicks._id"
}
}
]
}
},
{
"$project": {
_id: "$_id.oid",
"dayWiseClicks": 1
}
}
])
If you don't need the dayWiseClicks as nested or the first dayWiseClicks._id
It can be simplified to this:
https://mongoplayground.net/p/Zc9whjiLWdt
db.collection.aggregate([
{
$unwind: {
"path": "$dayWiseClicks"
}
},
{
$group: {
_id: {
"oid": "$_id",
"date": "$dayWiseClicks.date"
},
"dailyClicks": {
$sum: "$dayWiseClicks.dailyClicks"
}
}
},
{
$addFields: {
"_id": "$_id.oid",
"date": "$_id.date",
}
}
])

Mongoose group by an lookup with nested arrays

I have two schema's , first one is Quiz and the other one is quizResults, I want to get aggregated data from the quizResult while doing the lookup in quiz Schema. Below is my Quiz schema:
vidId: {type: Number, required: true},
status: {type: Number, enum: [statusType.quizStatusEnums.LIVE, statusType.quizStatusEnums.DELETED], default: statusType.quizStatusEnums.LIVE},
questions: [
{
questionNum: { type: Number },
questionName: { type: String },
answers: [
{
answerId: { type: String, default: uuid.v4() },
answerName: { type: String },
isCorrect: { type: Boolean },
answerType: { type: Number, enum: [statusType.quizTypeEnums.QUIZ, statusType.quizTypeEnums.SURVEY], default: statusType.quizTypeEnums.QUIZ},
hotspotId: { type: Number },
overlayId: {type: Number},
panelId: {type: String}
}
]
}
],
The Second one is QuizResults: The aggregation query needs to be performed on this collection.
created: {
type: Date,
},
vidId: {
type: Number,
required: true,
},
viewerId: {
type: String,
required: true,
},
quizId: {
type: Schema.Types.ObjectId,
ref: 'quiz'
},
questionId: {
type: Schema.Types.ObjectId,
ref: 'quiz'
},
answerId: {
type: String,
required: true
},
isCorrect: {
type: Boolean,
default: false,
},
whenAnswered: {
type: Date
},
I want the final aggregated result like that:
[
{
"questionNum": 2,
"questionName": "Which is the best selling record in history ?",
"correct": 10,
"incorrect": 20,
"totalAnswers": 30,
"answers": [
{
"answerId": "123abc",
"answerName": "Thriller Michel Jackson",
"numResponses": 10
},
{
"answerId": "234d",
"answerName": "A kind of Magic Queen",
"numResponses": 10
},
{
"answerId": "432e",
"answerName": "help The Beatles",
"numResponses": 10
}
]
},
{
"questionNum": 1,
"questionName": "What value has the number PI?",
"correct": 5,
"incorrect": 3,
"totalAnswers": 8,
"answers": [
{
"answerId": "111",
"answerName": "3.12",
"numResponses": 0
},
{
"answerId": "222",
"answerName": "3.14",
"numResponses": 5
},
{
"answerId": "333",
"answerName": "3.16",
"numResponses": 3
}
]
}
]
What I tried is :
aggregate([
{ "$match": { "vidId": 8225342, } },
{
"$group": {
"_id": "$questionId",
"Correct": {
"$sum": {
"$cond": [
{ "$eq": ["$isCorrect", true] },
1,
0
]
},
},
"Incorrect": {
"$sum": {
"$cond": [
{ "$eq": ["$isCorrect", false] },
1,
0
]
}
},
}
},
{
"$lookup": {
"from": "quiz",
"let": { "id": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$id", "$questions._id"] } } },
{ "$unwind": "$questions" },
{ "$match": { "$expr": { "$eq": ["$questions._id", "$$id"] } } },
],
"as": "quizData"
}
},
{ $unwind: '$quizData' },
{ "$project": {
"questionName": "$quizData.questions.questionName",
"questionNum": "$quizData.questions.questionNum",
"Correct": "$Correct",
"Incorrect": "$Incorrect",
"answers": "$quizData.questions.answers" } },
])
I got the results something like that:
{
"_id": "611632305bd3910929b95552",
"questionName": "Which is the best selling record in history?",
"questionNum": 5,
"Correct": 3,
"Incorrect": 0,
"answers": [
{
"answerId": "078f441b-373f-40e9-89e1-04fca0a9fc5d",
"answerType": 0,
"_id": "611632305bd3910929b95553",
"answerName": "Thriller Michel Jackson",
"isCorrect": true,
"hotspotId": 470114,
"overlayId": 3,
"panelId": "12abc"
},
{
"answerId": "644b80fe-5778-46fa-b3a6-1eff5989cdee",
"answerType": 0,
"_id": "611632305bd3910929b95554",
"answerName": "A kind of Magic Queen",
"isCorrect": false,
"hotspotId": 470113,
"overlayId": 4,
"panelId": "12345abc"
},
{
"answerId": "5bde2682-66fe-4c79-a728-aea67f6842a8",
"answerType": 0,
"_id": "611632305bd3910929b95555",
"answerName": "help The Beatles",
"isCorrect": false,
"hotspotId": 470112,
"overlayId": 3,
"panelId": "12abc"
}
]
},
how I can get the Answers array like that:
answers: [
{
answerId: "123abc",
answerName: "Thriller Michel Jackson",
numResponses: 10
},
{
answerId: "234d",
answerName: "A kind of Magic Queen",
numResponses: 10
},
{
answerId: "432e",
answerName: "help The Beatles",
numResponses: 10
}
]
         
You can try to approach it the other way around. Use $lookup to get quizResults filtered and aggregated and then run $map along with $filter to get matching statistics for each answer:
db.quiz.aggregate([
{
$match: { "vidId": 8225342 }
},
{
$lookup: {
from: "quizResults",
pipeline: [
{ $match: { "vidId": 8225342 } },
{
$group: {
_id: "$answerId",
count: { $sum: 1 }
}
}
],
as: "quizResults"
}
},
{
$project: {
_id: 1,
questions: {
$map: {
input: "$questions",
as: "q",
in: {
_id: "$$q._id",
questionName: "$$q.questionName",
questionNum: "$$q.questionNum",
answers: {
$map: {
input: "$$q.answers",
as: "a",
in: {
$mergeObjects: [
"$$a",
{
$let: {
vars: {
fst: {
$first: {
$filter: { input: "$quizResults", cond: { $eq: [ "$$this._id", "$$a._id" ] } }
}
}
},
in: { numResponses: "$$fst.count" }
}
}
]
}
}
}
}
}
}
}
}
])
Mongo Playground

MongoDB Aggregate sum nested number array

I have the following document structure
{
"_id": "60b7b7c784bd6c2a1ca57f29",
"user": "607c58578bac8c21acfeeae1",
"exercises": [
{
"executed_reps": [8,7],
"_id": "60b7b7c784bd6c2a1ca57f2a",
"exercise_name": "Push up"
},
{
"executed_reps": [5,5],
"_id": "60b7b7c784bd6c2a1ca57f2b",
"exercise_name": "Pull up"
}
],
}
In aggregation, I am trying to sum all the executed_reps so the end value in this example should be 25 (8+7+5+5).
Here is the code I have so far:
const exerciseStats = await UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$group: {
_id: null,
totalReps: {
$sum: {
$reduce: {
input: '$exercises.executed_reps',
initialValue: '',
in: { $add: '$$this' },
},
},
},
},
},
]);
This gives a result of 5 for totalReps. What am I doing wrong?
Well 10 minutes later I found the solution myself:
UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$project: {
total: { $sum: '$exercises.executed_reps' },
},
},
{
$group: {
_id: null,
totalExercises: { $sum: '$total' },
},
},])
Had to use $project first. :)
You can do it like this:
const exerciseStats = await UserWorkoutaggregate([
{
"$addFields": {
"total": {
"$sum": {
"$map": {
"input": "$exercises",
"as": "exercise",
"in": {
"$sum": "$$exercise.executed_reps"
}
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/5_fsPgSh8EP

Mongoose: Group by Object id and push in one array, sort by createdAt, populate all objects and paginate it

This is an example of getList of Arrivals by 10 items per page:
router.get('/arrivals', isAuthenticated, async (request, response, next) => {
jsonPreProcessor.response = response;
const resPerPage = 10;
const page = request.query.page || 1;
Arrival.find({})
.populate({
path: 'product',
populate: {
path: 'type'
}
})
.select('-__v')
.skip((resPerPage * page) - resPerPage)
.limit(resPerPage).then(arrivals => {
Arrival.countDocuments({}).then(numberOfResults => {
return jsonPreProcessor.paginate(arrivals, page, Math.ceil(numberOfResults / resPerPage), numberOfResults);
}).catch(error => {
return jsonPreProcessor.error(error.message);
});
}).catch(error => {
return jsonPreProcessor.error(error.message);
});
});
And this is an output:
{
"time": "2020-01-16T10:11:22.588Z",
"message": "success",
"success": true,
"data": {
"list": [
{
"quantity": 1,
"discount": 0,
"_id": "5e0db80a37dd4437b4329960",
"product": {
"_id": "5e0cecaaa9a5cc2c7c62e379",
"title": "Ортопедический",
"type": {
"_id": "5ddcbc4685e53838dc564a44",
"title": "fsdkjhfs",
"createdAt": "2019-11-26T05:46:46.797Z",
"updatedAt": "2019-11-26T05:46:46.797Z",
"alt": "fsdkjhfs",
"__v": 0
},
...
"mode": "simple",
"createdAt": "2020-01-01T19:02:02.840Z",
"updatedAt": "2020-01-01T19:02:02.840Z",
"alt": "ortopedicheskij",
"__v": 0
},
...
"sellPrice": 6,
"minSellPrice": 0,
"createdAt": "2020-01-02T09:29:46.688Z",
"updatedAt": "2020-01-13T09:30:26.126Z"
},
... // And other 9 items
],
"currentPage": 1,
"pages": 2,
"numberOfResults": 16,
"incompleteResults": true
},
"type": null
}
All those are arrivals, and as you see arrivals have product. Some arrivals can have same product (by id)
My problem is to group arrivals which have same product. (sorted by first item of arrivals array) populate all objects.. and paginate. I use aggregate, and unfortunately I don't have any idea how to use it (first of all aggregate's output is random) with pagination and of course output is not what I need. I used this link as solving of my problem.
router.get('/arrivalls', isAuthenticated, async (request, response, next) => {
jsonPreProcessor.response = response;
Arrival.aggregate(
[
// This is not working (may be it's not working in array)
// {
// "$sort": {
// "createdAt": 1
// }
// },
{
"$group": {
"_id": "$product",
"arrivals": {
"$push": "$$ROOT"
}
}
},
// {
// "$lookup": {
// "from": "arrivals",
// "localField": "product",
// "foreignField": "_id",
// "as": "product"
// }
// },
// {
// "$unwind": {
// "path": "$arrivals"
// }
// }
]
).then(arrivals => {
// console.log(arrivals);
return jsonPreProcessor.success(arrivals);
}).catch(error => {
return jsonPreProcessor.error(error.message);
});
});
Output should be like:
"data": {
"list": [
{
"_id": "5e1d5dba611485397cfb0386",
"arrivals": [
{
"_id": "5e1d5e26611485397cfb0387",
"quantity": 6,
"discount": 0,
"product": {
"_id": "5e1d5dba611485397cfb0386",
... // etc
},
"sellPrice": 5000,
"minSellPrice": 4500,
"createdAt": "2020-01-14T06:22:30.366Z",
"updatedAt": "2020-01-14T09:14:13.824Z",
"__v": 0
},
{
"_id": "5e1ff4d15d059430e8405f94",
"quantity": 2,
"discount": 0,
"product": {
"_id": "5e1d5dba611485397cfb0386",
... // etc
},
"sellPrice": 7000,
"minSellPrice": 6000,
"comment": "",
"createdAt": "2020-01-16T05:29:53.907Z",
"updatedAt": "2020-01-16T05:29:53.907Z",
"__v": 0
}
]
},
{
"_id": "5e1d84884d387d2334a7e9d9",
"arrivals": [
{
// etc...
}
]
}
],
"currentPage": 1,
"pages": 2,
"numberOfResults": 16,
"incompleteResults": true
},
"type": null
}
I solved my problem.. And here is grouping, sorting, populating and paginating of list.
const resPerPage = 10;
const page = request.query.page || 1;
Arrival.aggregate(
[
{
$lookup: {
from: 'products',
localField: 'product',
foreignField: '_id',
as: 'product'
}
},
{
$unwind: {
path: "$product",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'branchdans',
localField: 'branch',
foreignField: '_id',
as: 'branch'
}
},
{
$unwind: {
path: "$branch",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'colors',
localField: 'color',
foreignField: '_id',
as: 'color'
}
},
{
$unwind: {
path: "$color",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'types',
localField: 'product.type',
foreignField: '_id',
as: 'product.type'
}
},
{
$unwind: {
path: "$product.type",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'subcategories',
localField: 'product.subcategory',
foreignField: '_id',
as: 'product.subcategory'
}
},
{
$unwind: {
path: "$product.subcategory",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'categories',
localField: 'product.subcategory.category',
foreignField: '_id',
as: 'product.subcategory.category'
}
},
{
$unwind: {
path: "$product.subcategory.category",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'countries',
localField: 'product.country',
foreignField: '_id',
as: 'product.country'
}
},
{
$unwind: {
path: "$product.country",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'manufacturers',
localField: 'product.manufacturer',
foreignField: '_id',
as: 'product.manufacturer'
}
},
{
$unwind: {
path: "$product.manufacturer",
preserveNullAndEmptyArrays: true
}
},
{
$group: {
"_id": "$product._id",
"sizes": {
"$first": "$product.sizes"
},
"title": {
"$first": "$product.title"
},
"type": {
"$first": "$product.type"
},
"subcategory": {
"$first": "$product.subcategory"
},
"country": {
"$first": "$product.country"
},
"manufacturer": {
"$first": "$product.manufacturer"
},
"description": {
"$first": "$product.description"
},
"comment": {
"$first": "$product.comment"
},
"mode": {
"$first": "$product.mode"
},
"createdAt": {
"$first": "$product.createdAt"
},
"updatedAt": {
"$first": "$product.updatedAt"
},
"alt": {
"$first": "$product.alt"
},
arrivals: {
$push: "$$ROOT"
},
"date": {
$last: "$createdAt"
}
},
},
{
$unset: "arrivals.product"
},
{
$sort: {
"date": 1
}
},
{
$skip: (resPerPage * page) - resPerPage
},
{
$limit: resPerPage
}
]
).then(arrivals => {
Arrival.aggregate([
{
$group: {
"_id": "$product",
arrivals: {
$push: "$$ROOT"
},
"date": {
"$last": "$createdAt"
}
},
},
{
$sort: {
"date": 1
}
},
{
$count: "numberOfResults"
}
]).then(countArrivals => {
if(countArrivals.length === 0){
return jsonPreProcessor.error('Ошибка при высчитывании прибытий товаров');
}
// Todo make more practise
return jsonPreProcessor.paginate(arrivals, page, Math.ceil(countArrivals[0].numberOfResults / resPerPage), countArrivals[0].numberOfResults);
}).catch(error => {
return jsonPreProcessor.error(error.message);
});
// return jsonPreProcessor.success(arrivals);
}).catch(error => {
return jsonPreProcessor.error(error.message);
});

Categories