I have schema designed like this
const MerchantSchema = new mongoose.Schema({
mobile: {
type: Number,
required: true,
unique: true
},
user_type:{
type:String,
enum:['merchant'],
required: true
},
isVerified:{
type:Boolean,
default:()=>false
},
token:String,
location:{
type:{
type:String,
enum:['Point'],
default:()=>'Point'
},
coordinates:{
type:[Number],
required:true
}
},
images:[String],
reviews:[{
type:mongoose.Schema.Types.ObjectId,
ref:'reviews'
}],
});
MerchantSchema
.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
MerchantSchema.index({location:'2dsphere'});
module.exports = mongoose.model('merchants',MerchantSchema);
review schema
const mongoose = require('mongoose');
const ReviewSchema = new mongoose.Schema({
rating:{
type:Number,
required:true,
default:0
},
message: {
type: String,
required: true
},
owner_id:{
type:mongoose.Schema.Types.ObjectId,
ref:'merchants'
},
posted_by:{
type:mongoose.Schema.Types.ObjectId,
ref:'users',},
});
ReviewSchema
.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
module.exports = mongoose.model('reviews', ReviewSchema);
i want to get result based on geoLocation and with some condition
and i want additional field of avgRating in the result set.
i am trying with following query
try {
const merchants = await Merchant.aggregate([
// match merchant lat lng in radius
{
$geoNear: {
near: {
type: "Point",
coordinates: [
parseFloat(req.query.long),
parseFloat(req.query.lat)
]
},
distanceField: "distance",
maxDistance: Number(req.query.distance),
spherical: true
},
},
{ $match: { isActive: { $eq: true } } },
// {
// $unwind: {
// path: "$reviews"
// }
// },
{
$addFields: {
reviewCount: {
$size: "$reviews"
},
avgRating: {
$avg: "$reviews.rating",
}
}
},
// uncoment this to get all data except the reviews
{
$project: {
reviews: 0,
}
},
// uncomment this to get all the reviews withth slected fields
// if selected fields are not required then comment let section
// and pipeline section and uncomment local fiels and foreign fields
// {
// $lookup: {
// from: "reviews",
// let: { owner_id: "$_id" },
// pipeline: [
// {$match: { $expr: { $eq: [ "$owner_id", "$$owner_id" ] } } },
// { $project: { message: 1, posted_by: 1, _id: 0,rating:1 } }
// ],
// as: "reviews",
// }
// },
]);
return (merchants.length>0)?
apiResponse.successResponseWithData(res,"Success",merchants) :
apiResponse.ErrorResponse(res,{message:"No merchants found"});
} catch (error) {
return apiResponse.ErrorResponse(res, error.message || "Some error occurred while getting merchants.");
}
output i am getting is
{
"status": 1,
"message": "Success",
"data": [
{
"_id": "6223400356a5f3404b57a273",
"mobile": 9999999999,
"user_type": "merchant",
"location": {
"type": "Point",
"coordinates": [
25.75679373274295,
86.02907103277855
]
},
"images": [],
"isVerified": true,
"__v": 0,
"reviewCount": 2,
"facilityCount": 2,
"avgRating": null
}
]
}
expecting output
{
"status": 1,
"message": "Success",
"data": [
{
"_id": "6223400356a5f3404b57a273",
"mobile": 9999999999,
"user_type": "merchant",
"location": {
"type": "Point",
"coordinates": [
25.75679373274295,
86.02907103277855
]
},
"images": [],
"isVerified": true,
"__v": 0,
"reviewCount": 2,
"facilityCount": 2,
"avgRating": 3.0
}
]
}
what are the possibilities of getting the expecting outputs.
any suggestion would be highly appreciated
Your code works fine with me. I tried it on my own database. Below is the example.
db.Merchant.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: [
25.75679373274295,
86.02907103277855
]
},
distanceField: "distance",
maxDistance: 10,
spherical: true
}
},
{
$match: {
isActive: {
$eq: true
}
}
},
{
$lookup: {
from: "reviews",
let: {
owner_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$owner_id",
"$$owner_id"
]
}
}
},
{
$project: {
message: 1,
posted_by: 1,
_id: 0,
rating: 1
}
}
],
as: "reviews"
}
},
{
$addFields: {
reviewCount: {
$size: "$reviews"
},
avgRating: {
$avg: "$reviews.rating"
}
}
},
{
$project: {
reviews: 0
}
}
])
mongoplayground
Related
I have a debate collection, which holds all the debates and another collection holds the votes against each debate. So I wanted to retrieve all the debates with a new user flag (isVoted) if I found any user in the vote collection against each debate.
Vote model:
var voteSchema = new Schema({
user: { type: Schema.Types.ObjectId, required: true, ref: 'User' }, // One who votes
debate: { type: Schema.Types.ObjectId, required: true, ref: 'Debate' }
}, { timestamps: true });
Debate Model:
var debateSchema = new Schema({
category: { type: Schema.Types.ObjectId, required: true, ref: 'Category' },
question: { type: String, required: true },
Votes: { type: Number, default: 0 },
}, { timestamps: true });
Query
DebateData.aggregate([
{
$match: query
},
{
$sort : { createdAt : -1 }
},
{
$lookup: {
from: "votes", // must be the PHYSICAL name of the collection
localField: "_id",
foreignField: "debate",
as: "votes"
}
},
{
$addFields: {
'isVoted': {
$cond: { if: { $eq: [ '$votes.user', ObjectId(req.query.userId) ] }, then: 'true', else: 'false' }
}
}
},
{
$project: {
'_id': 1,
'question': 1,
'isVoted': 1,
'createdAt': 1
}
},
]).then(result => {
res.status(200).json({ success: true, status: 200, message: 'Debate videos', data: result});
}).catch(err => {
res.status(500).json({ success: false, status: 500, message: err.message })
});
Expected output:
{
"data": [
{
"_id": "60e81f8299a4809658290d80",
"votes": 10,
"category": [
{
"name": "Hockey"
}
],
"question": "What is football?",
"isVoted": true,
"createdAt": "2021-07-09T10:05:54.498Z"
},
{
"_id": "60e438f1194949add0cc2074",
"votes": 12,
"category": [
{
"name": "Cricket"
}
],
"question": "What is football?",
"isVoted": false,
"createdAt": "2021-07-06T11:05:21.654Z"
}
]
}
Current output:
{
"data": [
{
"_id": "60e81f8299a4809658290d80",
"votes": 10,
"category": [
{
"name": "Hockey"
}
],
"question": "What is football?",
"createdAt": "2021-07-09T10:05:54.498Z"
},
{
"_id": "60e438f1194949add0cc2074",
"votes": 12,
"category": [
{
"name": "Cricket"
}
],
"question": "What is football?",
"createdAt": "2021-07-06T11:05:21.654Z"
}
]
}
Vote Data:
{
data: [
{
"user": "69881f8299a480965829ytr267",
"debate": "60e81f8299a4809658290d80"
}
]
}
I have 2 schemas, this is parent collection schema:
const TimesheetSchema = Schema({
managersComment: {
type: String,
},
weekNum: {
type: Number,
},
year: {
type: Number,
},
user: { type: Schema.Types.ObjectId, ref: userModel },
status: {
type: String,
enum: ["Saved", "Submitted", "Approved", "Rejected"],
},
data: [{ type: Schema.Types.ObjectId, ref: TimesheetIndividualData }]
});
This is child collection schema
const TimesheetDataSchema = new Schema(
{
workingDate: {
type: Date,
},
dayVal: {
type: Number,
},
user: { type: Schema.Types.ObjectId, ref: userModel },
parentId: { type: String }
},
{ timestamps: true }
);
In TimesheetDataSchema parentId is basically the _id from TimesheetSchema.
Now i need to run a query which return docs from TimesheetDataSchema, but only the docs in which parentId(ObjectId) of TimesheetSchema has status Approved.
I am trying to do $lookup, but currently no success. Please help.
EDIT: Based upon #ashh suggestion tried this: but getting empty array.
const result = await TimesheetIndividualData.aggregate([
{
"$lookup": {
"from": "timesheetModel",
"let": { "parentId": "$parentId" },
"pipeline": [
{ "$match": { "status": "Approved", "$expr": { "$eq": ["$weekNum", "$parentId"] } } },
],
"as": "timesheet"
}
},
{ "$match": { "timesheet": { "$ne": [] } } }
])
You can use below aggregation
const result = await db.TimesheetDataSchema.aggregate([
{ "$lookup": {
"from": "TimesheetSchema",
"let": { "parentId": "$parentId" },
"pipeline": [
{ "$match": { "status": "approved", "$expr": { "$eq": ["$_id", "$$parentId"] }}},
],
"as": "timesheet"
}},
{ "$match": { "timesheet": { "$ne": [] }} }
])
But I would prefer two queries for better performance here
const timesheets = (await db.TimesheetSchema.find({ status: "approved" }, { _id: 1 })).map(({ _id }) => _id)
const result = await db.TimesheetDataSchema.find({ parentId: { $in: timesheets } })
I want to get the business information with businessId as a reference. However, I can't get the correct data because the previous developer did not use ObjectId type on the model. Now what I want to do is convert the businessId type to objectId withough altering the model, it would be easy if I do it but the old data will be affected, which is not good. Please see below for the model
const scanHistory = new Schema({
businessId: { type: String },
domains: [
{
domainId: { type: String },
scanType: { type: String },
status: { type: String },
reportUrl: { type: String },
scanStart: { type: Date, default: Date.now },
scanFinish: { type: Date, default: Date.now }
}
],
scanStart: { type: Date, default: Date.now },
scanStatus: { type: String },
scanType: { type: String }
});
This is my aggregate query
.collection("scanhistories")
.aggregate([
{
$addFields: {
businessObjId: {
$convert: {
input: "businessId",
to: "objectId",
onError: "Could not convert to type ObjectId."
}
}
}
},
{
$group: {
_id: { $max: "$businessId" },
businessObjId: { $max: "$businessId" },
scanStatus: { $max: "$scanStatus" },
scanStart: { $max: "$scanStart" },
domains: { $max: "$domains" }
}
},
{
$lookup: {
from: "businesses",
as: "businessInfo",
localField: "businessObjId",
foreignField: "_id"
}
},
{
$project: {
_id: 1,
businessObjId: 1,
primaryDomain: { $arrayElemAt: ["$businessInfo.primaryDomain", 0] },
businessName: { $arrayElemAt: ["$businessInfo.businessName", 0] },
frequency: { $arrayElemAt: ["$businessInfo.scanFrequency", 0] },
scanStatus: 1,
domains: 1
}
},
{
$match: {
scanStatus: { $in: ["running", "undef"] },
domains: { $exists: true }
}
}
])
.toArray();
for (let x = 0; x < history.length; x++) {
console.log(history[x]);
}
Now the output is like this which is not the one I expected.
{ _id: 5de09321bdb7cc07b7595de4,
businessObjId: 5de09321bdb7cc07b7595de4,
scanStatus: 'undef',
domains:
[ { _id: 5dfa626300007c243c1528b3,
domainId: '5de09321bdb7cc07b7595de5',
scanType: 'scheduled',
status: 'running',
reportUrl: '',
scanStart: 2019-12-18T17:31:14.754Z,
scanFinish: 2019-12-18T17:31:14.754Z } ] }
The expected result should have been with the lookup businessInfo that I wanted
{ _id: 5de09321bdb7cc07b7595de4,
businessObjId: 5de09321bdb7cc07b7595de4,
scanStatus: 'undef',
domains:
[ { _id: 5dfa626300007c243c1528b3,
domainId: '5de09321bdb7cc07b7595de5',
scanType: 'scheduled',
status: 'running',
reportUrl: '',
scanStart: 2019-12-18T17:31:14.754Z,
scanFinish: 2019-12-18T17:31:14.754Z } ],
primaryDomain: "mydomainxxx.xy",
businessName: "The biz",
scanFrequency: "daily"
}
Can you help me? I am really new to MongoDB and my background is PHP/SQL so all advises will be much appreciated. Thank you!
So I have found the solution for this. It was just a single mistake. :(
So on the aggregate code above where the group pipeline is. I made a mistake here.
Previous code above
{
$group: {
_id: { $max: "$businessId" },
businessObjId: { $max: "$businessId" },
scanStatus: { $max: "$scanStatus" },
scanStart: { $max: "$scanStart" },
domains: { $max: "$domains" }
}
},
Correct one
I change this part here:
businessObjId: { $first: "$businessObjId" }
{
$group: {
_id: { $max: "$businessId" },
businessObjId: { $first: "$businessObjId" },
scanStatus: { $max: "$scanStatus" },
scanStart: { $max: "$scanStart" },
domains: { $max: "$domains" }
}
},
I am getting this output:
Sorting on the inner array is not working. I have the two tables as shown below.
The pages schema is this:
const PageSchema = new Schema({
name: {
type: String,
required: true
},
created: {
type: Date
},
position: {
type: Number,
default: 0
}
});
module.exports = mongoose.model('pages', PageSchema);
The container schema is this:
const ContainerSchema = new Schema({
filename: {
type: String,
required: true
},pageId: {
type: Schema.Types.ObjectId,
ref: 'pages'
},
created: {
type: Date
}
});
For sorting the data I used this code:
Container.aggregate(match, {
"$group": {
"_id": {
"pageId": "$pageId",
"id": "$_id",
"filename": "$filename",
"position": "$position"
},
"containerCount": {
"$sum": 1
}
}
}, {
"$group": {
"_id": "$_id.pageId",
"container": {
"$push": {
"_id": "$_id.id",
"filename": "$_id.filename",
},
},
"position": {
"$first": "$_id.pageId.position"
}
"count": {
"$sum": "$containerCount"
}
}
}, {
"$project": {
"container": 1,
"count": 1
}
}, {
"$sort": {
"position": 1
}
}).exec()
I want the data sort according to the position field in the pages but it's not working.
You have forgotten to add position in $project.
Once you add in $project then its available in $sort
{
"$project": {
"position" :1,
"container": 1,
"count": 1
}
}
I'm trying to get a list of sorted comments by createdAt from a Post doc where an aggregate pipeline would be used to populate the owner of a comment in comments field with displayName and profilePhoto fields.
Post Schema:
{
_owner: { type: Schema.Types.ObjectId, ref: 'User', required: true },
...
comments: [
{
_owner: { type: Schema.Types.ObjectId, ref: 'User' },
createdAt: Number,
body: { type: String, maxlength: 200 }
}
]
}
User schema:
{
_id: '123abc'
profilePhoto: String,
displayName: String,
...
}
What I want to return:
[
{
"_id": "5bb5e99e040bf10b884b9653",
"_owner": {
"_id": "5bb51a97fb250722d4f5d5e1",
"profilePhoto": "https://...",
"displayName": "displayname"
},
"createdAt": 1538648478544,
"body": "Another comment"
},
{
"_id": "5bb5e96686f1973274c03880",
"_owner": {
"_id": "5bb51a97fb250722d4f5d5e1",
"profilePhoto": "https://...",
"displayName": "displayname"
},
"createdAt": 1538648422471,
"body": "A new comment"
}
]
I have some working code that goes from aggregate to get sorted comments first, then I populate separately but I want to be able to get this query just by using aggregate pipeline.
Current solution looks like this:
const postComments = await Post.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(postId) } },
{ $unwind: '$comments' },
{ $limit: 50 },
{ $skip: 50 * page },
{ $sort: { 'comments.createdAt': -1 } },
{$replaceRoot: {newRoot: '$comments'}},
{
$project: {
_owner: 1,
createdAt: 1,
body: 1
}
}
]);
await Post.populate(postComments, {path: 'comments._owner', select: 'profilePhoto displayName' } )
You can try below aggregation
const postComments = await Post.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(postId) } },
{ "$unwind": "$comments" },
{ "$lookup": {
"from": "users",
"localField": "comments._owner",
"foreignField": "_id",
"as": "comments._owner"
}},
{ "$unwind": "$comments._owner" },
{ "$replaceRoot": { "newRoot": "$comments" }},
{ "$sort": { "createdAt": -1 } }
{ "$limit": 50 }
])