Moongoose aggregate $match does not match id's
Asked Answered
R

7

58

I want to show products by ids (56e641d4864e5b780bb992c6 and 56e65504a323ee0812e511f2) and show price after subtracted by discount if available.

I can count the final price using aggregate, but this return all document in a collection, how to make it return only the matches ids

"_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)

this is my query

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)
        }
    })

and if i add this $in query, it returns empty array

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)
        }
    })
Riobard answered 24/3, 2016 at 4:27 Comment(0)
K
125

Your ids variable will be constructed of "strings", and not ObjectId values.

Mongoose "autocasts" string values for ObjectId into their correct type in regular queries, but this does not happen in the aggregation pipeline, as in described in issue #1399.

Instead you must do the correct casting to type manually:

ids = ids.map(function(el) { return mongoose.Types.ObjectId(el) })

Then you can use them in your pipeline stage:

{ "$match": { "_id": { "$in": ids } } }

The reason is because aggregation pipelines "typically" alter the document structure, and therefore mongoose makes no presumption that the "schema" applies to the document in any given pipeline stage.

It is arguable that the "first" pipeline stage when it is a $match stage should do this, since indeed the document is not altered. But right now this is not how it happens.

Any values that may possibly be "strings" or at least not the correct BSON type need to be manually cast in order to match.

Kaczmarek answered 24/3, 2016 at 4:52 Comment(5)
That's it, it now works. but usually i throw the id as string in findOneAndUpdate or other query, and it works fine, is the problem only happen in aggregate?Riobard
@MuhammadFasalirRahman This is exactly what I answered with. A .find() can use the Schema which of course has a default type of ObjectId for the _id field. Aggregation pipelines do not use the Schema, as I actually already explained.Kaczmarek
took me three days to get here; sigh.. I created a lamda inside my schema static method const castUserId = (userId) => mongoose.Types.ObjectId(userId) now i'm happyMonition
lol....hours wasted on this issue....Thanks for the solutionWrote
Nice work around, but any chance $in could be slower than exact match?Mazer
C
19
  1. In the mongoose , it work fine with find({_id:'606c1ceb362b366a841171dc'})

  2. But while using the aggregate function we have to use the mongoose object to convert the _id as object eg.

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

This will work fine.

Cheerio answered 8/4, 2021 at 5:14 Comment(1)
Awesome! Seems better than using $in!Mazer
C
10

You can simply convert your id to

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

and then match

 { $match: { _id: id } },
Compel answered 2/4, 2021 at 17:39 Comment(0)
L
4

instead of:

$match: { _id: "6230415bf48824667a417d56" }

use:

$match: { _id: ObjectId("6230415bf48824667a417d56") }
Levorotatory answered 15/3, 2022 at 8:4 Comment(1)
Hi Jafar, including where ObjectId comes from would make it easier to follow ;) Upvoted :)Mazer
E
1

Using mongoose 8+ and typescript, I use

let newId = new mongoose.mongo.ObjectId(id);

and then the match stage looks like this

"$match" : {
    "_id" : newId
}
Existentialism answered 2/1, 2024 at 4:58 Comment(1)
Yeah, I am using too but getting "The signature '(inputId: number): ObjectId' of 'mongoose.mongo.ObjectId' is deprecated." this over ObjectId. I m not sure this is give me lots of problem next times.Bobo
W
0

Use this

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

Because Mongoose autocasts string values for ObjectId into their correct type in regular queries, but this does not happen in the aggregation pipeline. So we need to define ObjectId cast in pipeline queries.

Waterworks answered 9/8, 2022 at 14:37 Comment(0)
C
0

//this works

{ $match: { createdBy: new mongoose.Types.ObjectId(req.user.userId) } }

Cancer answered 17/7, 2024 at 21:12 Comment(1)
Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?Stepaniestepbrother

© 2022 - 2025 — McMap. All rights reserved.