Project a specific field from the first element that matches the query condition on the array

923 views Asked by At

I have a families collection. The documents in it have a users array. Given this family document:

{
    "id" : "1",
    "users" : [ 
        {
            "id" : "2",
            "identities" : [ 
                {
                    "type" : "email",
                    "email" : "foo"
                }, 
                {
                    "type" : "email",
                    "email" : "bar"
                }
            ]
        }, 
        {
            "id" : "3",
            "identities" : [ 
                {
                    "type" : "email",
                    "email" : "baz"
                }, 
                {
                    "type" : "email",
                    "email" : "qux"
                }
            ]
        }
    ]
}

I want to project the type field of the identities array elements, but only from the specific user I am querying. The positional operator gets the right user:

db.families.findOne(
    { 'users.id': '2' },
    { 'users.$': 1 } 
  )

And I can specify the field I'm interested in with:

db.families.findOne(
    { 'users.id': '2' },
    { 'users.identities.type': 1 } 
  )

But when combining both, only the positional operator is respected:

db.families.findOne(
    { 'users.id': '2' },
    { 'users.$': 1, 'users.identities.type': 1 } 
  )

(That returns user with id 2, but returns the identities array elements with all fields). Thought about trying this:

db.families.findOne(
    { 'users.id': '2' },
    { 'users.$.identities.type': 1 } 
  )

But it's no good since per the docs:

MongoDB ignores the part of the path that follows the $

Is there a way to do it?

2

There are 2 answers

2
Haniel Baez On

You only have to use the $ operator to project an field.

Query:

db.collection.find({
  "users.id": "2"
},
{
  "users.identities.type.$": 1
});

Result:

[
  {
    "_id": "1",
    "users": [
      {
        "identities": [
          {
            "type": "email"
          },
          {
            "type": "email"
          }
        ]
      }
    ]
  }
]
3
R2D2 On

This will project only the type & users.id for the selected users.id:

 db.families.findOne({"users.id":"2"},{"users":{id:1,"identities.type.$":1}})

 {
"_id" : ObjectId("601f1974cf95940e8e0c814e"),
"users" : [
    {
        "id" : "2",
        "identities" : [
            {
                "type" : "email"
            },
            {
                "type" : "email"
            }
        ]
    }
]
}

( Tested in mongod 4.4.3 ) Can be re-written also like this ( also 4.4.3 ):

 db.families.findOne({"users.id":"2"},{"users":{id:1,"identities":{"type.$":1}}})

Same result in mongod 4.2.12 can be achieved with following aggregation:

 db.families.aggregate([  
{$project:{users:{$filter:{input:"$users",as:"us",cond:{$eq:["$$us.id","2"]}}}}}  , 
{$project:{"users.identities.type":1 , "users.id":1  }}     
 ])

aggregation explained:

  • in first $project stage you filter the array element for users.id:2
  • in second $project stage you project only the fields you need