mongodbmongoosesubdocument

Mongoose Find all Documents by subdocument and filter out subdocs that dont match


I am trying to query all documents in a collection that contain specific user data WITHOUT returning all subdocuments (there are a ton of subdocuments)

Example Documents

[
    {
        "id": 1,
        "title": "Document 1",
        "users": [
            { "id": "a", "name": "User 1" },
            { "id": "b", "name": "User 1" },
        ]
    },
    {
        "id": 2,
        "title": "Document 2",
        "users": [
            { "id": "b", "name": "User 1" },
        ]
    },
    {
        "id": 3,
        "title": "Document 3",
        "users": [
            { "id": "a", "name": "User 1" },
            { "id": "b", "name": "User 1" },
            { "id": "c", "name": "User 1" },
        ]
    }
]

Here we have 3 documents in which use with id A is in 2 of them, to Query all documents where user A exists I am doing:

collection.findManyByQuery({
    users: {
        $elemMatch: {
            id: 'a'
        }
    }
})

This returns me documents with id 1 and 3 which is correct. HOWEVER I am trying to return the documents with ONLY user A object inside the users array so my result would look like this

[
    {
        "id": 1,
        "title": "Document 1",
        "users": [
            { "id": "a", "name": "User 1" },
        ]
    },
    {
        "id": 3,
        "title": "Document 3",
        "users": [
            { "id": "a", "name": "User 1" },
        ]
    }
]

I have tried $unwind: 'users' and a couple of filters but have not got the desired result.


Solution

  • Use projection stage.

    According to docs:

    The projection parameter determines which fields are returned in the matching documents

    So using users.$: 1 you are telling mongo: "Return the value from users that matches the criteria". In this case the criteria is id: "a".

    db.collection.find({
      users: {
        $elemMatch: {
          id: "a"
        }
      }
    },
    {
      "users.$": 1
    })
    

    Example here

    Also you can use users.id into find query like in this example

    Maybe is a query more clean, only two lines:

    db.collection.find({
      "users.id": "a"
    },
    {
      "users.$": 1
    })
    

    Edit:

    To add more values to output (like title or id) you have to add to projection stage. By default projection only return the _id and the values with 1 or true.

    Check this example