34

I have a collection Notebook which has embedded array document called Notes. The sample

document looks like as shown below.

{
"_id" : ObjectId("4f7ee46e08403d063ab0b4f9"),
"name" : "MongoDB",
"notes" : [
            {
              "title" : "Hello MongoDB",
              "content" : "Hello MongoDB"
            },
            {
              "title" : "ReplicaSet MongoDB",
              "content" : "ReplicaSet MongoDB"
            }
         ]
}

I want to find out only note which has title "Hello MongoDB". I am not getting what should

be the query. Can anyone help me.

Amir Ismail
  • 3,865
  • 3
  • 20
  • 33
Shekhar
  • 5,771
  • 10
  • 42
  • 48

5 Answers5

89

You can do this with mongo version higher 2.2

the query like this:

db.coll.find({ 'notes.title': 'Hello MongoDB' }, {'notes.$': 1});

you can try with $elemMatch like Justin Jenkins

JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
kop
  • 939
  • 1
  • 6
  • 5
  • 4
    This would return the _id of the parent document alongside the embedded object unless you exclude the _id from the query: ...{'notes.$': 1, '_id':0}); – Ushox Jun 27 '15 at 10:03
  • Where is the documentation for this query structure you are using? – etech Feb 25 '16 at 14:32
  • It works like a charm, thank you. But I don't find document about it anywhere too :) – Tien Do Mar 03 '16 at 09:14
  • 5
    @etech Now I found document about $ projection here https://docs.mongodb.org/master/reference/operator/projection/positional/#proj._S_ – Tien Do Apr 07 '16 at 06:35
  • it just returns one embedded document even there're multiple matching, am I right?? better to use aggregation. – Inanc Gumus Aug 21 '17 at 16:31
  • can this be done in java? My use case requires exactly one embedded document to be returned even if there are multiple matching - aggregation seems like overkill – I. Kirilov Dec 30 '18 at 15:25
39

Outdated answer: See the other answers.


I don't believe what you are asking is possible, at least without some map-reduce maybe?

See here: Filtering embedded documents in MongoDB

That answer suggests you change your schema, to better suit how you'd like to work with the data.

You can use a either "dot notation" or $elemMatch to get back the correct, document that has the matching "note title" ...

> db.collection.find({ "notes.title" : "Hello MongoDB"}, { "notes.title" : 1"});

or ...

> db.collection.find({ "notes" : { "$elemMatch" : { "title" : "Hello MongoDB"} }});

But you will get back the whole array, not just the array element that caused the match.

Also, something to think about ... with your current setup it woud be hard to do any operations on the items in the array.

If you don't change your schema (as the answer linked to suggests) ... I would consider adding "ids" to each element in the array so you can do things like delete it easily if needed.

Inanc Gumus
  • 25,195
  • 9
  • 85
  • 101
Justin Jenkins
  • 26,590
  • 6
  • 68
  • 1,285
8

You can do this in MongoDb version 3.2+ with aggregation.

Query:

db.Notebook.aggregate(
    {
        $project: {
            "notes": {
                $filter: {
                    input: "$notes",
                    as: "note",
                    cond: { 
                        $eq: [ "$$note.title", "Hello MongoDB" ]
                    }
                }
            }
        }
    }
)

Result:

{ 
    "_id" : ObjectId("4f7ee46e08403d063ab0b4f9"), 
    "notes" : [ 
        { 
            "title" : "Hello MongoDB", 
            "content" : "Hello MongoDB" 
        } 
    ] 
}

$$ used here to access the variable. I used here to access the newly created note variable inside the $filter.

You can find additional details in the official documentation about $filter, $eq and $$.

$filter: Selects a subset of an array to return based on the specified condition. Returns an array with only those elements that match the condition. The returned elements are in the original order.

$eq: Compares two values and returns true/false when the values are equivalent or not (...).

$$: Variables can hold any BSON type data. To access the value of the variable, use a string with the variable name prefixed with double dollar signs ($$).


Note:

Justin Jenkin's answer is outdated and kop's answer here doesn't return multiple documents from the collection. With this aggregation query, you can return multiple documents if needed.

I needed this and wanted to post to help someone.

Community
  • 1
  • 1
Inanc Gumus
  • 25,195
  • 9
  • 85
  • 101
0

You can use $ or $elemMatch. The $ operator and the $elemMatch operator project a subset of elements from an array based on a condition.

The $elemMatch projection operator takes an explicit condition argument. This allows you to project based on a condition not in the query.

db.collection.find(
    {
        // <expression>
    },
    {
        notes: {
            $elemMatch: {
                title: 'Hello MongoDB'
            }
        },
        name: 1
    }
)

The $ operator projects the array elements based on some condition from the query statement.

db.collection.find(
    {
        'notes.title': 'Hello MongoDB'
    },
    {
        'notes.title.$': 1,
         name: 1
    }
)
Omar Makled
  • 1,638
  • 1
  • 19
  • 17
-3

You can perform the query like this:

db.coll.find({ 'notes.title': 'Hello MongoDB' });

You can also refer to the docs for more details.

Ren
  • 678
  • 4
  • 6
  • 10
    Hummm I believe what's being asked is how to get back ONLY the **note** with 'Hello MongoDB' as the **title**, your example would bring back the *entire document* with the notes array too! – Justin Jenkins Apr 06 '12 at 17:54