4

I have the following MongoDB document:

{
 _id: ObjectId(5), 
 items: [1,2,3,45,4,67,9,4]
}

I need to fetch that document with filtered items (1, 9, 4)

result:

{
 _id: ObjectId(5), 
 items: [1,9,4]
}

I've tried an $elemMatch projection but it returns only one item:

A.findById(ObjectId(5))
   .select({ items: { $elemMatch: {$in: [1, 9, 4]}}})
   .exec(function (err, docs) {
      console.log(doc); // { _id: ObjectId(5), items: [ 1 ] }
      done(err);
});

How can I get the document with items: 1, 9, 4 only?

Erik
  • 11,695
  • 42
  • 119
  • 194
  • Possible duplicate of [Retrieve only the queried element in an object array in MongoDB collection](https://stackoverflow.com/questions/3985214/retrieve-only-the-queried-element-in-an-object-array-in-mongodb-collection) – dnickless Oct 03 '18 at 23:30
  • @dnickless This isn't quite the same question as the potential duplicate you flagged. This example document has a simple array of numeric values, while the other question has an array of objects. There are definitely more aggregation options available in MongoDB, but if you want to point this out you should add as a comment rather than editing older answers. Any answer last updated in 2012 implicitly only describes available options at the time :). I've updated my answer to include a few examples with more modern operators. – Stennie Oct 04 '18 at 01:21
  • @Stennie: Point taken. Kindly note that I wasn't attempting to diminish your answer in any way but rather to avoid duplication of descriptions of what seemed to me like techniques that an average MongoDB user should be able to abstract from a whole bunch of other (quite similar but admittedly not identical) examples and also to prevent users from following the old style pattern that you suggested 6 years ago (and rightly so) because that'd be unnecessarily slow nowadays. So please accept my apologies. – dnickless Oct 04 '18 at 05:36
  • @dnickless No worries :). I would suggest commenting on old answers if an update might be appropriate. Much has changed in the last 6 years, so many old answers are no longer applicable or could use a refresh. The possible duplicate question flagged is even older (2010) and could also use a more recent answer. – Stennie Oct 04 '18 at 08:20

3 Answers3

4

A.items = A.items.filter( function(i) {return i == 1 || i == 9 || i == 4} );

Marcus
  • 6,205
  • 3
  • 17
  • 27
3

In modern versions of MongoDB (3.2+) you can use the $filter operator to select a subset of an array field to return based on a specified condition. Returned elements will be in the original order from the field array.

Example in the mongo shell:

db.items.aggregate([
    { $match : {
        _id: 5
    }},
    { $project: {
        items: {
            $filter: {
                input: "$items",
                cond: {
                    "$in": ["$$this", [1, 9, 4]]
                }
            }
        }
     }
}])

Note: because the original array in this question has the value 4 twice, the $filter command will return both occurrences:

{ "_id" : 5, "items" : [ 1, 4, 9, 4 ] }

For an alternative approach which will only return the unique matching items, the $setIntersection operator could be used:

db.items.aggregate([
    { $match : {
        _id: 5
    }},        
    { $project: {
        items: {
            $setIntersection: ['$items', [1,4,9]] 
        }
    }}
])

This will return: { "_id" : 5, "items" : [ 1, 4, 9 ] }.

(original answer from September, 2012 below)

If you want the document manipulation to happen on the server side, you can use the Aggregation Framework in MongoDB 2.2:

db.items.aggregate(

  // Match the document(s) of interest
  { $match : {
     _id: 5
  }},

  // Separate the items array into a stream of documents
  { $unwind : "$items" },

  // Filter the array
  { $match : {
    items: { $in: [1, 9, 4] }
  }},

  // Group the results back into a result document
  { $group : {
     _id: "$_id",
     items: { $addToSet : "$items" }
  }}
)

Result:

{
    "result" : [
        {
            "_id" : 5,
            "items" : [
                9,
                4,
                1
            ]
        }
    ],
    "ok" : 1
}
Stennie
  • 57,971
  • 14
  • 135
  • 165
  • Note: example here is using the `mongo` shell .. but similar approach should work in Mongoose. Mongoose 3.1.0 or higher has an [aggregate()](http://mongodb.github.com/node-mongodb-native/api-generated/collection.html#aggregate) helper. – Stennie Sep 26 '12 at 03:58
  • Thanks. What about performance of your aproatch? I need to make that query frequently. – Erik Sep 26 '12 at 20:16
  • @Erik: The `$match` can take advantage of an index and the rest of the document manipulation will happen in memory (on the `mongod` server). The Aggregation Framework docs also have some notes on [optimizing performance](http://docs.mongodb.org/manual/applications/aggregation/#optimizing-performance). If you do the same manipulation on the client side you'll be returning some extra data to discard, but free up a few cycles on the server. Not sure what the relative performance would be for server-side C++ code versus node.js, but I expect C++ has the advantage. – Stennie Sep 27 '12 at 00:45
0

Use underscore in your node.js app:

npm install underscore

var _ = require('underscore');

You can use the intersection function of arrays:

 intersection_.intersection(*arrays)

Computes the list of values that are the intersection of all the arrays. Each value in the result is present in each of the arrays.

_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2]

http://documentcloud.github.com/underscore/#intersection

chovy
  • 59,357
  • 43
  • 187
  • 234