Mongodb find maximum based on nested object key - mongodb

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
}
]
}
}
}
]

Related

Perform search with facets unknown upfront Atlas MongoDB

I have the following document structure in MongoDB:
{
// other keys,
tags: [
tagA: "red",
tagB: "green"
]
},
{
// other keys,
tags: [
tagA: "orange",
tagB: "green",
tagC: "car"
]
}
I want to perform a $facets search that gives me the following output (name of each tag + values that occur on that tag + count of these value):
{
[
tagA: {
red: 1,
orange: 1
},
tagB: {
green: 2
},
tagC: {
car: 1
}
]
}
The tricky part is that the facets are unknown upfront (they can vary), and every tutorial I found only works for a predefined set of facets.
Is it possible?
P.S.: how to get the output of this to come alongside with a given query? So that the return is something like:
{
queryResults: [all the results, as in a normal query],
facets: [result showed in accepted answer]
}
If you consider having this as input (i've added bracket around object in your array) :
[
{
tags: [
{
tagA: "red"
},
{
tagB: "green"
}
]
},
{
tags: [
{
tagA: "orange"
},
{
tagB: "green"
},
{
tagC: "car"
}
]
}
]
You could then do an aggregation pipeline as follow :
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$addFields": {
"kv": {
"$objectToArray": "$tags"
}
}
},
{
"$unwind": "$kv"
},
{
"$group": {
"_id": {
key: "$kv.k",
value: "$kv.v"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.key",
"value": {
"$push": {
"k": "$_id.value",
"v": "$count"
}
}
}
},
{
$project: {
val: [
{
k: "$_id",
v: {
"$arrayToObject": "$value"
}
}
]
}
},
{
$project: {
res: {
"$arrayToObject": "$val"
}
}
},
{
$replaceRoot: {
newRoot: "$res"
}
}
])
It would give you this result :
[
{
"tagA": {
"orange": 1,
"red": 1
}
},
{
"tagB": {
"green": 2
}
},
{
"tagC": {
"car": 1
}
}
]
You can see this on mongoplayground : https://mongoplayground.net/p/FZbM-BGJRBm
Hope this answer your question.
Detailled explanation :
I use $unwind on the tags field in order to get one object per object in tags array.
I use $objectToArray to get keys (tagsA, tagsB) as values.
$unwind to go from an array to objets.
$group with $sum accumulator to calculate the occurence of each unique combination.
$group by tagsA,tagsB, etc with $push accumulator to add value in array (will be usufull afterwards)
$arrayToObject to go from array to object
Same
$replaceRoot to display results better.
If you want to understand more each step, consider reading mongo doc of each pipeline aggregator i used. You can also use the mongoplayground link above, delete some code to see what happens after each step.

Variable set of attributes $facets MongoDB [duplicate]

I have the following document structure in MongoDB:
{
// other keys,
tags: [
tagA: "red",
tagB: "green"
]
},
{
// other keys,
tags: [
tagA: "orange",
tagB: "green",
tagC: "car"
]
}
I want to perform a $facets search that gives me the following output (name of each tag + values that occur on that tag + count of these value):
{
[
tagA: {
red: 1,
orange: 1
},
tagB: {
green: 2
},
tagC: {
car: 1
}
]
}
The tricky part is that the facets are unknown upfront (they can vary), and every tutorial I found only works for a predefined set of facets.
Is it possible?
P.S.: how to get the output of this to come alongside with a given query? So that the return is something like:
{
queryResults: [all the results, as in a normal query],
facets: [result showed in accepted answer]
}
If you consider having this as input (i've added bracket around object in your array) :
[
{
tags: [
{
tagA: "red"
},
{
tagB: "green"
}
]
},
{
tags: [
{
tagA: "orange"
},
{
tagB: "green"
},
{
tagC: "car"
}
]
}
]
You could then do an aggregation pipeline as follow :
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$addFields": {
"kv": {
"$objectToArray": "$tags"
}
}
},
{
"$unwind": "$kv"
},
{
"$group": {
"_id": {
key: "$kv.k",
value: "$kv.v"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.key",
"value": {
"$push": {
"k": "$_id.value",
"v": "$count"
}
}
}
},
{
$project: {
val: [
{
k: "$_id",
v: {
"$arrayToObject": "$value"
}
}
]
}
},
{
$project: {
res: {
"$arrayToObject": "$val"
}
}
},
{
$replaceRoot: {
newRoot: "$res"
}
}
])
It would give you this result :
[
{
"tagA": {
"orange": 1,
"red": 1
}
},
{
"tagB": {
"green": 2
}
},
{
"tagC": {
"car": 1
}
}
]
You can see this on mongoplayground : https://mongoplayground.net/p/FZbM-BGJRBm
Hope this answer your question.
Detailled explanation :
I use $unwind on the tags field in order to get one object per object in tags array.
I use $objectToArray to get keys (tagsA, tagsB) as values.
$unwind to go from an array to objets.
$group with $sum accumulator to calculate the occurence of each unique combination.
$group by tagsA,tagsB, etc with $push accumulator to add value in array (will be usufull afterwards)
$arrayToObject to go from array to object
Same
$replaceRoot to display results better.
If you want to understand more each step, consider reading mongo doc of each pipeline aggregator i used. You can also use the mongoplayground link above, delete some code to see what happens after each step.

How do I use $unwind and then $group in the same mongodb query

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.

MongoDB: count both matching documents and matching subdocuments, grouped by property of document

Given a collection of documents each containing an array of subdocuments (among other properties):
{
"prop1": False,
"prop2": "unique_value",
"subdocuments": [
{
"subprop1": 1,
"subprop2": 10
},
{
"subprop1": 30,
"subprop2": 40
},
{
"subprop1": 10,
"subprop2": 1
}
]
}
And a $match query covering both documents and subdocuments:
{
"prop1": False,
"$or": [
{"subdocuments.subprop1": {"$lt": 3}},
{"subdocuments.subprop2": {"$lt": 5}}
]
}
How can I create an aggregate query that returns the number of matching subdocuments and matching documents, grouped by a specific property of the root documents?
Just counting total subdocuments and matching documents is simple, but I'm struggling to also get the right count of matching subdocuments.
Ideally I'd like to have a result like this (if we consider the sample document, only subdoc 1 and 3 match the $or conditions):
{
"unique_value": {
"documents": 1,
"subdocuments": 2
}
}
In this case the results are being grouped by the value of "prop2".
You can use $size and $filter to get the count for matching subdocuments first. Then do a $sum to get the documentCount and subdocumentCount.
db.collection.aggregate([
{
"$match": {
"prop1": false,
"$or": [
{
"subdocuments.subprop1": {
"$lt": 3
}
},
{
"subdocuments.subprop2": {
"$lt": 5
}
}
]
}
},
{
"$addFields": {
"subdocumentCount": {
$size: {
"$filter": {
"input": "$subdocuments",
"as": "s",
"cond": {
"$or": [
{
$lt: [
"$$s.subprop1",
3
]
},
{
$lt: [
"$$s.subprop2",
5
]
}
]
}
}
}
}
}
},
{
$group: {
_id: "$prop2",
documentCount: {
$sum: 1
},
subdocumentCount: {
$sum: "$subdocumentCount"
}
}
},
{
$project: {
_id: 0,
k: "$_id",
v: {
documentCount: "$documentCount",
subdocumentCount: "$subdocumentCount"
}
}
},
{
$group: {
_id: null,
docs: {
$push: "$$ROOT"
}
}
},
{
"$addFields": {
"docs": {
"$arrayToObject": "$docs"
}
}
},
{
"$replaceRoot": {
"newRoot": "$docs"
}
}
])
Here is the Mongo playground for your reference.

total of all groups totals using mongodb

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], ... ]
}
]