Query only for numbers in nested array - arrays

I am trying to get an average number of an key in a nested array inside a document, but not sure how to accomplish this.
Here is how my document looks like:
{
"_id": {
"$oid": "XXXXXXXXXXXXXXXXX"
},
"data": {
"type": "PlayerRoundData",
"playerId": "XXXXXXXXXXXXX",
"groupId": "XXXXXXXXXXXXXX",
"holeScores": [
{
"type": "RoundHoleData",
"points": 2
},
{
"type": "RoundHoleData",
"points": 13
},
{
"type": "RoundHoleData",
"points": 3
},
{
"type": "RoundHoleData",
"points": 1
},
{
"type": "RoundHoleData",
"points": 21
}
]
}
}
Now, the tricky part of this is that I only want the average of points for holeScores[0] of all documents with this playerid and this groupid.
Actually, the best solution would be collecting all documents with playerid and groupid and create a new array with the average of holeScores[0], holeScores[1], holeScores[2]... But if I only can get one array key at the time, that would be OK to :-)
Here is what I am thinking but not quit sure how to put it together:
var allScores = dbCollection('scores').aggregate(
{$match: {"data.groupId": groupId, "playerId": playerId}},
{$group: {
_id: playerId,
rounds: { $sum: 1 }
result: { $sum: "$data.scoreTotals.points" }
}}
);
Really hoping for help with this issue and thanks in advance :-)

You can use $unwind with includeArrayIndex to get index and then use $group to group by that index
dbCollection('scores').aggregate(
{
$match: { "data.playerId": "XXXXXXXXXXXXX", "data.groupId": "XXXXXXXXXXXXXX" }
},
{
$unwind: {
path: "$data.holeScores",
includeArrayIndex: "index"
}
},
{
$group: {
_id: "$index",
playerId: { $first: "data.playerId" },
avg: { $avg: "$data.holeScores.points" }
}
}
)

You can try below aggregation
db.collection.aggregate(
{ "$match": { "data.groupId": groupId, "data.playerId": playerId }},
{ "$group": {
"_id": null,
"result": {
"$sum": {
"$arrayElemAt": [
"$data.holeScores.points",
0
]
}
}
}}
)

Related

Parsing a difficult MongoDB field (with multi-level array)

Hello Experts,
I am trying to parse a MongoDB collection row, and after using $unwind,
one of the remaining fields looks like that:
[
{
"account_id": "1234",
"cities": {
"cityname1": {
"param1": 1,
"param2": 2
}
}
},
{
"account_id": "2345",
"cities": {
"cityname2": {
"param1": 3,
"param2": 3
}
}
},
{
"account_id": "3456",
"cities": {
"cityname3": {
"param1": 8,
"param2": 6
}
}
}
]
Now, I would like to continue parsing this field, so I can extract the fieldname/value for account_id, for param1 and for param2, hoping then to sum up the param1 and param2 values.
However, when I try to use a second $unwind, I receive those fields with "null" value.
How should I parse this field correctly?
$set change cities into array by using $objectToArray
$unwind unwind cities array
$group group by account_id and cityname then sum up param1 and param2
(you can only group by account_id or cityname, just remove one of them)
aggregate
db.collection.aggregate([
{
"$set": {
cities: {
"$objectToArray": "$cities"
}
}
},
{
"$unwind": "$cities"
},
{
"$group": {
"_id": {
account_id: "$account_id",
"cityname": "$cities.k"
},
"sumOfParam1": {
"$sum": "$cities.v.param1"
},
"sumOfParam2": {
"$sum": "$cities.v.param2"
}
}
}
])
mongoplayground

Aggregating a total from subdocuments in MongoDB

I have a document like the one below, I'd essentially like to produce an aggregate for the items in a sub document.
Essentially each document is a sales record, which has details of the sales and a sub document / array with the qtys of each item sold.
I'd like to produce a summary of all the items sold.
So an example collection is:
{
non_relevant_1: "ABC",
non_relevant_2: "DEF",
items_array: {
"item_1": 1,
"item_2": 2,
"item_3": 1,
"item_4": 1
}
},
{
non_relevant_1: "HIJ",
non_relevant_2: "KLM",
items_array: {
"item_1": 3,
"item_2": 2,
"item_3": 4
}
}
I'd then like to be able to produce something like:
{
items_array: {
"item_1": 4,
"item_2": 4,
"item_3": 5,
"item_4": 1
}
}
Many thanks in advance.
I think you need to change your schema, you are saving data in keys.
MongoDB operators are not made to have unknown keys, for example we can't group by an unknown key.To do those we do complicated and slow things like $objectToArray.
Also the data that you want as results have the same problem.
If you look at the query only the middle $unwind and $group would be needed it, with a changed schema, and asking for data without data in keys.
I mean instead of
items_array: {
"item_1": 1,
"item_2": 2,
"item_3": 1,
"item_4": 1
}
Your collection should have being like(first part of the query does that changing your schema)
items_array: [
{"name" "item_1",
"qty" : 1},
{"name" "item_2",
"qty" : 2},
{"name" "item_3",
"qty" : 1},
{"name" "item_4",
"qty" : 1}
]
Also the results should have known keys only.
Maybe the reason you were stuck is that.You will make things much easier for you.
Test code here
Query (query works, for your schema but i told you what i think)
db.collection.aggregate([
{
"$addFields": {
"items_array": {
"$map": {
"input": {
"$map": {
"input": {
"$objectToArray": "$items_array"
},
"as": "m",
"in": [
"$$m.k",
"$$m.v"
]
}
},
"as": "item",
"in": {
"name": {
"$arrayElemAt": [
"$$item",
0
]
},
"qty": {
"$arrayElemAt": [
"$$item",
1
]
}
}
}
}
}
},
{
"$unwind": {
"path": "$items_array"
}
},
{
"$group": {
"_id": "$items_array.name",
"total-qty": {
"$sum": "$items_array.qty"
}
}
},
{
"$group": {
"_id": null,
"items_array": {
"$push": {
"$map": {
"input": {
"$map": {
"input": {
"$objectToArray": "$$ROOT"
},
"as": "m",
"in": [
"$$m.k",
"$$m.v"
]
}
},
"as": "i",
"in": {
"$arrayElemAt": [
"$$i",
1
]
}
}
}
}
}
},
{
"$project": {
"_id": 0
}
},
{
"$addFields": {
"items_array": {
"$arrayToObject": "$items_array"
}
}
}
])

Sum Quantity in Mongo Subdocument Based on Filter

I have a "shipment" document in MongoDB that has the following basic structure:
shipment {
"id": "asdfasdfasdf",
"shipDate": "2021-04-02",
"packages": [
{
"id": "adfasdfasdfasdf",
"contents": [
{
"product": {
"id": "asdfasdfasdfasd"
},
"quantity": 10
}
]
}
]
}
Please note that "product" is stored as a DBRef.
I want to find the total quantity of a specific product (based on the product ID) that has been shipped since a given date. I believe this is the appropriate logic that should be followed:
Match shipments with "shipDate" greater than the given date.
Find entries where "contents" contains a product with an "id" matching the given product ID
Sum the "quantity" value for each matching entry
Return the sum
So far, this is what I've come up with for the Mongo query so far:
db.shipment.aggregate([
{$match: {"shipDate": {$gt: ISODate("2019-01-01")}}},
{$unwind: "$packages"},
{$unwind: "$packages.contents"},
{$unwind: "$packages.contents.product"},
{
$project: {
matchedProduct: {
$filter: {
input: "$packages.contents.products",
as: "products",
cond: {
"$eq": ["$products.id", ObjectId("5fb55eae3fb1bf783a4fa97f")]
}
}
}
}
}
])
The query works, but appears to just return all entries that meet the $match criteria with a "products" value of null.
I'm pretty new with Mongo queries, so it may be a simple solution. However, I've been unable to figure out just how to return the $sum of the "contents" quantity fields for a matching product ID.
Any help would be much appreciated, thank you.
Query Which Solved The Problem
db.shipment.aggregate([
{
$match: {
"shipDate": {$gte: ISODate("2019-01-01")},
"packages.contents.product.$id": ObjectId("5fb55eae3fb1bf783a4fa98e")
}
},
{ $unwind: "$packages" },
{ $unwind: "$packages.contents" },
{ $unwind: "$packages.contents.product" },
{
$match: {
"packages.contents.product.$id": ObjectId("5fb55eae3fb1bf783a4fa98e")
}
},
{
$group: {
"_id": null,
"total": {
"$sum": "$packages.contents.quantity"
}
}
}
])
Demo - https://mongoplayground.net/p/c3Ia9L47cJS
Use { $match: {"packages.contents.product.id": 1 } }, to filter records by product id.
After that group them back and find the total { $group: {"_id": null,"total": { "$sum": "$packages.contents.quantity" } } }
db.collection.aggregate([
{ $match: {"shipDate": "2021-04-02","packages.contents.product.id": 1 } },
{ $unwind: "$packages" },
{ $unwind: "$packages.contents" },
{ $match: { "packages.contents.product.id": 1 } },
{ $group: { "_id": null,"total": { "$sum": "$packages.contents.quantity" } } }
])
Adding extra check at top { $match: {"shipDate": "2021-04-02","packages.contents.product.id": 1 } } for product id will filter only documents with produce id we need so query will be faster.
Option-2
Demo - https://mongoplayground.net/p/eo521luylsG
db.collection.aggregate([
{ $match: { "shipDate": "2021-04-02", "packages.contents.product.id": 1 }},
{ $unwind: "$packages" },
{ $project: { contents: { $filter: { input: "$packages.contents", as: "contents", cond: {"$eq": [ "$$contents.product.id", 1] }}}}},
{ $unwind: "$contents" },
{ $group: { "_id": null, "total": { "$sum": "$contents.quantity" }}}
])

Get frequency for multiple elements in all documents inside a collection mongodb

So heres my problem.
I am new to mongodb and have a collection which documents are saved like this:
{
"_id": {
"$oid": "60626db173b4ca321c02ee3e"
},
"year": "2021",
"name": "Book 1",
"authors": ["Joe, B", "Jessica, K"],
"createdAt": {
"$date": "2021-03-30T00:15:45.859Z"
}
},
{
"_id": {
"$oid": "60626db173b4ca321c02ee4e"
},
"year": "2021",
"authors": ["Carl, B", "Jessica, K"],
"name": "Book 2"
"createdAt": {
"$date": "2021-03-30T00:15:45.859Z"
}
},
I need to get both the frequency of all authors and the years of the books.
The expected result would be something like this (as long as i can get each element frequency it doesn't really matter how the results are returned):
{
"authors": {
"Joe, B": 1,
"Carl, B": 1,
"Jessica, K": 2
},
"year": {
"2021": 2
}
}
I've seen this thread How to count occurence of each value in array? which does the job in one array but i have no idea if its possible to adapt to get the frequency of multiple elements (year, authors) at the same time or how to do it.
I appreciate any help. Thank you.
Demo - https://mongoplayground.net/p/95JtQEThxvV
$group by year $push authors into the array get $sum count of the year occurrence, $unwind into individuals documents.
$group by authors and get $sum count of the author occurrence
$group by null to combine all documents, use $addToSet to push unique values and convert $arrayToObject to get final output in $project
$first
db.collection.aggregate([
{
$group: {
_id: { year: "$year" },
authors: { $push: "$authors" },
yearCount: { $sum: 1 }
}
},
{ $unwind: "$authors" },
{ $unwind: "$authors"},
{
$group: {
_id: { author: "$authors" },
year: { $first: "$_id.year" },
yearCount: { $first: "$yearCount" },
authors: { $push: "$authors" },
authorCount: { $sum: 1 }
}
},
{
"$group": {
_id: null,
years: {
$addToSet: { k: "$year", v: "$yearCount" }
},
authors: {
$addToSet: { k: "$_id.author", v: "$authorCount" }
}
}
},
{
$project: {
_id: 0,
years: { $arrayToObject: "$years" },
authors: { $arrayToObject: "$authors" }
}
}
])
Demo 2 - For author count grouped by year- https://mongoplayground.net/p/_elnjmknroF

Mongoose - How to retrieve an array of objects from each document in collection?

Each document in my collection looks something like this (JSON):
{
name: "Roger",
matches: [
{
opponent: "Rafael Nadal",
match_date: 1494536400000
},
{
opponent: "Nick Kyrgyos",
match_date: 1494557400000
}
]
}
I want to extract all the matches each player had and also sort them by match_date using Mongoose, but I don't know how.
Could you please help me with this?
You can try below aggregation
db.collection.aggregate([
{ "$unwind": "$matches" },
{ "$sort": { "matches.match_date": 1 }},
{ "$group": {
"_id": "$_id",
"matches": { "$push": "$matches" }
}},
{ "$limit": 20 },
{ "$project": { "matches": 1, "numberOfMatches": { "$size": "$matches" } }}
])

Resources