I am using MongoDB with mongoose as ORM library for my NodeJS project. Here I am putting two versions of same query, the problem is NoSqlBooster version is returning value correctly, where mongoose version is returning empty array, Please find the both versions of the query.
NoSQLBooster Version:
db.users.find({
isDeleted: false,
skills: { '$in': [ ObjectId('613b9951e42a5203c421cbcc'), ObjectId('613b9951e42a5203c421cbcd') ] },
interests: { '$in': [ ObjectId('613b9951e42a5203c421cbce'), ObjectId('613b9951e42a5203c421cbcf') ] },
university: { '$in': [ ObjectId('613b9951e42a5203c421cbd0'), ObjectId('613b9951e42a5203c421cbd1') ] },
title: { '$in': [ ObjectId('613b785b7bacfe1336ab9dbf'), ObjectId('613b9951e42a5203c421cbd2') ] }
});
which is working perfectly, but when I am pushing the same query on mongoose find() its returning empty array. Please find the mongoose version below.
{
isDeleted: false,
skills: { '$in': [ '613b9951e42a5203c421cbcc', '613b9951e42a5203c421cbcd' ] },
interest: { '$in': [ '613b9951e42a5203c421cbce', '613b9951e42a5203c421cbcf' ] },
university: { '$in': [ '613b9951e42a5203c421cbd0', '613b9951e42a5203c421cbd1' ] },
title: { '$in': [ '613b785b7bacfe1336ab9dbf', '613b9951e42a5203c421cbd2' ] }
}
Please note, above is the request I get from frontend itself, I am just pushing it to find.
Please find the schema structure for these specific fields,
{
skills: [
{ type: schema.Types.ObjectId, ref: "Skills" }
],
interests: [
{ type: schema.Types.ObjectId, ref: "Interests" }
],
university: [{ type: schema.Types.ObjectId, ref: "University" }],
title: [{ type: schema.Types.ObjectId, ref: "Titles"}]
}
here is the find() function that I am using,
if (
this.req.body &&
this.req.body.filterParam &&
!_.isEmpty(this.req.body.filterParam)
) {
makeQuery = JSON.parse(JSON.stringify(this.req.body.filterParam));
}
if(_.isEmpty(this.req.body.searchText) && _.isEmpty(this.req.body.filterParam)) {
makeQuery = { isDeleted: false };
}
console.log(makeQuery);
let getListOfUsers = await Users.find(makeQuery)
.sort(sort)
.skip(skip)
.limit(perPage)
.populate("skills", { type: 1 }, { isDeleted: false })
.populate("interests", { type: 1 }, { isDeleted: false })
.populate("university", { type: 1 }, { isDeleted: false })
.populate("business_tags", { type: 1 }, { isDeleted: false })
.populate("title", { type: 1 }, { isDeleted: false })
.populate("likesUserManagement", { likedFlag: 1 }, { isDeleted: false, likedFlag: true, createdBy: currentUser })
.select({
password: 0,
status: 0,
emailVerificationStatus: 0,
isProfileCompleted: 0,
isDeleted: 0,
previouslyUsedPasswords: 0,
ifForgetOtpVerified: 0,
portfolio_name: 0,
portfolio_s3_name: 0,
image_name: 0,
createdAt: 0,
updatedAt: 0,
__v: 0,
});
Can anyone help me to solve this issue. I am stuck with this. Please help me. Thanks.
Related
I'm working on a social media app and I'm having this problem, I have a collection for friends to store friend requests and the relationship between users and everything works fine, but I'm working on a post('save') middleware and aggrgate pipeline to count the number of friends per user per process which also worked But the problem is aggrgate pipeline counting friends before update collection eg if user accepts friend request it counts friends without this new friend.
Is there any solution to this or best way to do it?
Here is the code
const mongoose = require('mongoose');
const User = require('./userModel');
const FriendSchema = new mongoose.Schema({
sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
recipient: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
status: {
type: String,
enum: ['pending', 'accepted', 'cancelled'],
required: true,
},
createdAt: { type: Date, default: Date.now },
});
FriendSchema.statics.calcFriends = async function (senderID, recipientID) {
const senderIDstats = await this.aggregate([
{
$match: {
$or: [
{ sender: senderID, status: 'accepted' },
{ recipient: senderID, status: 'accepted' },
],
},
},
{
$group: {
_id: null,
count: { $sum: 1 },
},
},
]);
const recipientIDstats = await this.aggregate([
{
$match: {
$or: [
{ sender: recipientID, status: 'accepted' },
{ recipient: recipientID, status: 'accepted' },
],
},
},
{
$group: {
_id: null,
count: { $sum: 1 },
},
},
]);
await User.bulkWrite([
{
updateOne: {
filter: { _id: senderID },
update: {
friendsCount: senderIDstats.length > 0 ? senderIDstats[0]?.count : 0,
},
},
},
{
updateOne: {
filter: { _id: recipientID },
update: {
friendsCount:
recipientIDstats.length > 0 ? recipientIDstats[0]?.count : 0,
},
},
},
]);
};
FriendSchema.index({ sender: 1, recipient: 1 }, { unique: true });
FriendSchema.post('save', function () {
this.constructor.calcFriends(this.sender, this.recipient);
});
const Friend = mongoose.model('Friend', FriendSchema);
module.exports = Friend;
I have a list of task records (see schema below). I am attempting to return records for a specific projectId, group my task records by status, and populate the responsible field. However, the responsible field is not populating. I have attached a code snippet below. Can anyone advise what I am doing incorrectly?
Code Snippet:
const test = await Task.aggregate([
{
$match: { project: { $eq: mongoose.Types.ObjectId(projectId) } },
},
{
$group: {
_id: "$status",
data: {
$push: {
_id: "$status",
name: "$name",
responsible: "$responsible",
endDate: "$endDate",
},
},
},
},
{
$sort: { status: 1 },
},
]);
console.log("test1:", test);
// Populate Aggregated Data:
const tasks = await User.populate(test, { path: "data.responsible" });
console.log("test2:", tasks);
TaskSchema:
const TaskSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true,
},
responsible: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
endDate: {
type: Date,
required: false,
},
status: {
type: String,
enum: ["new", "active", "inactive", "closed"],
required: true,
},
project: {
type: mongoose.Schema.Types.ObjectId,
ref: "Project",
},
});
Desired Results:
results = [
{
_id: “new”
data: [
{
endDate: "2022-09-16T04:00:00.000Z”,
name: "test1”,
responsible: {
email: “jane.doe#ymail.com”,
firstName: “Jane”,
lastName: “Doe”
},
{
endDate: "2022-09-16T04:00:00.000Z”,
name: "test2”,
responsible: {
email: “john.doe#ymail.com”,
firstName: “John”,
lastName: “Doe”
},
]
},
]
I'm following Jonas Schmeddtman Node.js course and building a tour App. For some reason, when I send a request using postman on the route upon which this function is called, it returns an empty array instead of the manipulated data.
Below is my complete code. Thanks in advance.
exports.getTourStats=async(req,res)=>
{
try
{
const stats= await Tour.aggregate([
{
$match: { ratingsAverage: { $gte: 4.5 } }
},
{
$group:
{
_id: { $toUpper: '$difficulty' },
numTours: { $sum: 1 },
numRatings: { $sum: '$ratingsQuantity' },
avgRating: { $avg: '$ratingsAverage' },
avgPrice: { $avg: '$price' },
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' }
}
}
]);
res.status(200).json(
{
status:"success",
data:
{
stats
}
});
}
catch(error)
{
res.status(400).json(
{
status:"failed!",
message:error
})
}
}
//an example document is as below.
"id": 8,
"name": "The Northern Lights",
"duration": 3,
"maxGroupSize": 12,
"difficulty": "easy",
"ratingsAverage": 4.9,
"ratingsQuantity": 33,
"price": 1497,
"summary": "Enjoy the Northern Lights in one of the best places in the world",
"description": "dummy description",
"imageCover": "tour-9-cover.jpg",
"images": ["tour-9-1.jpg", "tour-9-2.jpg", "tour-9-3.jpg"],
"startDates": ["2021-12-16,10:00", "2022-01-16,10:00", "2022-12-12,10:00"]
// schema.
const tourSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'A tour must have a name'],
unique: true,
},
duration: {
type: Number,
required: [true, 'a tour must have a duaration'],
},
maxGroupSize: {
type: Number,
required: [true, 'a tour must have a max group size'],
},
difficulty: {
type: String,
required: [true, 'a tour must have a diffculty'],
},
ratingAverage: {
type: Number,
default: 4.5,
},
ratingQuantity: {
type: Number,
default: 0,
},
price: {
type: Number,
required: [true, 'A tour must have a price'],
},
priceDiscount: {
type: Number,
},
summary: {
type: String,
trim: true,
required: [true, 'A tour must have a summary'],
},
description: {
type: String,
trim: true,
},
imageCover: {
type: String,
required: [true, 'A tour must have an image cover'],
},
images: [String],
createdAt: {
type: Date,
default: Date.now(),
//to exclude the created at property from response sent back to user we put select property to false.
select: false,
},
startDates: [Date],
});
//creating a model out of the schema we defined.
const Tour = mongoose.model('Tour', tourSchema);
module.exports = Tour;
In the end of aggregation you should use toArray().Then only aggregated values get instead of getting the aggregationCursor as out.
exports.getTourStats=async(req,res)=>
{
try
{
const stats= await Tour.aggregate([
{
$match: { ratingsAverage: { $gte: 4.5 } }
},
{
$group:
{
_id: { $toUpper: '$difficulty' },
numTours: { $sum: 1 },
numRatings: { $sum: '$ratingsQuantity' },
avgRating: { $avg: '$ratingsAverage' },
avgPrice: { $avg: '$price' },
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' }
}
}
]).toArray()
res.status(200).json(
{
status:"success",
data:
{
stats
}
});
}
catch(error)
{
res.status(400).json(
{
status:"failed!",
message:error
})
}
}
This is my Mongoose Model:
const postSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
caption: {
type: String
},
action: {
type: [{
actionName: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}],
default: []
},
shares: [{
type: Schema.Types.ObjectId,
ref: 'User'
}];
});
All I want is to have a mongodb query with or without using .aggregate() to get the user & caption field as it is but instead of action and shares I want their counts for a particular document.
Sample Document
{
_id: "fgsergehegoieofgesfglesfg",
user: "dfjksjdnfkjsdfkjsdklfjglkbj",
caption: "This is the post caption",
action: [
{
actionName: 'e1', user: "sdfasdsdfasdfdsdfac951e5c"
},
{
actionName: 'e1', user: "asdfmadfadfee103c9c951e5d"
},
{
actionName: 'e2', user: "op34937cdbae0cd4160bbec"
},
{
actionName: 'e2', user: "2543ebbasdfd1750690b5b01c"
},
{
actionName: 'e3', user: "asdfcfebdb5dd1750690b5b01d"
},
],
shares: ["ewrebdb5ddadsf5069sadf1d", "asdfsdfbb85dd1750690b5b01c", "fasec92dsfasde103c9c95df5d"]
};
Desired output after query:
{
_id: "fgsergehegoieofgesfglesfg",
user: 'dfjksjdnfkjsdfkjsdklfjglkbj',
caption: 'This is the post caption',
actionCount: [{ count: 1, actionName: 'e3' },
{ count: 2, actionName: 'e2' },
{ count: 2, actionName: 'e1' }],
shareCount: 3
}
I am able do get following results using .aggregate():
Query:
let data = await Test.aggregate([
{ $match: { _id: mongoose.Types.ObjectId("fgsergehegoieofgesfglesfg") } },
{ $unwind: "$action" },
{
$group: {
_id: "$action.actionName",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
actionName: "$_id",
count: 1
}
}
]);
Result:
[
{ count: 1, actionName: 'e3' },
{ count: 2, actionName: 'e2' },
{ count: 2, actionName: 'e1' }
]
I just want to put this in the original document and get the result. Also, doing the same for share field. It would be better if this can be done in single query. I have tried using $replaceRoot along with $mergeObjects but don't know how to correctly use them. I am very new to mongodb and mongoose.
Please help. Thank you.
Since you're aggregating a nested array you need to run $grouptwice and $first can be used to preserve original document's field values:
await Test.aggregate([
{ $match: { _id: mongoose.Types.ObjectId("fgsergehegoieofgesfglesfg") } },
{ $unwind: "$action" },
{
$group: {
_id: { _id: "$_id", actionName: "$action.actionName" },
user: { $first: "$user" },
caption: { $first: "$caption" },
count: { $sum: 1 },
shareCount: { $first: { $size: "$shares" } }
}
},
{
$group: {
_id: "$_id._id",
user: { $first: "$user" },
caption: { $first: "$caption" },
shareCount: { $first: "$shareCount" },
actionCount: {
$push: {
actionName: "$_id.actionName",
count: "$count"
}
}
}
}
])
Mongo Playground
Hi I have following model of a document in mongodb
Schema is
const ProductionsSchema=new Schema({
name: {type: String, required: true, unique: true},
isActive: {type: Boolean, default: true},
locations: [{
name: {type: String},
isActive : {type: Boolean, default: false}
}],
trackno: {type: String}
})
Productions:[{
_id: 125,
name: 'John Smith',
locations: [{ name: 'Boston', isActive: true}]
isActive: true,
trackno: 2123
},
{
_id: 126,
name: 'Moe Adam',
locations: [{ name: 'Chicago', isActive: true}]
isActive: true,
trackno: 5663
},
{
_id: 126,
name: 'Henry Noel',
locations: [{ name: 'Houston', isActive: false}]
isActive: true,
trackno: 4552
},
{
_id: 128,
name: 'Tim Johnson',
locations: [{ name: 'Denver', isActive: true}]
isActive: false,
trackno: 6672
}
]
I am trying to find list of with both isActive true
Productions.find({"isActive" : true , "locations.isActive": true}, (err, list)=>{
if(err){
callback(err);
}
callback(null, list)
})
I am trying to write query so both isActive are true. In above sample data only first two records should be in the answer. But I keep getting all the records even ones with 'false' I even tried $elemMatch on locations.isActive still didnt work.
Please let me know how I can fix this so that I only get result that contains only true values for both isActive.
As the original comment explained, the only query conditions you need are:
{ isActive: true, "locations.isActive": true }
This is a basic AND condition, and you don't need any special operators just to verify a condition is met on a single property anywhere in an array, which is all you are asking.
Since this works exactly as expected, I can only think to show you a full working listing to use as a basis to work out what you are doing differently thus causing you to not get the same result as what is expected.
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/productions';
const opts = { useNewUrlParser: true };
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);
const productionSchema = new Schema({
name: String,
isActive: { type: Boolean, default: true },
locations: [{
name: String,
isActive: { type: Boolean, default: false }
}],
trackno: Number
})
const Production = mongoose.model('Production', productionSchema);
const data =
[
{
name: 'John Smith',
locations: [{ name: 'Boston', isActive: true}],
isActive: true,
trackno: 2123
},
{
name: 'Moe Adam',
locations: [{ name: 'Chicago', isActive: true}],
isActive: true,
trackno: 5663
},
{
name: 'Henry Noel',
locations: [{ name: 'Houston', isActive: false}],
isActive: true,
trackno: 4552
},
{
name: 'Tim Johnson',
locations: [{ name: 'Denver', isActive: true}],
isActive: false,
trackno: 6672
}
];
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean data
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// set up
await Production.insertMany(data);
// Query
let query = { isActive: true, "locations.isActive": true };
let results = await Production.find(query);
log(results);
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
Which outputs the two expected documents:
Mongoose: productions.deleteMany({}, {})
Mongoose: productions.insertMany([ { isActive: true, _id: 5c7f7e9367daed19d6773e9b, name: 'John Smith', locations: [ { isActive: true, _id: 5c7f7e9367daed19d6773e9c, name: 'Boston' } ], trackno: 2123, __v: 0 }, { isActive: true, _id: 5c7f7e9367daed19d6773e9d, name: 'Moe Adam', locations: [ { isActive: true, _id: 5c7f7e9367daed19d6773e9e, name: 'Chicago' } ], trackno: 5663, __v: 0 }, { isActive: true, _id: 5c7f7e9367daed19d6773e9f, name: 'Henry Noel', locations: [ { isActive: false, _id: 5c7f7e9367daed19d6773ea0, name: 'Houston' } ], trackno: 4552, __v: 0 }, { isActive: false, _id: 5c7f7e9367daed19d6773ea1, name: 'Tim Johnson', locations: [ { isActive: true, _id: 5c7f7e9367daed19d6773ea2, name: 'Denver' } ], trackno: 6672, __v: 0 } ], {})
Mongoose: productions.find({ isActive: true, 'locations.isActive': true }, { projection: {} })
[
{
"isActive": true,
"_id": "5c7f7e9367daed19d6773e9b",
"name": "John Smith",
"locations": [
{
"isActive": true,
"_id": "5c7f7e9367daed19d6773e9c",
"name": "Boston"
}
],
"trackno": 2123,
"__v": 0
},
{
"isActive": true,
"_id": "5c7f7e9367daed19d6773e9d",
"name": "Moe Adam",
"locations": [
{
"isActive": true,
"_id": "5c7f7e9367daed19d6773e9e",
"name": "Chicago"
}
],
"trackno": 5663,
"__v": 0
}
]