Related
Inspired by another question I was looking for a common way to add a field with the index to each item in a nested array.
Assuming my document looks like:
{
_id: ObjectId("5a934e000102030405000000"),
events: [
{
status: 0,
timestamp: ISODate("2022-05-29T13:26:00Z")
},
{
status: 8,
timestamp: ISODate("2022-05-29T14:41:00Z")
},
{
status: 4,
timestamp: ISODate("2022-05-31T10:13:00Z")
},
{
status: 3,
timestamp: ISODate("2022-05-31T10:18:00Z")
}
]
}
And I want each item to contain a new field which is the index of the item in the array:
{
_id: ObjectId("5a934e000102030405000000"),
events: [
{
arrayIndex: 0,
status: 0,
timestamp: ISODate("2022-05-29T13:26:00Z")
},
{
arrayIndex: 1,
status: 8,
timestamp: ISODate("2022-05-29T14:41:00Z")
},
{
arrayIndex: 2,
status: 4,
timestamp: ISODate("2022-05-31T10:13:00Z")
},
{
arrayIndex: 3,
status: 3,
timestamp: ISODate("2022-05-31T10:18:00Z")
}
]
}
Since mongoDB version 3.4, this can be done using an aggregation pipeline with a $reduce phase, which uses the size of the new accumulated array:
db.collection.aggregate([
{$project: {
events: {
$reduce: {
input: "$events",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{$mergeObjects: [
"$$this",
{arrayIndex: {$size: "$$value"}}
]}
]
]
}
}
}
}}
])
See how it works on the playground example
I have a collection called 'session_list' with the following rows.
{"user_id":"test#gmail.com","focus_score":[1, 2, 3, 4],"active_score":[3, 4, 1], "score" : 10}
{"user_id":"abcd#gmail.com","focus_score":[3, 4],"active_score":[3, 4, 1, 7, 7], "score" : 3}
{"user_id":"test#gmail.com","focus_score":[1, 2, 3, 4, 7],"active_score":[3, 9, 2], "score" : 7}
{"user_id":"abcd#gmail.com","focus_score":[5, 7, 8],"active_score":[1, 3, 7], "score" : 4}
How do I group by user_id and consolidate the focus_score array and then active_score array (without having any for loops) ?
Expected result:
{"user_id":"test#gmail.com","focus_score":[1, 2, 3, 4, 1, 2, 3, 4, 7], "active_score":[3, 4, 1, 3, 9, 2], "score_sum" : 17}
{"user_id":"abcd#gmail.com","focus_score":[3, 4, 5, 7, 8], "active_score":[3, 4, 1, 7, 7, 1, 3, 7], "score_sum" : 7}
My code:
db.session_list.aggregate([
{
$group: {
_id:{user_id:'$user_id'},
focus_score:{$push:'$focus_score'}
active_score:{$push:'$active_score'}
score_sum:{$sum:'$score_sum'}
}
}
])
But this does not provide the expected result.
db.collection.aggregate([
{
$group: {
_id: "$user_id",
focus_score: { $push: "$focus_score" },
active_score: { $push: "$active_score" },
score_sum: { $sum: "$score" }
}
},
{
$project: {
_id: 0,
score_sum: 1,
user_id: "$_id",
focus_score: {
$reduce: {
input: "$focus_score",
initialValue: [],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
},
active_score: {
$reduce: {
input: "$active_score",
initialValue: [],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
}
}
}
])
mongoplayground
Greetings fellow fans of MongoDB!
I've got here a data structure with board game data where achieved scores (after every round) are tracked as nested arrays associated with the player's name. Note that with each board game there's a different set of players:
{
"BoardGames" : {
"Game1" : {
"players" : {
"Anne" : [97, 165, 101, 67],
"Pete" : [86, 115, 134, 149],
"Scott" : [66, 89, 103, 74],
"Jane" : [113, 144, 125, 99],
"Katie" : [127, 108, 98, 151]
}
},
"Game2" : {
"players" : {
"Margot" : [1, 0, 0, 0],
"Pete" : [0, 0, 1, 1],
"Michael" : [0, 0, 0, 0],
"Jane" : [0, 0, 1, 0]
}
},
"Game3" : {
"players" : {
"Chris" : [6, 2, 4, 0, 5, 7],
"Pete" : [4, 5, 2, 5, 3, 1, 4],
"Julia" : [3, 7, 4, 0],
"Tom" : [3, 2, 4, 8, 2, 6, 7]
}
},
}
Game1: Players earn as many victory points per round as they can
Game2: Winning around earns 1, losing a round 0
Game3: Players may leave after every round, hence some players have played more rounds than others, so these arrays are different in their length
So, here are my questions:
Which player got the most points in each game? Who the least?
Who is the winner in the first round? 2nd round, etc.
Who is sitting on 1st, 2nd and 3rd rank from all played games?
I've done quite some queries with mongo, but so far with a nested array that is attached to a flexible/unpredictable parent node I have no idea how to write a query. Also, maybe this is not the best way how I structured the data. So in case you have a better idea, I'd be happy to learn!
Cheers!
P.S.: The insertMany statement to above JSON data:
db.boardGames.insertMany([
{
"_id" : 1,
"Game1" : {
"players" : {
"Anne" : [97, 165, 101, 67],
"Pete" : [86, 115, 134, 149],
"Scott" : [66, 89, 103, 74],
"Jane" : [113, 144, 125, 99],
"Katie" : [127, 108, 98, 151]
}
},
"Game2" : {
"players" : {
"Margot" : [1, 0, 0, 0],
"Pete" : [0, 0, 1, 1],
"Michael" : [0, 0, 0, 0],
"Jane" : [0, 0, 1, 0]
}
},
"Game3" : {
"players" : {
"Chris" : [6, 2, 4, 0, 5, 7],
"Pete" : [4, 5, 2, 5, 3, 1, 4],
"Julia" : [3, 7, 4, 0],
"Tom" : [3, 2, 4, 8, 2, 6, 7]
}
}
}]);
the schema you have is not ideal. if it was something like this: https://mongoplayground.net/p/o8m205t9UKG then you can query like the following:
find winner of each game:
db.collection.aggregate(
[
{
$set: {
Players: {
$map: {
input: "$Players",
as: "x",
in: {
Name: "$$x.Name",
TotalScore: { $sum: "$$x.Scores" }
}
}
}
}
},
{
$unwind: "$Players"
},
{
$sort: { "Players.TotalScore": -1 }
},
{
$group: {
_id: "$Name",
Winner: { $first: "$Players.Name" }
}
}
])
find top 3 ranking players across all games:
db.collection.aggregate(
[
{
$set: {
Players: {
$map: {
input: "$Players",
as: "x",
in: {
Name: "$$x.Name",
TotalScore: { $sum: "$$x.Scores" }
}
}
}
}
},
{
$unwind: "$Players"
},
{
$group: {
_id: "$Players.Name",
TotalScore: { $sum: "$Players.TotalScore" }
}
},
{
$sort: { TotalScore: -1 }
},
{
$limit: 3
},
{
$group: {
_id: null,
TopRanks: { $push: "$_id" }
}
},
{
$project: {
_id: 0,
TopRanks: 1
}
}
])
find the winner of each round across all games
db.collection.aggregate(
[
{
$set: {
"Players": {
$map: {
input: "$Players",
as: "p",
in: {
Scores: {
$map: {
input: "$$p.Scores",
as: "s",
in: {
Player: "$$p.Name",
Round: { $add: [{ $indexOfArray: ["$$p.Scores", "$$s"] }, 1] },
Score: "$$s"
}
}
}
}
}
}
}
},
{
$unwind: "$Players"
},
{
$unwind: "$Players.Scores"
},
{
$replaceRoot: { newRoot: "$Players.Scores" }
},
{
$sort: {
Round: 1,
Score: -1
}
},
{
$group: {
_id: "$Round",
Winner: { $first: "$Player" }
}
},
{
$project: {
_id: 0,
Round: "$_id",
Winner: 1
}
},
{
$sort: {
Round: 1
}
}
])
I'm just planning to switch from Orders collection structure to Items structure with a query.
Of course I'm able to to make it by iterating and with a few lines coding, but I'm sure there is an easier way to achieve it with a mongo query.
Each document in Orders collection can contain a User and many itemIds.
From this collection I would like to get an Items collection output with a query. What sort of aggregation/projection I need to use to get UserIds linked to each item ?
Source, Orders collection
{
"UserId" : "Acme",
"ItemIds" : [
1,
2,
3
]
},.....
Destination, Items Collection
{
"ItemId" : 1,
"UserIds" : [
1,
3
]
},....
P.S. this is not a DB design question and the number of items in Orders and itemIds are finite. I made up them to explain the problem.
You could use an aggregation pipeline with 3 stages:
$unwind itemIDs
$group by itemID, $push UserId into an array
$out to new collection
Similar to Joe's method, I used this to 'invert' a parent-child relationship:
the Orders data looks like:
[
{ userId: 1, itemIds: [5, 6, 7]},
{ userId: 2, itemIds: [7, 8, 9]},
{ userId: 1, itemIds: [4, 5, 6]},
{ userId: 3, itemIds: [5, 6, 7]}
]
Mongo Query:
db.getCollection('Orders').aggregate([
{$unwind: '$itemIds'},
{$project: {userId: 1, itemIds: 1}},
{$sort: {itemIds: 1, userId: 1}},
{$group: { _id: "$itemIds", userIds: { $push : "$userId"} }},
{$sort: { _id: 1 }}
])
$sort is optional, of course
the result should look like:
[
{ itemId: 4: userIds: [1] },
{ itemId: 5: userIds: [1, 1, 3] },
{ itemId: 6: userIds: [1, 1, 3] },
{ itemId: 7: userIds: [1, 2, 3] },
{ itemId: 8: userIds: [2] },
{ itemId: 9: userIds: [2] },
]
If you want unique userIds
{$group: { _id: "$itemIds", userIds: { $addToSet : "$userId"} }},
I need to convert a data like this:
{peopleList: [{id:1, name: 'joe'}, {id: 2, name: 'john'}], page: 1, rowPerPage: 8}
to this model:
{entities: {'0': {id: 0, name: 'joe'}, '1': {id: 1, name: 'john'}, page: 1, rowPerPage: 8}, result: [0, 1]}
but when I add this schema:
const people = new schema.Entity('peopleList');
const normalizedData = normalize(_data, { peopleList: [people] });
I get this output:
{
"entities": {
"peopleList": {
"1": {
"id": 1,
"name": "joe"
},
"2": {
"id": 2,
"name": "john"
}
}
},
"result": {
"peopleList": [
1,
2
],
"page": 1,
"rowPerPage": 8
}
}
I don't know exactly how to make a proper schema that create result filed as my desire. maybe the correct way is to have it in result and this output is correct. any idea?