How can I query a mongo file containing a subset of a nested array

Here is the document I have:

var docIHave = {
    _id: "someId",
    things: [
        {
            name: "thing1",
            stuff: [1,2,3,4,5,6,7,8,9]
        },
        {
            name: "thing2",
            stuff: [4,5,6,7,8,9,10,11,12,13,14]
        },
        {
            name: "thing3",
            stuff: [1,4,6,8,11,21,23,30]
        }
    ]
}

      

This is the document I want:

var docIWant = {
    _id: "someId",
    things: [
        {
            name: "thing1",
            stuff: [5,6,7,8,9]
        },
        {
            name: "thing2",
            stuff: [5,6,7,8,9,10,11]
        },
        {
            name: "thing3",
            stuff: [6,8,11]
        }
    ]
}

      

stuff of docIWant must only contain items greater than min = 4 and less than max = 12.

Background: I have a meteor app and I subscribe to a collection giving me docIHave. Based on the min and max parameters, I need docIWant on the fly. The original document should not be modified. I need a query or procedure that returns docIWant to me with a subset of stuff.

Nice to appreciate the practical code example.

+3


source to share


3 answers


Use mongo aggregation like below: First use $ unwind , this will disable stuff

, and then use $ match to find the elements exceeding 4

. After that $ group based data things.name

and add required fields to $project

.

The request will look like this:



db.collection.aggregate([
{
$unwind: "$things"
}, {
$unwind: "$things.stuff"
}, {
$match: {
    "things.stuff": {
        $gt: 4,
        $lt:12
    }
}
}, {
$group: {
    "_id": "$things.name",
    "stuff": {
        $push: "$things.stuff"
    }
}
}, {
$project: {
    "thingName": "$_id",
    "stuff": 1
}
}])

      

+1


source


Use an aggregation structure for this . In an aggregation pipeline, consider the statement as your first stage in the pipeline. This is necessary to optimize aggregation because you need to first filter documents that match the specified criteria before transferring them further down the pipeline. $match

Then use the operator . This deconstructs the array field from the input documents to output the document for each element. Each output document is an input document with the value of an array field replaced by an element. $unwind

things

Another one will be needed in the array as well . $unwind

things.stuff

The next stage of the pipeline will then filter additional purchases where the deconstructed things.stuff

meets the specified minimum and maximum criteria. To do this, use . $match



A operator must group the input documents using the specified id and apply an accumulator expression for each group. This creates an array expression for each group. $group

$push

Typically, your aggregation should end up like this (although I haven't tested it, it should get you going in the right direction):

db.collection.aggregate([
    {
        "$match": {
            "things.stuff": { "$gt": 4, "$lte": 11 }
        }
    },
    {
        "$unwind": "$things"
    },
    {
        "$unwind": "$things.stuff"
    },
    {
        "$match": {
            "things.stuff": { "$gt": 4, "$lte": 11 }
        }
    },
    {
        "$group": {
            "_id": {
                "_id": "$_id",
                "things": "$things"
            },
            "stuff": {
                "$push": "$things.stuff"
            }
        }
    },
    {
        "$group": {
            "_id": "$_id._id",  
            "things": {
                "$push": {
                    "name": "$_id.things.name",
                    "stuff": "$stuff"
                }
            }
        }
    }
])

      

+2


source


If you need to convert the document on the client for display, you can do something like this:

Template.myTemplate.helpers({
  transformedDoc: function() {
    // get the bounds - maybe these are stored in session vars
    var min = Session.get('min');
    var max = Session.get('max');

    // fetch the doc somehow that needs to be transformed
    var doc = SomeCollection.findOne();

    // transform the thing.stuff arrays
    _.each(doc.things, function(thing) {
      thing.stuff = _.reject(thing.stuff, function(n) {
        return (n < min) || (n > max);
      });
    });

    // return the transformed doc
    return doc;
  }
});

      

Then in your template: {{#each transformedDoc.things}}...{{/each}}

+2


source







All Articles