I am new in mongodb.
i have a simple email Schema which implemented like below :
const emailSchema = new Schema({
from:{
type: String
},
to: {
type: String
},
subject: {
type: String
},
content: {
type: String
},
provider:{
type: String
},
success:{
type: Boolean
}
now i want to query all email records and seprate them in two array. one for provider=x and provider = y . then count each one's success field.
how can i write such query ?
If you only want to count success: true for any provider
emailSchema.aggregate([{
$match: {
{ 'success': true }
},
$group: {
_id: {
provider: '$provider'
}, //group by provider
count: {
$sum: 1
}
} //sum of success fields
]);
If you only want to count success field exists or not it may be true or false for any provider
emailSchema.aggregate([{
$match: {
{'success': { $exists: true }}
},
$group: {
_id: {
provider: '$provider'
}, //group by provider
count: {
$sum: 1
}
} //sum of success fields
]);
Use aggregate query for group and count
emailSchema.aggregate([{
$group: {
_id: {
provider: '$provider'
}, //group by provider
count: {
$sum: '$success'
}
} //sum of success fields
])
Related
I have the following schema (NestJS + Mongoose):
#Schema({ timestamps: true })
export class Listing {
#Prop({
type: [{
bidderId: { type: Types.ObjectId, required: true, select: false, ref: User.name, index: true },
amount: { type: Number, required: true },
date: { type: Date, required: true }
}],
select: false
})
bids!: Bid[]
}
so basically every listing document has an array of bids.
now I notice that automatically mongoDB (or mongoose) creates _id field for every bid item I put into the bids array.
My question is, If I have a bid's _id, how can I query it's item from the listing's bids array? something like:
// Adding a new bid to the listing, and retrieving the updated listing
const listingWithAddedBid = await this.listingModel.findByIdAndUpdate(listingId, {
$push: {
bids: {
$each: [{
amount: bidInfo.amount,
bidderId: new Types.ObjectId(user.id),
date: new Date()
}],
$sort: { amount: -1 }
}
}
}, { new: true })
// Getting the new bid's _id from the array (it will be at index 0 because we sort buy amount)
const newBidId = listingWithAddedBid.bids[0]._id
// here how can I query the entire bid item from the array with 'newBidId'? this won't work
this.listingModel.findById(newBidId) // returns null
https://docs.mongodb.com/manual/tutorial/query-array-of-documents/
https://docs.mongodb.com/manual/indexes/#default-_id-index
this.listingModel.findOne({ "bids._id": newBidId, {}, {}, (error, doc) =>
{
if (error) {
....
}
if (doc) {
....
} else {
...//'Not Found';
}
});
I have a product schema like
quantity: { type: Number, required: true },
batch_no: {
type: [
{
batch_no: { type: String, required: true },
quantity: { type: Number, required: true },
created_at: { type: Date, default: Date.now() },
}
],
default: []
}
I am trying to update both the quantity fields in one query.
The code goes something like :
var expression = { $and: [{ "$inc": { "quantity": -10 } }, { "$inc": {"batch_no.$.batch_no": -10 } }] }
await ProductModel.findOneAndUpdate({ "sku": "TestSKU", "batch_no.$.batch_no":"Batch 1" }, { expression }, (err, response) => {
if (response) {
console.log("Update success")
} else if (err) {
res.status(400).send(err);
}
});
This nested query does not work.
Is there no way that I can update both the quantites at once?
$and is a query operator. If you just wanted to update multiple fields via $inc, you can pass them as key-value pairs object argument to $inc, like this:
var expression = { $inc: { "quantity": -10, "batch_no.$.batch_no": -10 } }
await ProductModel.findOneAndUpdate({ "sku": "TestSKU", "batch_no.$.batch_no": "Batch 1" }, expression, (err, response) => {
if (response) {
console.log("Update success")
} else if (err) {
res.status(400).send(err);
}
});
Also, you can just pass in expression directly as the 2nd argument, without wrapping it in another object.
Imagine a function that finds users by their name and returns them.
User.aggregate(
[
{ $sort: { userFirstName: 1, userLastName: 1 } },
{
$addFields: {
firstLastName: { $concat: ['$userFirstName', ' ', '$userLastName'] },
lastFirstName: { $concat: ['$userLastName', ' ', '$userFirstName'] }
}
},
{
$match: $match // Set from above with match crit
},
{
$group: {
_id: null,
total: { $sum: 1 },
data: {
$push: {
'_id': '$_id',
'userFirstName': '$userFirstName',
'userLastName': '$userLastName',
'userProfileImage': '$userProfileImage',
'userVihorCategory': '$userVihorCategory'
}
}
}
},
{
$project: {
total: 1,
data: { $slice: ['$data', start, limit] }
}
}
]
).exec((errAgg, results) => {...
This works, it splices them and returns them correctly.
There is another collection that tracks user connections.
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
userConnection: { type: Schema.Types.ObjectId, ref: 'User' },
userConnectionStatus: {
type: String,
enum: ['following', 'blocked', 'requested']
}
}
Eg User: me, userConnection: 'someone', userConnectionStatus: 'following'
What I am trying to achive is to return 2 more fields,
1. My userConnectionStatus to him
2. His userConnectionStatus to me
And not to return users who have blocked me.
What is the best approach when it comes to this DB structure.
Thank you for your time
Preventing blocked users was solved by selecting all blocked users, and adding $nin in match inside aggregate.
For connection status, I have resolved the problem by adding 2 virtual fields to User.
UserMongoSchema.virtual('userConnectionStatus', {
ref: 'UserConnection',
localField: '_id',
foreignField: 'user',
justOne: true
});
UserMongoSchema.virtual('connectionStatus', {
ref: 'UserConnection',
localField: '_id',
foreignField: 'userConnection',
justOne: true
});
And populating them on results
...
.exec((errAgg, results) => {
User.populate(results[0].data, [
{ path: 'userConnectionStatus', match: { userConnection: req.userCode }, select: 'userConnectionStatus' },
{ path: 'connectionStatus', match: { user: req.userCode }, select: 'userConnectionStatus' },
], (errPop, populateResponse) => {
if (errPop) { return next(errPop); }
populateResponse = populateResponse.map((row) => {
row['userConnectionStatus'] = row.userConnectionStatus ? row.userConnectionStatus.userConnectionStatus : null;
row['connectionStatus'] = row.connectionStatus ? row.connectionStatus.userConnectionStatus : null;
return row;
});
...
Looking at the order of actions, I think this won't affect performance since I am running populate only on those matched top X (max 100) results.
I won't mark this as Answer yet. If you have any opinion about if this is bad practice or if there is a better way of doing it, feel free to comment.
I am trying to make a query to find documents depending on another document in the same collection as below.
The first one finds the user and the second one finds the data by using that user data received. But I want to do it with one query like join in SQL
This is schema
var ConnectionSchema = new Schema({
socketId: {
type: String,
require: true
},
location: {
type: [Number],
index: '2dsphere'
},
user: { type: Schema.ObjectId, ref: "User" },
date: {
type: Date,
require: true,
default: new Date()
}
});
// queries
return mongoose.model("Connection").findOne({ user: userId }).populate("user").then(usr => {
return mongoose.model("Connection").find({
location: {
$near: {
$maxDistance: config.searchDistance,
$geometry: { type: Number, coordinates: usr.location }
}
},
user: { $ne: userId },
});
});
Is there any way to do that with a just single query?
Thanks.
yes there is a way you can do like this
return mongoose.model("Connection").findOne({ user: userId })
.populate("user" ,
match : {$and : [{location: {
$near: {
$maxDistance: config.searchDistance,
$geometry: { type: Number, coordinates: usr.location }
}
}},
{user: { $ne: userId }}]})
.then(usr => {
// perform your action
});
Model Schema
const PollSchema = new Schema({
title: { type: String, trim: true, required: true },
choices: [
{
title: { type: String, trim: true, required: true },
votes: { type: Number, default: 0, min: 0 },
}
],
date: { type: Date, default: Date.now },
url: { type: String, required: true, unique: true },
})
Update call
async vote (pollId, choiceId, unvoteId = '') {
try {
await pollModel.update(
{ '_id': pollId, 'choices._id': choiceId },
{ $inc: { 'choices.$.votes': 1 } },
)
if (unvoteId) {
await pollModel.update(
{
"$and": [
{ '_id': pollId },
{ 'choices._id': unvoteId },
{ 'choices.votes': { $gt: 0 } }
],
},
{ $inc: { 'choices.$.votes': -1 } },
)
}
return await pollModel.findById(pollId)
} catch (e) {
throw new ApiError(
500, 'Error: Poll:Vote', e
)
}
}
I have been trying a plethora of combinations trying to get this to work. Voting +1 works as intended, but when trying to -1, the query conditions are not properly matched. I have tried $and, $elemMatch, plain object with the 3 conditions (according to the docs this is sufficient and implicitly means and too.
Whenever I send through an unvoteId, no matter which _id I choose in the array, it will always match the FIRST element that has $gt 0 votes. It is as if choices._id is completely ignored, and as soon as it meets a choice that has > 0 votes, it returns that for the $ positional param.
Is this intended? I assumed $and would only match if all 3 conditions were satisfied.
What I am trying to do is update the votes atomically using $inc, while also ensuring that when someone votes, they cannot bring the value below 0. As the mongoose validators do not get run during updates, I am trying to validate this via the query itself.
Try something like this.
pollModel.update({
_id: pollId,
choices: {
$elemMatch: {
_id: unvoteId,
votes: {
$gt: 0
}
}
}
}, {
$inc: {
"choices.$.votes": -1
}
})