Björn
Björn

Reputation: 13207

How do I query a mongo document containing subset of nested array

Here is a doc 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 doc 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´s of docIWant should only contain items greater than min=4 and smaller than max=12.

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

A practical code example would be greatly appreciated.

Upvotes: 3

Views: 442

Answers (3)

David Weldon
David Weldon

Reputation: 64342

If you need to transform the document on the client for display purposes, you could 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}}

Upvotes: 2

Vishwas
Vishwas

Reputation: 7067

Use mongo aggregation like following : First use $unwind this will unwind stuff and then use $match to find elements greater than 4. After that $group data based on things.name and add required fields in $project.

The query will be as following:

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
}
}])

Upvotes: 1

chridam
chridam

Reputation: 103445

Use the aggregation framework for this. In the aggregation pipeline, consider the $match operator as your first pipeline stage. This is quite necessary to optimize your aggregation as you would need to filter documents that match the given criteria first before passing them on further down the pipeline.

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

Another $unwind operation would be needed on the things.stuff array as well.

The next pipeline stage would then filter dopcuments where the deconstructed things.stuff match the given min and max criteria. Use a $match operator for this.

A $group operator is then required to group the input documents by a specified identifier expression and applies the accumulator expression $push to each group. This creates an array expression to each group.

Typically your aggregation should end up like this (although I haven't actually tested it but this 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"
                }
            }
        }
    }
])

Upvotes: 2

Related Questions