MongoDB: Cannot increment with non-numeric argument - mongodb

I have a collection of User documents, containing several fields and an array of Bets. I am trying to set up an update that I will run on a schedule. In the User documents, the Balance field needs to be incremented by (Bets[index].multiplier * Bets[index].amount), and the Bets[index] needs to be marked as paid out and whether the bet was successful.
An example of a User document I'm trying to update. After running the update, User.bets[1].isPaidOut should be true, User.bets[1].didWin should be true, and User.balance should be incremented by 89 (that is, because ceiling(1.27 * 70) = 89).
{
"_id": {
"$oid": "63c9ca0217b00eaef7d6237f"
},
"guildId": "1061387401743831121",
"userId": "307884715677974530",
"userName": "Iron Man",
"balance": {
"$numberDouble": "100.0"
},
"lastClaimedAt": {
"$date": {
"$numberLong": "1674168834621"
}
},
"bets": [
{
"sport": "NFL",
"eventWeek": {
"$numberInt": "2"
},
"team": "New York Giants",
"opponentTeam": "Philadelphia Eagles",
"teamId": {
"$numberInt": "19"
},
"opponentTeamId": {
"$numberInt": "21"
},
"eventId": "401438004",
"amount": {
"$numberInt": "20"
},
"multiplier": {
"$numberDouble": "3.85"
},
"isPaidOut": true,
"didWin": false
},
{
"sport": "NFL",
"eventWeek": {
"$numberInt": "2"
},
"team": "Philadelphia Eagles",
"opponentTeam": "New York Giants",
"teamId": {
"$numberInt": "21"
},
"opponentTeamId": {
"$numberInt": "19"
},
"eventId": "401438004",
"amount": {
"$numberInt": "70"
},
"multiplier": {
"$numberDouble": "1.27"
},
"isPaidOut": false
},
{
"sport": "NFL",
"eventWeek": {
"$numberInt": "2"
},
"team": "San Francisco 49ers",
"opponentTeam": "Dallas Cowboys",
"teamId": {
"$numberInt": "25"
},
"opponentTeamId": {
"$numberInt": "6"
},
"eventId": "401438006",
"amount": {
"$numberInt": "200"
},
"multiplier": {
"$numberDouble": "1.49"
},
"isPaidOut": false
}
],
"createdAt": {
"$date": {
"$numberLong": "1674168834633"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1674338378566"
}
},
"__v": {
"$numberInt": "3"
}
}
This is what I have for my Update. When this is run, I receive this error: uncaught promise rejection: write exception: write errors: [Cannot increment with non-numeric argument: {balance: { $ceil: { $mul: [ "bets.$.amount", "bets.$.multiplier" ] } }}]. I thought this could have been because of mismatched types, but all documents have the same as the one above.
const winnerId = 21;
const eventId = "401438004";
usersCollection.updateMany(
{
bets: {
"$elemMatch": {
eventId: eventId,
isPaidOut: false,
teamId: winnerId
}
}
},
{
$set: { "bets.$.isPaidOut" : true, "bets.$.didWin": true },
$inc: {
balance: {$ceil: {$mul: {"bets.$.amount": "bets.$.multiplier"} }}
}
}
);

$ceil is an aggregation operator, not an update operator, so you can only use it with an update if you use the update with aggregation pipeline

You will have to use pipelined update. But then, you won't be able to use $ field names. Here's a solution using $map and $reduce.
db.collection.updateMany(
bets: {
"$elemMatch": {
eventId: "401438004",isPaidOut: false,teamId: 21
}
},
[
{
"$set": {
"balance": { //set the updated balance
$reduce: {
input: "$bets", //iterate through the bets array
initialValue: "$balance", //start with the existing balance
in: {
$add: [
"$$value",
{
$cond: [ //check the condition
{$and: [
{$eq: ["$$this.eventId","401438004"]},
{$eq: ["$$this.teamId",21]},
{$eq: ["$$this.isPaidOut",false]}
]},
{$ceil: {$multiply: ["$$this.amount","$$this.multiplier"]}}, //if true, perform the arithmetic operation andd add it to existing $$value
0 //if false, add 0
]
}
]
}
}
},
"bets": { //set the updated bets array
$map: { //iterate through the bets array
input: "$bets",
in: {
$cond: [ //check the condition
{$and: [
{$eq: ["$$this.eventId","401438004"]},
{$eq: ["$$this.teamId",21]},
{$eq: ["$$this.isPaidOut",false]}
]},
{
"$mergeObjects": [ //if true, merge the array element with updated fields
"$$this",
{"didWin": true,"isPaidOut": true}
]
},
{
"$mergeObjects": [ //if false, keep the array element as it is
"$$this"
]
}
]
}
}
}
}
}
])
Demo

Related

Need help filtering documents with criteria inside array - mongoDB

Need help with query to filter the records in mongoDB. I am using compass to run the que
We have thousands of records/documents where each record/document contains the following array. For few documents, the events in lifeCycleinfo are out of order i.e. payment.completed event comes before 1payment.completed1 event.
I need to filter those records where completed event comes before created event
{
"lifeCycleInfo": [
{
"eventId": "9b8b6adfae",
"eventSubType": "SendTransfer_Receipt",
"eventType": "SendTransfer",
"odsTimestamp": {
"$date": "2023-02-06T14:33:42.308Z"
},
"payload": "{}",
"timestamp": {
"$date": "2023-02-06T14:33:42.271Z"
}
},
{
"eventId": "06e8d144-531b02",
"eventSubType": "payment.created",
"eventType": "Notification",
"odsTimestamp": {
"$date": "2023-02-06T14:33:45.488Z"
},
"payload": "{}",
"timestamp": {
"$date": "2023-02-06T14:33:45.479Z"
}
},
{
"eventId": "9da54454d6",
"eventSubType": "payment.completed",
"eventType": "Notification",
"odsTimestamp": {
"$date": "2023-02-06T14:33:46.698Z"
},
"payload": "{}",
"timestamp": {
"$date": "2023-02-06T14:33:46.689Z"
}
}
]
}
I tried to find it based on array index but not working.
{"lifeCycleInfo[1].eventtype":"payment.completed"}
You can use $reduce with $switch to iterate through the lifeCycleInfo array and keep track of the status with an object {inOrder: <bool>, seenCreated: <bool>}. We can conditionally set the 2 bools and use the final result to get the matched documents.
db.collection.aggregate([
{
$set: {
inOrderResult: {
"$reduce": {
"input": "$lifeCycleInfo",
"initialValue": {
inOrder: false,
seenCreated: false
},
"in": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$$this.eventSubType",
"payment.created"
]
},
"then": {
inOrder: "$$value.inOrder",
seenCreated: true
}
},
{
"case": {
$and: [
{
$eq: [
"$$this.eventSubType",
"payment.completed"
]
},
{
$eq: [
"$$value.seenCreated",
true
]
}
]
},
"then": {
inOrder: true,
seenCreated: true
}
}
],
default: "$$value"
}
}
}
}
}
},
{
$match: {
"inOrderResult.inOrder": true
}
}
])
Mongo Playground

Nested arrays $unwind and $group back together in mongoDB

We have three nested arrays:
principalCredits with 2 objects
credits with 2 objects each
awardNominations.edges with variable totals from 0 to 3
The task is to add a field to the third array of objects awardNominations.edges based on a lookup from eventsCollection.
Here's the data I have (simplified, can copy and paste into MongoDB Compass):
[{
"principalCredits": [
{
"category": {
"id": "director",
"text": "Directors"
},
"totalCredits": 2,
"credits": [
{
"name": {
"id": "nm11813828",
"nameText": {
"text": "Pippa Ehrlich"
},
"awardNominations": {
"total": 2,
"edges": [
{
"node": {
"id": "an1393007",
"isWinner": true,
"award": {
"id": "an1393007",
"year": 2020,
"text": "Green Warsaw Award",
"event": {
"id": "ev0003786",
"text": "Millennium Docs Against Gravity"
},
"category": {
"text": null
}
}
}
},
{
"node": {
"id": "an1428940",
"isWinner": false,
"award": {
"id": "an1428940",
"year": 2021,
"text": "IDA Award",
"event": {
"id": "ev0000351",
"text": "International Documentary Association"
},
"category": {
"text": "Best Writing"
}
}
}
},
]
}
},
"category": {
"id": "director",
"text": "Director"
}
},
{
"name": {
"id": "nm1624755",
"nameText": {
"text": "James Reed"
},
"awardNominations": {
"total": 3,
"edges": [
{
"node": {
"id": "an0694012",
"isWinner": true,
"award": {
"id": "an0694012",
"year": 2015,
"text": "Best of Festival",
"event": {
"id": "ev0001486",
"text": "Jackson Wild Media Awards"
},
"category": {
"text": "Best of Festival"
}
}
}
},
{
"node": {
"id": "an0975779",
"isWinner": true,
"award": {
"id": "an0975779",
"year": 2017,
"text": "RTS West Television Award",
"event": {
"id": "ev0000571",
"text": "Royal Television Society, UK"
},
"category": {
"text": "Documentary"
}
}
}
},
{
"node": {
"id": "an0975781",
"isWinner": true,
"award": {
"id": "an0975781",
"year": 2015,
"text": "Grand Teton Prize",
"event": {
"id": "ev0001356",
"text": "Jackson Hole Film Festival"
},
"category": {
"text": "Best in Festival"
}
}
}
}
]
}
},
"category": {
"id": "director",
"text": "Director"
}
}
]
},
{
"category": {
"id": "writer",
"text": "Writers"
},
"totalCredits": 2,
"credits": [
{
"name": {
"id": "nm11813828",
"nameText": {
"text": "Pippa Ehrlich"
},
"awardNominations": {
"total": 2,
"edges": [
{
"node": {
"id": "an1393007",
"isWinner": true,
"award": {
"id": "an1393007",
"year": 2020,
"text": "Green Warsaw Award",
"event": {
"id": "ev0003786",
"text": "Millennium Docs Against Gravity"
},
"category": {
"text": null
}
}
}
},
{
"node": {
"id": "an1428940",
"isWinner": false,
"award": {
"id": "an1428940",
"year": 2021,
"text": "IDA Award",
"event": {
"id": "ev0000351",
"text": "International Documentary Association"
},
"category": {
"text": "Best Writing"
}
}
}
}
]
}
},
"category": {
"id": "writer",
"text": "Writer"
},
},
{
"name": {
"id": "nm1624755",
"nameText": {
"text": "James Reed"
},
"awardNominations": {
"total": 0,
"edges": []
}
},
"category": {
"id": "writer",
"text": "Writer"
},
}
]
}
]
}]
An example scored award should look like this:
{
"id": "an0975781",
"isWinner": true,
"award": { ... },
"score": 1.5
}
Once all the manipulation is done, the data needs to be in exactly the same shape as it was initially and with no null values. So in the case of the last array awardsNominations.edges it should be [] as it was, and not { node: { score: null }} or anything else.
To achieve this I have created an aggregation pipeline:
[
{
'$unwind': {
'path': '$principalCredits',
'preserveNullAndEmptyArrays': true
}
}, {
'$unwind': {
'path': '$principalCredits.credits',
'preserveNullAndEmptyArrays': true
}
}, {
'$unwind': {
'path': '$principalCredits.credits.name.awardNominations.edges',
'preserveNullAndEmptyArrays': true
}
}, {
'$lookup': {
'from': 'eventsCollection',
'localField': 'principalCredits.credits.name.awardNominations.edges.node.award.event.id',
'foreignField': 'id',
'as': 'matchingEvent'
}
}, {
'$unwind': {
'path': '$matchingEvent',
'preserveNullAndEmptyArrays': true
}
}, {
'$addFields': {
'principalCredits.credits.name.awardNominations.edges.node.score': {
'$multiply': [
'$matchingEvent.importance', {
'$cond': {
'if': '$principalCredits.credits.name.awardNominations.edges.node.isWinner',
'then': 1.5,
'else': 1.2
}
}
]
}
}
}
]
The above pipeline assigns the score to each award. However, the null values are still there and I have absolutely no idea how to group it back together. I have tried to group with:
{
'$group': {
'_id': '$id',
'titleDoc': {
'$first': '$$ROOT'
},
'allPrincipalCredits': {
'$push': '$principalCredits'
}
}
}
To keep the root and then somehow sort all the records back into shape but could not get back to the orginal object structure.
Any help in putting it all together will be much appriciated!
I'm fairly good with simple aggregations, but this seems to be too much for me currently and would love to learn how to $group things back properly.
I've tried and put together all the knowledge I have so far from different sources and similar answers but can't seem to get it to work.
Lookup collection eventsCollection contains objects like this:
{
"_id": { "$oid": "62c57125d6943d92f83f6fff" },
"id": "ev0030197",
"text": "#AmLatino Film Festival",
"importance": 1
}
So the "rule" in restoring to original structure is that for each $unwind you did to "deconstruct" the document you now have to do a $group to restore it.
As you can imagine in such a pipeline this could be VERY cumbersome. but definitely doable.
However let me propose a different approach that is still very messy but much easier compared to the alternative, additionally it is more efficient from a performance perspective.
(just minor sidenot the reason your score is still null is because you have a syntax error in your $multiply function)
Anyways, The idea is to first gather all the unique event ids that exist in the in nested documents.
Then execute one lookup to fetch all the relevant events.
And finally adding the score field using $map and $mergeDocuments instead of $unwinding and $grouping, like so:
Mongo Playground
db.collection.aggregate([
{
$addFields: {
allEvents: {
$reduce: {
input: {
$map: {
input: "$principalCredits",
in: {
$map: {
input: "$$this.credits",
as: "credit",
in: {
$map: {
input: "$$credit.name.awardNominations.edges",
as: "edge",
in: "$$edge.node.award.event.id"
}
}
}
}
}
},
initialValue: [],
in: {
"$concatArrays": [
{
"$reduce": {
input: "$$this",
initialValue: [],
in: {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
},
"$$value"
]
}
}
}
}
},
{
"$lookup": {
"from": "eventsCollection",
"localField": "allEvents",
"foreignField": "id",
"as": "matchingEvents"
}
},
{
$addFields: {
principalCredits: {
$map: {
input: "$principalCredits",
in: {
$mergeObjects: [
"$$this",
{
credits: {
$map: {
input: "$$this.credits",
as: "credit",
in: {
$mergeObjects: [
"$$credit",
{
name: {
"$mergeObjects": [
"$$credit.name",
{
"awardNominations": {
"$mergeObjects": [
"$$credit.name.awardNominations",
{
edges: {
$map: {
input: "$$credit.name.awardNominations.edges",
as: "edge",
in: {
node: {
$mergeObjects: [
"$$edge.node",
{
score: {
"$multiply": [
{
$cond: [
"$$edge.node.isWinner",
1.5,
1.2
]
},
{
$first: {
$map: {
input: {
$filter: {
input: "$matchingEvents",
as: "matchedEvent",
cond: {
$eq: [
"$$matchedEvent.id",
"$$edge.node.award.event.id"
]
}
}
},
as: "matched",
in: "$$matched.importance"
}
}
}
]
}
}
]
}
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
]
}
}
}
}
},
{
$unset: [
"allEvents",
"matchingEvents"
]
}
])
Mongo Playground
I will just mention that you can make this much much much cleaner by involving some code while keeping the same approach suggested. first getting unique eventid with distinct. then fetching the matching importance for each event. Finally execute a single query using arrayFilters you can construct with this information.
Final side not is that the provided pipeline did not deal with null or missing values. So if an array is missing an error will be thrown as $map expects input to be a valid array.
This can easily be solved by just wrapping each of these expressions with $ifNull, like so:
{
$map: {
input: {$ifNull: ["$$this.credits",[]]}
}
}
This will also replace null values with an empty []
The deep buried keys (...award.event.id) in arrays confounds an easy approach without 1) messing up the structure as the OP has noted 2) incurring potentially very expensive multiple $unwind calls.
Recommendation: Two pass approach. Get the necessary importance values for the principalCredits objects in question, then go back and manually iterate over the collection, diving into the structure and applying the logic score = importance * isWinner? 1.2 : 1.5
PASS 1: Get the ev data
c=db.foo.aggregate([
{$project: {
XX: {$reduce: {
// Rapidly get to things we need to lookup:
input: '$principalCredits.credits.name.awardNominations.edges.node.award.event.id',
// We end up with a mess incl. empty arrays...
// [ [[ev1,ev2], [ev3,ev4]], [], [[ev1,...], [] ... ] ]
// Need to collapse all those arrays of arrays of arrays into
// a single list of ev values, hence a reduce within a reduce:
initialValue: [],
in: {$concatArrays: [
'$$value',
{$reduce: {
input: '$$this',
initialValue: [],
in: {$concatArrays: [ '$$value', '$$this' ] }
}} ]}
}}
}}
// XX is now [ ev1,ev2,ev3,ev4,ev1 ... ]
// The empty arrays are ignored. Don't worry about dupes.
,{$lookup: {
from: "Xev",
let: { evids: "$XX" },
pipeline: [
{$match: {$expr: {$in: ["$id","$$evids"]} } }
],
as: 'XX' // overwrite XX...
}}
]);
evdict = {}
c.forEach(function(d) {
d['XX'].forEach(function(ww) {
evdict[ww['id']] = ww;
});
});
{
"ev0003786" : {
"_id" : ObjectId("62cd7f8138d0fbc0eacfb17f"),
"id" : "ev0003786",
"text" : "Millennium Docs Against Gravity",
"importance" : 1
},
"ev0000351" : {
"_id" : ObjectId("62cd7f8138d0fbc0eacfb180"),
"id" : "ev0000351",
"text" : "International Documentary Association",
"importance" : 2
},
"ev0000571" : {
"_id" : ObjectId("62cd7f8138d0fbc0eacfb181"),
"id" : "ev0000571",
"text" : "Royal Television Society, UK",
"importance" : 3
}
}
PASS 2: Iterate main collection
Left as exercise to reader.
Note that if
The number of events is small.
There is no need or value in performing $match on the initial principalCredits collection (i.e. before the fancy $project/$reduce) to significantly reduce the lookup set into events
then this whole thing is unnecessary. Simply slurp all events into evdict with a quick find and proceed to pass 2.
There is potentially a very cool solution that can do this in one pass
UPDATED
See Tom's answer below.
Note to MongoDB 5.0 users: The new $getField function allows you to pluck out fields by name instead of having to use the standard trick of using dot notation in the $in clause to access the field. This might be clearer to some:
{$getField: {
"field": "importance",
"input": {
$first: {
$filter: {
input: "$matchingEvents",
as: "matchedEvent",
cond: {
$eq: [
"$$matchedEvent.id",
"$$edge.node.award.event.id"
]
}
}
}
}
}
}

Filter documents that have id in another collection in MongoDB with aggregation framework

So I have two collection. collectionA and collectionB
collection A has following documents
db={
"collectiona": [
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
],
"collectionb": [
{
"_id": "6173ddf33ed0wef368a094zq",
"collectionaID": "6173ddf33ed09368a094e68a",
"data": [
{
"userID": "123",
"visibility": false,
"response": false
},
{
"userID": "2345",
"visibility": true,
"response": true
}
]
},
{
"_id": "6173ddf33ed09368awef4e68g",
"collectionaID": "61wefdf33ed09368a094e6dc",
"data": [
{
"userID": "5678",
"visibility": false,
"response": false
},
{
"userID": "674",
"visibility": true,
"response": false
}
]
}
]
}
So What I need is documents from collection A which has response false in collection B
and document should be sorted by first the ones that have visibility false and then the ones that have visibility true
for eg. userID : 123 should get 3 documents
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
whereas userID 2345 should get two
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
User 674 will receive 3 objects from collection A but second would be in the last as it has visibility true for that document
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
MongoDB Playground link : https://mongoplayground.net/p/3rLry0FPlw-
Really appreciate the help. Thanks
You can start from collectionA:
$lookup the collectionB for the record related to the user specified
filter out collectionB documents according to response
assign a helper sortrank field based on the visibility and whether collectionaID is a match
$sort according to sortrank
wrangle back to the raw collection A
db.collectiona.aggregate([
{
"$lookup": {
"from": "collectionb",
let: {
aid: "$_id"
},
"pipeline": [
{
$unwind: "$data"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$data.userID",
"2345"
]
},
{
$eq: [
"$collectionaID",
"$$aid"
]
}
]
}
}
}
],
"as": "collB"
}
},
{
$match: {
"collB.data.response": {
$ne: true
}
}
},
{
"$unwind": {
path: "$collB",
preserveNullAndEmptyArrays: true
}
},
{
"$addFields": {
"sortrank": {
"$cond": {
"if": {
$eq: [
"$collB.data.visibility",
false
]
},
"then": 1,
"else": {
"$cond": {
"if": {
$eq: [
"$collB.collectionaID",
"$_id"
]
},
"then": 3,
"else": 2
}
}
}
}
}
},
{
$sort: {
sortrank: 1
}
},
{
$project: {
collB: false,
sortrank: false
}
}
])
Here is the Mongo playground for your reference.

Change value of a field in embed document with condition in mongo DB

I try to get a data from the database. But I've had a problem in a section where I can't edit the value of the embedded fields. I want to put the boolean value instead of the mobile number. if it has a value, equal to the true and if it does not have a value, it will be false.
I have document like this in my collection:
{
"_id": ObjectId("606d6ea237c2544324925c61"),
"title": "newwww",
"message": [{
"_id": ObjectId("606d6e1037c2544324925c5f"),
"text": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"user_id": null,
"user_full_name": null,
"user_mobile_number": null,
"submit_date": {
"$date": "2021-04-07T08:32:16.093Z"
}
}, {
"_id": ObjectId("606d6edc546feebf508d75f9"),
"text": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"user_id": null,
"user_full_name": null,
"user_mobile_number": "9653256482",
"submit_date": {
"$date": "2021-04-07T08:35:40.881Z"
}
}],
"user_mobile_number": "9652351489",
}
Do query:
db.ticket.aggregate([{"$match": {"_id": ObjectId("606d6ea237c2544324925c61")}}, {
"$project": {"message.is_admin":{
"$let": {
vars: {
mobile_number: "$message.user_mobile_numebr"
},
in: {
"$cond": [{$eq: ['$$mobile_number', null]},false,true ]
}
}
}
}
}])
and result is:
[
{
"_id": ObjectId("606d6ea237c2544324925c61"),
"message": [
{
"is_admin": true
},
{
"is_admin": true
}
]
}
]
but i want result like this:
[
{
"_id": ObjectId("606d6ea237c2544324925c61"),
"message": [
{
"is_admin": false
},
{
"is_admin": true
}
]
}
]
means I want when message.user_mobile_number has value, get true and when value is null get false.
How can I do this?
Demo - https://mongoplayground.net/p/h_zkRfDbZrS
Use $map
db.collection.aggregate([
{
"$project": {
"message": {
"$map": {
input: "$message",
"as": "message",
"in": {
"$cond": [{ "$eq": [ "$$message.user_mobile_number", null ] }, { is_admin: false }, { is_admin: true } ]
}
}
}
}
}
])

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground