How to perform aggregate query in mongooose - javascript

I have the following data,
[ { type: 'sale' , amount: 2000 }, { type: 'expenditure' , amount: 1300 }, { type: 'sale' , amount: 4090 }, { type: 'expenditure' , amount: 3000 }]
The output expected,
[ { type: 'sale' , amount: 6090 }, { type: 'expenditure' , amount: 4300 }]
I mean I want to perform the summation of 2 types 'sale' and 'expenditure' and I tried as bellow,
`
store.aggregate(
[
{
$group: {
type: "sale",
total: {
$sum: "amount"
}
}
}
],
function(err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
}
);
});
`
store.aggregate(
[
{
$group: {
type: "sale",
total: {
$sum: "amount"
}
}
}
],
function(err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
}
);
});
`
[ { type: 'sale' , amount: 2000 }, { type: 'expenditure' , amount: 1300 }, { type: 'sale' , amount: 4090 }, { type: 'expenditure' , amount: 3000 }]
The output expected,
[ { type: 'sale' , amount: 6090 }, { type: 'expenditure' , amount: 4300 }]
I mean I want to perform the summation of 2 types 'sale' and 'expenditure' and I tried as bellow,

You can write group like this
$group: {
_id: "$type",
type: "$type",
amount: {
$sum: "$amount"
}
}

Related

How to aggregate a nested array using MongoDB?

I tried to use aggregate to find out each product's monthly sales in my order , but I ran into a problem.
Here's my data structures.
Order.model.ts
const OrderSchema: Schema = new Schema(
{
userId: {
type: String,
require: true,
},
products: [
{
product: {
_id: {
type: String,
},
title: {
type: String,
},
desc: {
type: String,
},
img: {
type: String,
},
categories: {
type: Array,
},
price: {
type: Number,
},
createdAt: {
type: String,
},
updatedAt: {
type: String,
},
size: {
type: String,
},
color: {
type: String,
},
},
quantity: {
type: Number,
default: 1,
},
},
],
quantity: {
type: Number,
},
total: {
type: Number,
},
address: {
type: String,
require: true,
},
status: {
type: String,
default: 'pending',
},
},
{ timestamps: true },
);
Order-service.ts
public async getIncome(productId?: string) {
const date = new Date();
const lastMonth = new Date(date.setMonth(date.getMonth() - 1));
const previousMonth = new Date(new Date().setMonth(lastMonth.getMonth() - 1));
//const lastYear = new Date(date.setFullYear(date.getFullYear() - 1));
const income = await this.order.aggregate([
{
$match: {
createdAt: { $gte: lastMonth },
...(productId && {
products: { $elemMatch: { product: { _id: productId } } },
}),
},
},
{
$project: {
month: { $month: '$createdAt' },
sales: '$total',
},
},
{
$group: {
_id: '$month',
total: { $sum: '$sales' },
},
},
]);
return income;
}
When I calculate whole sales without productId , it went well , I tried to use elemMatch to find productId , but it won't work , did I miss something ?
Try $unwind "products" array first, then apply $match:
const income = await this.order.aggregate([
{
$unwind: '$products'
},
{
$match: {
createdAt: { $gte: lastMonth },
product: { _id: productId },
},
},
{
$project: {
month: { $month: '$createdAt' },
sales: '$total',
},
},
{
$group: {
_id: '$month',
total: { $sum: '$sales' },
},
},
]);

mongoDB group by nested document with unknown path

i want group by value and my problem is screen_size and screen_resulation is changeable
is there is something like wildcard in nested document in mongodb ?
i have tried this but its not working
[
{
filters: {
screen_size: {
name: "حجم الشاشة",
value: "50",
},
screen_resulation: {
name: "دقة الشاشة",
value: "4k",
},
},
},
{
filters: {
screen_resulation: {
name: "دقة الشاشة",
value: "UHD",
},
screen_size: {
name: "حجم الشاشة",
value: "55",
},
},
},
{
filters: {
screen_resulation: {
name: "دقة الشاشة",
value: "4k",
},
screen_size: {
name: "حجم الشاشة",
value: "55",
},
},
},
];
{
$group : {
_id: "$filters.*.value", counter: { $sum: 1 }
}
}
You can get the desired result with the below approach as well:
[{$project: {
_id: '$_id',
filters: { $objectToArray: '$filters' }
}}, {$unwind: {
path: '$filters'
}}, {$group: {
_id: '$filters.v.value',
count: {$sum: 1}
}}]

aggregate data by date interval

This is how I'm doing an aggregation query to get the average time between start and end time (both in the format ISODate("2020-02-24T13:08:00.123Z")).
But I need to split the result data into two groups as I need to get the average data for all datasets with start time 04/2019 - 09/2020 and the second group all data with start time 10/2019 - 04/2020.
I don't get it how to group by these two interval for an ISODate value
const data = await Data.aggregate([
{
$match: {
type: { $exists: true },
statsIgnore: { $exists: false }
}
},
{
$group: {
_id: '$type',
Datasets: { $sum: 1 },
Average: {
$avg: {
$divide: [
{ $subtract: ['$timeEnd', '$timeStart'] },
60000
]
}
}
}
}
]).toArray()
My data structure
[
{
_id: ObjectId("5d9242cf863feb0b8d70d12e"),
timeStart: ISODate("2020-02-24T13:08:00.123Z"),
timeEnd: ISODate("2020-02-24T13:18:00.123Z"),
type: 'type1'
},
{
_id: ObjectId("5d9242cf863feb0b8d70d12f"),
timeStart: ISODate("2019-08-29T17:05:00.123Z"),
timeEnd: ISODate("2019-08-29T17:25:00.123Z"),
type: 'type1'
}
]
In this simple data example there is only one type with a single dataset for summer and a single dataset for winter interval.
So the result should be 10 minutes average for winter and 20 minutes average for summer (for type1 group).
The approach I took is to check that the timeStart is in the range you're looking for in the initial $match stage. Then I added an $addFields stage that checks if the season is summer based on the start date. Then I grouped by my new summer field.
[
{$match: {
type: {
$exists: true
},
statsIgnore: {
$exists: false
},
timeStart: {
$gte: ISODate("2019-04-01T00:00:00Z"),
$lt: ISODate("2020-04-01T00:00:00Z")
}
}},
{$addFields: {
summer: { $lt: ["$timeStart", ISODate("2019-09-01T00:00:00Z")]}
}},
{$group: {
_id: "$summer",
Average: {
$avg: {
$divide: [
{ $subtract: ['$timeEnd', '$timeStart'] },
60000
]
}
}
}}]
Check if this meets your requirements:
db.data.aggregate([
{
$match: {
type: {
$exists: true
},
statsIgnore: {
$exists: false
}
}
},
{
$group: {
_id: {
type: "$type",
season: {
$arrayElemAt: [
[
"None",
"Winter",
"Winter",
"Spring",
"Spring",
"Spring",
"Summer",
"Summer",
"Summer",
"Autumn",
"Autumn",
"Autumn",
"Winter"
],
{
$month: "$timeStart"
}
]
}
},
Datasets: {
$sum: 1
},
Average: {
$avg: {
$divide: [
{
$subtract: [
"$timeEnd",
"$timeStart"
]
},
60000
]
}
}
}
}
])
MongoPlayground

Trying to get data from Mongo DB with aggregate

I have "Offers" and "Requests" collections, I need to get all offers that user made, group them by requests and find the lowest "Offer.price" on each request, each offer has requestId field.
I am using aggregate to solve this,
db.Offer.aggregate([{
$match: {
ownerId: mongoose.Types.ObjectId(req.params.ownerId)
}
},
{
$group: {
_id: "$requestId",
price: {
$min: "$price"
}
}
}
])
and This is what i get :
[ { _id: 5dc47241af1406031489c65c, price: 14 },
{ _id: 5dc47241af1406031489c653, price: 3 },
{ _id: 5dc47241af1406031489c656, price: 5 },
{ _id: 5dc8add63f73953ff408f962, price: 6 },
{ _id: 5dc8add63f73953ff408f969, price: 22 },
{ _id: 5dc47241af1406031489c658, price: 1 } ]
Now I want to populate these with rest of the data from "Offer"
const OfferSchema = new Schema({
requestId: {
type: Schema.Types.ObjectId,
ref: 'Request'
},
ownerId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'User'
},
price: {
type: Number,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
isBest: {
type: Boolean,
default: false
},
isWinner: {
type: Boolean,
default: false,
}
});
What would be best way to do something like this?
Thank you for your help!
Consider the following dataset:
db.dum.insert({ownerId:1, requestId:'a', price:3, createdAt:3, isWinner:true})
db.dum.insert({ownerId:1, requestId:'a', price:1, createdAt:1, isWinner:false})
db.dum.insert({ownerId:1, requestId:'a', price:2, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:4, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:5, createdAt:1, isWinner:false})
db.dum.insert({ownerId:2, requestId:'b', price:5, createdAt:1, isWinner:false})
You could use $reduce
Here, for a grouping id, we keep all matching documents as an array (candidates).
On the project stage, for each group we iterate through the array, and reduce it to the minimal element found (by price that is)
db.dum.aggregate([{
$match: {
ownerId: 1
}
},
{
$group: {
_id: "$requestId",
candidates: { $push:'$$ROOT'}
}
},
{
$project:{
item: {
$reduce: {
input: '$candidates',
initialValue: '$candidates.0',
in: {
$cond: {
if: {
$lt: ['$$value.price', '$$this.price']
},
then:'$$value',
else:'$$this'
}
}
}
}
}
},
{
$replaceRoot:{newRoot:'$item'}
}
]).toArray()
output:
[
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb507"),
"ownerId" : 1,
"requestId" : "b",
"price" : 4,
"createdAt" : 2,
"isWinner" : true
},
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb505"),
"ownerId" : 1,
"requestId" : "a",
"price" : 1,
"createdAt" : 1,
"isWinner" : false
}
]

Fetching total count of matching items in paged Mongo aggregate query

I've seen basic questions on SO but not able to make it work in my case. Here's my query:
return Order.aggregateAsync([{
$match: { status: { $ne: 'incomplete' } }
}, {
$unwind: '$items'
}, {
$match: {... }
},
{
$limit: limit
}, {
$skip: skip
}, {
$group: {
_id: '$items._id',
product: {
$first: '$items.product'
},
qty: {
$first: '$items.qty'
},
ordered: {
$first: '$ordered'
}
}
}, {
$sort: { 'ordered': -1 }
},
])...
How can I return from this something such as:
{
items: [array of items by page/limit],
total: total items in db that match $match
}
I've tried adding this after $match:
{
$group: {
_id: null,
items: { $push: '$items' },
count: { $sum: 1 }
}
},
But it seems to not use limit then.
Sample data:
[{
_id:ObjectID
status: 'incomplete'
ordered: Date,
items: [{
_id: ObjectID
qty: 100,
product: ObjectID
}, {
_id: ObjectID
qty: 10,
product: ObjectID
}]
}, {
_id:ObjectID
status: 'incomplete'
ordered: Date,
items: [{
_id: ObjectID
qty: 200,
product: ObjectID
}]
}]
I want to return all the items, grouped by items as a paged query (limit,skip):
[{
_id: ObjectID
qty: 100,
product: ObjectID
}, {
_id: ObjectID
qty: 10,
product: ObjectID
}, {
_id: ObjectID
qty: 200,
product: ObjectID
}]
As well as the total count (3) of all items that match.
So:
result = {
docs: items array above,
total: 3
you try this query , its useful to you , and it will give you result for u want
db.getCollection('collectionName').aggregate([
{ $unwind: '$items'},
{
$group:
{
_id: "",
"doc": { $push: "$items" },
"total":{ $sum: 1}
}
},
{ $project : { doc : 1 , total : 1,_id:0 } }
])
output
{
"doc" : [
{
"_id" : 11,
"qty" : 100,
"product" : 12
},
{
"_id" : 111,
"qty" : 10,
"product" : 112
},
{
"_id" : 21,
"qty" : 100,
"product" : 22
},
{
"_id" : 211,
"qty" : 10,
"product" : 212
}
],
"total" : 4.0000000000000000
}

Categories

Resources