This is the query I used to get the average of rating, but the result was null:
db.products.aggregate([{$unwind: "$reviews"}, {$group: {_id: "$reviews", ratingAvg: {$avg: "$rating"}}}])
This is my product schema:
category: String,
name: String,
price: Number,
image: String,
description: String,
stock: Number,
reviews: [
{
type: mongoose.Schema.Types.ObjectID, ref: 'Review'
}
})
This is my review schema:
text: String,
rating: Number,
// author: {
// id: {type: mongoose.Schema.Types.ObjectID, ref: 'User'},
// name: String
// }
author: String
})
Every time I run this query I get:
{ "_id" : ObjectId("5f79d1b4b4b3c1061f1fbe52"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d1a5b4b3c1061f1fbe51"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d196b4b3c1061f1fbe4f"), "ratingAvg" : null }
It is showing ratingAvg as"null" whereas I have ratings for that product in my database.
$lookup helps to join two collections.
[
{
"$lookup": {
"from": "Review",
"localField": "reviews",
"foreignField": "_id",
"as": "reviews"
}
},
{
$unwind: "$reviews"
},
{
$group: {
_id: null,
ratingAvg: {
$avg: "$reviews.rating"
}
}
}
]
Then you can easily find the average using $avg along with $group after destructure the array using $uniwnd
Working Mongo playground
Related
Hello I have the following collections
const TransactionSchema = mongoose.Schema({
schedule: {
type: mongoose.Schema.ObjectId,
required: true,
ref: "Schedule"
},
uniqueCode: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
required: false
},
})
const ScheduleSchema = mongoose.Schema({
start: {
type: Date,
required: true,
},
end: {
type: Date,
required: false,
},
location: {
type: mongoose.Schema.ObjectId,
required: true,
ref: "Location"
},
})
and I want to return how many times the schedule appear in transaction ( where the status is equal to 'Active') and group it based on its location Id and then lookup the location collection to show the name.
For example I have the following data.
transaction
[
{
"_id":"identifier",
"schedule":identifier1,
"uniqueCode":"312312312312",
"created":"Date",
"status": 'Active'
},
{
"_id":"identifier",
"schedule":identifier1,
"uniqueCode":"1213123123",
"created":"Date",
"status": "Deleted"
}
]
schedule
[
{
"_id":identifier1,
"start":"date",
"end":"date",
"location": id1
},
{
"_id":identifier2,
"start":"date",
"end":"date",
"location": id2
}
]
and I want to get the following result and limit the result by 10 and sort it based on its total value:
[
{
"locationName":id1 name,
"total":1
},
{
"locationName":id2 name,
"total":0
}
]
thank you. Sorry for my bad english.
A bit complex and long query.
$lookup - schedule collection joins with transaction collection by matching:
_id (schedule) with schedule (transaction)
status is Active
and return a transactions array.
$lookup - schedule collection joins with location collection to return location array.
$set - Take the first document in location array so this field would be a document field instead of an array. [This is needed to help further stage]
$group - Group by location._id. And need the fields such as location and total.
$sort - Sort by total DESC.
$limit - Limit to 10 documents to be returned.
$project - Decorate the output documents.
db.schedule.aggregate([
{
$lookup: {
from: "transaction",
let: {
scheduleId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$schedule",
"$$scheduleId"
]
},
{
$eq: [
"$status",
"Active"
]
}
]
}
}
}
],
as: "transactions"
}
},
{
$lookup: {
from: "location",
localField: "location",
foreignField: "_id",
as: "location"
}
},
{
$set: {
location: {
$first: "$location"
}
}
},
{
$group: {
_id: "$location._id",
location: {
$first: "$location"
},
total: {
$sum: {
$size: "$transactions"
}
}
}
},
{
$sort: {
"total": -1
}
},
{
$limit: 10
},
{
$project: {
_id: 0,
locationName: "$location.name",
total: 1
}
}
])
Sample Mongo Playground
Ive been trying to make a MongoDB query with aggregation to produce a data in a structure I like. So i have to collections namely People and Calendar
People
_id : ObjectId,
name: string,
age: number,
occupation: string
address
Calendar
_id: ObjectId,
months: [
{
month: Date,
users: [
{
PersonId: ObjectId(Reference from People._id)
...some other details
},
{
PersonId: ObjectId(Reference from People._id)
...some other details
},
{
PersonId: ObjectId(Reference from People._id)
...some other details
},
]
},
{
month: Date,
users: [same as above]
},
{
month: Date,
users: [same as above]
},
]
So, what I would like the final data to look is like below.
Final Result
Calendar
_id: ObjectId,
months: [
{
month: Date,
users: [
{
PersonId: ObjectId(Reference from People._id)
Person : data from Person Collection
...some other details
},
{
PersonId: ObjectId(Reference from People._id)
Person : data from Person Collection
...some other details
},
{
PersonId: ObjectId(Reference from People._id)
Person : data from Person Collection
...some other details
},
]
},
{
month: Date,
users: [same as above]
},
{
month: Date,
users: [same as above]
},
]
I have been trying with below code but could not get it right.
.aggregate([
{$unwind: "$months"},
{$unwind: "$months.users"},
{
$lookup:
{
from: "Person",
localField: "months.users.PersonId",
foreignField: "_id",
as: "PersonList"
}
},
{$unwind: "$PersonList"},
{
$group:
{
"_id": "$_id",
}
}
])
How do I achieve this?
Is this something do-able?
So you can do something like this,
db.calender.aggregate([
{
$unwind: "$months"
},
{
$unwind: "$months.users"
},
{
$lookup: {
from: "people",
localField: "months.users.personId",
foreignField: "_id",
as: "months.users.people"
}
},
{
$group: {
"_id": {
_id: "$_id",
months: "$months.month"
},
users: {
$addToSet: "$months.users"
}
}
},
{
$group: {
_id: "$_id._id",
months: {
$push: {
month: "$_id.months",
users: "$users"
}
}
}
}
])
You need to add the details to nested array, So we use unwind two times, when we re-group it, we need to use two groups. The months array is uniquely identify by a unique field. (As you said months.month is unique, if it is not unique, give another filed _id which should be unique)
Working Mongo playground
I am using mongodb databases and want to apply $lookup on 2 collections but with specific conditions.
I have one collection named Company like this
new Schema({
name: String,
benefit: String,
benefitDesc: String,
company_url: String,
logoUrl: String,
coverUrl: String,
desc: String,
createdAt: String,
categoryId: { type: Schema.Types.ObjectId, ref: 'categories' },
})
And another collection named Referrallinks like this
new Schema({
referral_link: String,
referral_code: String,
isLink: Number,
offer_name: String,
offer_desc: String,
user_email: String,
companyId: { type: Schema.Types.ObjectId, ref: 'companies' },
addedByAdmin: { type: Boolean, default: true },
number_of_clicks: Number,
referral_country: String,
link_status: String,
categoryId: { type: Schema.Types.ObjectId, ref: 'categories' },
number_of_clicks: { type: Number, default: 0 },
createdAt: String,
updatedAt: String,
userId: { type: Schema.Types.ObjectId, ref: 'users' }
})
Now when i apply this $lookup
Company.aggregate([{
$lookup: {
from: 'referrallinks',
localField: '_id',
foreignField: 'companyId',
as: 'referrals'
}
}])
So i am getting all companies with referrals as array like this
[
{name : '', benefit : '', benefiDesc : '', referrals : []},
{name : '', benefit : '', benefiDesc : '', referrals : []},
{name : '', benefit : '', benefiDesc : '', referrals : []},
.....
]
But i want to have only referrals in each company's referrals array that have link_status value Approved.
**Note : ** I also want to have category object with company and tried this aggregation but it gives error
{
from: "referrallinks",
let: { company_id: "$_id" },
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: [ "$$company_id", "$companyId" ] },
{ $eq: [ "$link_status", "Approved" ] }
]
}
},
{
$lookup : {
from : "categories",
let : {"category_id" : "$categoryId"},
pipeline : [
{
$match : {
$expr : {
$eq : ["category_id","$_id"]
}
}
}
],
as : "company"
}
}
}],
as: "referrals"
}
How can i achieve this?
Use $lookup with custom pipeline to specify additional filtering condition:
Company.aggregate([{
$lookup: {
from: "referrallinks",
let: { company_id: "$_id" },
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: [ "$$company_id", "$companyId" ] },
{ $eq: [ "$link_status", "Approved" ] }
]
}
}
}],
as: "referrals"
}
}])
EDIT: to run nested $lookup your code should look like below:
Company.aggregate([{
$lookup: {
from: "referrallinks",
let: { company_id: "$_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: [ "$$company_id", "$companyId" ] },
{ $eq: [ "$link_status", "Approved" ] }
]
}
}
},
{
$lookup: {
from: "categories",
localField: "categoryId",
foreignField: "_id",
as: "company"
}
}
],
as: "referrals"
}
}])
The code example is in Mongo Playground
https://mongoplayground.net/p/W4Qt4oX0ZRP
Assume the following documents
[
{
_id: "5df1e6f75de2b22f8e6c30e8",
user: {
name: "Tom",
sex: 1,
age: 23
},
dream: [
{
label: "engineer",
industry: "5e06b16fb0670d7538222909",
type: "5e06b16fb0670d7538222951",
},
{
label: "programmer",
industry: "5e06b16fb0670d7538222909",
type: "5e06b16fb0670d7538222951",
}
],
works: [
{
name: "any engineer",
company: "5dd7fd51b0ae1837a08d00c8",
skill: [
"5dc3998e2cf66bad16efd61b",
"5dc3998e2cf66bad16efd61e"
],
},
{
name: "any programmer",
company: "5dd7fd9db0ae1837a08d00e2",
skill: [
"5dd509e05de2b22f8e67e1b7",
"5dd509e05de2b22f8e67e1bb"
],
}
]
}
]
I tried to use aggregate $lookup $unwind
db.coll.aggregate([
{
$unwind: {
path: "$dream",
}
},
{
$lookup: {
from: "industry",
localField: "dream.industry",
foreignField: "_id",
as: "dream.industry"
},
},
{
$unwind: {
path: "$dream.industry",
}
},
{
$lookup: {
from: "type",
localField: "dream.type",
foreignField: "_id",
as: "dream.type"
},
},
{
$unwind: {
path: "$dream.type",
}
},
{
$unwind: {
path: "$works",
}
},
{
$lookup: {
from: "company",
localField: "works.company",
foreignField: "_id",
as: "works.company"
},
},
{
$unwind: {
path: "$works.company",
}
},
{
$lookup: {
from: "skill",
localField: "works.skill",
foreignField: "_id",
as: "works.skill"
},
},
])
Executing the above code did not get the desired result!
This is what i expect
{
_id: "5df1e6f75de2b22f8e6c30e8",
user: {
name: 'Tom',
sex: 1,
age: 23
},
dream: [
{
label: 'engineer',
industry: {
_id: "5e06b16fb0670d7538222909", // Industry doc _id
name: 'IT',
createdAt: "2019-12-28T01:35:44.070Z",
updatedAt: "2019-12-28T01:35:44.070Z"
},
type: {
_id: "5e06b16fb0670d7538222951", // Type doc _id
name: 'job',
createdAt: "2019-12-28T01:35:44.070Z",
updatedAt: "2019-12-28T01:35:44.070Z"
},
},
{
label: 'programmer',
industry: {
_id: "5e06b16fb0670d7538222909", // Industry doc _id
name: 'IT',
createdAt: "2019-12-28T01:35:44.070Z",
updatedAt: "2019-12-28T01:35:44.070Z"
},
type: {
_id: "5e06b16fb0670d7538222951", // Type doc _id
name: 'job',
createdAt: "2019-12-28T01:35:44.070Z",
updatedAt: "2019-12-28T01:35:44.070Z"
}
}
],
works: [
{
name: 'any engineer',
company: {
_id: "5dd7fd51b0ae1837a08d00c8", // Company doc _id
name: 'alibaba',
area: 'CN',
},
skill: [
{
_id: "5dc3998e2cf66bad16efd61b", // Skill doc _id
name: 'Java'
},
{
_id: "5dc3998e2cf66bad16efd61e", // Skill doc _id
name: 'Php'
},
]
},
{
name: 'any programmer',
company: {
_id: "5dd7fd9db0ae1837a08d00e2", // Company doc _id
name: 'microsoft',
area: 'EN',
},
skill: [
{
_id: "5dd509e05de2b22f8e67e1b7", // Skill doc _id
name: 'Golang'
},
{
_id: "5dd509e05de2b22f8e67e1bb", // Skill doc _id
name: 'Node.js'
}
]
},
]
}
The expected result is dream is an array, works is an array, and dream.industry changed from ObjectId to document, dream.type changed from ObjectId to document, works.company changed from ObjectId to document
When I use populate, I can do it easily
Model.find()
.populate('dream.industry')
.populate('dream.type')
.populate('works.company')
.populate('works.skill')
.lean()
I refer to the following questions
mongoose aggregate lookup array (Almost the same as my question, But not resolved)
$lookup on ObjectId's in an array
hope to get everyone's help, thank you!
To make it easier i would not change the current pipeline but just add a $group stage to end of it in order to re-structure the data.
{
$group: {
_id: "$_id",
user: {$first: "$user"},
dream: {$addToSet: "$dream"},
works: {$addToSet: "$works"}
}
}
With that said if you are using Mongo version 3.6+ i do recommend you use the "newer" version of $lookup to re-write your pipeline to be a bit more efficient by avoiding all these $unwind's.
I have Two Collection like following, I'm confused with the query can any one please help to solve,
Group:
var WorkKrewSchema = mongoose.Schema({
content_detail: {
name: String,
description: String
},
business_info: {
name: String,
address: String,
city: String,
state: String,
zipcode: String,
website: String,
starting_hours: Date,
ending_hours: Date,
days: Array
},
created_by: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
created_on: Date,
updated_on: Date
});
Members:
var KrewPartnerSchema = mongoose.Schema({
Work_krew_id: {
type: mongoose.Schema.Types.ObjectId
},
members: [
{
refrence_id: mongoose.Schema.Types.ObjectId,
is_sub_admin: {
type: Boolean,
default: false
},
status: {
type: String,
enum: ['accepted', 'declined', 'pending']
},
date_of_join: Date
}
]
});
get Groups where:
User is not owner of that group as well as not member,
If user is member then status should be Pending
Assuming you have a collection Groups that contains documents of the following form:
{
"_id" : ObjectId("598311f3c63f57ec3dcac86f"),
"grp_name" : "groupA",
"created_by" : "userA"
}
...and a collection Members that contains documents of the following form:
{
"_id" : ObjectId("59831310c63f57ec3dcac891"),
"grp_id" : ObjectId("598311f3c63f57ec3dcac86f"),
"members" : [
{
"user_id" : "UserY",
"status" : "pending"
},
{
"user_id" : "UserA",
"status" : "pending"
},
{
"user_id" : "UserB",
"status" : "approved"
}
]
}
...you can run the following query for a user called "UserA" which should do what you need:
db.getCollection('Members').aggregate(
{
$lookup:
{
from: "Groups",
localField: "grp_id",
foreignField: "_id",
as: "group"
}
},
{
$match:
{
$or:
[
{
"members.user_id": { $ne: "UserA" },
"group.0.created_by": { $ne: "UserA" }
},
{
"members":
{
$elemMatch:
{
"user_id": "UserA",
"status": "pending"
}
}
}
]
}
}
)
I would suggest, though, that you merge those two collections into one. A group document should simply contain all its members in which case you wouldn't need the $lookup stuff and could get away with simple queries (without the aggregation framework).