简体   繁体   中英

Mongoose find documents by child criteria

I'm struggling trying to find all the docs with a specific property in the child. For example, I want to find all the users with their child active.

These are my models

const userSchema = new mongoose.Schema({
    child: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'child'
    }
});

const childSchema = new mongoose.Schema({
    active: {
        type: Boolean,
        default: true
    }
});

I tried with populate and match ( .populate({path:'child', match: {active: true}}) ) but I'm getting all the users with the property child as null if not active. I need only the users with an active child. All my researches head to use the dot syntax, but for any reason I get an empty array. See below:

let usersWithActiveChild = await User.find({'child.active': true}));
console.log(usersWithActiveChild) // --> displays '[]' 

Thanks for your help!

When you use a ref to refer to another schema, Mongoose stores the documents in separate collections in MongoDB.

The actual value stored in the child field of the user document is a DBRef .

If you were to look at the data directly in MongoDB you would find something similar to this:

User collection

{
  _id: ObjectId("5a934e000102030405000000")
  child: DBRef("child",ObjectId("5a934e000102030405000001"),"DatabaseName")
}

Child collection:

{

  _id: ObjectId("5a934e000102030405000001"),
  active: true
}

When you populate the user object, Mongoose fetches the user document, and then fetches the child. Since the user documents have been retrieved already, the match in the populate call filters the children, as you noted.

The dotted notation 'child.active' can only be used if the child is stored in MongoDB as a subdocument, like

{
  _id: ObjectId("5a934e000102030405000000")
  child:{
    _id: ObjectId("5a934e000102030405000001"),
    active: true
  }
}

But your child is defined as a ref, so this will not be the case.

In order to filter the list of user documents based on the content of the referenced child, you will need to either
- populate with match as you have done and then filter the result set, or
- aggregate the user collection, lookup the child documents, and then match the child field.

This can be accomplished easily by using aggregation framework.

First we join two collections with $lookup . Lookup result is array, but our relation with User and Child is one to one, so we get the first item by using $arrayElemAt: ["$child", 0] .

And lastly, we apply our filter "child.active": true, using $match .

Playground

  let usersWithActiveChild = await User.aggregate([
    {
      $lookup: {
        from: "childs",    //must be PHYSICAL collection name
        localField: "child",
        foreignField: "_id",
        as: "child",
      },
    },
    {
      $addFields: {
        child: {
          $arrayElemAt: ["$child", 0],
        },
      },
    },
    {
      $match: {
        "child.active": true,
      },
    },
  ]);

Sample docs:

db={
  "users": [
    {
      "_id": ObjectId("5a834e000102030405000000"),
      "child": ObjectId("5a934e000102030405000000")
    },
    {
      "_id": ObjectId("5a834e000102030405000001"),
      "child": ObjectId("5a934e000102030405000001")
    },
    {
      "_id": ObjectId("5a834e000102030405000002"),
      "child": ObjectId("5a934e000102030405000002")
    },

  ],
  "childs": [
    {
      "_id": ObjectId("5a934e000102030405000000"),
      "active": true
    },
    {
      "_id": ObjectId("5a934e000102030405000001"),
      "active": false
    },
    {
      "_id": ObjectId("5a934e000102030405000002"),
      "active": true
    }
  ]
}

Output:

[
  {
    "_id": ObjectId("5a834e000102030405000000"),
    "child": {
      "_id": ObjectId("5a934e000102030405000000"),
      "active": true
    }
  },
  {
    "_id": ObjectId("5a834e000102030405000002"),
    "child": {
      "_id": ObjectId("5a934e000102030405000002"),
      "active": true
    }
  }
]

Or as a better approach would be first getting activ childs, and then lookup with users like this:

db.childs.aggregate([
  {
    $match: {
      "active": true
    }
  },
  {
    $lookup: {
      from: "users",
      localField: "_id",
      foreignField: "child",
      as: "user"
    }
  }
])

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM