How to get only not joined (excluded) documents between two collections? - node.js

The problem is around two collections Users and Forms. Each user is joined with his form by Id.So every form in a Forms collection has a userId foreign key.
I need to get all documents from a Users collection , which doesn't have any form connected to it (there is no such a form).
In SQL I would do something like this:
SELECT *
FROM Users U
LEFT JOIN Forms F
ON U._id= F.userId
WHERE F.userId IS NULL
U=A , F=B
How can we achieve this functionality in MongoDb , may be with Mongoose on a nodeJs side? I couldn't find any solution based on a $lookup aggregation.
Schemas:
*employeeId is a reference to Users _id
let FormsSchema = new Schema({
employeeId:{
type:Schema.Types.ObjectId , ref:'User'
},
companyId:{
type:Schema.Types.ObjectId
},
formData : {
type:String
},
formType : {
type:String
},
formDate :{
type:Date,default: Date.now
},
formState: {
type: String, required: true, enum: ["new","draft","aproved","pendingHR","pendingEMP","pendingSigned"], default: "new"
},
messageData : {
type:Schema.Types.Mixed
},
formIdkunDate:{
type:Date,default: Date.now
},
attachmentDirty:{
type:Boolean
},
company : {
type:Schema.Types.Mixed
},
formShnatMas : {
type:String
},
sendDate : {
type:Date,default: Date.now
},
draftDate : {
type:Date,default: Date.now
},
fixDate : {
type:Date,default: Date.now
},
is101FormOpenedAfterArchive : {
type:Boolean
},
}
let UserSchema = new Schema({
userName:{
type:String,
trim:true
},
CreateDate :{
type:Date,default: Date.now
},
UpdateDate :{
type:Date,default: Date.now
},
password : {
type:String,
required: true,
trim:true
},
isFirstEntrance : {
type:Boolean
},
resetToken : {
type:String
},
resetPasswordExpires : {
type:String
},
resetOtpPassExpires : {
type:String
},
otpPass : {
type:String,
trim:true
},
userType: {
type: String, required: true, enum: ["employee","hr","admin"], default: "employee"
} ,
employeeData : {
type:Schema.Types.Mixed
},
partnerData : {
type:Schema.Types.Mixed
},
externalId : {
type:String
},
isChangedFromPrevious : {
type:Boolean
},
isFirstYearWorker101 : {
type:Boolean
},
lastLogin :{
type:Number
},
loginAttempts : {
type:Number
},
});

I have followed the schema given by you and I am able to get all documents from a Users collection , which doesn't have any form connected to it using $lookup. Please use below aggregate query
db.user.aggregate([
{
$lookup:{
"localField": "_id",
"from": "form",
"foreignField": "employeeId",
"as": "forminfo"
}
},{
$match:{
forminfo: {$size: 0}
}
}
])

Related

How can I find fields with the same values in mongoose?

I want to find matches with the same match_id but I am unable to solution using mongoose . Any help would be appreciated , thank you. My match_id is type String and I want to check for duplicate matches that are being created .
Match Schema :
const MatchSchema = new mongoose.Schema({
match_id: {
type: String,
},
player1Id: {
type: String,
},
player2Id: {
type: String,
},
player1VideoId: {
type: String,
default: "blank",
},
player2VideoId: {
type: String,
default: "blank",
},
player1Score: {
type: Object,
default:{'noOne':0}
},
player2Score: {
type: Object,
default:{'noOne':0}
},
isWinner:{
type:String,
},
round:{
type: Number,
},
tournamentCategory:{
type:String,
},
tournamentId:{
type:ObjectID,
},
player1Details: {
type: mongoose.Schema.Types.ObjectId,
ref: "Contestant",
},
player2Details: {
type: mongoose.Schema.Types.ObjectId,
ref: "Contestant",
},
});
you should use $group for grouping data based on match_id , and select fields with count > 1, like this
let x = await Model.aggregate([
{
$group:{
_id : "$match_id",
result : {$push:"$$ROOT"},
count: { $sum: 1 }
}
},
{ "$match": { "count": { "$gte": 2 } } }
])

Cascaded join using mongoose node js

I am trying to fetch data from MongoDB using Node Js. I have three schemas: Projects, Users, and Teams.
I need to retrieve the project details based on it's type with the worker users.
I got stuck in making join for these schemas:
Projects:
const Project = new Schema({
projectName: { type: String, required: true, trim: true },
type: { type: String, required: true, trim: true },
teamID: { type: Schema.Types.ObjectId, required: true },
});
Teams
const Team = new Schema({
teamId: { type: Schema.Types.ObjectId, required: true, trim: true },
users: { type: [Schema.Types.ObjectId], required: true, trim: true },
teamName: { type: String, required: true },
});
Users:
const User = new Schema({
userId: { type: Schema.Types.ObjectId, required: true, trim: true },
name: { type: String, required: true, trim: true },
profilePicture: { type: String, required: true, trim: true },
});
I am trying to find a way to get
[
{
projectName: "s",
type: "w",
users: ["Jon", "Ali", "Mark"]
},
{
projectName: "a",
type: "w",
users: ["Jon", "Mark"]
}, {
projectName: "s",
type: "w",
users: ["Jon", "Ali", "Mark"]
},
]
I tried to use $lookup, but I can not use it because the relation is complex many to many relations.
Is there a way more efficient than retrieving all users, all teams, and all projects?
I think there is no other efficient way except aggregation and without lookup we can't join collections, You can use nested lookup,
$match condition for type
$lookup to join Team collection using teamID
$match teamID
$lookup to join User collection using users array
$project to convert user's name array using $map
$addFields to get users array in users using $arrayElemAt
db.Project.aggregate([
{ $match: { type: "w" } },
{
$lookup: {
from: "Team",
let: { teamID: "$teamID" },
as: "users",
pipeline: [
{ $match: { $expr: { $eq: ["$$teamID", "$teamId"] } } },
{
$lookup: {
from: "User",
localField: "users",
foreignField: "userId",
as: "users"
}
},
{
$project: {
users: {
$map: {
input: "$users",
in: "$$this.name"
}
}
}
}
]
}
},
{ $addFields: { users: { $arrayElemAt: ["$users.users", 0] } } }
])
Playground
Second possible way, you can combine $project and $addFields stages in single stage,
{
$addFields: {
users: {
$arrayElemAt: [
{
$map: {
input: "$users.users",
in: "$$this.name"
}
},
0
]
}
}
}
Playground

updateMany and elemMatch in with nested schemas in mongoose (Node.js)

I'm trying to query a MongoDB database via mongoose to updateMany the fields of my database. I suppose that the first request is correct because mongoose doesn't fire any error, but for the nested schemas, I'm getting the following error.
My goal is to delete the occurences of the userTag in friends and remove the friendRequestsSent when userTarget equals userTag, friendRequestsReceived when userRequest equals userTag and notification when data equals userTag.
Here are the schemas of my Model
const NotificationSchema = new Schema({
title: String,
type: Number,
icon: String,
data: String,
createdAt: { type: Date, default: Date.now },
})
const FriendRequestSchema = new Schema({
userRequest: { type: String, required: true },
userTarget: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
})
const UserSchema = new Schema({
tag: { type: String, required: true, unique: true },
friendRequestsSent: { type: [FriendRequestSchema] },
friendRequestsReceived: { type: [FriendRequestSchema] },
friends: { type: [String] },
notifications: { type: [NotificationSchema] },
})
The request
const updateResponse = await User.updateMany(
{
friends: { $elemMatch: { $eq: userTag } },
friendRequestsSent: {
userTarget: {
$elemMatch: { $eq: userTag },
},
},
friendRequestsReceived: {
userRequest: {
$elemMatch: { $eq: userTag },
},
},
notifications: {
data: {
$elemMatch: { $eq: userTag },
},
},
},
{
$pull: {
friends: userTag,
friendRequestsSent: { userTarget: userTag },
friendRequestsReceived: { userRequest: userTag },
notifications: { data: userTag },
},
}
)
The error
Error while deleting the user account: Cast to String failed for value "{ '$elemMatch': { '$eq': '0eQzaAwpt' } }" at path "userRequest" for model "User"
The userRequest field in friendRequestsReceived is type String, not array so $elemMatch will not work. Also, you don't need to use $elemMatch because you specify only a single condition in the $elemMatch expression as it says in the docs:
If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch.
In your case, you just need to do something like (details here):
await User.updateMany({
friends: userTag,
"friendRequestsSent.userTarget" : userTag,
"friendRequestsReceived.userRequest": userTag,
"notifications.data": userTag
}...

Node.js with mongoose delete one array element

I am trying to delete one array element when I click delete button on jade view page.
When clicked, it's going to send selected instructor objected as req.body.
At sever side, it will find courses that contain the instructor objectId.
Any idea for me?
Thank you for reading it.
here is my code:
var id = req.body._id;
clist.find({ instructors: { $in: [id] } }).exec(function (err, result) {
result.forEach(function (obj) {
clist.update(
{ _id: new mongoose.Types.ObjectId(obj._id)},
{ $pull: { instructors : [new mongoose.Types.ObjectId(id)] } }
);
console.log(new mongoose.Types.ObjectId(obj._id) + ' was deleted');
});
});
Schema Clist and ilist:
var instructorlist = mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true },
gender: { type: String, required: true },
DOB: { type: Date, required: true, default: Date.now },
email: { type: String, required: true },
phone: { type: Number, required: true },
address: { type: String, required: true },
dateofstart: { type: Date, required: true},
courses: [{
type: mongoose.Schema.Types.ObjectId,
ref: "clist"
}]
});
var courselist = mongoose.Schema({
coursename: { type: String, required: true },
coursenumber: { type: String, required: true },
coursecredit: { type: Number, required: true },
courseroom: { type: String, required: false },
courseregisteddate: {type: Date, default: Date.now},
students: [{
type: mongoose.Schema.Types.ObjectId,
ref: "slist"
}],
instructors: [{
type: mongoose.Schema.Types.ObjectId,
ref: "ilist"
}]
});
one example for mongodb :
{
"_id": {
"$oid": "591a7a3b391a1842e8a69e23"
},
"coursename": "JDKD",
"coursenumber": "COMP4483",
"coursecredit": 4,
"courseroom": "sdaf",
"instructors": [
{
"$oid": "591a374422a3a13d38c0bbe5"
}
],
"students": [],
"courseregisteddate": {
"$date": "2017-05-16T04:04:11.848Z"
},
"__v": 0
}
When I add instructor objectID in Course.
var newcourse = new clist({
'coursename': req.body.coursename, 'coursenumber': req.body.coursenumber, 'coursecredit': req.body.coursecredit
, 'courseroom': req.body.room, 'instructors': instructors._id
});
Use same operation to find and update multiple
clist.update(
{ instructors: { $in: [id] }},
{ $pull: { instructors : { _id : new mongoose.Types.ObjectId(id) } } }, //or{ $pull: { instructors: mongoose.Types.ObjectId(id) } }
{
multi:true
},
function(error, success){
if(error){
console.log("error",error)
}
console.log("success",success)
});

Mongoose find last messages grouping by two fields

I would like to put together a contact list based on the last messages sent or received from a contact.
To do this, I have to sort the last messages (both sent and received), add them and return the last one.
My response should be in the following format:
{
"success": true,
"chatlist": [
{
"id_user": "54228fe2c0df8d1120ed091b",
"lastMessage": {
"content": "message 6",
"date": "2016-11-09T02:54:41.687Z"
},
"unreadMessages": 3,
"name": "user 1"
},
{
"id_user": "12228fe2c0df8d11204g4d",
"lastMessage": {
"content": "message 3",
"date": "2016-11-09T02:54:23.329Z"
},
"unreadMessages": 2,
"name": "user 2"
},
{
"id_user": "58228fe2c0df8d1120e12sd",
"lastMessage": {
"content": "message 1",
"date": "2016-11-09T02:54:19.313Z"
},
"unreadMessages": 1,
"name": "user 3"
}
],
"pages": 2
}
My User Schema is:
var schema = new Schema({
name: {type: String, required: true},
email: {type: String, required: true, unique: true},
password: {type: String, required: true, select: false},
created_at: {type: Date, required: true, default: Date.now}
});
My Message Schema is:
var schema = new Schema({
content: {type: String, required: true},
type: {type: String, required: true, default: 'text'},
status: {type: String, default: 'not_read'},
created_at: {type: Date, default: Date.now},
read_at: {type: Date},
userFrom: {type: Schema.Types.ObjectId, ref: 'User', required: true},
userTo: {type: Schema.Types.ObjectId, ref: 'User', required: true}
});
I have tryed this:
var itensPerPage = 15;
var skip = page !== undefined ? page * itensPerPage : 0;
pages = Math.ceil(pages / itensPerPage);
Message
.aggregate([
{ '$sort': {
'created_at': -1
}},
{ "$skip": skip },
{ "$limit": itensPerPage },
{ '$match': {
$or: [
{ userFrom: user.id_user },
{ userTo: user.id_user }
]
}},
{ '$group': {
'_id': {
'userFrom': '$userFrom',
'userTo': '$userTo'
},
}
},
])
.exec(function (err, messages) {
res.send({"success": true, "chatlist": messages, "pages": pages});
});
How can I modify my query to get the desired response?
Thank you.
Try this
var itensPerPage = 15;
var skip = page !== undefined ? page * itensPerPage : 0;
pages = Math.ceil(pages / itensPerPage);
Message
.aggregate([
{ '$sort': {
'created_at': -1
}},
{ "$skip": skip },
{ "$limit": itensPerPage },
{ '$match': {
$or: [
{ userFrom: user.id_user },
{ userTo: user.id_user }
]
}},
{$unwind : $chatlist},
{$sort : {chatlist.date : -1}},
{$group : {_id : $_id},chatlist : {$push : $chatlist}},
])
.exec(function (err, messages) {
res.send({"success": true, "chatlist": messages, "pages": pages});
});

Resources