I'm trying to get a list that counts all the victories and battles grouped by player name out of this json that I obtain from an API:
[
{
"createdDate": 1541411260,
"players": [
{
"tag": "tag1234",
"name": "name1",
"battles": 2,
"wins": 1
},
{
"tag": "tag124567",
"name": "name2",
"battles": 1,
"wins": 0
},
{
"tag": "tag1234",
"name": "name3",
"battles": 3,
"wins": 3
}
]
},
{
"createdDate": 1541411460,
"players": [
{
"tag": "tag1234",
"name": "name1",
"battles": 1,
"wins": 1
},
{
"tag": "tag124567",
"name": "name2",
"battles": 1,
"wins": 1
},
{
"tag": "tag1234",
"name": "name3",
"battles": 0,
"wins": 0
},
{
"tag": "tag124567",
"name": "name4",
"battles": 1,
"wins": 0
}
]
},
{
"createdDate": 1541455260,
"players": [
{
"tag": "tag1234",
"name": "name1",
"battles": 0,
"wins": 0
},
{
"tag": "tag124567",
"name": "name2",
"battles": 4,
"wins": 4
},
{
"tag": "tag1234",
"name": "name3",
"battles": 6,
"wins": 6
}
]
}
]
The mongo query I'm using is the following but I can't get the names and battles/wins:
db.getCollection("logs").aggregate([
{ $unwind : '$players' },
{
$group: {
_id: { name: '$players.name' },
numBattles: { $sum: '$players.battles' },
numWins: { $sum: '$players.wins' }
}
},
{
$project: {
name: "$_id.name",
numBattles: '$_id.numBattles',
numWins: '$_id.numWins',
_id: 0
}
]
).pretty();
This gave me 0 results.
Also tried the following but it's returning a full group of players and their stats:
db.getCollection("logs").aggregate([
{
$group: {
_id: { name: '$players.name' },
numBattles: { $sum: '$players.battles' },
numWins: { $sum: '$players.wins' }
}
},
{
$project: {
name: "$_id.name",
numBattles: '$_id.numBattles',
numWins: '$_id.numWins',
_id: 0
}
}
]
).pretty();
The idea is to get something like this:
name1 - 3 battles and 2 wins,
name2 - x battles and y wins,
...
Any ideas?
Thank you.
In your $project stage you're referring to _id which contains only name and other fields should be referenced directly so you just need to change your $project to:
{
$project: {
name: "$_id.name",
numBattles: "$numBattles",
numWins: "$numWins",
_id: 0
}
}
Related
Is it possible to use MongoDB's aggregation framework to group channels using folder key and without joining documents?
[{key: 1, channels: {A: [], B: [], etc}}, {key: 2, channels: {A: [], B: [], etc}}]
I am trying to do using $unwind and then $group by folder name but it seems impossible.
Mongo Playground
Documents:
[
{
key: 1,
channels: [
{
"id": 1,
"name": "XXX",
"folder": "C"
},
{
"id": 2,
"name": "XXX",
"folder": "A"
},
{
"id": 3,
"name": "XXX",
"folder": "B"
},
{
"id": 4,
"name": "XXX",
"folder": "A"
},
{
"id": 5,
"name": "XXX",
"folder": "B"
},
{
"id": 6,
"name": "XXX",
"folder": "C"
}
]
},
{
key: 2,
channels: [
{
"id": 1,
"name": "XXX",
"folder": "D"
},
{
"id": 2,
"name": "XXX",
"folder": "B"
},
{
"id": 3,
"name": "XXX",
"folder": "A"
},
{
"id": 4,
"name": "XXX",
"folder": "C"
},
{
"id": 5,
"name": "XXX",
"folder": "A"
},
{
"id": 6,
"name": "XXX",
"folder": "D"
}
]
}
]
Expected Result:
[
{
key: 1,
channels: {
A: [{
"id": 2,
"name": "XXX"
},{
"id": 4,
"name": "XXX"
}],
B: [{
"id": 3,
"name": "XXX"
}, {
"id": 5,
"name": "XXX"
}],
C: [{
"id": 1,
"name": "XXX"
},{
"id": 6,
"name": "XXX"
}]
}
},
{
"key": 2,
"channels": ...
}
]
Thank you very much in advance.
$group by key and folder, construct channels array by providing required fields
$group by only key and construct the channels array in key-vlaue format
$arrayToObject convert above constructed channels to object
db.collection.aggregate([
{ $match: { key: { $in: [1, 2] } } },
{ $unwind: "$channels" },
{
$group: {
_id: {
id: "$key",
folder: "$channels.folder"
},
channels: {
$push: {
id: "$channels.id",
name: "$channels.name"
}
}
}
},
{
$group: {
_id: "$_id.id",
channels: {
$push: {
k: "$_id.folder",
v: "$channels"
}
}
}
},
{ $project: { channels: { $arrayToObject: "$channels" } } }
])
Playground
You just have to add one more $group stage followed by project stage which will alter the data as per requirement.
db.collection.aggregate([
{
$match: {
key: {
$in: [
1,
2
]
}
}
},
{
$unwind: "$channels"
},
{
$group: {
_id: {
key: "$key",
folder: "$channels.folder"
},
value: {
$push: "$channels",
},
"foldersRef": {
$push: "$channels.folder"
},
}
},
{
$group: {
_id: "$_id.key",
"channels": {
"$push": "$value"
},
},
},
{
"$project": {
"channels": {
"$map": {
"input": "$channels",
"as": "channel",
"in": {
"$arrayToObject": [
[
{
"v": {
"$map": {
"input": "$$channel",
"as": "subChannel",
"in": {
"id": "$$subChannel.id",
"name": "$$subChannel.name",
}
}
},
"k": {
"$arrayElemAt": [
"$$channel.folder",
0
]
},
}
]
],
},
}
},
},
},
])
Mongo Playground Sample Execution
What you have done is more appreciated. You might need two grop to easly preform the aggregations
$unwind to deconstruct the array
$group to group by key and foldername and second group to group by key only, but make the grp as key value pair which helps for $arrayToObject
$replaceRoot to make it to root with other fields _id ($mergeobjects)
$project for projection
Here is the code
db.collection.aggregate([
{ $unwind: "$channels" },
{
"$group": {
"_id": { key: "$key", fname: "$channels.folder" },
"channels": { "$push": "$channels" }
}
},
{
"$group": {
"_id": "$_id.key",
"grp": { "$push": { k: "$_id.fname", v: "$channels" } }
}
},
{ "$addFields": { "grp": { "$arrayToObject": "$grp" } } },
{
"$replaceRoot": {
"newRoot": { "$mergeObjects": [ "$grp", "$$ROOT" ] }
}
},
{
"$project": { grp: 0 }
}
])
Working Mongo playground
I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here
Based on the documents below, I need to count the records that the difference between the inicial_date and end_date is greater than and less than 5 minutes and the name = A.
[{
"_id": 1,
"name": "A",
"inicial_date": "2019-01-01 12:00:00",
"end_date": "2019-01-01 12:01:00"
},{
"_id": 2,
"name": "A",
"inicial_date": "2019-01-01 12:05:00",
"end_date": "2019-01-01 12:07:00"
},{
"_id": 3,
"name": "A",
"inicial_date": "2019-01-01 12:00:00",
"end_date": "2019-01-01 12:06:00"
},{
"_id": 4,
"name": "A",
"inicial_date": "2019-01-01 12:04:00",
"end_date": "2019-01-01 12:05:00"
},
"_id": 5,
"name": "A",
"inicial_date": "2019-01-01 12:10:00",
"end_date": "2019-01-01 12:20:00"
},{
"_id": 6,
"name": "A",
"inicial_date": "2019-01-01 12:00:00",
"end_date": "2019-01-01 12:08:00"
},{
"_id": 7,
"name": "A",
"inicial_date": "2019-01-01 13:00:00",
"end_date": "2019-01-01 13:01:00"
},{
"_id": 8,
"name": "B",
"inicial_date": "2019-01-01 14:00:00",
"end_date": "2019-01-01 14:09:00"
}]
The expected result:
{
"less_than_5": 4,
"greater_than_5": 3
}
Well, you can do this using an aggregate query,
db.collection.aggregate([
{
$match: {
name: "A"
}
},
{
// find time difference, the result is in milliseconds
$project: {
timeDiffInMilliseconds: {
$subtract: [
{
$toDate: "$end_date"
},
{
$toDate: "$inicial_date"
}
]
}
}
},
{
// convert the time difference to minutes
$project: {
timeDiffInMinutes: {
$divide: [
"$timeDiffInMilliseconds",
60000
]
}
}
},
{
// check if the number of minutes is greater than 5 mins or not
$project: {
timeDiffGreaterThan5Mins: {
$cond: [
{
$gt: [
"$timeDiffInMinutes",
5
]
},
1,
0
]
}
}
},
{
// group according to greater than 5 minutes or not
$group: {
_id: "null",
greater_than_5: {
$sum: {
$cond: [
{
$eq: [
"$timeDiffGreaterThan5Mins",
1
]
},
1,
0
]
}
},
less_than_5: {
$sum: {
$cond: [
{
$eq: [
"$timeDiffGreaterThan5Mins",
0
]
},
1,
0
]
}
}
}
}
])
Making it a bit more efficient,
db.collection.aggregate([
{
$match: {
name: "A"
}
},
{
$project: {
more_than_5_mins: {
$cond: [
{
$gt: [
{
$subtract: [
{
$toDate: "$end_date"
},
{
$toDate: "$inicial_date"
}
]
},
300000
]
},
1,
0
]
}
}
},
{
$group: {
_id: "",
less_than_5: {
$sum: {
$cond: [
{
$eq: [
"$more_than_5_mins",
0
]
},
1,
0
]
}
},
greater_than_5: {
$sum: {
$cond: [
{
$eq: [
"$more_than_5_mins",
1
]
},
1,
0
]
}
}
}
}
])
Having the sample data below, I'm trying to get a total count of students and the combined topScore of each subject of each section and floor:
[{
"section": "east",
"floor": "1st",
"classrom": 100,
"tests": [
{
"subject": "math",
"students": 30,
"topScore": 90
},
{
"subject": "english",
"students": 40,
"topScore": 80
}]
},
{
"section": "east",
"floor": "1st",
"classrom": 150,
"tests": [
{
"subject": "math",
"students": 35,
"topScore": 85
},
{
"subject": "english",
"students": 45,
"topScore": 70
}]
}]
Desired result:
[{
"section": "east",
"floor": "1st",
"classroms": [100, 150],
"tests": [
{
"subject": "math",
"totalStudents": 65,
"combinedTopScores": 175
},
{
"subject": "english",
"totalStudents": 85,
"combinedTopScores": 150
}]
}]
What I have so far is:
db.collection.aggregate([{
"$group": {
"_id": {
"section": "$section",
"floor": "$floor"
},
"classrooms": { "$push": "$classroom" },
"tests": { "$push": "$tests" }
}
}])
Which gives me:
{
"_id":
{
"section": "east",
"floor": "1st"
},
"classrooms": [100, 150],
"tests": [
[{
"subject": "math",
"students": 30,
"topScore": 90
},
{
"subject": "english",
"students": 40,
"topScore": 80
}],
[{
"subject": "math",
"students": 35,
"topScore": 85
},
{
"subject": "english",
"students": 45,
"topScore": 70
}]
]
}
So I'm having a hard time figuring out the $sum of the tests array. Specially because it has to be grouped by subject.
Can anybody point me a direction? Is that even possible?
Thanks!
You need to $unwind tests array to be able to group by section+floor+subject. Then you can calculate totals and perform second $group stage just by section + floor. Since classroms will be an array of arrays and might contain duplicates you can use $reduce with $setUnion to flatten those arrays and remove duplicated values. Try:
db.collection.aggregate([
{ $unwind: "$tests" },
{
$group: {
_id: {
section: "$section",
floor: "$floor",
subject: "$tests.subject"
},
totalStudents: { $sum: "$tests.students" },
combinedTopScores: { $sum: "$tests.topScore" },
classroms: { $push: "$classrom" }
}
},
{
$group: {
_id: { section: "$_id.section", floor: "$_id.floor" },
classroms: { $push: "$classroms" },
tests: {
$push: {
subject: "$_id.subject",
totalStudents: "$totalStudents",
combinedTopScores: "$combinedTopScores"
}
}
}
},
{
$project: {
section: "$_id.section",
floor: "$_id.floor",
classroms : {
$reduce: {
input: "$classroms",
initialValue: [],
in: { $setUnion: [ "$$this", "$$value" ] }
}
},
tests: 1
}
}
])
I am new to mongo queries. Currently I have a collection like this, which is used to create a d3 force-directed graph.
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763
},
{
"id": "jason#gmail.com",
"count": 1,
"nodeUpdatetime": 1525447783
}
],
"links": [{
"source": "bmw#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525312111
}, {
"source": "car#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525334013
}, {
"source": "bmw#gmail.com",
"target": "car#gmail.com",
"timestamp": 1525334118
}]
}
Using a mongo query, I would like to generate the output to something like this. Basically for the nested data under "nodes", add a new field called "topn" and rank them by count from 1 to 5. The remainder values are null. Can anyone help? Thank you!
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732,
"topn": 1
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null,
"topn": 2
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742,
"topn": 4
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847,
"topn": null
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758,
"topn": 5
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763,
"topn": 3
},
..............
The following should get you what you want:
db.collection.aggregate({
$unwind: "$nodes" // flatten the "nodes" array
}, {
$sort: { "nodes.count": -1 } // sort descending by "count"
}, {
$group: { // create the original structure again - just with sorted array elements
_id: "$_id",
nodes: { "$push": "$nodes" }
}
}, {
$addFields: {
"nodes": {
$zip: { // zip two arrays together
inputs: [
"$nodes", // the first one being the existing and now sorted "nodes" array
{ $range: [ 1, 6 ] } // and the second one being [ 1, 2, 3, 4, 5 ]
],
useLongestLength: true // do not stop after five elements but instead continue using a "null" value
}
}
}
}, {
$addFields: {
"nodes": {
$map: { // transform the "nodes" array
input: "$nodes",
as: "this",
in: {
$mergeObjects: [ // by merging two objects
{ $arrayElemAt: [ "$$this", 0] }, // the first sits at array position 0
{
topn: { $arrayElemAt: [ "$$this", 1] } // the second will be a new entity witha a "topn" field holding the second element in the array
}
]
}
}
}
}
})