Use of $nin on embedded document - node.js

I have a schema like this in MongoDB:
var Customers = new Schema({
name: { type: String, trim: true, index: true, default: null, sparse: true },
facebookId: { type: String, default: null, trim: true, index: true },
friends: [friends]
});
var friends = new Schema({
customer: { type: Schema.ObjectId, ref: 'Customers', required: true },
lastGame: { type: Schema.ObjectId, ref: 'games', required: true, default: null },
lastGameTime: { type: Date, default: null }
});
Now in friends array I have reference of all the customer who are Facebook friend of the particular customer.
Now what I want to do is I have a screen where I want to show all the customers but there I don't want to those customers who are already my Facebook friends i.e for an example lets suppose I have 10 customer in total from 1 to 10, I am customer no 4 who have customer 1,3,5,6 in friends array so my result on the screen should be of user 2,7,8,9,10
I will really be thankful if someone can tell me the way to this stuff by using Query of MongoDb. I have searched and found $nin usage but that works in simple array. I don't get how I can implement this query on an embedded document as in the case of mine.

Look into $elemMatch. This will allow you to identify a specific document in an embedded document array
ElemMatch MongoDB documentation
You can also use $elemMatch in combination with $nin

I solved this issue by using $nin with a simple approach first of all I select all the friends of a particular customer in an array then by using $nin I filtered all the friends from the query and got my result.

Related

MongoDB pipeline to get Posts that User has liked, sorted by most recent

I currently have a table of posts which has the post details and an ID, and a likes table which contains entries of a user ID, a post ID, and a timestamp.
const likeSchema = Schema({
user: {
type: mongoose.Types.ObjectId,
ref: "User",
required: true
},
listing: {
type: mongoose.Types.ObjectId,
ref: "Listing",
required: true
},
timestamp: {
type: Date,
required: true
}
});
I am trying to return an list of posts that a user has liked, ordered by the like timestamp.
I understand that this can probably be done in some way with a aggregation/pipeline although I'm struggling to figure it out.
My alternative would be to fetch all likes of a user, then fetch all listing ID's associated with them in a separate request, although this seems inefficient and doesn't keep them in order of the like timestamp.

Mongoose aggregate and append

I have a Mongo DB (latest version) that I am accessing with Mongoose (v6.5.4)
The project is using a discriminator pattern to keep all documents in the same collection.
There are many instances where i need to join documents.
Set up:
// Models:
const UserSchema = new Schema<IUser>(
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
});
// There are other similar models to <Team>
const TeamSchema = new Schema<ITeam>(
{
name: {
type: String,
required: true,
},
userIds: {
type: [Schema.Types.ObjectId],
required: true,
ref: "User",
default: [],
},
});
Problem:
I can use populate to return collections of Teams and the userIds be an array of user objects.
Where I am stuck is querying getting an array of users with an added field of teams[].
I've been trying aggregate to no success, I can loop over the users collection and return a list of Teams but this feels wrong and expensive in terms of read units (production data base is on a pay as you go service)
As data models go there is not much going for it - but it is an existing solution
Can anyone advise?
I was being stupid. The from field in my look up was wrong.
Should have been 'teams' not 'Team' which is the model name.

Retweet schema in MongoDB

What is the best way to model retweet schema in MongoDB? It is important that I have createdAt times of both original message and the time when retweet occurred because of pagination, I use createdAt as cursor for GraphQL query.
I also need a flag weather the message itself is retweet or original, and id references to original message and original user and reposter user.
I came up with 2 solutions, first one is that I keep ids of reposters and createdAt in array in Message model. The downside is that I have to generate timeline every time and for subscription its not clear what message to push to client.
The second is that I treat retweet as message on its own, I have createdAt and reposterId in place but I have a lot of replication, if I were to add like to message i have to push in array of every single retweet.
I could use help with this what is the most efficient way to do it in MongoDB?
First way:
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema(
{
text: {
type: mongoose.Schema.Types.String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likesIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
reposts: [
{
reposterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
createdAt: { type: Date, default: Date.now },
},
],
},
{
timestamps: true,
},
);
const Message = mongoose.model('Message', messageSchema);
Second way:
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema(
{
text: {
type: mongoose.Schema.Types.String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likesIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
isReposted: {
type: mongoose.Schema.Types.Boolean,
default: false,
},
repost: {
reposterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
originalMessageId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Message',
},
},
},
{
timestamps: true,
},
);
const Message = mongoose.model('Message', messageSchema);
export default Message;
Option 2 is the better choice here. I'm operating with the assumption that this is a Twitter re-tweet or Facebook share like functionality. You refer to this functionality as both retweet and repost so I'll stick to "repost" here.
Option 1 creates an efficiency problem where, to find reposts for a user, the db needs to iterate over all of the repost arrays of all the messageSchema collections to ensure it found all of the reposterIds. Storing ids in mongo arrays in collection X referencing collection Y is great if you want to traverse from X to Y. It's not as nice if you want to traverse from Y to X.
With option 2, you can specify a more classic one-to-many relationship between messages and reposts that will be simpler and more efficient to query. Reposts and non-repost messages alike will ultimately be placed into messageSchema in the order the user made them, making organization easier. Option 2 also makes it easy to allow reposting users to add text of their own to the repost, where it can be displayed alongside the repost in the view this feeds into. This is popular on facebook where people add context to the things they share.
My one question is, why are three fields being used to track reposts in Option 2?
isReposted, repost.reposterId and repost.originalMessageId provide redundant data. All that you should need is an originalMessageId field that, if not null, contains a messageSchema key and, if null, signifies that the message is not itself a repost. If you really need it, the userId of the original message's creator can be found in that message when you query for it.
Hope this helps!

MongoDB reusing a collection for every user

I'm not sure the best way to set this up in MongoDB.
I have two collections User and Skill. The collection for Skill has a list of skills that every user should have.
var SkillSchema = new Schema({
name: { type: String, required: true, trim: true },
category: { type: String, required: true, trim: true }
});
mongoose.model('Skill', SkillSchema);
var UserSchema = new Schema({
_id: { type: String, required: true, trim: true },
first_name: { type: String, required: true, trim: true },
last_name: { type: String, required: true, trim: true },
email_address: { type: String, required: true, trim: true },
skills: [{
skill: { type: ObjectId, ref: 'Skill' },
count: { type: Number, default: 0 },
last_performed: { type: Date }
}]
});
mongoose.model('User', UserSchema);
What I am trying to do is have a list of skills, and each user has a count property that shows how many times they have performed the skill, and a last_performed property that has the date they last performed it.
My problem is, I want the list of skills to be same for each user, but I can update their count and last_performed properties uniquely for each user.
The way I have got it currently is referencing the skill id, and then having the count/date in the user schema. The problem with this, is if I add another skill to the Skills schema, the user's skills array won't be updated with the new skill. I figured updating every user every time I add/remove a skill, to reflect the new skills list, wouldn't be the optimal way to do this.
Can you sync the user's skills array to match the Skills schema?
Would it be better to just add each user's count/date to the skill schema directly? The only problem with this is if there are thousands of users, would this have any performance problems, and would it be easy enough to sort the skills by count/date for each user, and query the user's skills individually without returning the counts for every user?
Cheers,
Ben
skills : [{ type: ObjectId, ref: 'Skill' }]. Just push the Ids to the array if you want to add skills when you do a save or an update.
you can populate the array of skills and you can count the skills array. That will give you the count.
If you have last_performed in the Skill model. then you will get access to it after you populate

Return Mongo document using Mongoose where subdocument does NOT exist?

Help! I'm losing my mind. I need to simply return a Mongo document, using Mongoose, IF a sub document does not exist.
My schemas:
var userSchema = new mongoose.Schema({
email: {type: String, unique: true, lowercase: true},
password: {type: String, select: false},
displayName: String,
picture: String,
facebook: String,
deactivation: deactiveSchema
});
var deactiveSchema = new mongoose.Schema({
when : { type: Date, default: Date.now, required: true },
who : { type: Schema.Types.ObjectId, required: true, ref: 'User' }
});
My goal is to lookup a user by their facebook ID if they have not been deactivated.
If they have been deactivated, then a deactivation subdocument will exist. Of course, to save space, if they are active then a deactivation will not exist.
On a side note, I'm also worried about how to properly construct the index on this logic.
I'd post snippets but every attempt has been wrong. =(
You can use $exists operator:
userSchema.find({deactivation:{$exists:false}}).exec(function(err,document){
});
or $ne:
userSchema.find({deactivation:{$ne:null}}).exec(function(err,document){
});
Since you are retiring data and not deleting, I'd go with one of two approaches:
Flag for retired (Recommended)
add to your schema:
retired: {
type: Boolean,
required: true
}
and add an index for this query:
userSchema.index({facebook: 1, retired: 1})
and query:
User.find({facebook: facebookId, retired: false}, callback)
Query for existence
User.find().exists("deactivation", false).exec(callback)
The latter will be slower, but if you really don't want to change anything, it will work. I'd recommend taking some time to read through the indexing section of the mongo docs.
Mongoose has many options for defining queries with conditions and a couple of styles for writing queries:
Condition object
var id = "somefacebookid";
var condition = {
facebook : id,
deactivation: { $exists : true}
};
user.findOne(condition, function (e, doc) {
// if not e, do something with doc
})
http://mongoosejs.com/docs/queries.html
Query builder
Alternatively, you may want to use the query builder syntax if you are looking for something closer to SQL. e.g.:
var id = "somefacebookid";
users
.find({ facebook : id }).
.where('deactivation').exists(false)
.limit(1)
.exec(callback);

Resources