How to slice and mondify a mongodb aggregation - mongodb

I am new to MongoDB and I am trying to project the amount field from a decimal to a double when queried. Right now I add $$ROOT to an array and then slice that array to just get a subset of transactions. I figure I can probably convert it when I push to the array ($group stage) or after slicing ($project stage) but I can't figure out how. Any tips? End goal is to present the sum, count, and most recent transactions (I need to add sorting logic later).
db.transactions.aggregate([
{$match:{}},
{$group:{
_id:null,
transactions:{
$push:"$$ROOT"
},
count:{$sum: 1},
sum: {$sum: "$amount"}
}},
{$project:{
_id:0,
summary:{ count: "$count", sum: {$convert:{input:"$sum",to:"double"}}},
transactions: {$slice:["$transactions", 2]}
}}
])

I figured out that $facet allow me to do this:
db.transactions.aggregate([{
$facet:{
summary: [
{$match:{}},
{$group:{
_id:null,
count:{$sum: 1},
sum: {$sum: "$amount"}
}},
{$project:{
_id:0,
count: "$count",
sum: {$convert:{input:"$sum",to:"double"}}}
}
],
transactions: [
{$match:{}},
{$sort:{date:-1,timestamp:-1}},
{$limit:1},
{$addFields:{amount: {$convert:{input:"$amount",to:"double"}}}}
]
}
}]);

Related

Find documents between two dates, but include documents before first and after last result

We have a collection with multiple documents ordered with respect to a given timestamp. We want to aggregate documents between two timestamps (let's say startTime and stopTime): that is a simple match stage in our aggregation that has a query such as timestamp: {$gte: startTime, $lte: stopTime}. However, we'd like to include two extra documents in the result of this step: the closest document right before startTime, no matter how far back in time we would need to look, and also the closest document right after stopTime. Is there a way to achieve this with the aggregation framework in MongoDB?
One option if you are already after filtering out these documents, is using a $lookup step with a pipeline. It looks a bit clumsy after the $lookups, but I could not think about another way to continue without grouping all the documents, which is not the best way to go.
$match - This is a "fake" step in order to level up with your situation. You already have it in your current pipeline, thus don't need it here
$set the "$$ROOT" in order to use it latter
$lookup twice in order to get your requested documents from the original collection
For each document create an array of documents, in order to get the before and after out of the current documents
$unwind to separate into documents
$group by _id in order to remove the duplicates of the before and after documents
Format
db.collection.aggregate([
{$match: {timestamp: {$gte: startTime, $lte: stopTime}}},
{$set: {data: "$$ROOT"}},
{$lookup: {
from: "collection",
let: {},
pipeline: [
{$match: {timestamp: {$lt: startTime}}},
{$sort: {timestamp: -1}},
{$limit: 1}
],
as: "before"
}},
{$lookup: {
from: "collection",
let: {},
pipeline: [
{$match: {timestamp: {$gt: stopTime}}},
{$sort: {timestamp: 1}},
{$limit: 1}
],
as: "after"
}},
{$project: {_id: 0, data: {$concatArrays: ["$after", "$before", ["$data"]]}}},
{$unwind: "$data"},
{$group: {_id: "$data._id", data: {$first: "$data"}}},
{$replaceRoot: {newRoot: "$data"}},
{$sort: {timestamp: 1}}
])
See how it works on the playground example
Chain up $unionWith with $sort and $limit: 1 to get the documents out of range.
db.collection.aggregate([
{
$match: {
datetime: {
$gte: ISODate("2022-10-18"),
$lte: ISODate("2022-10-19")
}
}
},
{
"$unionWith": {
"coll": "collection",
"pipeline": [
{
$match: {
datetime: {
$lt: ISODate("2022-10-18")
}
}
},
{
$sort: {
datetime: -1
}
},
{
$limit: 1
}
]
}
},
{
"$unionWith": {
"coll": "collection",
"pipeline": [
{
$match: {
datetime: {
$gt: ISODate("2022-10-19")
}
}
},
{
$sort: {
datetime: 1
}
},
{
$limit: 1
}
]
}
}
])
Here is the Mongo Playground for your reference.

MongoDB $group + $project + weekly average

I'm trying to get the average of a puntations for some stores weekly in Mongo but it don't work. Could u help me?
db.collection.aggregate(
[
{$match:{storeId:{$in:[
ObjectId("e069d1b76557685b9e235v"),ObjectId("e069d1b76557685b9t7j8n"),
ObjectId("e069d1b76557685b9e2fg6"),ObjectId("e069d1b76557685b9p56r2")
]}}},
{$group:{_id:"$storeId", week: { date: new Date("$createdAt") }, totalPoints: {$sum: "$points"}, averagePoints: {$avg: "$points"}} },
{$sort: {totalPoints:-1}}
])
It doesn't work. But if I delete the part of the week, the code work it but make a wrong average
db.collection.aggregate(
[
{$match:{storeId:{$in:[
ObjectId("e069d1b76557685b9e235v"),ObjectId("e069d1b76557685b9t7j8n"),
ObjectId("e069d1b76557685b9e2fg6"),ObjectId("e069d1b76557685b9p56r2")
]}}},
{$group:{_id:"$storeId", totalPoints: {$sum: "$points"}, averagePoints: {$avg: "$points"}} },
{$sort: {totalPoints:-1}}
])
For groupBy week you can use $week function from mongo and add start Date in $match operation.
db.collection.aggregate(
[
{$match:{storeId:{$in:[
ObjectId("e069d1b76557685b9e235v"),ObjectId("e069d1b76557685b9t7j8n"),
ObjectId("e069d1b76557685b9e2fg6"),ObjectId("e069d1b76557685b9p56r2")
]},
timeStamp: {$gt: ISODate(createdAt)},
}},
{$group:{_id:"$storeId", week: { $week: '$timeStamp'}, totalPoints: {$sum: "$points"}, averagePoints: {$avg: "$points"}} },
{$sort: {totalPoints:-1}}
])

how to getting the count with all records

I am trying this mongodb aggregation. I got the output but how can I get the count value with all records.
db.STREETLIGHTS.aggregate(
[
{$match : {"CreateDate":{$gt:new Date(ISODate("2018-04-09T23:54:16.064Z") - 24*60*60 * 1000)}}},
{ $project: {_id:1, SLC_ID:1,LONGITUDE:1,LATITUDE:1,DCUID:1,CUMILITIVE_KWH:1,LAMPSTATUS:1,CreateDate:1 } },
]
)
Please try this.
db.STREETLIGHTS.aggregate([
{$match : {"CreateDate":{$gt:new Date(ISODate("2018-04-09T23:54:16.064Z") - 24*60*60 * 1000)}}},
{$project: {_id:1, SLC_ID:1,LONGITUDE:1,LATITUDE:1,DCUID:1,CUMILITIVE_KWH:1,LAMPSTATUS:1,CreateDate:1 } },
{$group: {_id: null, count: {$sum: 1}}}
])
You need to use the $sum operator in the $project stage :
{$project: {
// Your others projections
total: {$sum: 1}
}

MongoDB max in group results

I grouped results by mvid and the counted number of occurrences in each group.
db.some_details.aggregate([
{$group: {_id: "$mvid", count: {$sum: 1}}}
])
Now, I would like to select group with max count. Here is my naive guess:
db.some_details.aggregate([
{$group: {_id: "$mvid", count: {$sum: 1}}},
{$max: "count"}
])
It, obviously, generates an error. I need to use max with group, but I don't have anything to group on.
What also would work is sorting the result and then using only the first element:
db.some_details.aggregate([
{$group: {_id: "$mvid", count: {$sum: 1}}},
{$sort: {"count": -1}},
{$limit: 1 }
])
The advantage that I see compared to chridam's solution is that you also get the id of the group with the maximum count, but I expect it to be slower (not tested).
The aggregation pipeline allows you to could include another $group operator pipeline that would give you the maximum count of all the groups:
db.some_details.aggregate([
{
$group: {
_id: "$mvid",
count: { $sum: 1 }
}
},
{
$group: {
_id: 0,
maxCount: { $max: "$count" }
}
}
])

MongoDB Aggregation Framework $Project additional ObjectId reference field

Say I have the following:
this.aggregate(
{$unwind: "$tags"},
{$match: {tags: {$in: pip.activity.tags}}},
{$group : {_id : '$_id',matches:{$sum:1}}},
{$project: { _id: 0,matches:1}},
{$sort: {matches:-1 }},
callback
);
how would I go about including an additional 'external' objectId field in the results? e.g if I have the following:
var otherField = new ObjectId('xxxxxxx');
this.aggregate(
{$unwind: "$tags"},
{$match: {tags: {$in: pip.activity.tags}}},
{$group : {_id : '$_id',matches:{$sum:1}}},
{$project: { _id: 0,matches:1,otherField:otherField}}, <-- include otherField
{$sort: {matches:-1 }},
callback
);
Is this possible or should I be using a forLoop or MapReduce for this particular step? I'm looking for something really efficient.
The $project pipeline operator would not let you inject the object, but you can probably insert the object id earlier in the $group operator. If you have a collection:
db.foo.save({_id:1,tags:['a','b']})
db.foo.save({_id:2,tags:['b','c']})
db.foo.save({_id:3,tags:['c','d']})
You can then write:
db.foo.aggregate({
$unwind: "$tags"},{
$match: { tags: {$in: ['b','c'] } }},{
$group: { _id: "$_id", matches: {$sum: 1 }, otherField: {$min: new ObjectId()} }},{
$project: { _id: 0, matches: 1, otherField: 1 }},{
$sort: { matches: -1 }})
The $min or $max can be used here, but it expects an operator or reference to a field so you have to give it one..