Efficiently querying indexed subarray using mongoose - node.js

I'm trying to store in MongoDB a collection of cards for each user. A collection is basically an array of owned cards with an associated quantity.
Collection {
user: ...,
cards: [ {cardId: 133, qty: 3}, {cardId: 22, qty: 1} ]
}
I'm building an API with node.js and mongoose where I receive a request in the form of [ {cardId: 133, qty: 2}, {cardId: 111, qty: 4} ].
Now I need to either create the card entry in the cards array if it doesn't exist or update the quantity if it is already there.
I need to do this efficiently as collections may contain thousands of cards so I came up with this Schema:
var OwnedCard = new Schema({
cardId: { type: String, index: true, required: true},
qty: { type: Number, required: true}
});
var Collection = new Schema({
userId: { type: String, index: true },
cards: [OwnedCard]
});
I'm not sure however how to take advantage of the index on cardId to quickly locate and update (or create if missing) cards in the subarray
Essentially, for each { cardId: ..., qty: xxx } in request => find/update, or create the right entry in the cards array.
So far I have (to locate the collection of the user):
Collection.findOne({userId: userId}, function (err, collection) {
var cards = collection.cards; // the cards
});
But I don't want to filter through them as a Javascript object since it doesn't take advantage of the index and might be slow, and instead look for a way to get mongo to retrieve the individual card entry quickly.
Any ideas on how to achieve this?

Related

MongoDB/Mongoose: How can I count documents in one collection and add it to another based on id?

Hello beautiful community. Hope you guys are doing okay.
So I'm working on this app where I have two MongoDB collections, Posts and CommentsView.
Posts collection consists of post title, post type and their comments. The comments is an array of objects that consists of how many times each comment is viewed and the id.
In the CommentsView collection, I intend to store all the comments when they are viewed. Duplicates is not a problem.
Here's how the schema looks like:
Posts Schema:
const postsSchema = new mongoose.Schema( {
postTitle: {
type: String
},
comments: [ {
totalViews: {
type: Number
},
uid: {
type: String
}
}],
postId: {
type: String
}
} );
CommentsView Schema:
const commentsViewSchema = new mongoose.Schema( {
text: {
type: String,
},
uid: {
type: String
}
} );
Suppose I have a post with two comments with uid 'A' and 'B' respectively. Whenever the comment with uid 'A' is viewed, I will create a comment in CommentsView collection. And automatically add 1 view in Posts collection's totalView field. When the same comment is viewed again, I will first add it in CommentsView collection then increment totalView field in Posts collection.
Suppose I have these documents in Comments collection:
{
text: 'Life is good',
uid: 'A'
},
{
text: 'Boom Boom',
uid: 'B'
},
{
text: 'Bam Bam',
uid: 'A'
},
So the Posts document will look like this:
{
postTile: '60 seconds to Mars',
comments: [
{
uid: 'A',
totalViews: 2,
},
{
uid: 'B',
totalViews: 1,
},
],
postId: '1s93njs'
}
I have not tried anything yet as I have no idea where to start. It seems so complicated.
How can I achieve this if I want the whole process to be automatic?
Since you are using two different schema, I recommend you to use references,
beacause if you want to add more features like number of likes and threads then this model suggested below will be useful
Don't use String type for storing Id's of the model use the type ObjectId provided by the mongoose module.
Post Model
const postsSchema = new mongoose.Schema( {
postTitle: {
type: String
},
comments: [
type:ObjectId,
ref:"model name"
],
postId: {
type: String
}
}, {timestamps:true});
Comment Model
const commentsViewSchema = new mongoose.Schema( {
text: {
type: String,
required:true
},
totalView:{
type: Number,
default:0
}
},{timestamps:true} );
now each time a post is viewed you can search for the commment id and increment the count in the document. and everything will be reflected in the post model if you populate all the comments in the array
Example
work flow
User Viewed the comment-->(getcomment_id) --> update the comment
Now all subsequent request for the post with this comment will also have the updated view because of reference.
query to update the comments
comment.findByIdAndUpdate({_id:<comment_id>}, {$inc:{totalView:1}})
other way is to have embedded do, I recommend this if you think there won't be much comments for the posts, otherwise having the above model is good.
you can further refer to these articles
rules to follow for designing mongodb schema
Hope this helped you get a good idea

How to create mongoDb schema for subdocument

I have document like below. Here peoples contains array of ObjectId which points to user collection. And contribution field contains as many number of subdocument as peoples field. Length is variable like if some group has 2 ObjectId in people then contribution will have 2 sub document. I need to create mongoDb schema for this, please tell me schema for this.
{
name: "person name",
_id: ObjectId(""),
creater: ObjectId("1"), //referencing to user collection
peoples: [ObjectId("1"), ObjectId("2"),...upto n], //all referencing to user table
contribution: {
ObjectId("1"):{
paid: 1200,
due: 1000,
prevDue: 200,
Advance: 0
},
ObjectId("2"):{
paid: 1200,
due: 1000,
prevDue: 200,
Advance: 0
},
//upto end of lists in peoples array
},
estimated: 30000,
collected: 15379,
left: 14721
}
You just need to reference the nested schema in your main schema. For eg:
let user = new Schema({
name: String
})
let schema = new Schema({
followers: [user]
})
In the followers field of schema, you just referenced the user schema. Whenever this kind of reference is done, the types of nested schema get injected into the reference point.
Here is an implementation. Notice that I defined contribution property as an array of peopleContributionSchema. This makes the collection data easier to be accessed as you can loop for items in that array. The way you implemented is not flexible as ObjectId is a property of contribution, so you would need to know before hand the number of people in the contribution.
var peopleContributionSchema = new Schema({
_id: Schema.Types.ObjectId,
paid: Number,
due: Number,
prevDue: Number,
advance: Number
});
var parentSchema = new Schema({
_id: Schema.Types.ObjectId, // not necessary to define. Mongoose will add by default
name: String,
creater: Schema.Types.ObjectId,
peoples: [Schema.Types.ObjectId],
contribution: [peopleContributionSchema],
estimated: Number,
collected: Number,
left: Number
});
I got it what you want to do but you can't iteration a schema object like an 'n' no of series. An object schema in MongoDB is like key-value pair in JSON Object, you can't iterate it like a loop. Instead, the approach to do that is, define two different schemas and assign one schema in an array to use it in another schema as a subschema.
Follow the below-mentioned code:
{
name: "person name",
_id: ObjectId(""),
creater: ObjectId[1],
peoples: [ObjectId[0], ObjectId[1],...upto n-1],
ObjectId: [
{
paid: 1200,
due: 1000,
prevDue: 200,
Advance: 0
},
{
paid: 1200,
due: 1000,
prevDue: 200,
Advance: 0
}
],
estimated: 30000,
collected: 15379,
left: 14721
}

Hi, I have 209580 objects in an array, how to save all at once in MongoDB using mongoose?

I have 209580 objects in an array, how to save all at once in MongoDB using mongoose?
The better option would be to save the contents as CSV/JSON and import them directly.
This might be a help https://code.tutsplus.com/articles/bulk-import-a-csv-file-into-mongodb-using-mongoose-with-nodejs--cms-29574
The insert of mongoDB (available through mongoose too) allows to insert array of items at once. According to the documentation you can send this kind of request:
Products.insert([
{ _id: 11, item: "pencil", qty: 50, type: "no.2" },
{ item: "pen", qty: 20 },
{ item: "eraser", qty: 25 }
], callback)
Array containing the objects
var arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' },...];
Use the inertMany method to save the objects in the array.
Movies.insertMany(arr, function(error, docs) {})
More Information can be found here

Get one element from an array of objects that's part of one document (mongoose)

I have a schema which contains Friends . Friends is an array where each element is an object that contains an id, gender, and emoji.
var userSchema = new Schema({
id: {
type: String,
unique: true,
required: true
},
gender: String,
Friends: [{
id: String
gender: String
emoji: String
}]
});
In the code below, I'm accessing one document which contains a Friends array and specifying that search with distinct such it only shows the document's array of Friends. I need to access only one element of that array that contains a specified id. Instead of filtering that array after the query like what's done in the code, is there a way to just get the element purely from the query? In other words, is there an extra functionality to distinct or some kind of mongoose operator that allows that?
User.findOne().distinct('Friends', { id: req.body.myId }).then(function(myDoc) {
var friendAtt = myDoc.filter(function(obj) {
return obj.id == req.body.id
})[0]
})
Thanks to bertrand I was able to find that the answer lies in 'Projection'. In mongodb it's '$', in mongoose its select. Here is how I made it work:
User.findOne({id: req.body.myId}).select({ Friends: {$elemMatch: {id: req.body.id}}}),
It only returns the element that matched the id specified in friends.
You don't really need distinct here, you can use find() on Friends.id and filter the first subdocument that match Friends.id with the positional parameter $ :
db.user.find(
{ 'id': 'id1', 'Friends.id': 'id2'},
{ 'Friends.$': 1 }
)
In mongoose :
User.find({ 'id': req.body.myId, 'Friends.id': req.body.id }, { 'Friends.$': 1 }).then(function(myDoc) {
console.log("_id :" + myDoc[0].Friends[0].id);
console.log("gender:" + myDoc[0].Friends[0].gender);
})

Finding a match inside an object of an array field, using mongoose

I have this schema:
var eventSchema = new mongoose.Schema({
name: String,
tags: [{
tagId: mongoose.Schema.Types.ObjectId,
name: String,
description: String
}],
});
var Event = mongoose.model('Event', eventSchema);
And a array of tags ids, eg:
var arrayOfTagsIds = [23, 55, 71];
// in this example I'm not using real mongo id's, which are strings
How can I use mongoose's find to search Events that have any of these tags?
For example, this event should be in the results because it has tagId 23 and tagId 55:
{
_id: ...,
name: 'Football tournament',
tags: [{ tagId: 23, name: 'football', description: '' },
{ tagId: 55, name: 'tournament', description: '' }]
}
How must be the query inside find function?
I was thinking of using: tags.tagId: {$in: arrayOfTagsIds} but I don't think that is going to work (it doesn't sound okay to use tags.tagId, because tags is an array)
Event
.find({tags.tagId: {$in: arrayOfTagsIds}})
.exec(function (err, events) { ... }
Also, doing this kind of query is too slow?
Yes, you can use dot notation in keys for both embedded sub-documents and arrays. However, you do need to quote your key because it contains a dot:
Event
.find({'tags.tagId': {$in: arrayOfTagsIds}})
.exec(function (err, events) { ... }
To speed up this query you could create an index on 'tags.tagId'.

Resources