What's the best way to update a Map/Dictionary which is part of a MongoDb document? - mongodb

I'm new to MongoDb so I'm not sure what's the best approach regarding the following:
I have a MongoDb document which contains multiple fields, including a map/dictionary.
e.g. -> priceHistogram:
rents {
_id:"1234",
city:"London",
currentPrice:"500",
priceHistogram: {"14-02-2021" : "500"}
}
I would like to update the currentPrice field with the latest price but also add to the price histogram taday's date and the price'; e.g. if today's price would be 600, I would like to obtain the following:
rents {
_id:"1234",
city:"London",
currentPrice:"600",
priceHistogram: {"14-02-2021" : "500", "20-02-2021" ": "600"}
}
What would be the most efficient MongoDb function/approach allowing me to achieve this (everything else remains the same - _id/city)?
Thank you

Not sure how your schema looks like, I will assume that the schema looks similar to:
const rentsSchema = mongoose.Schema(
{
city: { type: String, required: true },
currentPrice: {type: String},
priceHistogram: {type: Map, of:String}
}
)
const rents = mongoose.model('Rents', histSchema);
And the update:
rents.updateOne({city:"London"},{
currentPrice:"600",
"priceHistogram.24-02-2021": "600"
})
Since as I have understood Map is another way to add arbitrary properties.

Related

mongoose indexing? grouping?

I'm kinda new to mongoose, and I'm not sure if it's a right term.
what I'm building is a community site (like redit), and I have a schema like below
const postSchema = new mongoose.Schema({
content: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
board: {
type: String,
required: true,
enum: ['board1','board2'],
},
created_at: {
type: Date,
default: Date.now,
},
updated_at: {
type: Date,
},
})
there are many kinds of 'board'
and I'm not sure if it can be 'indexed'.
purpose of it is for getting posts faster
for example in sql (assume that board column is indexed)
--> select * from post where board = 'board1' ;
I'm confusing about the terms, need some direction..
Short answer:
You need to create an index on the field board by doing:
db.post.createIndex(
{ board: 1 } ,
{ name: "borad index" }
)
Long answer:
Indexing in mongodb uses memory in order to save running time.
Let's take an example: say you have all words in English in your DB. And you are reading a book and from time to time you need to search for a word to check its meaning.
How would you do that? A dictionary. You'll sort the words alphabetically and then you could easily search for every word you wanted.
Indexing apply the same concept. When you create an index on the field board it takes all its values, sort them and save it in a table (and reference for each entry the full document from your collection).
Now when you search for select * from post where board = 'board1' it first use the memorized table of sorted boards, finds the ones that equal to board1 and then by the reference gives you the full documents that belongs to it. You can continue reading here.

Accessing nested documents within nested documents

I'm having a problem that is really bugging me. I don't even want to use this solution I don't think but I want to know if there is one.
I was creating a comment section with mongodb and mongoose and keeping the comments attached to the resource like this:
const MovieSchema = new mongoose.Schema({
movieTitle: {type: String, text: true},
year: Number,
imdb: String,
comments: [{
date: Date,
body: String
}]
})
When editing the comments body I understood I could access a nested document like this:
const query = {
imdb: req.body.movie.imdb,
"comments._id": new ObjectId(req.body.editedComment._id)
}
const update = {
$set: {
"comments.$.body": req.body.newComment
}
}
Movie.findOneAndUpdate(query, update, function(err, movie) {
//do stuff
})
I then wanted to roll out a first level reply to comments, where every reply to a comment or to another reply just appeared as an array of replies for the top level comment (sort of like Facebook, not like reddit). At first I wanted to keep the replies attached to the comments just as I had kept the comments attachted to the resource. So the schema would look something like this:
const MovieSchema = new mongoose.Schema({
movieTitle: {type: String, text: true},
year: Number,
imdb: String,
comments: [{
date: Date,
body: String,
replies: [{
date: Date,
body: String
}]
}]
})
My question is how would you go about accessing a nested nested document. For instance if I wanted to edit a reply it doesn't seem I can use two $ symbols. So how would I do this in mongodb, and is this even possible?
I'm pretty sure I'm going to make Comments have its own model to simplify things but I still want to know if this is possible because it seems like a pretty big drawback of mongodb if not. On the other hand I'd feel pretty stupid using mongodb if I didn't figure out how to edit a nested nested document...
according to this issue: https://jira.mongodb.org/browse/SERVER-27089
updating nested-nested elements can be done this way:
parent.update({},
{$set: {“children.$[i].children.$[j].d”: nuValue}},
{ arrayFilters: [{ “i._id”: childId}, { “j._id”: grandchildId }] });
this is included in MongoDB 3.5.12 development version, in the MongoDB 3.6 production version.
according to https://github.com/Automattic/mongoose/issues/5986#issuecomment-358065800 it's supposed to be supported in mongoose 5+
if you're using an older mongodb or mongoose versions, there are 2 options:
find parent, edit result's grandchild, save parent.
const result = await parent.findById(parentId);
const grandchild = result.children.find(child => child._id.equals(childId))
.children.find(grandchild => grandchild._id.equals(grandchildId));
grandchild.field = value;
parent.save();
know granchild's index "somehow", findByIdAndUpdate parent with:
parent.findByIdAndUpdate(id,
{ $set: { [`children.$.children.${index}.field`]: value }});

Performance on sorting by populated field using mongoose

I have learned that it is not possible to sort by populated field in mongodb during querying. Suppose I have a schema like below, and I have 1 million data in record. And i only need to return 10 records for each query, depending of the column sorting (asc/desc) and page defined. What are the effective solution to this problem?
Simplify problem:
In the front end, I will have a data table with column firstname, lastname, test.columnA and test.columnB. Each of this column is sortable by user.
My initial solution was to query everything out in mongoose, flattening it to json and using javascript to reorder and finally response the final 10 data only. But this will have bad performance impact with increasing data set.
var testSchema = {
columnA: { type: String },
columnB: { type: String },
}
var UserSchema = {
firstname: { type: string },
lastname: { type: string },
test: {
type: ObjectId,
ref: 'Test'
}
}

MongoDB Mongoose dynamic fields

I'm developing a website in which each user has a number of balances for different currencies. Throughout the lifetime of the website I will regularly add new currencies.
I'm trying to figure out the best way to store the balances using mongoose. I currently atore the balances like this:
var UserSchema = new Schema({
...
balances: {
mck: {
type: Number,
default: 100.0,
addresses: String
},
btc:{
type: Number,
default: 10.0,
address: String
}
}
});
But it doesn't seem like the best approach. each time I want to add a new currency the existing documents will not contain it. Are there disadvantages to allowing documents in the database which are out of sync with the schema?
I thought of making the schema more dynamic by using a subdocument to store currencies and their respective balances like this:
var BalanceSchema = new Schema({
currency: String,
amount: Number,
address: String
});
But there would be a painful number of callbacks to deal with when changing balances etc.
Which of these methods would be the best approach? Or is there another I have missed?
If you have the need to add currencies dynamically in the future, you should opt to have "balances" as an array.
balances: [
{
curr: "mck",
bal: 123,45
},
{
curr: "btc",
bal: 42
}
]
It helps with queries in the future (like so) and it also gives you a lot of flexibility with each document.
Or why not go for a flat schema like:
{
user: "user1",
currency1balance:54,76,
currency5balance:1024
}

Is there a MongoDB maximum bson size work around?

The document I am working on is extremely large. It collects user input from an extremely long survey (like survey monkey) and stores the answers in a mongodb database.
I am unsurprisingly getting the following error
Error: Document exceeds maximal allowed bson size of 16777216 bytes
If I cannot change the fields in my document is there anything I can do? Is there some way to compress down the document, by removing white space or something like that?
Edit
Here is the structure of the document
Schema({
id : { type: Number, required: true },
created: { type: Date, default: Date.now },
last_modified: { type: Date, default: Date.now },
data : { type: Schema.Types.Mixed, required: true }
});
An example of the data field:
{
id: 65,
question: {
test: "some questions",
answers: [2,5,6]
}
// there could be thousands of these question objects
}
One thing you can do is to build your own mongoDB :-). Mongodb is an open source and the limitation about the size of a document is rather arbitrary to enforce a better schema design. You can just modify this line and build it for yourself. Be careful with this.
The most straight forward idea is to have each small question in a different document with a field which reference to its parent.
Another idea is to limit number of documents in the parent. Lets say you limit is N elements then the parent looks like this:
{
_id : ObjectId(),
id : { type: Number, required: true },
created: { type: Date, default: Date.now }, // you can store it only for the first element
last_modified: { type: Date, default: Date.now }, // the same here
data : [{
id: 65,
question: {
test: "some questions",
answers: [2,5,6]
}
}, ... up to N of such things {}
]
}
This way modifying number N you can make sure that you will be in 16 MB of BSON. And in order to read the whole survey you can select
db.coll.find({id: the Id you need}) and then combine the whole survey on the application level. Also do not forget to ensureIndex on id.
Try different things, do a benchmark on your data and see what works for you.
You should be using gridfs. It allows you to store documents in chunks. Here's the link: http://docs.mongodb.org/manual/reference/gridfs/