Search code examples
python-3.xmongodbmongodb-querypymongo

Querying Subdocuments in MongoDb Documents and Only Return Matching Subdocuments


Ive looked here: https://codelikethis.com/lessons/db/mongodb-array-queries and here: https://www.mongodb.com/community/forums/t/what-is-the-best-way-to-query-an-array-of-sub-documents-in-mongodb/115315 to try and figure out how to query only matching subdocuments from a parent document.

Here is an example of the data:

{'testname':'process',
'jobId':"job1",
"vt_cond":"cond1",
"testData":[{
'chip':'c1',
'name':'block1'},
{
'chip':'c1',
'name':'block1'},
{
'chip':'c1',
'name':'block2'},
{
'chip':'c1',
'name':'block3'}]}

It contains subdocuments within the "testData" field. What I want to do is something like the following:

db.collection.find({'jobId':'job1', 'testData.name':'block3'})

and have it return the following:

{'testname':'process',
'jobId':"job1",
"vt_cond":"cond1",
"testData":[{'chip':'c1',
'name':'block3'}]}

I understand that the mongodb documentation states it will query and return a document which matches the conditions in the subdocument query conditions. It does. I get the entire example document as above. Is there anyway where I can make a similar query with similar conditions as above, and only return the parent node with the desired subdocument, instead of ALL the subdocuments?


Solution

  • Option 1: You can use $elemMatch in the project part as follow:

    db.collection.find({
      "jobId": "job1",
      "testData.name": "block3"
    },
    {
     "testData": {
       "$elemMatch": {
          name: "block3"
      }
    }
    })
    

    Explained:

    Find the object and project only the array sub-object that match the criteria

    Attention: This is only working if you need only 1x matching element from array , if there is more then one element in the array matching the criteria better use Option 2.

    Playground

    Option 2: aggregate/$filter ( cover the case when you have more then one objects in the array matching by the filter )

      db.collection.aggregate([
     {
      $match: {
      "jobId": "job1",
      "testData.name": "block3"
      }
     },
     {
      "$addFields": {
       "testData": {
        "$filter": {
          "input": "$testData",
          "as": "t",
          "cond": {
            $eq: [
              "$$t.name",
              "block3"
              ]
            }
          }
        }
       }
     }
    ])
    

    Explained:

    1. Match the document
    2. Filter only the matching objects inside the array of objects

    Playground 2