yandex
loader

please wait

  • Liza Sain Feb-15-2019 06:50:51 AM ( 3 months ago )

    Retrieve only the queried element in an object array in MongoDB collection

     

    Suppose you have the following documents in my collection:

      { "_id":ObjectId("562e7c594c12942f08fe4192"), "shapes":[ { "shape":"square", "color":"blue" }, { "shape":"circle", "color":"red" } ] }, { "_id":ObjectId("562e7c594c12942f08fe4193"), "shapes":[ { "shape":"square", "color":"black" }, { "shape":"circle", "color":"green" } ] }

    Do query:

    db.test.find({"shapes.color": "red"}, {"shapes.color": 1})

    Or

    db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})

    Returns matched document (Document 1), but always with ALL array items in shapes:

    { "shapes": [ {"shape": "square", "color": "blue"}, {"shape": "circle", "color": "red"} ] }

    However, I'd like to get the document (Document 1) only with the array that contains color=red:

    { "shapes": [ {"shape": "circle", "color": "red"} ] }

    How can I do this?

  • Priyanka Chadda Feb-15-2019 06:57:37 AM ( 3 months ago )

    MongoDB 2.2's new $elemMatch projection operator provides another way to alter the returned document to contain only the first matched shapes element:

    db.test.find(
        {"shapes.color": "red"}, 
        {_id: 0, shapes: {$elemMatch: {color: "red"}}});

    Returns:

    {"shapes" : [{"shape": "circle", "color": "red"}]}

    In 2.2 you can also do this using the $ projection operator, where the $ in a projection object field name represents the index of the field's first matching array element from the query. The following returns the same results as above:

    db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});

    MongoDB 3.2 Update

    Starting with the 3.2 release, you can use the new $filter aggregation operator to filter an array during projection, which has the benefit of including all matches, instead of just the first one.

    db.test.aggregate([
        // Get just the docs that contain a shapes element where color is 'red'
        {$match: {'shapes.color': 'red'}},
        {$project: {
            shapes: {$filter: {
                input: '$shapes',
                as: 'shape',
                cond: {$eq: ['$$shape.color', 'red']}
            }},
            _id: 0
        }}
    ])

    Results:

    [ 
        {
            "shapes" : [ 
                {
                    "shape" : "circle",
                    "color" : "red"
                }
            ]
        }
    ]
     
  • Imran Malik Feb-15-2019 07:00:19 AM ( 3 months ago )

    See the option using $filter to get all matches. 

  • Karlie Kloss Feb-15-2019 07:01:40 AM ( 3 months ago )

    This also works: db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});

  • Deepak Parmar Feb-15-2019 07:02:33 AM ( 3 months ago )

    The new Aggregation Framework in MongoDB 2.2+ provides an alternative to Map/Reduce. The $unwind operator can be used to separate your shapes array into a stream of documents that can be matched:

    db.test.aggregate(
      // Start with a $match pipeline which can take advantage of an index and limit documents processed
      { $match : {
         "shapes.color": "red"
      }},
      { $unwind : "$shapes" },
      { $match : {
         "shapes.color": "red"
      }}
    )

    Results in:

    {
        "result" : [
            {
                "_id" : ObjectId("504425059b7c9fa7ec92beec"),
                "shapes" : {
                    "shape" : "circle",
                    "color" : "red"
                }
            }
        ],
        "ok" : 1
    }
     
  • Ayushi Jain Feb-15-2019 07:04:16 AM ( 3 months ago )

    For other searchers, in addition to this I also tried adding { $project : { shapes : 1 } } - which seemed to work and would be helpful if the enclosing documents were large and you just wanted to view the shapes key values.

  • Vikrant Srivastava Feb-15-2019 07:05:22 AM ( 3 months ago )

    The field selector parameter is limited to complete properties. It cannot be used to select part of an array, only the entire array. I tried using the $ positional operator, but that didn't work.

    The easiest way is to just filter the shapes in the client.

    If you really need the correct output directly from MongoDB, you can use a map-reduce to filter the shapes.

    function map() {
      filteredShapes = [];
    
      this.shapes.forEach(function (s) {
        if (s.color === "red") {
          filteredShapes.push(s);
        }
      });
    
      emit(this._id, { shapes: filteredShapes });
    }
    
    function reduce(key, values) {
      return values[0];
    }
    
    res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } })
    
    db[res.result].find()
     
  • Katie George Feb-15-2019 07:06:29 AM ( 3 months ago )

    Another interesing way is to use $redact, which is one of the new aggregation features of MongoDB 2.6. If you are using 2.6, you don't need an $unwind which might cause you performance problems if you have large arrays.

    db.test.aggregate([
        { $match: { 
             shapes: { $elemMatch: {color: "red"} } 
        }},
        { $redact : {
             $cond: {
                 if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
                 then: "$$DESCEND",
                 else: "$$PRUNE"
             }
        }}]);

    $redact "restricts the contents of the documents based on information stored in the documents themselves". So it will run only inside of the document. It basically scans your document top to the bottom, and checks if it matches with your if condition which is in $cond, if there is match it will either keep the content($$DESCEND) or remove($$PRUNE).

    In the example above, first $match returns the whole shapes array, and $redact strips it down to the expected result.

    Note that {$not:"$color"} is necessary, because it will scan the top document as well, and if $redact does not find a color field on the top level this will return false that might strip the whole document which we don't want.

     
  • Nageshwer Reddy Feb-15-2019 07:09:17 AM ( 3 months ago )

    Better you can query in matching array element using $slice is it helpful to returning the significant object in an array.

    db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

    $slice is helpful when you know the index of the element, but sometimes you want whichever array element matched your criteria. You can return the matching element with the $ operator.

     
     
    12
    db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

    OUTPUTS

    {

    "shapes" : [ { "shape" : "circle", "color" : "red" } ] }

     
  • Apoorva Saxena Feb-15-2019 07:11:09 AM ( 3 months ago )

    The syntax for find in mongodb is

        db.<collection name>.find(query, projection);

    and the second query that you have written, that is

        db.test.find(
        {shapes: {"$elemMatch": {color: "red"}}}, 
        {"shapes.color":1})

    in this you have used the $elemMatch operator in query part, whereas if you use this operator in the projection part then you will get the desired result. You can write down your query as

         db.users.find(
         {"shapes.color":"red"},
         {_id:0, shapes: {$elemMatch : {color: "red"}}})

    This will give you the desired result.

Please login

Similar Discussion

Recommended For You