Home > Back-end >  Mongoose findOne not working as expected on nested records
Mongoose findOne not working as expected on nested records

Time:10-19

I've got a collection in MongoDB whose simplified version looks like this:

    Dealers = [{
    Id: 123,
    Name: 'Someone',
    Email: '[email protected]',
    Vehicles: [
        {
            Id: 1234,
            Make: 'Honda',
            Model: 'Civic' 
        },
        {
            Id: 2345,
            Make: 'Ford',
            Model: 'Focus'
        },
        {
            Id: 3456,
            Make: 'Ford',
            Model: 'KA'
        }
    ]
}]

And my Mongoose Model looks a bit like this:

const vehicle_model = mongoose.Schema({
    Id: {
        Type: Number
    },
    Email: {
        Type: String
    },
    Vehicles: [{
        Id: {
            Type: Number
        },
        Make: {
            Type: String
        },
        Model: {
            Type: String
        }
    }]
})

Note the Ids are not MongoDB Ids, just distinct numbers.

I try doing something like this:

const response = await vehicle_model.findOne({ 'Id': 123, 'Vehicles.Id': 1234 })

But when I do:

console.log(response.Vehicles.length)

It's returned all the Vehicles nested records instead on the one I'm after.

What am I doing wrong?

Thanks.

CodePudding user response:

This question is asked very frequently. Indeed someone asked a related question here just 18 minutes before this one.

When query the database you are requesting that it identify and return matching documents to the client. That is a separate action entirely than asking for it to transform the shape of those documents before they are sent back to the client.

In MongoDB, the latter operation (transforming the shape of the document) is usually referred to as "Projection". Simple projections, specifically just returning a subset of the fields, can be done directly in find() (and similar) operations. Most drivers and the shell use the second argument to the method as the projection specification, see here in the documentation.

Your particular case is a little more complicated because you are looking to trim off some of the values in the array. There is a dedicated page in the documentation titled Project Fields to Return from Query which goes into more detail about different situations. Indeed near the bottom is a section titled Project Specific Array Elements in the Returned Array which describes your situation more directly. In it is where they describe usage of the positional $ operator. You can use that as a starting place as follows:

db.collection.find({
  "Id": 123,
  "Vehicles.Id": 1234
},
{
  "Vehicles.$": 1
})

Playground demonstration here.

If you need something more complex, then you would have to start exploring usage of the $elemMatch (projection) operator (not the query variant) or, as @nimrod serok mentions in the comments, using the $filter aggregation operator in an aggregation pipeline. The last option here is certainly the most expressive and flexible, but also the most verbose.

  • Related