Lets say I have the following document in my collection Classes Collecton
{
"_id": ObjectId("5df58d45244a850d54b922c8"),
"mentors" : {
"numOfMentors" : NumberInt(1),
"mentorList" : [
ObjectId("5c9ba636347bb645e0865283")
]
},
"lessons": [
{
"_id": ObjectId("5db221be211d7b68ac8be618"),
"mentorData": {
"objective": "Ensuring students that making mistakes is normal and that it is a part of life",
"task": "Post a video explaining obstacles that you had to overcome as a programmer",
"dueDate": "2019-12-14T15:26:10.000+0000"
},
"studentData": {
"objective": "Learning that failures help you grow",
"task": "Post a video explaining obstacles that you have overcame",
"dueDate": "2020-01-14T22:26:10.000+0000" <---- CHECKING THIS DATE
},
"title": "How to overcome obstacles",
"comments": []
}
]
}
And I am aggregating as follows:
Class.aggregate([
{ $match: { "students.studentList": req.user._id } },
{ $unwind: "$lessons" },
{
$addFields: {
date: {
$dateToString: {
format: "%Y-%m-%d",
date: "$lessons.studentData.dueDate"
}
}
}
},
{
$match: {
$and: [
{ date: { $gte: startDate } },
{ date: { $lte: endDate } }
]
}
},
{ $group: { _id: "$_id", lessons: { $push: "$lessons" } } }
])
Which returns this...
{
"lessonImage": {
"url": "https://stemsandbox.blob.core.windows.net/stemuli/lesson-picture-a406bd19-0677-4b60-909e-7de2ca3c6f93.jpg",
"originalname": "nintendo-switch-console.jpg",
"mimetype": "image/jpeg",
"blobName": "lesson-picture-a406bd19-0677-4b60-909e-7de2ca3c6f93.jpg",
"container": "stemuli",
"blob": "lesson-picture-a406bd19-0677-4b60-909e-7de2ca3c6f93.jpg",
"size": "147469",
"etag": "\"0x8D797B8F1EC9C39\"",
"createdOn": "2020-01-12T23:41:28.588Z"
},
"_id": "5db221be211d7b68ac8be619",
"mentorData": {
"objective": "Learn to make a single web page web app",
"task": "Create a short video instructing how to setup environment",
"dueDate": "2019-02-02T22:26:10.000Z"
},
"studentData": {
"objective": "Program a single page web app in React",
"task": "Program a single page web app in React and submit by october 30th",
"dueDate": "2020-01-22T22:26:10.000Z"
},
"title": "Learning to program in React",
}
I want it to be like this
Note mentors field
{
"lessonImage": {
"url": "https://stemsandbox.blob.core.windows.net/stemuli/lesson-picture-a406bd19-0677-4b60-909e-7de2ca3c6f93.jpg",
"originalname": "nintendo-switch-console.jpg",
"mimetype": "image/jpeg",
"blobName": "lesson-picture-a406bd19-0677-4b60-909e-7de2ca3c6f93.jpg",
"container": "stemuli",
"blob": "lesson-picture-a406bd19-0677-4b60-909e-7de2ca3c6f93.jpg",
"size": "147469",
"etag": "\"0x8D797B8F1EC9C39\"",
"createdOn": "2020-01-12T23:41:28.588Z"
},
"_id": "5db221be211d7b68ac8be619",
"mentorData": {
"objective": "Learn to make a single web page web app",
"task": "Create a short video instructing how to setup environment",
"dueDate": "2019-02-02T22:26:10.000Z"
},
"studentData": {
"objective": "Program a single page web app in React",
"task": "Program a single page web app in React and submit by october 30th",
"dueDate": "2020-01-22T22:26:10.000Z"
},
"title": "Learning to program in React",
"classId": "5e1baea87fcee8639cbce29d",\
//FIELD BELOW ADDED
"mentors": [
ObjectId("5c9ba636347bb645e0865283")
]
}
You can add $push to your $group and then run $reduce as a next stage to flatten an array of arrays:
{ $group: { _id: "$_id", lessons: { $push: "$lessons" }, mentors: { $push: "$mentors.mentorList" } } },
{ $addFields: { mentors: { $reduce: { input: "$mentors", initialValue: [], in: { $setUnion: [ "$$value", "$$this" ] } } } } }
Mongo Playground
EDIT: $setUnion will remove any potential duplicates
Related
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"
]
}
}
}
}
}
}
I have a DB in which each document has an array of many different objects, of which I'm interested in working with only 6 specific ones. 5 of which are integers and 1 is categorical (text).
In order to leave only the fields I need for the aggregation, I've used $unwind on the fields array - multiplying each document by the number of fields it has. After this I filtered the specific fields I want using a basic $match.
This is where I hit some trouble - I've managed to write two queries where each gives me half of the end result I need. But I'm unable to combine them together into one general query. Specifically, I have one query that gives me 5 integer fields, each is the $sum of each integer fields, and another query that uses the categorical field in order to $count the number of times each category appears.
The desired output would give me a single document that has 5 k:v fields (1 for each sum calculation), and an additional object that includes k:v fields (where each key is a category and the value is the number of times it appears. this must be its own object because the categories that appear may vary).
The sample data I've added has been striped of most of it's structure and includes only the crucial parts relevant for this query. This is in order to secure our clients privacy.
I've tried solving this from every angle I could think of - and would greatly appreciate any feedback!
The first query:
[{$match: {
fields: {
$elemMatch: {
field_id: 174196148,
'values.start': {
$gte: ISODate('2022-02-01T00:00:00.000Z'),
$lt: ISODate('2022-02-03T00:00:00.000Z')
}
}
}
}}, {$unwind: {
path: '$fields'
}}, {$match: {
$or: [
{
'fields.field_id': 226577699
},
{
'fields.field_id': 225330844
},
{
'fields.field_id': 158472699
},
{
'fields.field_id': 191195626
},
{
'fields.field_id': 219444876
}
]
}}, {$unwind: {
path: '$fields.values'
}}, {$addFields: {
'Specific - Field Value': {
$round: [
{
$toDecimal: '$fields.values.value'
}
]
}
}}, {$group: {
_id: '$fields.label',
SumCalculation: {
$sum: {
$toDecimal: '$Specific - Field Value'
}
}
}}, {$group: {
_id: null,
SumArray: {
$push: {
k: '$_id',
v: '$SumCalculation'
}
}
}}, {$project: {
_id: 0,
Final: {
$arrayToObject: '$SumArray'
}
}}]
The second query:
[{$match: {
fields: {
$elemMatch: {
field_id: 174196148,
'values.start': {
$gte: ISODate('2022-01-01T00:00:00.000Z'),
$lt: ISODate('2022-03-31T00:00:00.000Z')
}
}
}
}}, {$unwind: {
path: '$fields'
}}, {$match: {
'fields.field_id': 177278285
}}, {$unwind: {
path: '$fields.values'
}}, {$group: {
_id: '$fields.values.value.text',
ModelCount: {
$sum: 1
}
}}, {$group: {
_id: null,
Full: {
$push: {
k: '$_id',
v: '$ModelCount'
}
}
}}, {$project: {
_id: 0,
Final: {
$arrayToObject: '$Full'
}
}}]
The desired output:
{
"Final": {
"Business Model": [
{
"K": "Solar Lease",
"V": 3
},
{
"K": "Solar Purchase",
"V": 112
}
],
"System Size - Signed Contract": 73,
"Additional Payment for O&M": 2000,
"O&M Years Included (Paid)": 2,
"Total Price Including VAT": 396660,
"1st Milestone - Down Payment": 30280
}
}
Sample data:
{
"_id": 1946794344,
"fields": [
{
"type": "money",
"field_id": 226577699,
"label": "1st Milestone - Down Payment ",
"values": [
{
"currency": "ILS",
"value": "6120.0000"
}
],
"config": {
"settings": {
"allowed_currencies": [
"ILS"
]
},
"mapping": null,
"label": "1st Milestone - Down Payment "
},
"external_id": "1st-milestone-down-payment-2"
},
{
"type": "money",
"field_id": 225330844,
"label": "Additional Payment for O&M",
"values": [
{
"currency": "ILS",
"value": "0.0000"
}
],
"config": {
"settings": {
"allowed_currencies": [
"ILS"
]
},
"mapping": null,
"label": "Additional Payment for O&M"
},
"external_id": "additional-payment-for-om"
},
{
"type": "money",
"field_id": 158472699,
"label": "Total Price Including VAT",
"values": [
{
"currency": "ILS",
"value": "61270.0000"
}
],
"config": {
"settings": {
"allowed_currencies": [
"ILS"
]
},
"mapping": null,
"label": "Total Price Including VAT"
},
"external_id": "money"
},
{
"type": "number",
"field_id": 191195626,
"label": "System Size - Signed Contract",
"values": [
{
"value": "11.6600"
}
],
"config": {
"settings": {
"decimals": 2
},
"mapping": null,
"label": "System Size - Signed Contract"
},
"external_id": "hspq-hmrkt"
},
{
"type": "number",
"field_id": 219444876,
"label": "O&M Years Included (Paid)",
"values": [
{
"value": "0.0000"
}
],
"config": {
"settings": {
"decimals": 0
},
"mapping": null,
"label": "O&M Years Included (Paid)"
},
"external_id": "om-years-gifted-for-free"
},
{
"type": "category",
"field_id": 177278285,
"label": "Business Model",
"values": [
{
"value": {
"status": "active",
"text": "Solar Purchase",
"id": 6,
"color": "DCEBD8"
}
}
],
"external_id": "mvdl-sqy"
}
]
}
I'm really new to mongodb coming from a sql background and struggling to work out how to run a simple report that will group a value from a nested document with a count and in a sort order with highest count first.
I've tried so many ways from what I've found online but I'm unable to target the exact field that I need for the grouping.
Here is the collection.
{
"_id": {
"$oid": "6005f95dbad14c0308f9af7e"
},
"title": "test",
"fields": {
"6001bd300b363863606a815e": {
"field": {
"_id": {
"$oid": "6001bd300b363863606a815e"
},
"title": "Title Two",
"datatype": "string"
},
"section": "Section 1",
},
"6001bd300b363863423a815e": {
"field": {
"_id": {
"$oid": "6001bd3032453453606a815e"
},
"title": "Title One",
"datatype": "string"
},
"section": "Section 1",
},
"6001bd30453534863423a815e": {
"field": {
"_id": {
"$oid": "6001bd300dfgdfgdf06a815e"
},
"title": "Title One",
"datatype": "string"
},
"section": "Section 1",
}
},
"sections": ["Section 1"]
}
The result I need to get from the above example would be:
"Title One", 2
"Title Two", 1
Can anyone please point me in the right direction? Thank you so much.
Having dynamic field names is usually a poor design.
Try this one:
db.collection.aggregate([
{ $set: { fields: { $objectToArray: "$fields" } } },
{ $unwind: "$fields" },
{ $group: { _id: "$fields.v.field.title", count: { $count: {} } } },
{ $sort: { count: -1 } }
])
Here's another way to do it. The $project throws away everything except for the deep-dive to "title". Then just $unwind and $sortByCount.
db.collection.aggregate([
{
"$project": {
"titles": {
"$map": {
"input": {
"$objectToArray": "$fields"
},
"in": "$$this.v.field.title"
}
}
}
},
{
"$unwind": "$titles"
},
{
"$sortByCount": "$titles"
}
])
Try it on mongoplayground.net.
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
I'm trying to make a query to mongodb. I want to get an array containing [location, status] of every document.
This is how my collection looks like
{
"_id": 1,
"status": "OPEN",
"location": "Costa Rica",
"type": "virtual store"
},
{
"_id": 2,
"status": "CLOSED",
"location": "El Salvador"
"type": "virtual store"
},
{
"_id": 3,
"status": "OPEN",
"location": "Mexico",
"type": "physical store"
},
{
"_id": 4,
"status": "CLOSED",
"location": "Nicaragua",
"type": "physical store"
}
I made a query, using the aggregate framework, trying to get all documents that match that specific type of store.
{
{'$match': {
'type': { '$eq': "physical store"}
}
}
What I want is something like this:
{
{
'stores': [
["Mexico", "OPEN"],
["Nicaragua", "CLOSED"]
]
},
}
I tried with the $push but couldn't make it.
Could someone please guide me on how to do it.
Since { $push: ["$location", "$status"] } would give you the error The $push accumulator is a unary operator. You would have to work around it a bit by passing to it a single object that output your desired array. One way to do it would be:
[
{
"$match": {
"type": {
"$eq": "physical store"
}
}
},
{
"$group": {
"_id": null,
"stores": {
"$push": {
"$slice": [["$location", "$status"], 2]
}
}
}
}
]
If the given documents are not sub-documents, then below is the approach:
db.collection.find({
type: {
$eq: "physical store"
}
},
{
location: 1,
status: 1
})
MongoPlayGround link for the above
If, they are the part of a field (means they are sub-documents), then below is the approach:
db.collection.aggregate([
{
$project: {
stores: {
$filter: {
input: "$stores",
as: "store",
cond: {
$eq: [
"$$store.type",
"physical store"
]
}
}
}
}
},
{
$unwind: "$stores"
},
{
$project: {
location: "$stores.location",
status: "$stores.status",
_id: "$stores._id"
}
}
])
MongoPlayGround link for the above