mongodb aggregation where document field is less than another field - mongodb

Using mongoose, I'm trying to make a query that searches for tasks where timeSpent is greater than timeBilled.
Task schema:
const myTaskSchema = new Schema({
date: { type: Date, default: Date.now },
timeSpent: { type: Number },
timeBilled: { type: Number }
})
The query I've tried:
myTaskSchema.aggregate([
{
$match: {
timeSpent: { $gt: '$timeBilled' }
}
}
])
.then(data => {
console.log(data)
})
But I'm getting zero results (I know there should be results)
NOTE: Not every task has a timeSpent or timeBilled.field if that matters.

here is my dirty solution. It'd be nice if I didnt have to add a field but this gets me where I want to be.
myTaskSchema.aggregate([
{
$addFields: {
needToBill: { $gt: ['$timeSpent', '$timeBilled'] }
}
},
{
$match: {
needToBill: true
}
},
{
$project: {
timeSpent: 1,
timeBilled: 1
}
}
])

Related

Conditional operator in mongoose

I am trying to conditionally execute two different mongoose operators but it just return no error and doesn't work to update the document.
My Schema:
const CartSchema: Schema<Document<ICart>> = new Schema({
clientID: { type: String, required: true },
sourceID: { type: String, required: true },
items: { type: Array },
source: { type: String, required: true },
}, { collection: "carts", timestamps: true });
The way I am trying to implement that:
await CartModel.findOneAndUpdate(
{ sourceID: clientID, 'items.id': Types.ObjectId(itemID) },
{
$cond: {
if: {
$eq: 1,
},
then: { $pull: { 'items.$.id': Types.ObjectId(itemID) }},
else: { $inc: { 'items.$.amount': -1 }},
}
},
{ new: true }
).lean({ virtuals: true })
And I also tried to have this kind of query: { sourceID: clientID } but it didn't help. I thought maybe I could not find the element and it just silently pass through.
The main idea here of what I am gonna do is - have a conditional mongoose request where I'll either remove the object from the array if the current value in the field amount is equal to 1, or decrement the value to -1.
Apparently, the $cond operator works only in the aggregation framework but in my Atlas tier I cannot check if the query works properly, but I suppose it should look something like this:
await CartModel.aggregate([
{ $match: {
sourceID: clientID,
'items.id': Types.ObjectId(itemID)
}
},
{
$cond: {
if: {
$eq: 1,
},
then: { $pull: { 'items.$.id': Types.ObjectId(itemID) }},
else: { $inc: { 'items.$.amount': -1 }},
}
}
])

How can i limit each ID i pass to $in operator inside the $match stage to only 4 elements

I have an aggregate like this :
const files = await File.aggregate([
{
$match: { facilityId: { $in: facilities } }
},
{
$sort: { createdAt: 1 }
},
{
$project: {
file: 0,
}
}
])
And i would like to have each "facility" return only 4 files, i used to do something like facilities.map(id => query(id)) but id like to speed things up in production env.
Using $limit will limit the whole query, that's not what i want, i tried using $slice in the projection stage but got en error :
MongoError: Bad projection specification, cannot include fields or add computed fields during an exclusion projection
how can i achieve that in a single query ?
Schema of the collections is :
const FileStorageSchema = new Schema({
name: { type: String, required: true },
userId: { type: String },
facilityId: { type: String },
patientId: { type: String },
type: { type: String },
accessed: { type: Boolean, default: false, required: true },
file: {
type: String, //
required: true,
set: AES.encrypt,
get: AES.decrypt
},
sent: { type: Boolean, default: false, required: true },
},
{
timestamps: true,
toObject: { getters: true },
toJSON: { getters: true }
})
And i would like to returns all fields except for the file fields that contains the encrypted blob encoded as base64.
Also: i have the feeling that my approach is not correct, what i really would like to get is being able to query all facilityId at once but limited to the 4 latest file created for each facility, i though using an aggregate would help me achieve this but im starting to think it's not how its done.
From the question the schema is not clear. So I have two answers based on two Schemas. Please use what works for you
#1
db.collection.aggregate([
{
$match: {
facilityId: {
$in: [
1,
2
]
}
}
},
{
$group: {
_id: "$facilityId",
files: {
$push: "$file"
}
}
},
{
$project: {
files: {
$slice: [
"$files",
0,
4
],
}
}
}
])
Test Here
#2
db.collection.aggregate([
{
$match: {
facilityId: {
$in: [
1,
2
]
}
}
},
{
$project: {
facilityId: 1,
file: {
$slice: [
"$file",
4
]
}
}
}
])
Test Here

How to create multiple groups from a single selection in Mongoose?

I would like to select all events with a certain type from an events collection and then return 2 different groups using a single selection.
For example I currently have the following 2 selections:
const sessions = await Event.aggregate([
{
$match: {
isAdmin: { $ne: true }
}
}, {
$group: {
_id: '$sessionId'
}
}
]);
const users = await Event.aggregate([
{
$match: {
isAdmin: { $ne: true }
}
}, {
$group: {
_id: '$userId'
}
}
]);
I would like to achieve an end result of:
{
numberOfSessions: sessions.length,
numberOfUsers: users.length
}
By using a single query.
Thanks in advance!
You could use facet aggregation pipeline which will provide the capability to create multi-dimensions data within a single stage. For Eg:
const sessions = await Event.aggregate([
{
$match: {
isAdmin: { $ne: true }
}
}, {
$facet: {
sessions: [{
$sortByCount: "$sessionId"
}],
users: [{
$sortByCount: "$userId"
}]
}
}
]);

MongoDB + Mongoose Aggregate w/ Asnyc

I've got the following route in my express file, which takes parameters passed in from a middleware function and queries my backend MongoDB database. But for some reason, it only ever returns an empty array.
I'd like to convert the Mongoose model that allows me to use aggregate functions into async/await to conform with the rest of my code. It's online here.
module.exports = {
search: asyncWrapper(async(req, res, next) => { // Retrieve and return documents from the database.
const {
filterTarget,
filter,
source,
minDate,
maxDate,
skip,
limit,
sortBy,
sortOrder
} = req.search;
try {
const mongoData = await Model.aggregate([
{
$match: {
date: {
$gt: minDate, // Filter out by time frame...
$lt: maxDate
}
}
},
{
$match: {
[filterTarget]: filter // Match search query....
}
},
{
$set: {
[filterTarget]: { $toLower: `$${filterTarget}` } // Necessary to ensure that sort works properly...
}
},
{
$sort: {
[sortBy]: sortOrder // Sort by date...
}
},
{
$group: {
_id: null,
data: { $push: "$$ROOT" }, // Push each document into the data array.
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
count: 1,
data: {
$slice: ["$data", skip, limit]
},
}
}
])
return res.status(200).json({ data: mongoData.data || [], count: mongoData.count || 0 });
} catch (err) {
next(err);
}
})
};
For some reason, the route is only returning an empty array every time. I've double and triple checked my variables, they are not the problem.
How can I use the Mongoose.aggregate() function in an async await route?

MongoDB Find/Get Last Item w/ Parent Name and Object Array Value

Schema({
name: String,
items: [{
body: String,
active: Boolean,
_id: mongoose.Schema.Types.ObjectId,
added: { type: Date, default: Date.now }
}],
date: { type: Date, default: Date.now }
});
I want to get the last item in 'items' with a 'name' where 'items.active' is true.
Tried many different configurations.. this is basically what i want to do:
List.find({ name: LIST_NAME, 'items.active': true }, { items: { $slice: -1 } }, function(err, result){...do stuff w/ result.body...});
Appreciate any help, thx.
Using Aggregation was ultimately the solution:
List.aggregate(
{ $match: { name: req.params.list }},
{ $unwind: '$items' },
{ $match: { 'items.active': true }},
{ $sort: { 'items.added': -1 }},
{ $limit: 1 }, function (err, result) {
if(err) {
res.status(500);
res.send(err);
} else {
console.log(result);
res.send(result[0].items.body);
}
});