How to fix mongo db query? - node.js

I am writing a request for statistics from the db. Here's the code:
Stats.aggregate([
{
$group: {
_id: {
'advertiser': '$advertiser',
'offer': '$offer'
},
stats: {
$push: {
'date': '$date',
'spent': '$spent',
'revenue': '$revenue'
}
}
}
},
{
$group: {
_id: '$_id.advertiser',
offers: {
$push: {
_id: '$_id.offer',
stats: '$stats'
}
}
}
}], callback);
I want that the advertiser had all his offers, and inside the offers there was statistics on the days along with spent and revenue. The problem is that statistics by day can be more than one and I get this answer:
stats […]
0 {…}
date 2018-01-30T22:00:00.000Z
spent 100
revenue 200
1 {…}
date 2018-01-30T22:00:00.000Z
spent 20
revenue 20
But, I need to have the same days folded into one and spent and revenue added
Also, I want to add offer info from offers collection into result offers, like I do with advertisers. Maybe someone know how to do this. Thank you
Stats in db:
{
"_id": {
"$oid": "5a4f873d381727000404d171"
},
"advertiser": {
"$oid": "5a4f74619f0251000438fe4a"
},
"offer": {
"$oid": "5a4f748c9f0251000438fe4b"
},
"spent": 415.19,
"revenue": 780.92,
"date": "2018-01-02T22:00:00.000Z",
"user": {
"$oid": "5a4f74259f0251000438fe40"
},
"__v": 0
}
Stats Schema
const StatsSchema = mongoose.Schema({
advertiser: {
type: ObjectId,
ref: 'Advertiser',
required: true
},
offer: {
type: ObjectId,
ref: 'Offer',
required: true
},
spent: {
type: Number,
required: true
},
revenue: {
type: Number,
required: true
},
date: {
type: String,
required: true
},
user: {
type: ObjectId,
ref: 'User',
required: true
}});

You can first project the day, month and year then group accordingly as below. This is a relatively long query you could add in some optimisations based on your workflow.
db.createCollection('statistics');
db.statistics.insertMany([
{advertiser: "1", offer: "1", date: "2018-01-30T22:00:00.000Z", spent: 10, revenue: 20},
{advertiser: "1", offer: "1", date: "2018-01-30T21:00:00.000Z", spent: 20, revenue: 20},
{advertiser: "2", offer: "2", date: "2018-01-30T22:00:00.000Z", spent: 10, revenue: 20},
{advertiser: "2", offer: "2", date: "2018-01-30T21:00:00.000Z", spent: 1000, revenue: 2000},
{advertiser: "2", offer: "2", date: "2018-01-31T22:00:00.000Z", spent: 25, revenue: 50}
])
transform_date = {
$project: {
advertiser: 1,
offer: 1,
date: { $dateFromString: { dateString: { $arrayElemAt: [ {$split: ["$date", "Z"]}, 0 ] }, timezone: 'UTC' } },
spent: 1,
revenue: 1
}
}
project_year_month_day = {
$project: {
advertiser: 1,
offer: 1,
date: 1,
spent: 1,
revenue: 1,
year: { $year: "$date" },
month: { $month: "$date" },
day: { $dayOfMonth: "$date" }
}
}
group_by_date_advertiser_offer_and_sum = {
$group: {
_id: {
advertiser: "$advertiser",
offer: "$offer",
day: "$day",
month: "$month",
year: "$year"
},
spent: { $sum: "$spent" },
revenue: { $sum: "$revenue" },
dates: { $push: "$date" }
}
}
group_advertiser_offer_and_push = {
$group: {
_id: {
advertiser: "$_id.advertiser",
offer: "$_id.offer"
},
stats: {
$push: {
dates: "$dates",
spent: "$spent",
revenue: "$revenue"
}
}
}
}
group_advertiser_and_push = {
$group: {
_id: "$_id.advertiser",
offers: {
$push: {
_id: "$_id.offer",
stats: "$stats"
}
}
}
}
db.statistics.aggregate([
transform_date,
project_year_month_day,
group_by_date_advertiser_offer_and_sum,
group_advertiser_offer_and_push,
group_advertiser_and_push
])
OUTPUT
[{
"_id": "2",
"offers": [{
"_id": "2",
"stats": [{
"dates": [ISODate("2018-01-31T22:00:00Z")],
"spent": 25,
"revenue": 50
}, {
"dates": [ISODate("2018-01-30T22:00:00Z"), ISODate("2018-01-30T21:00:00Z")],
"spent": 1010,
"revenue": 2020
}]
}]
} {
"_id": "1",
"offers": [{
"_id": "1",
"stats": [{
"dates": [ISODate("2018-01-30T22:00:00Z"), ISODate("2018-01-30T21:00:00Z")],
"spent": 30,
"revenue": 40
}]
}]
}]

Related

I am trying to get retrive data from mongodb but not getting expected output

DB Data -
[{
title: "Vivo X50",
category: "mobile",
amount: 35000
},
{
title: "Samsung M32",
category: "mobile",
amount: 18000
},
{
title: "Lenovo 15E253",
category: "laptop",
amount: 85000
},
{
title: "Dell XPS 15R",
category: "laptop",
amount: 115000
}]
Expected Output:
[{
category: "mobile",
qty: 2,
totalAmount: 53000
},
{
category: "laptop",
qty: 2,
totalAmount: 200000
}]
Code I am running (Using mongoose)
let products = await Product.aggregate([
{
$project: { _id: 0, category: 1, amount: 1 },
},
{
$group: {
_id: "$category",
qty: { $sum: 1 },
totalAmount: { $sum: "$amount" },
},
},
]);
Result I am Getting.
[
{
"_id": "laptop",
"count": 2,
"totalSum": 200000
},
{
"_id": "mobile",
"count": 2,
"totalSum": 53000
}
]
As you can clearly see that I am able to get correct data but I want correct name also category instead of _id. Please help me with that. Thanks in advance
You need $project as the last stage to decorate the output document.
{
$project: {
_id: 0,
category: "$_id",
qty: "$qty",
totalAmount: "$totalAmount"
}
}
Meanwhile, the first stage $project doesn't need.
db.collection.aggregate([
{
$group: {
_id: "$category",
qty: {
$sum: 1
},
totalAmount: {
$sum: "$amount"
}
}
},
{
$project: {
_id: 0,
category: "$_id",
qty: "$qty",
totalAmount: "$totalAmount"
}
}
])
Sample Mongo Playground
You can use the following query to get your expected output. cheers~
await Product.aggregate([
{
$group: {
_id: "$category",
qty: {
$sum: 1
},
totalAmount: {
$sum: "$amount"
},
},
},
{
$addFields: {
category: "$_id"
}
},
{
$project: {
_id: 0
},
}
])

I need to aggregate two collections based on userIds but couldn't manage it

I want to aggregate the collections (Review and Account) below but couldn't manage it properly so I needed to ask you guys.
Current Review Collection is written below
{
lawyerId: { type: mongoose.Schema.Types.ObjectId },
reviews: [
{
userId: { type: mongoose.Schema.Types.ObjectId, unique: true },
message: { type: String },
rate: { type: Number },
createdAt: { type: Date, default: Date.now },
},
],
}
If you recommend Review Collection can be refactored like this
{
lawyerId: { type: mongoose.Schema.Types.ObjectId },
userId: { type: mongoose.Schema.Types.ObjectId },
message: { type: String },
rate: { type: Number },
createdAt: { type: Date, default: Date.now },
}
Account Collection
{
_id: { type: mongoose.Schema.Types.ObjectId}
email: { type: String, unique: true },
firstName: { type: String },
lastName: { type: String },
},
The expected result of fetching reviews
{
averageRate: 3.2,
reviews: [
{
firstName: 'Jack',
lastName: 'Harden',
message: 'I dont like it',
rate: 2,
createdAt: '2020-01-01T14:58:23.330+00:00'
},
{
firstName: 'Takeshi',
lastName: 'San',
message: 'Thats nice',
rate: 5,
createdAt: '2020-03-02T10:45:10.120+00:00'
}
],
}
You should be able to achieve this using an aggregation.
You can view a live demo here, which allows you to run this query.
The Query:
// Assuming we are searching for an lawyerId of 3
db.review.aggregate([
{
$match: {
lawyerId: 3
}
},
{
$lookup: {
from: "account",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$group: {
_id: "$lawyerId",
averageRate: {
$avg: "$rate"
},
reviews: {
$push: {
createdAt: "$createdAt",
firstName: "$user.firstName",
lastName: "$user.lastName",
message: "$message",
rate: "$rate"
}
}
}
},
{ // *******************************************
$project: { // *******************************************
_id: 0, // If you comment out/remove all of these lines
averageRate: 1, // then the return also contains the 'lawyerId',
reviews: 1 // as '_id', which I would find useful...
} // *******************************************
} // *******************************************
])
The Results:
The query from above, using the data set from above, produces the following results:
[
{
"averageRate": 3.25,
"reviews": [
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "First",
"lastName": "Uno",
"message": "Message meh",
"rate": 3
},
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "Second",
"lastName": "Dos",
"message": "Message blah",
"rate": 4
},
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "First",
"lastName": "Uno",
"message": "Message foo",
"rate": 4
},
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "Third",
"lastName": "Tres",
"message": "Message bar",
"rate": 2
}
]
}
]
The Dataset:
In Mongo Playground, you can build out databases with multiple collections, this explains the data structure:
db={ // <---- Database 'db'
"account": [ // <---- Collection 'account'
{
_id: 21,
email: "first.uno#gmail.com",
firstName: "First",
lastName: "Uno"
},
{
_id: 22,
email: "second.dos#yahoo.com",
firstName: "Second",
lastName: "Dos"
},
{
_id: 23,
email: "third.tres#hotmail.com",
firstName: "Third",
lastName: "Tres"
}
],
"review": [ // <---- Collection 'review'
{
lawyerId: 3,
userId: 21,
message: "Message meh",
rate: 3,
createdAt: ISODate("2015-02-28T00:00:00Z")
},
{
lawyerId: 3,
userId: 22,
message: "Message blah",
rate: 4,
createdAt: ISODate("2015-02-28T00:00:00Z")
},
{
lawyerId: 3,
userId: 21,
message: "Message foo",
rate: 4,
createdAt: ISODate("2015-02-28T00:00:00Z")
},
{
lawyerId: 3,
userId: 23,
message: "Message bar",
rate: 2,
createdAt: ISODate("2015-02-28T00:00:00Z")
}
]
}
You can try this pipeline to get all reviews from review collection:
db.reviews.aggregate([
{
$lookup: {
from: "accounts",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$addFields: {
"firstName": "$user.firstName",
"lastName": "$user.lastName"
}
},
{
$group: {
"_id": null,
"average_rate": {
$avg: "$rate"
},
"reviews": {
$push: "$$ROOT"
}
}
},
{
$unset: [
"_id",
"reviews._id",
"reviews.user",
"reviews.userId",
"reviews.lawyerId"
]
}
])
Results:
[
{
"average_rate": 3.5,
"reviews": [
{
"createdAt": "Some Review Date",
"firstName": "Jack",
"lastName": "Harden",
"message": "I dont like it",
"rate": 2
},
{
"createdAt": "Some Review Date",
"firstName": "Takeshi",
"lastName": "San",
"message": "That's nice",
"rate": 5
}
]
}
]
Demo here

Group by not working using populate in mongoose

I have trouble to fill the fields of group in .populate({}), I tried many times but I could not understand the problem:
Company.find({
_id: req.body.idCompany,
})
.populate({
path: 'listAgencys',
model: 'Agency',
match: {
idClient: req.body.idClient,
createdAt: {
$gte: new Date(req.body.startDate),
$lte: new Date(req.body.endDate)
}
},
group: {
_id: {
subscriptionType: '$subscriptionName',
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
month: {
$let: {
vars: {
monthsInString: [, 'Jan.', 'Fev.', 'Mars', ......]
},
in: {
$arrayElemAt: ['$$monthsInString', { $month: '$createdAt' }]
}
}
}
},
countLines: { $sum: 1 },
} ,
group: {
_id: "$_id.subscriptionType",
info: {
$push: {
year: "$_id.year",
month: "$_id.month",
allLines: "$countLines",
}
},
}
})
.exec();
SCHEMAS:
const companySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
type: String,
trim: true,
},
listAgencys: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Agency",
}
]
});
module.exports = mongoose.model("Company", companySchema);
--
const agencySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
idClient: {
type: Number,
trim: true,
//unique: true,
},
adress: {
type: String,
trim: true,
lowercase: true,
},
createdAt: {
type: Date,
default: Date.now()
},
listOfSubscribers: [{
numberSubscriber: {
type: Number,
},
numberPhone: {
type: Number,
},
subscriptionName: {
type: String
},
}],
});
module.exports = mongoose.model("Agency", agencySchema);
Example of parameters;
{
"idCompany": "5c71ba1c1376b034f8dbceb6",
"startDate":"2019-02-23",
"endDate":"2019-03-31",
"idClient" : "021378009"
}
I want to display the number of subscribers per month and subscriptionType according to idAgency and idCompany that will be passed in parameter.
Edit1 with aggregate:
Company.aggregate(
[
{ $unwind: "$listAgencys" },
{
$match: {
_id: req.body.idCompany,
idClient: req.body.idClient,
createdAt: {
"istAgencys.createdAt": {
$gte: new Date(req.body.startDate),
$lte: new Date(req.body.endDate)
}
}
},
{
$group: {
_id: {
subscriptionType: '$listAgencys.subscriptionName',
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
month: {
$let: {
vars: {
monthsInString: [, 'Jan.', 'Fev.', 'Mars', ......]
},
in: {
$arrayElemAt: ['$$monthsInString', { $month: '$createdAt' }]
}
}
}
}
countLines: { $sum: 1 },
} ,
{
$group: {
_id: "$_id.subscriptionType",
info: {
$push: {
year: "$_id.year",
month: "$_id.month",
allLines: "$countLines",
}
}
}
}
])
Result of edit1:
{
success: true,
datalines : []
}
Example of output:
{
"success": true,
"datalines": [
{
"idClient": 0213400892124
{
"_id": {
"subscriptionType": "ADL",
"year": 2019,
"month" : "Fev."
},
"allLines": 3,
},
{
"_id": {
"subscriptionType": "Lines",
"year": 2019,
"month" : "Jan."
},
"allLines": 10,
},
{
"_id": {
"subscriptionType": "Others",
"year": 2019,
"month" : "Mars"
},
"allLines": 35,
}
},
{
"idClient": 78450012365
.........
}
]
}
thank you in advance,

how to group data based on months in nodejs?

Sale.aggregate({
$match: filter
}, {
$group: {
"_id": {
"store_id": "$store_id",
//"created_on": { $dateToString: { format: "%Y-%m-%d", date: "$strBillDate" } }
},
month: {
$month: "$strBillDate"
},
store_id: {
$first: "$store_id"
},
strBillAmt: {
$sum: "$strBillAmt"
},
strBillNumber: {
$sum: 1
}
}
})
Instead of date, I need to group sales in months, how to group sales in months in nodejs
I used a projection first in the aggregate chain to extract monthly and yearly values and did the grouping afterwards:
<doc-name>.aggregate([
{ $project:
{ _id: 1,
year: { $year: "$date" },
month: { $month: "$date"},
amount: 1
}
},
{ $group:
{ _id: { year: "$year", month: "$month" },
sum: { $sum: "$amount" }
}
}])
I also tried with your model:
var testSchema = mongoose.Schema({
store_id: { type: String },
strBillNumber: { type: String },
strBillDate: { type: Date },
strBillAmt: { type: Number }
});
var Test = mongoose.model("Test", testSchema);
Create some test data:
var test = new Test({
store_id: "1",
strBillNumber: "123",
strBillDate: new Date("2016-04-02"),
strBillAmt: 25
});
var test2 = new Test({
store_id: "1",
strBillNumber: "124",
strBillDate: new Date("2016-04-01"),
strBillAmt: 41
});
var test3 = new Test({
store_id: "3",
strBillNumber: "125",
strBillDate: new Date("2016-05-13"),
strBillAmt: 77
});
Run the query:
Test.aggregate([
{ $project:
{ store_id: 1,
yearBillDate: { $year: "$strBillDate" },
monthBillDate: { $month: "$strBillDate" },
strBillAmt: 1
}
},
{ $group:
{ _id: {yearBillDate: "$yearBillDate", monthBillDate:"$monthBillDate"},
sum: { $sum: "$strBillAmt" }
}
}
], function(err, result) {
console.log(err, result)});
And got a reasonable result:

Mongoose aggregate returning empty result [duplicate]

This question already has answers here:
Moongoose aggregate $match does not match id's
(5 answers)
Closed 3 years ago.
I have problems with a mongoose aggregate request.
It's kind of driving me crazy cause I can't find a solution anywhere. I would be very grateful for any support.
The schema:
var EvalSchema = new Schema({
modified: {type: Date, default: Date.now},
created : {type: Date, default: Date.now},
username: {type: String, required: true},
item: {type: String, required: true},
criteria: [{
description: {
type: String
},
eval: {
type: Number
}
}]
});
mongoose.model('Eval', EvalSchema);
and I'm using an aggregation to compute the sum of evaluations for each criterion for a given item.
Eval.aggregate([{
$match: {
item: item.id
}
}, {
$unwind: "$criteria"
}, {
$group: {
_id: "$criteria.description",
total: {
$sum: "$criteria.eval"
},
count: {
$sum: 1
}
}
}, {
$project: {
total: 1,
count: 1,
value: {
$divide: ["$total", "$count"]
}
}
}], function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
Result is always empty....
I'm logging all queries that mongoose fire in the application. When I run the query in Mongodb, it returns the correct result.
coll.aggregate([{
'$match': {
item: 'kkkkkkkkkkk'
}
}, {
'$unwind': '$criteria'
}, {
'$group': {
_id: '$criteria.description',
total: {
'$sum': '$criteria.eval'
},
count: {
'$sum': 1
}
}
}, {
'$project': {
total: 1,
count: 1,
value: {
'$divide': ['$total', '$count']
}
}
}])
Result:
{
result: [{
"_id": "Overall satisfaction",
"total": 4,
"count": 1,
"value": 4
}, {
"_id": "service",
"total": 3,
"count": 1,
"value": 3
}, {
"_id": "Quality",
"total": 2,
"count": 1,
"value": 2
}, {
"_id": "Price",
"total": 1,
"count": 1,
"value": 1
}],
ok: 1
}
The model is referencing the correct collection.
Thank you :)
Your item.id in the $match function is a String, therefore you will need to convert it to an ObjectID, like so:
$match: { item: mongoose.Types.ObjectId(item.id) }
You can refer to this issue on GitHub aggregate for more details.

Resources