27

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?

robsch
  • 9,358
  • 9
  • 63
  • 104
Swapnil Sonawane
  • 1,445
  • 3
  • 22
  • 37

4 Answers4

21

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.

Community
  • 1
  • 1
Gates VP
  • 44,957
  • 11
  • 105
  • 108
  • This has been fixed in the meantime; have a look at http://stackoverflow.com/questions/3985214/mongodb-extract-only-the-selected-item-in-array – Dan Dascalescu Nov 04 '12 at 13:41
  • The second parameter to find specifies which fields to retrieve, which is effectively specifying which sub-documents you need. – Leopd Apr 07 '13 at 20:37
5

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

Alex Bolotov
  • 8,781
  • 9
  • 53
  • 57
bm_i
  • 568
  • 5
  • 9
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})

Stephan
  • 41,764
  • 65
  • 238
  • 329
Swapnil Sonawane
  • 1,445
  • 3
  • 22
  • 37
2

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' } ] }
King Friday
  • 25,132
  • 12
  • 90
  • 84