Moongoose aggregate $match does not match id's

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

Upvotes: 58

Views: 59755

Answers (7)

Pritam Gayen
Pritam Gayen

Reputation: 1

//this works

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

Upvotes: 0

300baud
300baud

Reputation: 560

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
}

Upvotes: 1

Ahsan Khan
Ahsan Khan

Reputation: 830

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.

Upvotes: 0

Jafar Amini
Jafar Amini

Reputation: 415

instead of:

$match: { _id: "6230415bf48824667a417d56" }

use:

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

Upvotes: 4

Amit Kumar
Amit Kumar

Reputation: 189

  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.

Upvotes: 19

Sehrish Waheed
Sehrish Waheed

Reputation: 1555

You can simply convert your id to

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

and then match

 { $match: { _id: id } },

Upvotes: 10

Blakes Seven
Blakes Seven

Reputation: 50406

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.

Upvotes: 125

Related Questions