Get particular element from mongoDB array [duplicate]

I have mongo collection like below

{
  "auther" : "xyz" , 
  "location" : "zzz" , 
  "books" : 
    [
      {"book1" : "b1" , "date" : 2-3-00} ,
      {"book1" : "b2" , "date" : 4-9-00}
    ]
}

{
  "auther" : "pqr",
  "location" : "zzz" , 
  "books" : 
    [
      {"book1" : "b1" , "date" : 2-4-00}
    ]
}

I want to get the only the date of book b1 and author xyz .

i have make query like below

db.coll.find({"auther" : "xyz" , "books.book1" : "b1"} , {"books.date" : 1})

but it's gives output as follows

"books" : {"date" : 2-4-00} , "books" : {"date" : 4-9-00}

I want to get the only the date of book b1 and other xyz .means only "books" : {"date" : 2-4-00}

is it possible in mongo or am I doing something wrong?


Solution 1:

The MongoDB query language is designed to return all matching Documents.

There is no support for returning only sub-documents.

This issue has an outstanding ticket in MongoDB's ticket tracker.


UPDATE: it looks like the ticket has been marked as fixed.

See here for an example of how to use this.

Solution 2:

It can be done using map/reduce, just emit the sub element into a temporary inline collection. Its a Hack and it works however I'd advise against it as map/reduce is single threaded, and has a large overhead for what you want to achieve, it is much easier to just extract the sub-element in your application.

Something like this...

map:

m = function() { 
    this.books.forEach(function(book){ 
        if(book1 == 'b1'){
           emit("books", {date: book.date,});
         }
     });
}

reduce:

r = function(key, values) {
      return this;
    }

query:

    db.coll.mapReduce(m, r, {query : {"auther" : "xyz" , "books.book1" : "b1"}, out: { inline : 1}})

Solution 3:

if you want to select only matching element you can query like this.

b.coll.find({"auther" : "xyz" , "books.book1" : "b1"},{"books.$.date" : 1})

Solution 4:

With a little imagination (pre mongo v 2.6) ...

You can do this with aggregate or map reduce. Aggregate is newer, easier and more optimized. Here's a sample of returning a sub document with aggregate assuming your collection is named "Authors". I took the liberty of spelling things correctly.

Authors.aggregate([
  { $match: { author: 'xyz' } },
  { $unwind: '$books' },
  { 
    $project: {  
      _id: '$books.book1',
      date: '$books.date'
    }
  },
  { $match: { '$_id' : 'b1' } } 
]);

You'll get back an array with a single entry like so:

[{ _id: 'b1', date: '2-4-00' }]

Otherwise, if mongo 2.6+ you can do the really easy way:

Authors.find({
  author: 'xyz',
  books: { $elemMatch: { book1: 'b1' } }
},'books')

Where you will get back the books collection if found and only a single record within:

{ _id: 'xyz', books: [ { book1: 'b1', date: '2-4-00' } ] }