Specific aggregated query using mongoose / mongodb - node.js

I need help to achieve the following task.
I want to query all documents from a mongodb using mongoose of type TokenBalance. The schema looks like this
const Schema = mongoose.Schema(
{
address : { type: String, index: true },
ethervalue: {type: Number, required:true},
balances : []
},
{ timestamps: true }
);
The balances [] Array inside that schema holds multiple objects of this structure
{address: addresshash, symbol: someSymbol, balance: somebalance, usdvalue: usdvalueOfBalance}
What I need, is to query all Docs of Type TokenBalance within a timespan that is given and then from all of these documents sum the usdvalues grouped by symbols.
For example the output I need should look like this:
[
{symbol: BTC, balance: 100.000},
{symbol: ETC, balance: 120.000}
...
]
I'm having difficulties writing the correct aggregation for this. Especially I don't know how to group by the documents "balances" array and its symbols and sum the values.
I hope my question is clear and someone could help me.
Thank you in advance

Related

Mongoose Query with Dynamic Key that also contains the "and" operator

So I'm trying to query my MongoDB database using mongoose to fetch documents that have a specific family AND a specific analysis ID at the same time. Here is an example of the document structure:
_id: ObjectId("62b2fb397fda9ba6fe24aa5c")
day: 1
family: "AUTOMOTIVE"
prediction: -233.99999999999892
analysis: ObjectId("629c86fc67cfee013c5bf147")
The problem I face in this case is that the name of the key of the family field is set dynamically and could therefore have any other name such as "product_family", "category", etc. This is why, in order to fetch documents with a dynamic key name, I have to use the where() and equals() operators like so:
// Get the key of the field that is set dyncamically.
let dynamicKey = req.body.dynamicKey;
// Perform a query using a dynamic key.
documents = await Model.find().where(dynamicKey).equals(req.body.value);
HOWEVER, my goal here is NOT to just fetch all the documents with the dynamic key, but rather to fetch the documents that have BOTH the dynamic key name AND ALSO a specific analysis Id.
Had the family field NOT been dynamic, I could have simply used a query like so:
documents = await Model.find({
$and: [{analysis: req.body.analysis_id}, {family: req.body.value}]
});
but this does not seem possible in this case since the keys inside the find() operator are mere text strings and not variables. I also tried using the following queries with no luck:
documents = await Model.find().where(dynamicKey).equals(req.body.value).where('analysis').equals(req.body.analysis_id);
documents = await Model.find().where(dynamicKey).equals(req.body.value).where('analysis').equals(req.body.analysis_id);
Can somebody please help?
As #rickhg12hs mentioned in the comments, part of the answer is to use the [] brackets to specify your dynamic key like so:
await Model.find({[dynamicKey]: req.body.value, analysis: req.body.analysis_id});
I also found out that another query that can work is this:
await Model.find({analysis:req.body.analysis_id}).where(dynamicKey).equals(req.body.value);
However, it seems that for either of these solutions to work you also need to set your schema's strict mode to "false", since we are working with a dynamic key value.
Example:
var predictionSchema = new mongoose.Schema({
day: {
type: Number,
required: true
},
prediction: {
type: Number,
required: true
},
analysis: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Analysis', // Reference the Analysis Schema
required: true
}
}, { strict: false });

How to insert Array of objects in mongoDB?

I am very new to MONGO DB so please bear with me.I am having a problem my array of objects is not working properly .
Here is my schema
const playerSchema = new mongoose.Schema({
name: String,
stats :{
wins:Number,
losses:Number,
xp:Number
},
achievement:[
{
name:String,
date: String
}
] });
Here is my document
const fluffy = new playerModel({
"name":"nic raboy",
"stats":{
"wins":5,
"losses":10,
"xp":300
},
"achievements":[
{"name":"Massive XP","date" :"25-08-21"},
{"name":"instant loss","date":"24-08-21"}
]
});
however in mongodb atlas its only showing array...and i cant see the objects inside...
SCREENSHOT
Your schema is correct, it seems your input is wrong,
In schema definition you named it achievement, whereas in input document it is achievements. Correct this everything will work as you expected.
Explanation
The schema is expecting achievement and you inserted achievements, that is why it is shown as an empty array in the database. To avoids this kind of typos in the future, use the required flag.
const playerSchema = new mongoose.Schema({
name: String,
stats: {
wins: Number,
losses: Number,
xp: Number
},
achievements: [
{
name: {
type: String,
required : true,
},
date: {
type: String,
required : true, // required informs for missing fields
}
}
]
})
Refer this link for more on validation
You can use insertMany see the doc here.
Of course, a while loop should work find calling multiple times insertOne, though I advise you to use the insertMany() method.
If you're new to MongoDB, I strongly encourage you to have a look at MongoDB University's MongoDB basics course as well as the MongoDB for JavaScript Developers course.

Proper way of updating average rating for a review system using Mongoose

I'm currently learning some backend stuff using an Udemy course and I have an example website that lets you add campgrounds (campground name, picture, description, etc.) and review them. I'm using the Express framework for Node.js, and Mongoose to access the database.
My campground schema looks like:
const campgroundSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
price: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
],
rating: {type: Number, default: 0}
});
And my comment/review schema looks like:
const commentSchema = new mongoose.Schema({
text: String,
rating: {
type: Number,
min: 1,
max: 5,
validate: {validator: Number.isInteger}
},
campground: {type: mongoose.Schema.Types.ObjectId, ref: "Campground"}
});
Campgrounds and Comments also have references to a User but I've left that out for simplicity.
I'm looking to know the best practice for updating and displaying the campground average rating.
The method used by the tutorial I'm following is to recalculate the average rating each time a comment is added, changed, or deleted. Here's how it would work for a new comment:
Campground.findById(campgroundId).populate("comments").exec(function(err, campground) {
Comment.create(newComment, function(err, comment) {
campground.comments.push(comment);
campground.rating = calculateRating(campground.comments);
campground.save();
});
});
"calculateRating" iterates through the comment array, gets the total sum, and returns the sum divided by the number of comments.
My gut instinct tells me that there should be a way to make the "rating" field of Campground perform the functionality of the "calculateRating" function, so that I don't have to update the rating every time a comment is added, changed, or removed. I've been poking around documentation for a while now, but since I'm pretty new to Mongoose and databases in general, I'm a bit lost on how to proceed.
In summary: I want to add functionality to my Campground model so that when I access its rating, it automatically accesses each comment referenced in the comments array, sums up their ratings, and returns the average.
My apologies if any of my terminology is incorrect. Any tips on how I would go about achieving this would be very much appreciated!
Love,
Cal
I think what you are trying to do is get a virtual property of the document that gets the average rating but it does not get persisted to the mongo database.
according to mongoosejs :- Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. They are set on the schema.
You can do this:
CampgroundSchema.virtual('averageRating').get(function() {
let ratings = [];
this.comments.forEach((comment) => ratings.push(comment.rating));
return (ratings.reduce((a,b)=>a+b)/ratings.length).toFixed(2);
});
After that on your view engine after finding campgrounds or a campground, all you need to call is ; campground.averageRating;
Read more here : https://mongoosejs.com/docs/guide.html#virtuals
also note that you can not make any type of query on virtual properties.

Mongodb aggregation - geoNear and text search in joined collection

I have a tricky query that hits my MongoDB know-how. Here the simplified szenario.
We have a collection Restaurant and a collection Subsidary.
They look roughly like this (simplified - using mongoose):
const restaurantSchema = new Schema(
{
name: { type: String, required: true },
categories: { type: [String], required: true },
...
})
const subsidarySchema = new Schema(
{
restaurant: { type: Schema.Types.ObjectId, ref: 'Restaurant' },
location: {
type: { type: String, enum: ['Point'], required: true },
coordinates: { type: [Number], required: true },
},
...
})
What is required:
Always: Find restaurants that have a subsidary within 3.5 KM radius and sort by distance.
Sometimes filter those restaurants also by a string that should fuzy-match the Restaurant name.
Apply further filters and pagination (e.g. filter by categories, ...)
I'm trying to tackle this with a mongodb aggregation. The problem:
The aggregation pipeline stages geoNear and text require each to be first in the pipeline - which means they exclude each other.
Here my thought so far:
Start aggregation with subsidary, $geoNear stage first. This cuts away already all restaurants outside the 3.5 KM.
$group the subsidaries by restaurant and keep the minimal distance value per cluster.
$lookup to get the matchin restaurant for each cluster. Maybe $unwind here.
??? Here the text/search match should be, fuzy-matching the restaurants' name. ???
$match for other values (category, openingHours, ...)
$sort and $limit and $skip for sorting andd pagination.
Here the same as illustration.
Question
Does this approach make sense? What would be a possible way to implement stage 4?
I was searching a lot but there seems no way to use something like { $match: { $text: { $search: req.query.name } } } as a 4th stage.
An alternative would be to run a second query before that just handles the text search and then build an intersection. This could lead to a massive amount of restaurant IDs being passed in that stage. Is that something mongodb could handle?
I'm very thankful for your comments!
Some ways around the requirement that both text search and geo query must be the first stage:
Use text search as the first stage, then manually calculate the distance using $set/$expr in a subsequent stage.
Use geo query as the first stage, then perform text filtering in your application (allowing you also to use any text matching/similarity algorithm you like).

check and update nested documents in mongoose mongodb

i am new in mongodb and mongoose and i don't know how i can change an object in array which is in other object .
here is simple version of my schema
const shiftSchema = new Schema({
phone: String,
order: {
startTime: String,
finishTime: String,
orders: [{
stat: boolean,
orderId: Number
}]
}
});
orderId is actually the time every 5 so first object in orders has startTime+5 value as orderId and the last one has finishTime value.
so now i want to find the doc that has for example 0911111 as phone , 04090405 as startTime , 04090900 as finishTime , and inside it i want to update just the object that it's orderId is 04090435 if it's stat was false and also update 2 objects before it . which are 04090430 and 04090425 and also check their stat to be false and if even one of these 3 objects has true stat do not update it and send error.
i know it has answered many times but i can't find any solution for mine in specific conditions ,so please help me.
Note: i know that for updating this kind of arrays i can do something like this :
Shift.updateOne({
'order.orders': {$elemMatch: {orderId: {$lte: start}, stat: "true"}}
}, {
'$set': {'order.orders.$.stat': 'ready'}
})
but it just update whole Shift Collection but i only just want to update one doc.
All tips will be appreciated. Thank You

Resources