i did this Aggregate pipeline , and i want add a field contains the Global Total of all groups total.
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: {
_id:"$_id",
value:"$value",
transaction:"$transaction",
paymentMethod:"$paymentMethod",
createdAt:"$createdAt",
...
}
},
count:{$sum:1},
total:{$sum:"$value"}
}}
{
//i want to get
...project groups , goupsTotal , groupsCount
}
,{
"$skip":cursor.skip
},{
"$limit":cursor.limit
},
])
you need to use $facet (avaialble from MongoDB 3.4) to apply multiple pipelines on the same set of docs
first pipeline: skip and limit docs
second pipeline: calculate total of all groups
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: "$$CURRENT"
},
count:{$sum:1},
total:{$sum:"$value"}
}
},
{
$facet: {
docs: [
{ $skip:cursor.skip },
{ $limit:cursor.limit }
],
overall: [
{$group: {
_id: null,
groupsTotal: {$sum: '$total'},
groupsCount:{ $sum: '$count'}
}
}
]
}
the final output will be
{
docs: [ .... ], // array of {_id, items, count, total}
overall: { } // object with properties groupsTotal, groupsCount
}
PS: I've replaced the items in the third pipe stage with $$CURRENT which adds the whole document for the sake of simplicity, if you need custom properties then specify them.
i did it in this way , project the $group result in new field doc and $sum the sub totals.
{
$project: {
"doc": {
"_id": "$_id",
"total": "$total",
"items":"$items",
"count":"$count"
}
}
},{
$group: {
"_id": null,
"globalTotal": {
$sum: "$doc.total"
},
"result": {
$push: "$doc"
}
}
},
{
$project: {
"result": 1,
//paging "result": {$slice: [ "$result", cursor.skip,cursor.limit ] },
"_id": 0,
"globalTotal": 1
}
}
the output
[
{
globalTotal: 121500,
result: [ [group1], [group2], [group3], ... ]
}
]
Related
I have the current aggregation output as follows:
[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
]
The array has two documents. I would like to combine all the documents into a single document having all the fields in mongoDB
db.collection.aggregate([
{
$group: {
_id: 0,
merged: {
$push: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": "$merged"
}
}
}
])
Explained:
Group the output documents in one field with push
Replace the document root with the merged objects
Plyaground
{
$group: {
"_id": "null",
data: {
$push: "$$ROOT"
}
}
}
When you add this as the last pipeline, it will put all the docs under data, but here data would be an array of objects.
In your case it would be
{ "data":[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
] }
Another approach would be,
db.collection.aggregate([
{
$group: {
"_id": "null",
f: {
$first: "$$ROOT",
},
l: {
$last: "$$ROOT"
}
}
},
{
"$project": {
"output": {
"courseCount": "$f.courseCount",
"registeredStudentsCount": "$l.registeredStudentsCount"
},
"_id": 0
}
}
])
It's not dynamic as first one. As you have two docs, you can use this approach. It outputs
[
{
"output": {
"courseCount": 14,
"registeredStudentsCount": 1
}
}
]
With extra pipeline in the second approach
{
"$replaceRoot": {
"newRoot": "$output"
}
}
You will get the output as
[
{
"courseCount": 14,
"registeredStudentsCount": 1
}
]
I have the following mongodb structure...
[
{
track: 'Newcastle',
time: '17:30',
date: '22/04/2022',
bookmakers: [
{
bookmaker: 'Coral',
runners: [
{
runner: 'John',
running: true,
odds: 3.2
},
...
]
},
...
]
},
...
]
I'm trying to find filter the bookmakers array for each document to only include the objects that match the specified bookmaker values, for example:
{ 'bookmakers.bookmaker': { $in: ['Coral', 'Bet365'] } }
At the moment, I'm using the following mongodb query to only select the bookmakers that are specified, however I need to put the documents back together after they've been seperated by the '$unwind', is there a way I can do this using $group?
await HorseRacingOdds.aggregate([
{ $unwind: "$bookmakers" },
{
$group: {
_id: "$_id",
bookmakers: "$bookmakers"
}
},
{
$project: {
"_id": 0,
"__v": 0,
"lastUpdate": 0
}
}
])
How about a plain $addFields with $filter?
db.collection.aggregate([
{
"$addFields": {
"bookmakers": {
"$filter": {
"input": "$bookmakers",
"as": "b",
"cond": {
"$in": [
"$$b.bookmaker",
[
"Coral",
"Bet365"
]
]
}
}
}
}
},
{
$project: {
"_id": 0,
"__v": 0,
"lastUpdate": 0
}
}
])
Here is the Mongo playground for your reference.
There are two collections (view and click) like following:
# View collection
_id publisher_id created_at
617f8ea98e0f54f05e10e796 1 2021-11-01T00:00:00.000Z
617f8eab8e0f54f05e10e798 1 2021-11-01T00:00:00.000Z
617f8eac8e0f54f05e10e79a 1 2021-11-01T00:00:00.000Z
617f90cea187d30ebbecdee9 2 2021-11-01T00:00:00.000Z
# Click collection
_id publisher_id created_at
617f8ea98e0f54f05e10e796 1 2021-11-01T00:00:00.000Z
617f8eab8e0f54f05e10e798 2 2021-11-01T00:00:00.000Z
How can I get the following expected results with one query?
(or)
What is the best way for the following expected results?
# Expected For Publisher ID(1)
_id view_count click_count
2021/11/1 3 1
# Expected For Publisher ID(2)
_id view_count click_count
2021/11/1 1 1
Currently, I am using 2 queries for both collections and combining results as one in code.
For View
db.view.aggregate([
/*FirstStage*/
{
$match:
{
"$and":
[
{
"publisher_id": 1
},
{
"created_at": {$gte: new ISODate("2021-11-01"), $lt: new ISODate("2021-11-28")}
}
]
}
},
/*SecondStage*/
{
$group:
{
_id: {$dateToString: {format: '%Y/%m/%d', date: "$created_at"}},
count: {
$sum: 1
}
}
}
])
For Click
db.click.aggregate([
/*FirstStage*/
{
$match:
{
"$and":
[
{
"publisher_id": 1
},
{
"created_at": {$gte: new ISODate("2021-11-01"), $lt: new ISODate("2021-11-28")}
}
]
}
},
/*SecondStage*/
{
$group:
{
_id: {$dateToString: {format: '%Y/%m/%d', date: "$created_at"}},
count: {
$sum: 1
}
}
}
])
Because you are querying two different collections there is no "good" way to merge this into one query, the only way I can think of is using $facet, where the first stage is the "normal" one, and the other stage starts with a $lookup from the other collection.
This approach does add overhead, which is why I recommend to just keep doing the merge in code, however for the sake of answering here is a sample:
db.view.aggregate([
{
$facet: {
views: [
{
$match: {
"$and": [
{
"publisher_id": 1
},
{
"created_at": {
$gte: ISODate("2021-11-01"),
$lt: ISODate("2021-11-28")
}
}
]
}
},
],
clicks: [
{
$limit: 1
},
{
$lookup: {
from: "click",
let: {},
pipeline: [
{
$match: {
"$and": [
{
"publisher_id": 1
},
{
"created_at": {
$gte: ISODate("2021-11-01"),
$lt: ISODate("2021-11-28")
}
}
]
}
},
],
as: "clicks"
}
},
{
$unwind: "$clicks"
},
{
$replaceRoot: {
newRoot: "$clicks"
}
}
]
}
},
{
$project: {
merged: {
"$concatArrays": [
"$views",
"$clicks"
]
}
}
},
{
$unwind: "$merged"
},
{
$group: {
_id: {
$dateToString: {
format: "%Y/%m/%d",
date: "$merged.created_at"
}
},
count: {
$sum: 1
}
}
}
])
Mongo Playground
I have below schema where I need to identify the object which has highest rank.
{ "team" : {
"member1" : [ { "rank": 2, "goal": 50 } ],
"member2" : [ { "rank": 5, "goal": 30 } ],
"member3" : [ { "rank": 1, "goal": 80 } ]
}}
$unwind will not work on the nested objects. Tried to convert this object as Array and tried to find the max of rank key. Any help would be appreciated.
If the intent is to only find the maximum rank that exists: The idea is a two stage aggregation query using $project and using $objectToArray to have common keys from which $max on required attribute can be applied.
Query: playground link
db.collection.aggregate([
{
$project: {
teamsData: {
$objectToArray: "$team"
}
}
},
{
$project: {
maxRank: {
$max: "$teamsData.v.rank"
}
}
}
]);
To get the object details that has the maximum rank: Use $unwind on the array projected from previous stage to help in sorting by rank $sort and then picking the the first item $first at $group stage.
Query: playgorund link
db.collection.aggregate([
{
$project: {
team: {
$objectToArray: "$team"
}
}
},
{
$unwind: "$team"
},
{
$sort: {
"team.v.rank": -1
}
},
{
$group: {
_id: null,
maxRankObj: {
$first: "$$ROOT"
}
}
}
]);
Sample O/P:
[
{
"_id": null,
"maxRankObj": {
"_id": ObjectId("5a934e000102030405000000"),
"team": {
"k": "member2",
"v": [
{
"goal": 30,
"rank": 5
}
]
}
}
}
]
I have 2 mongo aggregate queries that work well separately -
db.transfer_orders.aggregate([
{
$match: {
"request_timestamp": { $gte: ISODate("2017-10-00T00:00:00.000Z") },
"request_timestamp": { $lt: ISODate("2017-10-12T00:00:00.000Z") },
"purpose": "POSITIONING"
}
},
{
$group: {
_id: null,
to_count: { $sum: 1 },
qty: { $sum: "$quantity" }
}
},
{
$project: {
_id: 0,
"to_count": "$to_count",
"qty": "$qty"
}
}
])
and
db.transfer_orders.aggregate([
{
$match: {
"request_timestamp": { $gte: ISODate("2017-10-00T00:00:00.000Z") },
"request_timestamp": { $lt: ISODate("2017-10-12T00:00:00.000Z") },
"purpose": "POSITIONING"
}
},
{
$unwind: "$adjustments"
},
{
$group: {
_id: null,
totalChangeQty: { $sum: "$adjustments.change_in_quantity"}
}
},
{
$project: {
_id: 0,
"adjusted_quantity": "$totalChangeQty"
}
}
])
The first query returns aggregate data of elements at the top level of the document, { "to_count" : 7810, "qty" : 19470 }
The second query returns aggregate data of elements at one level below the top level for the "adjustments" array - { "adjusted_quantity" : -960 }
Is there a way to write this as one query that will return both sets of data since the match criteria is the same for both?
The following aggregate operation should suffice since it has a pipeline after the $match step that introduces the new field adjusted_quantity. This is made possible using the $sum which returns the sum of the specified list of expressions for each document.
Once it reaches the $group stage, you can retain the value using the $sum operator.
db.transfer_orders.aggregate([
{
"$match": {
"request_timestamp": { "$gte": ISODate("2017-10-00T00:00:00.000Z") },
"request_timestamp": { "$lt": ISODate("2017-10-12T00:00:00.000Z") },
"purpose": "POSITIONING"
}
},
{
"$addFields": {
"adjusted_quantity": {
"$sum": "$adjustments.change_in_quantity"
}
}
},
{
"$group": {
"_id": null,
"to_count": { "$sum": 1 },
"qty": { "$sum": "$quantity" },
"adjusted_quantity": { "$sum": "$adjusted_quantity" }
}
}
])