Reputation: 1165
I want to create a query that will return a collection of incomes for a given year. My schema is like this:
export const IncomeSchema = new mongoose.Schema({
name: { type: String, required: true },
amount: { type: Number, required: true },
amountAfterTax: { type: Number, required: false },
dateFrom: {
month: Number,
year: Number
},
dateTo: {
month: Number,
year: Number
},
isMonthly: { type: Boolean, required: true },
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}, { toJSON: { virtuals: true } });
// somehow use the req.params.year
return DB.Incomes.find({ userId: req.user ).exec().then(incomes => {
let incomesForMonth = incomes.filter(income => !income.isMonthly ? (income.dateFrom.year== req.params.year)
: (income.dateFrom.year <= req.params.year && income.dateTo.year >= req.params.year)
});
The thing is that i want the query to run that way: - if income isMonthly then the year has to be equal dateFrom.year - if income !isMonthly then the year has to be between dateFrom.year and dateTo.year
Till now i was returning everything from db and processed them in memory, which is not a greatest solution. In SQL Server i would use the CASE WHEN statement. What can i use in mongoose?
Upvotes: 2
Views: 1292
Reputation: 103375
Unfortunately with your current version I can't think of any perform-ant solution that has native MongoDB operators which mimic the case statements/expressions.
However, for MongoDB Server 3.4 and mongoose >=4.7.3, there is a solution where you can use the aggregation framework in particular the new $addFields
stage and $switch
operator for such queries.
The $addFields
stage is equivalent to a $project
stage that explicitly specifies all existing fields in the input documents and adds new fields to documents. In your case you need it to create a new field field that holds the filter logic and is then used in your $match
query.
The $switch
operator evaluates a series of case expressions. When it finds an expression which evaluates to true, $switch
executes a specified expression and breaks out of the control flow.
Let's demonstrate this with a mongo shell example:
Populate Test Collection:
db.test.insert([
{
name: "foo",
amount: 4,
amountAfterTax: 3.2,
dateFrom: {
month: 11,
year: 2016
},
dateTo: {
month: 2,
year: 2017
},
isMonthly: true,
userId: ObjectId("5864b49ab5a589b63ee298e8")
},
{
name: "test",
amount: 547.74,
amountAfterTax: 507.15,
dateFrom: {
month: 4,
year: 2016
},
dateTo: {
month: 4,
year: 2017
},
isMonthly: true,
userId: ObjectId("5864b49ab5a589b63ee298e8")
},
{
name: "bar",
amount: 56,
amountAfterTax: 47.54,
dateFrom: {
month: 5,
year: 2016
},
dateTo: {
month: 7,
year: 2016
},
isMonthly: false,
userId: ObjectId("5864b49ab5a589b63ee298e8")
}
])
Run Aggregate Query
year = 2016;
db.test.aggregate([
{
"$addFields": {
"incomesForMonth": {
"$switch": {
"branches": [
{
"case": "$isMonthly", /* same as "case": { "$eq": [ "$isMonthly", true ] }, */
"then": { "$eq": [ "$dateFrom.year", year ] }
},
{
"case": { "$eq": [ "$isMonthly", false ] },
"then": {
"$and": [
{ "$lte": [ "$dateFrom.year", year ] },
{ "$gte": [ "$dateTo.year", year ] }
]
}
}
]
}
}
}
},
{ "$match": { "incomesForMonth": true } }
])
Sample Output
/* 1 */
{
"_id" : ObjectId("586ea7bafedfbcfd0ed15f9a"),
"name" : "foo",
"amount" : 4.0,
"amountAfterTax" : 3.2,
"dateFrom" : {
"month" : 11.0,
"year" : 2016.0
},
"dateTo" : {
"month" : 2.0,
"year" : 2017.0
},
"isMonthly" : true,
"userId" : ObjectId("5864b49ab5a589b63ee298e8"),
"incomesForMonth" : true
}
/* 2 */
{
"_id" : ObjectId("586ea7bafedfbcfd0ed15f9b"),
"name" : "test",
"amount" : 547.74,
"amountAfterTax" : 507.15,
"dateFrom" : {
"month" : 4.0,
"year" : 2016.0
},
"dateTo" : {
"month" : 4.0,
"year" : 2017.0
},
"isMonthly" : true,
"userId" : ObjectId("5864b49ab5a589b63ee298e8"),
"incomesForMonth" : true
}
/* 3 */
{
"_id" : ObjectId("586ea7bafedfbcfd0ed15f9c"),
"name" : "bar",
"amount" : 56.0,
"amountAfterTax" : 47.54,
"dateFrom" : {
"month" : 5.0,
"year" : 2016.0
},
"dateTo" : {
"month" : 7.0,
"year" : 2016.0
},
"isMonthly" : false,
"userId" : ObjectId("5864b49ab5a589b63ee298e8"),
"incomesForMonth" : true
}
Upvotes: 2