I have this Event array but can't figure out how to query into the 'guests' nested array and do two things.
count the number of guests
count the number of attended guests (marked 'Y')
{ _id: new ObjectId('1'),
name: event1,
guests: [
{
phone: +12222222222,
_id: new ObjectId,
attended: 'Y'
},
{
phone: +12344466666,
_id: new ObjectId,
attended: 'Y'
},
{ phone: +11234567890,
_id: new ObjectId,
attended:
},
{
phone: +14443332222,
_id: new ObjectId,
attended: 'Y'
},
{ phone: +19090909090,
_id: new ObjectId
attended:
}
],
},
{ _id: new ObjectId('2'),
name: event2,
guests: [
{
phone: +11111111111,
_id: new ObjectId,
attended:
},
{
phone: +12222222222,
_id: new ObjectId,
attended: 'Y'
},
{
phone: +133333333333,
_id: new ObjectId,
attended: 'Y'
}
],
},
My code below is on its 20th iteration without getting any closer.
const event = await Event.findById(req.params.id);
For MongoDB query, you can use $size for calculating the size of the array field and $filter to filter specific document(s) that matched the criteria in the projection.
db.collection.find({
_id: "1"
},
{
_id: 1,
name: 1,
guests: 1,
totalGuests: {
$size: "$guests"
},
attendedGuests: {
$size: {
"$filter": {
input: "$guests",
cond: {
$eq: [
"$$this.attended",
"Y"
]
}
}
}
}
})
Sample Mongo Playground
Yong Shun helped above but didn't get 100% of the way -- here is the full answer in case anyone encounters something like this in the future:
module.exports.showEvent = async (req, res) => {
const event = await Event.findById(req.params.id).populate('artist');
const { guest_id } = req.cookies;
let totalGuests = 0;
let attendedGuests = 0;
const eventData = await Event.aggregate([
{
"$match": {
"_id": objectId(req.params.id)
}
},
{
$project: {
_id: 1,
name: 1,
guests: 1,
totalGuests: { $cond: { if: { $isArray: "$guests" }, then: { $size: "$guests" }, else: "NA" } },
attendedGuests: {
$size: {
$filter: {
input: "$guests",
as: "guest",
cond: {
$and: [{
$eq: ["$$guest.attended", "Y"]
}]
}
}
}
}
}
}
])
if (eventData && Array.isArray(eventData) && eventData.length > 0) {
totalGuests = eventData[0].totalGuests;
attendedGuests = eventData[0].attendedGuests;
}
if (!event) {
req.flash('error', 'Cannot find that Event');
return res.redirect('/events');
}
res.render('events/show', { event, eventData });
console.log(totalGuests, attendedGuests);
};
I have "Offers" and "Requests" collections, I need to get all offers that user made, group them by requests and find the lowest "Offer.price" on each request, each offer has requestId field.
I am using aggregate to solve this,
db.Offer.aggregate([{
$match: {
ownerId: mongoose.Types.ObjectId(req.params.ownerId)
}
},
{
$group: {
_id: "$requestId",
price: {
$min: "$price"
}
}
}
])
and This is what i get :
[ { _id: 5dc47241af1406031489c65c, price: 14 },
{ _id: 5dc47241af1406031489c653, price: 3 },
{ _id: 5dc47241af1406031489c656, price: 5 },
{ _id: 5dc8add63f73953ff408f962, price: 6 },
{ _id: 5dc8add63f73953ff408f969, price: 22 },
{ _id: 5dc47241af1406031489c658, price: 1 } ]
Now I want to populate these with rest of the data from "Offer"
const OfferSchema = new Schema({
requestId: {
type: Schema.Types.ObjectId,
ref: 'Request'
},
ownerId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'User'
},
price: {
type: Number,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
isBest: {
type: Boolean,
default: false
},
isWinner: {
type: Boolean,
default: false,
}
});
What would be best way to do something like this?
Thank you for your help!
Consider the following dataset:
db.dum.insert({ownerId:1, requestId:'a', price:3, createdAt:3, isWinner:true})
db.dum.insert({ownerId:1, requestId:'a', price:1, createdAt:1, isWinner:false})
db.dum.insert({ownerId:1, requestId:'a', price:2, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:4, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:5, createdAt:1, isWinner:false})
db.dum.insert({ownerId:2, requestId:'b', price:5, createdAt:1, isWinner:false})
You could use $reduce
Here, for a grouping id, we keep all matching documents as an array (candidates).
On the project stage, for each group we iterate through the array, and reduce it to the minimal element found (by price that is)
db.dum.aggregate([{
$match: {
ownerId: 1
}
},
{
$group: {
_id: "$requestId",
candidates: { $push:'$$ROOT'}
}
},
{
$project:{
item: {
$reduce: {
input: '$candidates',
initialValue: '$candidates.0',
in: {
$cond: {
if: {
$lt: ['$$value.price', '$$this.price']
},
then:'$$value',
else:'$$this'
}
}
}
}
}
},
{
$replaceRoot:{newRoot:'$item'}
}
]).toArray()
output:
[
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb507"),
"ownerId" : 1,
"requestId" : "b",
"price" : 4,
"createdAt" : 2,
"isWinner" : true
},
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb505"),
"ownerId" : 1,
"requestId" : "a",
"price" : 1,
"createdAt" : 1,
"isWinner" : false
}
]
When I run the aggregate in the mongo throw terminal it returns the correct data, but when I run the same aggregate in the code it returns an error.
I had tried to use just match and group to test, but I still get the same error even if I used mongoose functions but without any luck
I plan to try to improve my aggregate game but for now, I need this to run and I'm stuck here for the last couple of days.
"APIError: Arguments must be aggregate pipeline operators\n at new ExtendableError (/vagrant/server/helpers/APIError.js:15:11)\n at new APIError (/vagrant/server/helpers/APIError.js:31:5)\n at app.use (/vagrant/config/express.js:64:22)\n at Layer.handle_error (/vagrant/node_modules/express/lib/router/layer.js:71:5)\n at trim_prefix (/vagrant/node_modules/express/lib/router/index.js:315:13)\n at /vagrant/node_modules/express/lib/router/index.js:284:7\n at Function.process_params (/vagrant/node_modules/express/lib/router/index.js:335:12)\n at next (/vagrant/node_modules/express/lib/router/index.js:275:10)\n at /vagrant/node_modules/express/lib/router/index.js:635:15\n at Immediate.next (/vagrant/node_modules/express/lib/router/index.js:260:14)\n at Immediate._onImmediate (/vagrant/node_modules/express/lib/router/index.js:635:15)\n at runCallback (timers.js:706:11)\n at tryOnImmediate (timers.js:676:5)\n at processImmediate (timers.js:658:5)"
customer model
const mongoose = require('mongoose');
const customerContactsSchema = require('./customerContacts.model');
const appSettings = require('../../config/appSettings');
/**
* Customer Schema
*/
const CustomerSchema = new mongoose.Schema({
avatar: {
type: String,
required: false
},
firstName: {
type: String,
required: false
},
lastName: {
type: String,
required: false
},
password: {
type: String,
required: false,
select: false
},
address: {
country: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
required: false
},
city: {
type: mongoose.Schema.Types.ObjectId,
ref: 'City',
required: false
},
},
language: {
type: String,
required: true,
enum: appSettings.languages,
default: 'english'
},
mobileNumber: {
number: {
type: String,
required: true,
trim: true
},
country: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
required: true
}
},
email: {
type: String,
lowercase: true,
trim: true,
required: false
},
birthday: {
type: Date,
required: false
},
gender: {
type: String,
enum: ['male', 'female'],
required: false
},
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: false
}],
contacts: [customerContactsSchema],
favourites: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Company',
required: false
}],
transactions: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'CustomerTransaction',
required: false
}],
googleId: {
type: String
},
facebookId: {
type: String
},
twitterId: {
type: String
},
fcmToken: {
type: String
},
otpToken: {
type: Number,
required: false,
default: 1234
},
otpTokenExpiration: {
type: Date,
required: false
},
status: {
type: String,
enum: ['active', 'pending', 'suspended', 'blocked', 'deleted'],
required: true,
default: 'pending'
}
}, { timestamps: true });
/**
* #typedef Customer
*/
module.exports = mongoose.model('Customer', CustomerSchema);
CustomerTransaction model
const mongoose = require('mongoose');
const locationSchema = require('./location.model');
/**
* CustomerTransaction Schema
*/
const CustomerTransactionSchema = new mongoose.Schema({
company: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Company',
required: true
},
customer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: true
},
type: {
type: String,
// debit - , credit +
enum: ['debit', 'credit'],
required: true
},
expirationDate: {
type: Date,
required: false
},
point: {
amount: {
type: Number,
required: false
},
value: {
type: Number,
required: false
}
},
closeValue: {
type: Number,
required: false
},
closePoints: {
type: Number,
required: false
},
offer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Offer',
required: false
},
voucher: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Voucher',
required: false
},
stamp: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Stamp',
required: false
},
punches: {
type: Number,
required: false
},
date: {
type: Date,
required: false,
default: Date.now()
},
gift: {
type: Boolean,
default: false
},
giftSender: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: false
},
giftReceiver: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: false
},
status: {
type: String,
enum: ['active', 'used', 'sent', 'expired', 'deleted'],
required: true,
default: 'active'
},
location: locationSchema
}, { timestamps: true });
/**
* #typedef CustomerTransaction
*/
module.exports = mongoose.model('CustomerTransaction', CustomerTransactionSchema);
aggregate
db.getCollection('customers').aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
birthday: {
$gte: ISODate('1991-04-29T00:00:00.000Z'),
$lte: ISODate('2019-04-29T00:00:00.000Z'),
},
createdAt: {
$gte: ISODate('1991-04-29T00:00:00.000Z'),
$lte: ISODate('2019-04-29T00:00:00.000Z'),
},
}
},
{
$lookup: {
from: 'customertransactions',
localField: '_id',
foreignField: 'customer',
as: 'CustomerTransaction'
}
},
{
$match: {
'CustomerTransaction.company': ObjectId('5cd2af3a5bfc5b1a40b7de49'),
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerTransaction: 1,
CustomerStampTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{
$and: [
{ $gte: ['$$el.stamp', 1] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', ObjectId('5cd2af3a5bfc5b1a40b7de49')] }
]
},
{
_id: '$$el._id'
},
false
]
}
}
},
[false]
]
},
CustomerPointTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{
$and: [
{ $lt: ['$$el.stamp', 0] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', ObjectId('5cd2af3a5bfc5b1a40b7de49')] }
]
},
{
_id: '$$el._id',
createdAt: '$$el.createdAt',
closePoints: '$$el.closePoints',
},
false
]
}
}
},
[false]
]
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerPointTransaction: 1,
stampCount: {
$cond: {
if: { $isArray: '$CustomerStampTransaction' },
then: { $size: '$CustomerStampTransaction' },
else: 0
}
},
//"CustomerTransaction": "$CustomerTransaction"
}
},
{
$unwind: {
path: '$CustomerPointTransaction',
preserveNullAndEmptyArrays: true
}
},
{ $sort: { 'CustomerPointTransaction.createdAt': 1 } },
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
CustomerPointTransaction: { $last: '$CustomerPointTransaction' }
}
},
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
pointCount: { $first: '$CustomerPointTransaction.closePoints' }
}
},
]);
aggregate result
{ "_id" : ObjectId("5c8918ab9979f345214158ba"), "firstName" : "mohammed", "lastName" : "alqahtani", "stampCount" : 1, "pointCount" : null }
{ "_id" : ObjectId("5cc161039f11d13109185b15"), "firstName" : "Mohammedaadsxxxa", "lastName" : "AlQahtaniss", "stampCount" : 0, "pointCount" : 9 }
code
function search(req, res) {
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
},
{
$lookup: {
from: 'customertransactions',
localField: '_id',
foreignField: 'customer',
as: 'CustomerTransaction'
}
},
{
$match: {
'CustomerTransaction.company': req.company._id,
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerTransaction: 1,
CustomerStampTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{ $and: [
{ $gte: ['$$el.stamp', 1] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', req.company._id] }
] },
{
_id: '$$el._id'
},
false
]
}
}
},
[false]
]
},
CustomerPointTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{ $and: [
{ $lt: ['$$el.stamp', 0] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', req.company._id] }
] },
{
_id: '$$el._id',
createdAt: '$$el.createdAt',
closePoints: '$$el.closePoints',
},
false
]
}
}
},
[false]
]
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerPointTransaction: 1,
stampCount: { $cond: { if: { $isArray: '$CustomerStampTransaction' }, then: { $size: '$CustomerStampTransaction' }, else: 0 } },
//"CustomerTransaction": "$CustomerTransaction"
}
},
{
$unwind: {
path: '$CustomerPointTransaction',
preserveNullAndEmptyArrays: true
}
},
{ $sort: { 'CustomerPointTransaction.createdAt': 1 } },
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
CustomerPointTransaction: { $last: '$CustomerPointTransaction' }
}
},
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
pointCount: { $first: '$CustomerPointTransaction.closePoints' }
}
},
], { cursor: {} })
.then((customers) => {
console.log('kkkkkkkkk');
if (customers) {
res.json(Response.success(customers));
} else {
res.json(Response.success('klklk'));
}
})
.catch(err => res.status(httpStatus.INTERNAL_SERVER_ERROR)
.json(Response.failure(err)));
}
keep in mind even if I run the code like the following it's still throwing the same error
function search(req, res) {
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
], { cursor: {} })
.then((customers) => {
console.log('kkkkkkkkk');
if (customers) {
res.json(Response.success(customers));
} else {
res.json(Response.success('klklk'));
}
})
.catch(err => res.status(httpStatus.INTERNAL_SERVER_ERROR)
.json(Response.failure(err)));
}
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
], { cursor: {} }) // ERROR IS HERE
With mongoose and .aggregate, there is no option parameter like how it is on .find. Instead try this
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
]).cursor()
UPDATED
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
]).exec((error, docs) => {
console.log(docs)
res.json(docs)
})
will this worked with me
async function search(req, res) {
try {
const customers = await Customer.aggregate([
{
$match: {
gender: 'male'
}
}
]
)
.cursor({})
.exec();
customers.get(function(err, ress){
if (ress) {
res.json(Response.success(ress));
} else {
res.json(Response.success('klklk'));
}
});
} catch(e) {
res.status(httpStatus.INTERNAL_SERVER_ERROR) .json(Response.failure(e));
}
}
but I could not figure out why it's working and what was wrong with my previous code
I am needing to filter an array of object by another array of objects. Here is my scenario:
I have a drivers endpoint where I am taking params of tripId, departTime, and returnTime. From there I pull an array of all my drivers. I then use aggregate to pull in the conflicted drivers. These are the two arrays I need to filter by.
router.get("/:id/:departTime/:returnTime", [auth, admin], async (req, res) => {
const trip = await Trip.findById(req.params.id);
if (!trip) return res.status(404).send("Trip with given ID not found");
//console.log(trip);
const allDrivers = await User.find({ isDriver: true });
const conflictedDrivers = await Trip.aggregate([
{
$unwind: "$drivers"
},
{
$match: {
_id: { $ne: trip._id },
$or: [
{
departTime: {
$gte: new Date(req.params.departTime),
$lte: new Date(req.params.returnTime)
}
},
{
returnTime: {
$gte: new Date(req.params.departTime),
$lte: new Date(req.params.returnTime)
}
}
]
}
},
{
$project: {
_id: "$drivers._id",
name: "$drivers.name",
email: "$drivers.email"
}
}
]);
console.log("conflicted drivers: ", conflictedDrivers);
if (conflictedDrivers.length === 0) return res.send(allDrivers);
const availableDrivers = allDrivers.filter(driver => {
return !conflictedDrivers.find(cd => {
return driver._id === cd._id;
});
});
console.log("available drivers: ", availableDrivers);
res.send(availableDrivers);
});
My problem is that the comparisons between conflicted drivers Id and and all drivers Id are not being accurately returned. If I
return cd.email === driver.email
then my my returned filtered array is correct.
Here is my userSchema:
const userSchema = new Schema({
name: {
type: String,
min: 3,
max: 50,
required: true
},
email: {
type: String,
required: true,
min: 5,
max: 255,
unique: true
},
password: {
type: String,
required: true
},
isAdmin: {
type: Boolean,
default: false
},
isSupervisor: {
type: Boolean,
default: false
},
isDriver: {
type: Boolean,
default: false
},
google: {
id: String,
token: String,
email: String,
name: String
}
});
and my tripSchema:
const tripSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
destination: String,
physicalAddress: String,
departTime: Date,
returnTime: Date,
departureLocation: String,
organization: String,
distance: Number,
cost: Number,
occupants: Number,
tripOwner: {
type: new mongoose.Schema({
name: {
type: String,
minlength: 5,
maxlength: 50
},
email: {
type: String,
minlength: 5,
maxlength: 100
}
})
},
phoneNumber: String,
vehicleTypeReq: {
type: new mongoose.Schema({
name: {
type: String
}
})
},
numberOfPrimaryVehicles: Number,
supportVehicles: Number,
estimateNeeded: Boolean,
numberOfDrivers: Number,
totalVehicles: Number,
comments: String,
isDenied: Boolean,
isArranged: {
type: Boolean,
default: false
},
supervisor: {
type: new mongoose.Schema({
name: {
type: String,
minlength: 5,
maxlength: 50
},
email: {
type: String,
minlength: 5,
maxlength: 100
}
})
},
isApproved: {
type: Boolean,
default: false
},
drivers: [userSchema],
vehicles: [vehicleSchema]
});
I would just give in and use the comparison using email but I will need to do a very similar filter using vehicles next.
Am I taking the right approach here? Maybe there is a way to handle this in the mongo query?
I was able to take care of it thanks to Rohit Dalal's suggestion.
router.get("/:id/:departTime/:returnTime", [auth, admin], async (req, res) => {
const trip = await Trip.findById(req.params.id);
if (!trip) return res.status(404).send("Trip with given ID not found");
const conflictedDrivers = await Trip.aggregate([
{
$unwind: "$drivers"
},
{
$match: {
_id: { $ne: trip._id },
$or: [
{
departTime: {
$gte: new Date(req.params.departTime),
$lte: new Date(req.params.returnTime)
}
},
{
returnTime: {
$gte: new Date(req.params.departTime),
$lte: new Date(req.params.returnTime)
}
}
]
}
},
{
$project: {
_id: "$drivers._id",
name: "$drivers.name",
email: "$drivers.email"
}
}
]);
const conflictedDriversIdArray = conflictedDrivers.map(driver => {
return driver._id;
});
const availableDrivers = await User.find({
$and: [{ _id: { $nin: conflictedDriversIdArray } }, { isDriver: true }]
});
res.send(availableDrivers);
});