2

I have 2 collections:

Vehicles:

[
    {
        "_id": "a1",
        "type:": "car",
        "make": "Honda",
        "specifications": ["1", "2"]
    },
    {
        "_id": "a2",
        "type:": "car",
        "make": "Toyota",
        "specifications": ["3", "4"]
    },
    {
        "_id": "a3",
        "type:": "car",
        "make": "Honda",
        "specifications": []
    },
    {
        "_id": "a4",
        "type:": "car",
        "make": "Toyota"
    }
]

Specifications:

[
    {
        "_id": "1",
        "color": "Black"
    },
    {
        "_id": "2",
        "sunroof": "yes"
    },
    {
        "_id": "3",
        "engine": "1800 CC"
    },
    {
        "_id": "4",
        "bodyType": "Sedan"
    }
]

I want to fetch those records which has at least one specification. And also the details from specifications collections should appear in Vehicles collections somewhere.

Expected response:

[
    {
        "_id": "a1",
        "make": "Honda",
        "type:": "car",
        "carSpecifications": [
            {
                "color": "Black"
            },
            {
                "sunroof": "yes"
            }
        ],

    },
    {
        "_id": "a2",
        "make": "Toyota",
        "type:": "car",
        "specifications": [
            {
                "engine": "1800 CC"
            },
            {
                "bodyType": "Sedan"
            }
        ]
    }
]

Now what I tried so far is:

db.vehicles.find({type: "car", "specifications": {$exists: true}}, {fields: {"specifications.$": 1}}).fetch()

this query is returning all the records from Vehicles.

After getting all the records I put a loop on the records I get and check manually if specifications.length > 0 than I query from Specifications collection accordingly.

Can I achieve all this with a single query?

3
  • The type field in the cars collection was named as `type: ", is it a typo? Commented Jul 30, 2022 at 7:48
  • @YongShun no it isnt a typo. its an attribute, but just an example value Commented Jul 30, 2022 at 7:51
  • @YongShun I just updated my question a bit, have a look now Commented Jul 30, 2022 at 7:52

1 Answer 1

1

You should look for an aggregation query.

  1. $match - Filter documents with "type:" "car" and specifications is not an empty array (with $ifNull, default as [] when specifications field is null or not existed).

  2. $lookup - Vehicles collection join specifications collection (Refer to Use $lookup with an Array). Work with pipeline to return the array without the _id field (Refer to Correlated Subqueries Using Concise Syntax).

MongoDB v5 query

db.vehicles.aggregate({
  $match: {
    $and: [
      {
        "type:": "car"
      },
      {
        $expr: {
          $ne: [
            {
              $ifNull: [
                "$specifications",
                []
              ]
            },
            []
          ]
        }
      }
    ]
  }
},
{
  $lookup: {
    from: "specifications",
    localField: "specifications",
    foreignField: "_id",
    pipeline: [
      {
        $project: {
          _id: 0
        }
      }
    ],
    as: "specifications"
  }
})

Sample Mongo Playground (v5)


MongoDB v4 query

db.vehicles.aggregate({
  $match: {
    $and: [
      {
        "type:": "car"
      },
      {
        $expr: {
          $ne: [
            {
              $ifNull: [
                "$specifications",
                []
              ]
            },
            []
          ]
        }
      }
    ]
  }
},
{
  $lookup: {
    from: "specifications",
    let: {
      specifications: "$specifications"
    },
    pipeline: [
      {
        $match: {
          $expr: {
            $in: [
              "$_id",
              "$$specifications"
            ]
          }
        }
      },
      {
        $project: {
          _id: 0
        }
      }
    ],
    as: "specifications"
  }
})

Sample Mongo Playground (v4)

Sign up to request clarification or add additional context in comments.

5 Comments

Isn't there any shorter or optimized way to query this? for me it seems like we could shorten the check for specifications to something like specifications.length > 0
No, MongoDB doesn't support such specifications.length > 0, this is JS syntax. Or probably work with $size to get array length and use $gt operator. Demo
I am getting this error when I run your example according to my schema: MongoError: $lookup with 'pipeline' may not specify 'localField' or 'foreignField'
Which MongoDB version are you used? I think you are not able to use 'localField' or 'foreignField' with pipeline if your MongoDB version is lower than v5.
I am using MongoDB server version: 4.2.5

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.