在填充mongoose之后查找

25

我在使用mongoose进行关联查询后,无法通过值匹配在文档中查询。

我的schemas大致如下:

var EmailSchema = new mongoose.Schema({
  type: String
});

var UserSchema = new mongoose.Schema({
  name: String,
  email: [{type:Schema.Types.ObjectId, ref:'Email'}]
});

例如,我想获取所有电子邮件类型为“Gmail”的用户。

以下查询返回空结果:

Users.find({'email.type':'Gmail').populate('email').exec( function(err, users)
    {
      res.json(users);
    });

我不得不像这样使用JS过滤结果:

users = users.filter(function(user)
        {
          for (var index = 0; index < user.email.length; index++) {
            var email = user.email[index];
            if(email.type === "Gmail")
            {
              return true;
            }
          }
          return false;
        });

有没有办法直接从mongoose查询类似这样的内容?

3个回答

37

@Jason Cust 已经解释得很好了 - 在这种情况下,最好的解决方案通常是更改架构,以防止通过存储在单独集合中的文档属性来查询 Users

但是,这里是我能想到的最佳解决方案,不过它不会强制你这样做(因为你在评论中说你不能这样做)。

Users.find().populate({
  path: 'email',
  match: {
    type: 'Gmail'
  }
}).exec(function(err, users) {
  users = users.filter(function(user) {
    return user.email; // return only users with email matching 'type: "Gmail"' query
  });
});
我们在这里做的是只填充与额外查询(在.populate()调用中的match选项)匹配的电子邮件 - 否则Users文档中的电子邮件字段将被设置为null。
剩下的就是对返回的用户数组进行.filter,就像您原来的问题一样 - 只需进行非常通用的简单检查即可。正如您所看到的 - 要么电子邮件存在,要么不存在。

11
如果“users”集合很大,采用这种方法可能存在一些问题,我只是想提出一点关注。 - Jason Cust
1
@JasonCust: 真的!这可能是为什么最好的选择仍然是正确修改模式(如果您已经在数据库中有部分工作应用程序和一些数据,这可能不太有趣)。我也考虑过另一种解决方案:1.获取正确过滤的电子邮件的 _idEmails.find() ),2.仅加载与其匹配的电子邮件用户(使用 $in 运算符的 Users.find() )。如果您有兴趣,我也可以发布此解决方案的示例。一方面,没有加载所有用户文档。另一方面,它似乎更像是一个hack-ish/workaround-ish解决方案。 - bardzusny
1
但是这种方法在分页时不起作用。我也陷入了这种情况,需要对数据进行分页处理。现在的情况是,在第一页上我得到了一个空数组,但在第二页上我得到了筛选后的数据。有人能帮忙吗? - Tarun Chauhan
我浪费了太多时间来访问填充路径的字段。虽然我可以正确记录填充字段“User”,但如果我直接尝试访问其中的字段“user.firstName”,它会抛出错误。属性“firstName”在类型“string”上不存在,因为它是TS接口和模型上的引用类型。//正确记录 { user:{ firstName:"Hasan", lastName:"Mehmet" } }我只是累了,打算像你一样过滤结果数组。 - yalcinozer

10
Mongoose的populate函数不会直接在Mongo中执行。相反,在初始find查询返回一组文档后,populate将创建一个单独的find查询数组,用于执行引用集合,然后将结果合并回原始文档。因此,实质上,您的find查询正在尝试使用引用文档的属性(尚未获取,因此为undefined)来过滤原始结果集。
在这种情况下,似乎更适合将电子邮件存储为子文档数组,而不是单独的集合以实现您想要做的事情。此外,作为一般文档存储设计模式之一,这是存储数组作为子文档的用例之一:有限的大小和非常少的修改。
更新模式为:
var EmailSchema = new mongoose.Schema({
  type: String
});

var UserSchema = new mongoose.Schema({
  name: String,
  email: [EmailSchema]
});

然后以下查询应该可以工作:
Users.find({'email.type':'Gmail').exec(function(err, users) {
  res.json(users);
});

2
我很感激关于populate如何工作的解释,这非常有见地。我所举的例子是一个简化版,我的实际用例确实需要引用文档,因此我必须找到一种不改变模型的解决方案。 - monokh
3
目前在MongoDB中并没有“join”的概念,因此MongooseJS无法做更多的事情,只能按照当前的设计来处理。我认为尝试以这种方式查询集合的一个问题是,如果集合很大,填充操作将会是一项代价高昂的操作。如果必须使用这种模式,建议考虑使用流和过滤器来避免在填充期间造成瓶颈。 - Jason Cust

0

除了使用Aggregate,我找不到其他解决方案。虽然会更麻烦,但我们将使用Lookup。

{
       $lookup:
         {
           from: <collection to join>,
           localField: <field from the input documents>,
           foreignField: <field from the documents of the "from" collection>,
           as: <output array field>
         }
}

虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。仅有链接的答案如果链接页面发生更改可能会变得无效。 - Vimal Patel
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。-【来自审查】 - Shashank Gb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接