NodeJS Nested group aggregation with MongoDB - node.js

I have a data set of payments that I want to group together to show on page in a report form. Below is the data:-
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab1"
},
"type": "Card",
"reason": "Deposit",
"item": "Dress",
"amount": 100,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab2"
},
"type": "Cash",
"reason": "Deposit",
"item": "Dress",
"amount": 400,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab3"
},
"type": "Cash",
"reason": "Deposit",
"item": "Dress",
"amount": 100,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab4"
},
"type": "Cash",
"reason": "Full Balance",
"item": "Dress",
"amount": 200,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab5"
},
"type": "Card",
"reason": "Full Balance",
"item": "Dress",
"amount": 300,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab6"
},
"type": "Cash",
"reason": "Deposit",
"item": "Jacket",
"amount": 400,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab7"
},
"type": "Card",
"reason": "Deposit",
"item": "Jacket",
"amount": 150,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab8"
},
"type": "Cash",
"reason": "Full Balance",
"item": "Jacket",
"amount": 250,
},
{
"_id": {
"$oid": "5bed91bfbbcf4d0014ec3ab9"
},
"type": "Card",
"reason": "Full Balance",
"item": "Jacket",
"amount": 350,
}
I can group the data by 1 item without problem, so for example I can group and total the amounts by item (Dress, Jacket etc.)
I would like to group by all three fields so would get an output that looks like this and structured so I can display them on the Frontend.
{
Dress: {
reason: {
"Deposit" : {
type: {
"Cash": 500 //Combination of 2 records for example
"Card": 100
}
}
"Full Balance" : {
type: {
"Cash": 200
"Card": 300
}
}
}
}
Jacket: {
reason: {
"Deposit" : {
type: {
"Cash": 400
"Card": 150
}
}
"Full Balance" : {
type: {
"Cash": 250
"Card": 350
}
}
}
}
I attempted to start this but I am struggling to get the concept right to be able to produce the right data. Obviously this doesn't produce the right output.
$group: {
_id: {item: "$item"},
totalAmount: {$sum: "$amount"}
},
$group: {
_id: {item: "$_id.item"},
total: {
"$push": {
type: {type: "$type"},
amount: {$sum : "$amount"}
}
}
}
Any help is appreciated.

Related

How to set up aggregation pipeline for specific use case in MongoDB

`I have two collections in MongoDB. One is users and the other is companies. I need to be able to populate the users.endpoints with the endpoints from companies that match ObjectID's which are in the user.endPoints property. It is a bit hard for me to wrap my head around considering there are two steps.
This is the companies collection.
{
"_id": {
"$oid": "63ed39162bfc2cf8065b76cf"
{
"_id": {
"$oid": "63ed39162bfc2cf8065b76cf"
},
"companyName": "Sowegatel",
"amount": 0,
"signalwireSid": "id",
"numbers": [
{
"number": "+12068133580",
"_id": {
"$oid": "63ed39c72bfc2cf8065b76f0"
},
"createdAt": {
"$date": {
"$numberLong": "1676491207931"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1676491207931"
}
}
}
],
"endPoints": [
{
"userName": "4009",
"_id": "fb1d0ef9-c713-400e-a3c9-cbc6823aaf57"
},
{
"userName": "4019",
"_id": "506e710d-14a6-4345-bc89-8488af4cabe4"
},
{
"userName": "4020",
"_id": "c80bd1ab-ca8d-4649-9d35-56d64ef8fab5",
"type": "sip_endpoint"
}
],
"__v": 6
} },
"companyName": "Sowegatel",
"amount": 0,
"signalwireSid": "id",
"numbers": [
{
"number": "+12068133580",
"_id": {
"$oid": "63ed39c72bfc2cf8065b76f0"
},
"createdAt": {
"$date": {
"$numberLong": "1676491207931"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1676491207931"
}
}
}
],
"endPoints": [
{
"userName": "4009",
"_id": "fb1d0ef9-c713-400e-a3c9-cbc6823aaf57"
},
{
"userName": "4019",
"_id": "506e710d-14a6-4345-bc89-8488af4cabe4"
},
{
"userName": "4020",
"_id": "c80bd1ab-ca8d-4649-9d35-56d64ef8fab5",
"type": "sip_endpoint"
}
],
"__v": 6
}
This is the users collection.
{
"_id": {
"$oid": "63ed39162bfc2cf8065b76d2"
},
"firstName": "mark",
"lastName": "thomas",
"email": "mt#st.com",
"password": "",
"companyId": {
"$oid": "63ed39162bfc2cf8065b76cf"
},
"active": true,
"role": "companyAdmin",
"tokens": [],
"__v": 2,
"endpoints": [
// I need to populate to endpoints here!!!! //
"c80bd1ab-ca8d-4649-9d35-56d64ef8fab5"
]
}
I can do a single step aggregation, but this is a little complicated for me.`

Select by category and count by status mongodb

I have a list of records and want to group by categories and after this count buy isArchived status.
I'm just starting to learn MongoDB and I can't do the query described below, I would appreciate your tips.
This is peace of data.
[
{
"_id": "63356af2d77a56d764f362e4",
"noteName": "string",
"category": "IDEA",
"content": "string",
"isArchived": true,
"createdAt": "2022-09-29T09:52:50.477Z",
"updatedAt": "2022-09-29T09:52:50.477Z",
"__v": 0
},
{
"_id": "63356afad77a56d764f362ea",
"noteName": "string",
"category": "IDEA",
"content": "string",
"isArchived": false,
"createdAt": "2022-09-29T09:52:58.765Z",
"updatedAt": "2022-09-29T09:52:58.765Z",
"__v": 0
},
{
"_id": "63356afbd77a56d764f362ee",
"noteName": "string",
"category": "IDEA",
"content": "string",
"isArchived": false,
"createdAt": "2022-09-29T09:52:59.180Z",
"updatedAt": "2022-09-29T09:52:59.180Z",
"__v": 0
},
{
"_id": "63356b04d77a56d764f362f4",
"noteName": "string",
"category": "TASK",
"content": "string",
"isArchived": false,
"createdAt": "2022-09-29T09:53:08.261Z",
"updatedAt": "2022-09-29T09:53:08.261Z",
"__v": 0
},
{
"_id": "63356b09d77a56d764f362fc",
"noteName": "string",
"category": "TASK",
"content": "string",
"isArchived": true,
"createdAt": "2022-09-29T09:53:13.980Z",
"updatedAt": "2022-09-29T09:53:13.980Z",
"__v": 0
},
{
"_id": "63356b0ad77a56d764f362fe",
"noteName": "string",
"category": "TASK",
"content": "string",
"isArchived": true,
"createdAt": "2022-09-29T09:53:14.445Z",
"updatedAt": "2022-09-29T09:53:14.445Z",
"__v": 0
}]
This is request
getStats() {
const stats = this.noteModel
.aggregate([
{
$group: {
_id: {
category: '$category',
isArchived: '$isArchived',
},
count: {
$sum: 1,
},
},
},
{ $sort: { _id: 1 } },
])
.project({
_id: 0,
category: '$_id.category',
isArchived: '$_id.isArchived',
average: 1,
count: '$count',
});
return stats;
}
Now I receive like this
[
{
"category": "IDEA",
"isArchived": false,
"count": 5
},
{
"category": "IDEA",
"isArchived": true,
"count": 3
},
{
"category": "QUOTE",
"isArchived": false,
"count": 4
},
{
"category": "QUOTE",
"isArchived": true,
"count": 3
},
{
"category": "RANDOM THOUGHT",
"isArchived": false,
"count": 2
},
{
"category": "RANDOM THOUGHT",
"isArchived": true,
"count": 3
}....
]
But I want to receive it like this
[
{
"category": "IDEA",
"archived": 5,
"unArchived": 3
},
{
"category": "QUOTE",
"archived": 5,
"unArchived": 3
},
{
"category": "RANDOM THOUGHT",
"archived": 2,
"unArchived": 3
},
{
"category": "TASK",
"archived": 3,
"unArchived": 3
},
]
What should be changed in the query to get the required view?
Group by category only and calculate the archived and unArchived counts by checking the condition,
archived condition is if isArchived is true then return 1 otherwise 0
unArchived condition is if isArchived is true then return 0 otherwise 1
const stats = this.noteModel.aggregate([
{
$group: {
_id: "$category",
archived: {
$sum: { $cond: ["$isArchived", 1, 0] }
},
unArchived: {
$sum: { $cond: ["$isArchived", 0, 1] }
}
}
},
{ $sort: { _id: 1 } }
])
Playground

Aggregation query

Node js using mongoose as ORM. I have set of documents like below
I want to calculate the average time for createdAt in ISO String(format) field in duration of hours on
grouping the category field. I have tried many ways to use aggregate but couldn't get the result.
[{
"_id": {
"$oid": "62dfc2cf25735e8b1b475ff1"
},
"numLikes": 0,
"numViews": 0,
"numShares": 0,
"hasUserLiked": false,
"title": "sample-broadcast",
"description": "broadcast-dewscription",
"projectId": {
"$oid": "62d903a5dade1714382b27af"
},
"content": [
{
"_id": {
"$oid": "62dfc2cf25735ee18d475ff2"
},
"downloadLink": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/51daead4-1134-4921-a544-fea845f03d1c/uploadbroadcast/1658831544",
"label": "lance-anderson-QdAAasrZhdk-unsplash (1).jpg",
"contentType": "jpeg"
}
],
"delivery": "scheduleBroadcast",
"category": "GENERAL_UPDATES",
"groupId": "51daead4-1134-4921-a544-fea845f03d1c",
"author": {
"userId": "83314517-9326-430f-9c4e-8fedb050e6b0",
"profilePic": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/4f670832-dfd7-43ee-a6ad-e2f43f48df6a/uploadprofilepic/1658239806",
"name": "Biswajit Rout"
},
"projectLogo": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/4f670832-dfd7-43ee-a6ad-e2f43f48df6a/uploadcompanylogo%2A/1658239764",
"createdAt": {
"$date": {
"$numberLong": "1658831567577"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1658831567577"
}
},
"__v": 0
},{
"_id": {
"$oid": "62dfebae25735e015f476dfb"
},
"numLikes": 0,
"numViews": 0,
"numShares": 0,
"hasUserLiked": false,
"title": "testing-broadcast",
"description": "testing-description",
"projectId": {
"$oid": "62d903a5dade1714382b27af"
},
"content": [
{
"_id": {
"$oid": "62dfebae25735e291c476dfc"
},
"downloadLink": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/51daead4-1134-4921-a544-fea845f03d1c/projectimages0/1658842001",
"label": "lance-anderson-QdAAasrZhdk-unsplash (1).jpg",
"contentType": "jpeg"
},
{
"_id": {
"$oid": "62dfebae25735e0321476dfd"
},
"downloadLink": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/51daead4-1134-4921-a544-fea845f03d1c/projectimages1/1658842024",
"label": "Get_Started_With_Smallpdf.pdf",
"contentType": "pdf"
}
],
"delivery": "immediate",
"link": "http://localhost:3000",
"category": "GENERAL_UPDATES",
"groupId": "51daead4-1134-4921-a544-fea845f03d1c",
"author": {
"userId": "83314517-9326-430f-9c4e-8fedb050e6b0",
"profilePic": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/4f670832-dfd7-43ee-a6ad-e2f43f48df6a/uploadprofilepic/1658239806",
"name": "Biswajit Rout"
},
"projectLogo": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/4f670832-dfd7-43ee-a6ad-e2f43f48df6a/uploadcompanylogo%2A/1658239764",
"createdAt": {
"$date": {
"$numberLong": "1658842030827"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1658842030827"
}
},
"__v": 0
},{
"_id": {
"$oid": "62e144677fc76b0373f40152"
},
"numLikes": 0,
"numViews": 0,
"numShares": 0,
"hasUserLiked": false,
"title": "Broker Offer-1",
"description": "50% off on the membership for early birds. \n\nOffer Applied to first fifty users only",
"projectId": {
"$oid": "62d903a5dade1714382b27af"
},
"content": [
{
"_id": {
"$oid": "62e144677fc76b2b79f40153"
},
"downloadLink": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/51daead4-1134-4921-a544-fea845f03d1c/projectimages0/1658930275",
"label": "50-off-PNG-Picture.png",
"contentType": "png"
}
],
"delivery": "immediate",
"link": "",
"category": "OFFER_BROKERS",
"groupId": "51daead4-1134-4921-a544-fea845f03d1c",
"author": {
"userId": "83314517-9326-430f-9c4e-8fedb050e6b0",
"profilePic": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/4f670832-dfd7-43ee-a6ad-e2f43f48df6a/uploadprofilepic/1658239806",
"name": "Biswajit Rout"
},
"projectLogo": "https://builder-broadcast-media.s3.ap-south-1.amazonaws.com/builder-broadcast-media/4f670832-dfd7-43ee-a6ad-e2f43f48df6a/uploadcompanylogo%2A/1658239764",
"createdAt": {
"$date": {
"$numberLong": "1658930279871"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1658930279871"
}
},
"__v": 0
}]
My expected result should be like this
{
OFFER_BROKERS: 5 <hrs>,
GENERAL_UPDATES : 4 <hrs>
}
Do you want to convert milliseconds to real time and give it to orm

Why don't store properly on nested object MongoDB

I need to update a document with an object, but my approach looks like don't apply the changes on DB, can someone explain me what I'm doing wrong, please?
let dishes = await this.dishesModel.find({});
let dishesPromise = dishes.map((dish, i) => {
dish.ingredients.forEach((item) => {
if (item?.item) {
if (item['item']['unit']['_id'].toString() === payload._id) {
item['item']['unit'] = unit;
}
}
});
return dish.save();
});
await Promise.all(dishesPromise)
In the above code I get unit previously and the objective is to use this unit in: item['item']['unit']
A dish document looks like:
{
"_id": { "$oid": "62cc00bfad995c0e7d8bbb7d" },
"name": "Bologne pasta",
"price": 6.99,
"ingredients": [
{
"quantity": 3,
"item": {
"_id": { "$oid": "62cbe92821464145f0eb280e" },
"name": "Cow beef",
"code": "meat-01",
"price": 9.99,
"value": 100,
"internal": true,
"categories": [
{
"_id": { "$oid": "62cbe83221464145f0eb27ea" },
"name": "Meat",
"disabled": false
}
],
"unit": {
"name": "GRAMASO 2",
"abbr": "gr.",
"_id": { "$oid": "62befe5ff6f95cf46a4cfe5b" }
},
"disabled": false,
"__v": 0,
"translation": { "es": { "name": "Carne de vaca" } }
}
},
{
"quantity": 2,
"item": {
"_id": { "$oid": "62cbea3121464145f0eb283a" },
"name": "Tomato",
"code": "vegetable-02",
"price": 1.5,
"value": 20,
"internal": true,
"categories": [
{
"_id": { "$oid": "62cbe82921464145f0eb27e6" },
"name": "Vegetables",
"disabled": false
}
],
"unit": {
"name": "GRAMASO 2",
"abbr": "gr.",
"_id": { "$oid": "62befe5ff6f95cf46a4cfe5b" }
},
"disabled": false,
"__v": 0,
"translation": { "es": { "name": "Tomate" } }
}
},
{
"quantity": 1,
"item": {
"_id": { "$oid": "62cbee5621464145f0eb2865" },
"name": "Spagetti",
"code": "pasta-01",
"price": 1.5,
"value": 100,
"internal": true,
"categories": [
{
"_id": { "$oid": "62cbee3e21464145f0eb285f" },
"name": "Pasta",
"disabled": false
}
],
"unit": {
"name": "GRAMASO 2",
"abbr": "gr.",
"_id": { "$oid": "62befe5ff6f95cf46a4cfe5b" }
},
"disabled": false,
"__v": 0,
"translation": { "es": { "name": "Espaguetti" } }
}
}
],
"categories": [
{
"_id": { "$oid": "62cbe84721464145f0eb27f6" },
"name": "Italian",
"disabled": false,
"translation": { "en": { "name": "Italiana" } }
}
],
"__v": 0,
"translation": { "es": { "name": "Pasta con salsa boloƱesa" } }
}
At last, the changes are not reflecting on DB, but I don't figure out what's wrong.

Unwind inside lookup in Mongoose

I have 2 entities to merge: Customer and Feedback. Feedback contains an embedded array of upvotes (Upvote)
A customer is not able to upvote more than once for a specific feedback.
What I would like to achieve is - given a specific feedback id - get the complete list of customers with an additional virtual attribute that states whether he/she upvoted the given feedback.
Customer.aggregate(
[
{
$match: { company_id: new ObjectID(req.user.company_id) }
},
{
$lookup: {
from: 'feedbacks',
let: { 'c_id': '$_id' },
pipeline: [
{
$unwind: '$upvotes'
},
{
$match: { $expr: { $eq: ['$upvotes.customer_id._id', '$$c_id'] } }
}
],
as: 'upvotes'
}
}
],
function(err, customers) {
if (err) {
console.log(err);
res.status(400).send(err);
} else {
res.send({ customers });
}
}
);
To do that I have to look through the list of upvotes for that specific feedback and, then, join it with the customer table using the customer_id.
The above mentioned approach does not work. Any suggestion what I am doing wrong?
Sample data (Feedback)
{
"size": 0,
"points": 50,
"status": "open",
"potential": 0,
"real": 5000,
"_id": "5c3d033271ceb7edc37d156c",
"title": "Custom Invoice Templates",
"description": "Provide an editor to create custom invoices.",
"owner_id": {
"_id": "5c3b684f7cec8be977c2a465",
"email": "maurizio#acme.com"
},
"company_id": "5c3b684f7cec8be977c2a462",
"project_id": "5c3b68507cec8be977c2a468",
"upvotes": [
{
"_id": "5c3fa5b371ceb7edc37d159a",
"comments": "bbbb",
"priority": "should",
"customer_id": {
"size": 0,
"potential": 0,
"real": 5000,
"_id": "5c3b68507cec8be977c2a485",
"name": "Oyomia Ltd."
},
"owner_id": {
"_id": "5c3b684f7cec8be977c2a465",
"email": "maurizio#acme.com"
}
}
],
"updatedAt": "2019-01-16T21:44:19.215Z",
"createdAt": "2019-01-14T21:46:26.286Z",
"__v": 0
}
Sample data (Customer)
{
"size": 0,
"potential": 0,
"real": 5000,
"_id": "5c3b68507cec8be977c2a485",
"name": "Oyomia Ltd.",
"contact": {
"_id": "5c40f8de71ceb7edc37d15ab",
"name": "Nick Page",
"email": "np#oyoma.com"
},
"company_id": "5c3b684f7cec8be977c2a462",
"deals": [
{
"value": 5000,
"_id": "5c3b68507cec8be977c2a487",
"name": "Armour batch",
"status": "won",
"type": "non_recurring",
"updatedAt": "2019-01-13T16:33:20.870Z"
}
],
"__v": 0,
"updatedAt": "2019-01-17T21:51:26.877Z"
}

Resources