Moongoose聚合 $match无法匹配id

55

我想通过id(56e641d4864e5b780bb992c656e65504a323ee0812e511f2)展示产品,并在折扣后显示价格。

我可以使用聚合来计算最终价格,但这会返回集合中的所有文档,如何使它只返回匹配的id。

"_id" : ObjectId("56e641d4864e5b780bb992c6"), 
"title" : "Keyboard", 
"discount" : NumberInt(10),
"price" : NumberInt(1000)

"_id" : ObjectId("56e65504a323ee0812e511f2"), 
"title" : "Mouse", 
"discount" : NumberInt(0),
"price" : NumberInt(1000)

"_id" : ObjectId("56d90714a48d2eb40cc601a5"), 
"title" : "Speaker", 
"discount" : NumberInt(10),
"price" : NumberInt(1000)

这是我的查询

productModel.aggregate([
        {
            $project: {
                title   : 1,
                price: {
                    $cond: {
                        if: {$gt: ["$discount", 0]}, then: {$subtract: ["$price", {$divide: [{$multiply: ["$price", "$discount"]}, 100]}]}, else: "$price"
                    }

                }
            }
        }
    ], function(err, docs){
        if (err){
            console.log(err)
        }else{
            console.log(docs)
        }
    })

如果我加上这个$in查询,它会返回空数组

productModel.aggregate([
            {
                $match: {_id: {$in: ids}}
            },
            {
                $project: {
                    title   : 1,
                    price: {
                        $cond: {
                            if: {$gt: ["$discount", 0]}, then: {$subtract: ["$price", {$divide: [{$multiply: ["$price", "$discount"]}, 100]}]}, else: "$price"
                    }

                }
            }
        }
    ], function(err, docs){
        if (err){
            console.log(err)
        }else{
            console.log(docs)
        }
    })
5个回答

119
你的ids变量将由“字符串”构成,而不是ObjectId值。
Mongoose会自动将字符串值转换为正确的类型以进行常规查询中的ObjectId,但是在聚合管道中不会发生这种情况,如问题#1399所述。
相反,您必须手动执行正确的类型转换:
ids = ids.map(function(el) { return mongoose.Types.ObjectId(el) })

然后您可以在管道阶段中使用它们:
{ "$match": { "_id": { "$in": ids } } }

原因在于聚合管道“通常”会改变文档结构,因此mongoose不假设“模式”适用于任何给定管道阶段的文档。
可以争论的是,当第一个管道阶段是$match阶段时,应该这样做,因为实际上文档没有被改变。但现在还没有这样做。
任何可能是“字符串”或至少不是正确BSON类型的值都需要手动转换才能匹配。

就是这样,现在它可以工作了。但通常我会将ID作为字符串传递给findOneAndUpdate或其他查询,而且它也能正常工作,问题只出现在聚合操作中吗? - Muhammad Fasalir Rahman
1
@MuhammadFasalirRahman 这正是我所回答的。.find() 可以使用 Schema,当然 _id 字段的默认类型是 ObjectId。聚合管道不使用 Schema,这一点我已经解释过了。 - Blakes Seven
3
我花了三天时间才到这里,叹气...我在模式的静态方法中创建了一个lambda,即const castUserId = (userId) => mongoose.Types.ObjectId(userId),现在我很高兴。 - timebandit
2
哈哈,这个问题浪费了好几个小时……感谢解决方案。 - Abhishek Pankar
很好的解决方案,但 $in 是否比精确匹配慢的可能性有多大? - shramee

18
  1. 在mongoose中,使用find({_id:'606c1ceb362b366a841171dc'})可以正常工作。

  2. 但是,在使用聚合函数时,我们需要使用mongoose对象将_id转换为对象,例如:

$match: { "_id": mongoose.Types.ObjectId("606c1ceb362b366a841171dc") }

这样就可以正常工作了。


太棒了!似乎比使用$in更好! - shramee

9

你可以简单地将你的id转换为

 let id = mongoose.Types.ObjectId(req.query.id);

然后进行匹配

 { $match: { _id: id } },

3

改为:

$match: { _id: "6230415bf48824667a417d56" }

用途:

$match: { _id: ObjectId("6230415bf48824667a417d56") }

嗨,Jafar,包括 ObjectId 的来源会让跟踪更容易 ;) 已点赞 :) - shramee

0

使用这个

$match: { $in : [ {_id: mongoose.Types.ObjectId("56e641d4864e5b780bb992c6 ")}, {_id: mongoose.Types.ObjectId("56e65504a323ee0812e511f2")}] }

因为Mongoose在常规查询中将字符串值自动转换为ObjectId的正确类型,但在聚合管道中不会发生这种情况。因此,我们需要在管道查询中定义ObjectId转换。

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