how to select a property depending another property - mongodb - node.js

I'm using mongoose. I want to select users property depending on another property at this here type.
for example when my type is private I want to select users.
Conversation.find({
users: {
$elemMatch: {
user: _id
}
}
},{
title: 1,
type: 1,
users:1 // when `type` is `private` I want to this field to be one.
});
my Schema:
const ConversationSchema = new Schema({
type: {type: String, enum: ['private', 'group'], default: 'private'},
creator: {type: Schema.Types.ObjectId, ref: 'User', index: true, required: true}, // creator
// for group,
title: String,
picture: String,
description: String,
users: [
{
user: { type: Schema.Types.ObjectId, index: true, reuqired: true, unique: true },
role: { type: String, enum: ['admin', 'member'], default: 'member' },
mute: { type: Boolean, default: false },
type: {type: String, enum: ['private', 'group'], default: 'private'},
}
],
}, { timestamps: true });

You can conditionally exclude fields by using REMOVE in aggregation. In your case, it should be:
Conversation.aggregate([
{$match: {"users.user": id}},
{
$project: {
title: 1,
type: 1,
users: {
$cond: {
if: { $eq: [ "$type", "private" ] },
then: "$users",
else: "$$REMOVE"
}
}
}
}
])
Side note: If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch.

You can use aggregate like this, it will select all users if you want to users detail then will have to use populate.
db.getCollection("Conversation").aggregate([
{
$unwind: "$users"
},
{
$match: {
"type": 'private'
}
}
]);

Related

Mongoose, how to sort the Date of a selected object inside a collection

I have this mongoose schema where I want to sort menus items according to their createdAt field
user:
...
menus: [
{
name: { type: String },
recipes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Recipe',
},
],
image: {
url: { type: String },
type: {
type: String,
enum: avatarTypes,
}
},
createdAt: {
type: Date,
required: true,
default: Date.now
}
},
],
and in nodeJs when I fetch them I want to sort them according the createdAt in the menus (latest first)
and this is my code but it's not working
const menus = await User.findOne({
_id: id,
}).sort('-menus.createdAt')

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
}...

How we can perform sort on ObjectId column in mongodb using mongoose with expressjs-nodejs?

We are struct at one mongodb point which I would like to describe here.
We are using mongoose 5.4 and create a model like below :
var userSchema = mongoose.Schema({
id:{ type: Number, default: 1 },
first_name: String,
last_name: String,
mail: String,
password: String,
dob: { type: String, default: '' },
gender: { type: String, default: '' },
profile_photo: { type: String, default: '' },
ethnicity: { type: String, default: '' },
contact_number: { type: String, default: '' },
user_type: Number,
address1: { type: String, default: '' },
address2: { type: String, default: '' },
area: { type: String, default: '' },
city: { type: String, default: '' },
country: { type: String, default: '' },
postcode: { type: String, default: '' },
business_name: { type: String, default: '' },
ip_address: String,
status: Number,
tag_line: { type: String, default: '' },
is_influencer: Number,
wallet_id: String,
token_balance: { type: Number, default: 0 },
point_balance: { type: Number, default: 0 },
badges: [String],
membership: { type: Schema.Types.ObjectId, ref: 'Membership' },
transaction: [{ type: Schema.Types.ObjectId, ref: 'Transaction' }],
property: [{ type: Schema.Types.ObjectId, ref: 'Property' }],
reviews: [{ type: Schema.Types.ObjectId, ref: 'Review' }],
created_date: String,
});
var User = mongoose.model('User', userSchema);
var propertySchema = mongoose.Schema({
id: Number,
property_name: String,
address1: String,
area: String,
post_code: String,
category: [{ type: Schema.Types.ObjectId, ref: 'Category' }],
category_id: [Number],
property_desc: String,
property_images: String,
slug : String,
user_id: Number,
business_key: String,
user: { type: Schema.Types.ObjectId, ref: 'User' },
reviews: [{ type: Schema.Types.ObjectId, ref: 'Review' }],
status: Number,
is_claimed: Number,
created_date: String,
});
var Property = mongoose.model('Property', propertySchema);
var categorySchema = mongoose.Schema({
id: Number,
category_name: String,
status: Number,
user: { type: Schema.Types.ObjectId, ref: 'User' },
property: [{ type: Schema.Types.ObjectId, ref: 'Property' }],
created_date: String,
updated_date: String
});
var Category = mongoose.model('Category', categorySchema);
we are also using async-await for this project. we are fetching locations using below method to get location data as well User and Category.
sortClause = {};
sortClause.user=parseInt(-1);
let properties = await Property.find({'status':{$in:[1,2]}}).populate({path: 'user',
model: 'User',select: 'first_name last_name mail contact_number'}).populate({path: 'category',
model: 'Category',select: 'category_name id'}).sort(sortClause).skip(skip).limit(perpage).exec();
so when we get data in properties object after the execution of above line we are getting properties but as we are sorting by user's column of property model it does fetch data properly. It sorts on objectID and we would like to fetch on user's first_name.
I have tried by specifing `{'user.first_name':-1} in sort order but it doesn't work at all.
When we sort on string or Number column it works fine, but here my expected result will be bit different, Here we would like apply sort on user.first_name (Here in above example user is the ObjectId column which populate Users data and I would like to sort on first_name column of user)
How can we get properties based on user's first_name column? Is there any suggestion for this problem.
You can try below aggregation from mongodb 3.6 and above
Property.aggregate([
{ "$match": { "status": { "$in": status }} },
{ "$lookup": {
"from": "users",
"let": { "user": "$user" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$user" ] } } },
{ "$project": { "first_name": 1, "last_name": 1, "mail": 1, "contact_number": 1 }}
],
"as": "user"
}},
{ "$lookup": {
"from": "categories",
"let": { "category": "$category" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$category" ] } } },
{ "$project": { "category_name": 1, "id": 1 }}
],
"as": "category"
}},
{ "$unwind": "user" },
{ "$sort": { "user.first_name": -1 }},
{ "$skip": skip },
{ "$limit": perpage }
])

Mongoose sub field aggregation with full text search and project

I have a Mongoose model called Session with a field named course (Course model) and I want to perform full text search on sessions with full text search, also I wanna aggregate results using fields from course sub field and to select some fields like course, date, etc.
I tried the following:
Session.aggregate(
[
{
$match: { $text: { $search: 'web' } }
},
{ $unwind: '$course' },
{
$project: {
course: '$course',
date: '$date',
address: '$address',
available: '$available'
}
},
{
$group: {
_id: { title: '$course.title', category: '$course.courseCategory', language: '$course.language' }
}
}
],
function(err, result) {
if (err) {
console.error(err);
} else {
Session.deepPopulate(result, 'course course.trainer
course.courseCategory', function(err, sessions) {
res.json(sessions);
});
}
}
);
My models:
Session
schema = new mongoose.Schema(
{
date: {
type: Date,
required: true
},
course: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course',
required: true
},
palnning: {
type: [Schedule]
},
attachments: {
type: [Attachment]
},
topics: {
type: [Topic]
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
address: {
type: Address
},
quizzes: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'quiz'
},
path: {
type: String
},
limitPlaces: {
type: Number
},
status: {
type: String
},
available: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: new Date()
},
updatedAt: {
type: Date
}
},
{
versionKey: false
}
);
Course
let schema = new mongoose.Schema(
{
title: {
type: String,
required: true
},
description: {
type: String
},
shortDescription: {
type: String
},
duration: {
type: Duration
},
slug: {
type: String
},
slugs: {
type: [String]
},
program: {
content: {
type: String
},
file: {
type: String
}
},
audience: [String],
requirements: [String],
language: {
type: String,
enum: languages
},
price: {
type: Number
},
sections: [Section],
attachments: {
type: [Attachment]
},
tags: [String],
courseCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course-category',
required: true
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
status: {
type: String,
default: 'draft',
enum: courseStatus
},
path: {
type: String
},
cover: {
type: String,
required: true
},
duration: {
type: Number,
min: 1
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date
}
},
{ versionKey: false }
);
I am not sure if what I tried is gonna bring me what I want and I am getting this error concerning the $unwind operator:
MongoError: exception: Value at end of $unwind field path '$course'
must be an Array, but is a OID
Any kind of help will be really appreciated.
You can try below aggregation.
You are missing $lookup required to pull course document by joining on course object id from session document to id in the course document.
$project stage to keep the desired fields in the output.
Session.aggregate([
{
"$match": {
"$text": {
"$search": "web"
}
}
},
{
"$lookup": {
"from": "courses",
"localField": "course",
"foreignField": "_id",
"as": "course"
}
},
{
"$project": {
"course": 1,
"date": 1,
"address": 1,
"available": 1
}
}
])
Course is an array with one course document. You can use the $arrayElemAt to project the document.
"course": {"$arrayElemAt":["$course", 0]}

Resources