MongoDB: use of subdocuments - mongodb

TLDR; Should you use subdocuments or relational Id?
This is my PostSchema:
const Post = new mongoose.Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true
},
comments: [Comment.schema]
})
And this is my Comment Schema:
const Comment = new mongoose.Schema({
body: {
type: String,
required: true
}
})
In Postgres, I would have a post_id field in Comment, instead of having an array of comments inside Post. I am sure you can do the same in MongoDB but I don't know which one is more conventional. If people use subdocuments over references (and joining tables) in MongoDB, why is that? In other words, why should I ever use subdocuments? If it's advantageous, should I do the same in Postgres as well?

What I understood from your question, answering based on that.
If you will keep sub documents, you don't have to query two tables to know comments specific to one post.
Let's say we have following db structure for post:-
[{
_id:1,
title:'some title',
comments:[
{
...//some fields that belongs to comments
} ,
{
...//some fields that belongs to comments
} ,
...
]
},
{
_id:2,
title:'some title',
comments:[
{
...//some fields that belongs to comments
} ,
{
...//some fields that belongs to comments
} ,
...
]
}]
Now you can query based on _id of the post (1) and can get comments array that belongs to the specific post.
If you will just keep the comment's id inside post, you have to query both the tables, which I don't think is a good idea.
EDIT :-
If you are keeping post id inside comments record, then it will help you to track which comment is for which post i.e. if you want to query comments table based on post id and you need only fields from comments records.
What I think, use case will be which post contains what all comments. So keeping comment inside post will give you comments fields as well as fields from post record.
So it's totally depends on your requirement, how you will design your data structure.

Related

MongoDB joining user and likes collection based on reference ids

I am trying to create a little social network using ExpressJS and MongoDB. I have a little problem relating to likes and posts collection. I know you can embed a likes inside a posts collection, but I have decided to separate both of the collection and use reference ids so I can join them later on. The main problem I have currently is this, how do I include the likes reference on the posts collection?
Let's say my posts schema looks something like this:
const PostSchema = new Schema({
content: { type: String, required: true },
isLiked: false,
}, { timestamps: true });
and my likes schema looks something like this:
const LikeSchema = new Schema(
{
// The user who is liking the post.
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
// The post that is being liked.
question: {
type: Schema.Types.ObjectId,
ref: 'Question',
required: true
},
},
{ timestamps: true }
);
I wanna make it so that whenever I try to query the posts collection, I can also get the likes embedded in it by referencing the collection and not modifying the schema to have embedded likes in it.
An example response:
{
_id: ObjectId("test"),
content: 'A post',
isLiked: false,
likes: ["A user object here based on the `likes collection`"]
}
You have to obtain them before sending the response:
Find all the likes of that post, something similar to Like.find({ question: <postId> })
Then you can resolve the users of that likes, in the command above you can concatenate .populate('user') with the mongoose populate feature
If you are interested only to the user object and not the entire like object, you can extract resolved user: const users = likes.map(x => x.user)
Then you can add the users array to the post object and sending the final object as response

populate or aggregate 2 collection with sorting and pagination

I just use mongoose recently and a bit confused how to sort and paginate it.
let say I make some project like twitter and I had 3 schema. first is user second is post and third is post_detail. user schema contains data that user had, post is more like fb status or twitter tweet that we can reply it, post_detail is like the replies of the post
user
var userSchema = mongoose.Schema({
username: {
type: String
},
full_name: {
type: String
},
age: {
type: Number
}
});
post
var postDetailSchema = mongoose.Schema({
message: {
type: String
},
created_by: {
type: String
}
total_reply: {
type: Number
}
});
post_detail
var postDetailSchema = mongoose.Schema({
post_id: {
type: String
}
message: {
type: String
},
created_by: {
type: String
}
});
the relation is user._id = post.created_by, user._id = post_detail.created_by, post_detail.post_id = post._id
say user A make 1 post and 1000 other users comment on that posts, how can we sort the comment by the username of user? user can change the data(full_name, age in this case) so I cant put the data on the post_detail because the data can change dynamically or I just put it on the post_detail and if user change data I just change the post_detail too? but if I do that I need to change many rows because if the same users comment 100 posts then that data need to be changed too.
the problem is how to sort it, I think if I can sort it I can paginate it too. or in this case I should just use rdbms instead of nosql?
thanks anyway, really appreciate the help and guidance :))
Welcome to MongoDB.
If you want to do it in the way you describe, just don't go for Mongo.
You are designing the schema based on relations and not in documents.
Your design requires to do joins and this does not work well in mongo because there is not an easy/fast way of doing this.
First, I would not create a separate entity for the post details but embedded in the Post document the post details as a list.
Regarding your question:
or I just put it on the post_detail and if user change data I just
change the post_detail too?
Yes, that is what you should do. If you want to be able to sort the documents by the userName you should denormalize it and include in the post_details.
If I had to design the schema, it would be something like this:
{
"message": "blabl",
"authorId" : "userId12",
"total_reply" : 100,
"replies" : [
{
"message" : "okk",
"authorId" : "66234",
"authorName" : "Alberto Rodriguez"
},
{
"message" : "test",
"authorId" : "1231",
"authorName" : "Fina Lopez"
}
]
}
With this schema and using the aggregation framework, you can sort the comments by username.
If you don't like this approach, I rather would go for an RDBMS as you mentioned.

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 }});

Node.js - Mongoose/MongoDB - Model Schema

I am creating a blog system in Node.js with mongodb as the db.
I have contents like this: (blog articles):
// COMMENTS SCHEMA:
// ---------------------------------------
var Comments = new Schema({
author: {
type: String
},
content: {
type: String
},
date_entered: {
type: Date,
default: Date.now
}
});
exports.Comments = mongoose.model('Comments',Comments);
var Tags = new Schema({
name: {
type: String
}
});
exports.Tags = mongoose.model('Tags',Tags);
// CONTENT SCHEMA:
// ---------------------------------------
exports.Contents = mongoose.model('Contents', new Schema({
title: {
type: String
},
author: {
type: String
},
permalink: {
type: String,
unique: true,
sparse: true
},
catagory: {
type: String,
default: ''
},
content: {
type: String
},
date_entered: {
type: Date,
default: Date.now
},
status: {
type: Number
},
comments: [Comments],
tags: [Tags]
}));
I am a little new to this type of database, im used to MySQL on a LAMP stack.
Basically my question is as follows:
whats the best way to associate the Contents author to a User in the
DB?
Also, whats the best way to do the tags and categories?
In MYSQL we would have a tags table and a categories table and relate by keys, I am not sure the best and most optimal way of doing it in Mongo.
THANK YOU FOR YOUR TIME!!
Couple of ideas for Mongo:
The best way to associate a user is e-mail address - as an attribute of the content/comment document - e-mail is usually a reliable unique key. MongoDB doesn't have foreign keys or associated constraints. But that is fine.
If you have a registration policy, add user name, e-mail address and other details to the users collection. Then de-normalize the content document with the user name and e-mail. If, for any reason, the user changes the name, you will have to update all the associated contents/comments. But so long as the e-mail address is there in the documents, this should be easy.
Tags and categories are best modelled as two lists in the content document, IMHO.
You can also create two indices on these attributes, if required. Depends on the access patterns and the UI features you want to provide
You can also add a document which keeps a tag list and a categories list in the contents collection and use $addToSet to add new tags and categories to this document. Then, you can show a combo box with the current tags as a starting point.
As a final point, think through the ways you plan to access the data and then design documents, collections & indices accordingly
[Update 12/9/11] Was at MongoSv and Eliot (CTO 10gen) presented a pattern relevant to this question: Instead of one comment document per user (which could grow large) have a comment document per day for a use with _id = -YYYYMMDD or even one per month depending on the frequency of comments. This optimizes index creation/document growth vs document proliferation (in case of the design where there is one comment per user).
The best way to associate the Content Authors to a User in the MongoDB, is to take an array in Author Collection which keeps an reference to User. Basically Array because One Content/Book may have multiple Authors i.e. you need to associate one Content to many Users.
The best way for category is to create a different collection in your DB and similarly as above keep a array in Contents.
I hope it helps at-least a little.

Searching embedded documents Mongoose + nodejs

Im new to Mongoose, and i'm facing a problem in searching.
These are my Schemas:
var CommentSchema = new Schema({
body : String
, comments : [CommentSchema]
});
var PostSchema = new Schema({
body : String
, comments : [CommentSchema]
});
There is a deep nesting of comments. When somebody answers to the existing comment, how can I find that one?
you can look at the mongoose test suite on github for examples.
model_querying_test
Here is what you are looking for:
test finding based on embedded document fields:
function () {
var db = start(), BlogPostB = db.model('BlogPostB', collection);
BlogPostB.create({comments: [{title: 'i should be queryable'}]}, function (err, created) {
should.strictEqual(err, null);
BlogPostB.findOne({'comments.title': 'i should be queryable'}, function (err, found) {
should.strictEqual(err, null);
found._id.should.eql(created._id);
db.close();
});
});
},
One solution to this is to store Comments as a separate Model which you can query directly, and store references to the related ObjectIds and paths between Comments and Posts.
Using the Populate feature in Mongoose related documents can function similarly to embedded documents, although there are some important differences in the way you query them, and you have to be more careful to keep the relationships populated.
Set it up like this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.Types.ObjectId;
var PostsSchema = new Schema({
body : String,
stories : [{ type: ObjectId, ref: 'Story' }]
});
var CommentsSchema = new Schema({
body : String,
post : { type: ObjectId, ref: 'Post' },
comments : [{ type: ObjectId, ref: 'Comment' }]
});
var Story = mongoose.model('Post', PostsSchema);
var Comment = mongoose.model('Comment', CommentsSchema);
If you do it this way it requires more queries to get the post with all its comments (which will be slower than being able to load the Post and its complete comment hierarchy with a single query) however you'll be able to query comments directly and retrieve the Post they were made on (but not easily find the full path to the comment when its nested).
These are all trade-offs; the best decision (either to recursively search for comments, or store them independently then recursively load them) should be made in the context of your application and its expected usage patterns.
One other caveat; the populate feature is currently limited to a single-level of linked ObjectIds; you have to call it on each comment that is returned to get the full nested dataset. There are several plugins that help with this, such as mongoose-subpopulate, and soon enough it'll be supported natively in Mongoose - see the github issue here.