I made that aggregate query below to handle a social network main feed that will be able to show the posts and then answer then. Is there a way to simplify that query or the right choice for that case is to lookup, unwind and project?
`[
{
$match: {
_id: mongoose.Types.ObjectId(myId),
},
},
{
$addFields: {
following: {
$concatArrays: ["$following", [mongoose.Types.ObjectId(myId)]],
},
},
},
{
$lookup: {
from: "users",
localField: "following",
foreignField: "_id",
as: "following",
},
},
{
$unwind: {
path: "$following",
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
_id: "$following._id",
name: "$following.name",
surname: "$following.surname",
profilePicPath: "$following.profilePicPath",
},
},
{
$lookup: {
from: "posts",
localField: "_id",
foreignField: "parentId",
as: "posts",
},
},
{
$unwind: {
path: "$posts",
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
name: true,
surname: true,
profilePicPath: true,
_id: "$posts._id",
parentId: "$posts.parentId",
content: "$posts.content",
date: "$posts.date",
},
},
{
$lookup: {
from: "answerposts",
localField: "_id",
foreignField: "parentId",
as: "answerPosts",
},
},
{
$unwind: {
path: "$answerPosts",
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
name: true,
surname: true,
profilePicPath: true,
_id: true,
parentId: true,
content: true,
date: true,
answerPosts_id: "$answerPosts._id",
answerPostsOwnerId: "$answerPosts.ownerId",
answerPostsContent: "$answerPosts.content",
answerPostsDate: "$answerPosts.date",
},
},
{
$lookup: {
from: "users",
localField: "answerPostsOwnerId",
foreignField: "_id",
as: "answerPostsOwner",
},
},
{
$unwind: {
path: "$answerPostsOwner",
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
name: true,
surname: true,
profilePicPath: true,
_id: true,
parentId: true,
content: true,
date: true,
answerPosts_id: true,
answerPostsContent: true,
answerPostsDate: true,
answerPostsOwner_name: "$answerPostsOwner.name",
answerPostsOwner_surname: "$answerPostsOwner.surname",
answerPostsOwner_profilePicPath: "$answerPostsOwner.profilePicPath",
},
},
{
$group: {
_id: "$_id",
parentId: {
$first: "$parentId",
},
name: {
$first: "$name",
},
surname: {
$first: "$surname",
},
profilePicPath: {
$first: "$profilePicPath",
},
content: {
$first: "$content",
},
date: {
$first: "$date",
},
answerPosts: {
$push: {
_id: "$answerPosts_id",
name: "$answerPostsOwner_name",
surname: "$answerPostsOwner_surname",
content: "$answerPostsContent",
profilePicPath: "$answerPostsOwner_profilePicPath",
date: "$answerPostsDate",
},
},
},
},
{
$addFields: {
answerPosts: {
$cond: [
{
$in: [{}, "$answerPosts"],
},
[],
"$answerPosts",
],
},
},
},
{
$match: {
_id: {
$ne: null,
},
},
},
{
$sort: {
date: -1,
"answerPosts.date": 1,
},
},
]`
It works that way but I want to know if there is a simpler way to make this query.
Related
so i have document for users with this structure in JSON format:
[
{
"_id": {
"$oid": "6369aeb83ce0f8168520f42f"
},
"fullname": "Jokona",
"password": "$2b$10$MUAe7XIc/xtJTGVh/y1DeuShCARbwxCSejUbHaqIPZfjekNrn0.Yy",
"NIK": "MT220047",
"status": "active",
"department": "Logistic",
"position": "Management Trainee",
"Group_Shift": "Non Shift",
"role": "admin",
"createdAt": 1667870392,
"updatedAt": 1668564835,
"__v": 0
},
{
"_id": {
"$oid": "6369b17b11e02557349d8de5"
},
"fullname": "Warana",
"password": "$2b$10$0xaqz5V8bar/osWmsCiofet5bY10.ORn8Vme3QC7Dh0HwLHwYOm3a",
"NIK": "17000691",
"status": "active",
"department": "Production",
"position": "Foreman",
"Group_Shift": "R1",
"role": "user",
"__v": 0,
"createdAt": 1667871099,
"updatedAt": 1668496775
},
]
it try to lookitup using mongodb $lookup to get the fullname by joining using the NIK as the foreignnkey,here is what i have try:
const dataAnaylitics = await Answer.aggregate([
// $match stage
{
$group: {
_id: {
username: "$username",
title: "$title",
date: "$date",
},
count: {
$sum: 1,
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$lookup: {
from: "users",
localField: "username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { fullname: 0 } }],
},
},
{
$group: {
_id: {
username: "$_id.username",
title: "$_id.title",
},
dates: {
$push: {
k: "$_id.date",
v: "$count",
},
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$project: {
_id: 0,
username: "$_id.username",
title: "$_id.title",
position: 1,
department: 1,
dates: 1,
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{
$arrayToObject: "$dates",
},
],
},
},
},
{
$unset: "dates",
},
]);
but the result doesnt returning the fullname field, is there is something wrong with my code? i seek for documentation and already follow the step
In your group stage, since you are grouping based on username, the resulting document will have _id.username as the field. Use this field as localField in your lookup.
{
$lookup: {
from: "users",
localField: "_id.username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { fullname: 0 } }],
}
i have fix it, hope it will helps other..
const dataAnaylitics = await Answer.aggregate([
// $match stage
{
$group: {
_id: {
username: "$username",
title: "$title",
date: "$date",
},
count: {
$sum: 1,
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$lookup: {
from: "users",
localField: "_id.username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { _id: 0, fullname: 1 } }],
},
},
{
$group: {
_id: {
username: "$_id.username",
title: "$_id.title",
},
dates: {
$push: {
k: "$_id.date",
v: "$count",
},
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
fullname: {
$first: { $arrayElemAt: ["$fullname.fullname", 0] },
},
},
},
{
$project: {
_id: 0,
username: "$_id.username",
title: "$_id.title",
position: 1,
department: 1,
dates: 1,
fullname: 1,
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{
$arrayToObject: "$dates",
},
],
},
},
},
{
$unset: "dates",
},
]);
I have these models:
const UserSchema = new Schema({
profile: {
type: Schema.Types.ObjectId,
ref: "profiles",
},
});
And this model:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users",
},
education: [
{
institution: {
type: Schema.Types.ObjectId,
ref: "institutions",
},
major: {
type: Schema.Types.ObjectId,
ref: "majors",
},
},
],
date: {
type: Date,
default: Date.now,
},
});
I've been trying to populate the user.profile.education array using aggregate.
Particularly, the fields institution and major.
So the expected result is the array of education to have its education elements populated.
So the expected result should be something like this:
[
// user 1
{
profile: {
education: [
{ institution: "institution_1_data", major: "major_1_data" },
{ institution: "institution_2_data", major: "major_2_data" },
{ institution: "institution_3_data", major: "major_3_data" },
],
},
},
// user 2
{
profile: {
education: [
{ institution: "institution_1_data", major: "major_1_data" },
{ institution: "institution_2_data", major: "major_2_data" },
],
},
},
];
This is the query that I wrote:
const getUsersWithPopulatedMajorAndInstitution = async () => {
const unwind_education_stage = {
$unwind: "$education",
};
const populate_education_stage = {
$lookup: {
from: "majors",
let: { major: "$education.major" },
pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$major"] } } }],
as: "education.major",
},
$lookup: {
from: "institutions",
let: { institution: "$education.institution" },
pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$institution"] } } }],
as: "education.institution",
},
};
const populate_profile_stage = {
$lookup: {
from: "profiles",
let: { profile_id: "$profile" },
pipeline: [
{
$match: {
$expr: { $eq: ["$_id", "$$profile_id"] },
},
},
unwind_education_stage,
populate_education_stage,
{
$project: {
education: "$education",
},
},
],
as: "profile",
},
};
let users = await User.aggregate([populate_profile_stage]);
return users;
};
There are two problems with this query.
PROBLEM 1:
It only populates institution because the institution $lookup stage was added after the major $lookup stage.
This makes no sense to me, as I've been using aggregate for a while and would expect both major and institution to be populated.
PROBLEM 2:
Using $unwind means education field would be unwinded.
So if the education array contains more than 1 education element (like the examples above), three "copies" of the user will be created and the end result is something like this:
[
// user 1
{
profile: {
education: [{ institution: "institution_1_data", major: "major_1_data" }],
},
},
{
profile: {
education: [{ institution: "institution_2_data", major: "major_2_data" }],
},
},
{
profile: {
education: [{ institution: "institution_3_data", major: "major_3_data" }],
},
},
// user 2
{
profile: {
education: [{ institution: "institution_1_data", major: "major_1_data" }],
},
},
{
profile: {
education: [{ institution: "institution_2_data", major: "major_2_data" }],
},
},
];
But, that's not the expected result as I mentioned above.
What should I change/add in the query?
Solved this this way:
const getUsersWithPopulatedMajorAndInstitution = async (
user_name_surname_input_value
) => {
const unwind_education_stage = {
$unwind: "$education",
};
const look_up_institution_stage = {
$lookup: {
from: "institutions",
localField: "education.institution",
foreignField: "_id",
as: "education.institution",
},
};
const look_up_major_stage = {
$lookup: {
from: "majors",
localField: "education.major",
foreignField: "_id",
as: "education.major",
},
};
const populate_stage = {
$lookup: {
from: "profiles",
let: { profile_id: "$profile" },
pipeline: [
{
$match: {
$expr: { $eq: ["$_id", "$$profile_id"] },
},
},
unwind_education_stage,
look_up_major_stage,
look_up_institution_stage,
// This is necessary to regroup education elements after unwinding them
{
$group: {
_id: null,
education: {
$push: "$education",
},
},
},
{
$project: {
education: "$education",
},
},
],
as: "profile",
},
};
let filtered_users = await User.aggregate([
populate_stage,
]);
return filtered_users;
};
I've inherited a Azure Cosmos database with a MongoDB API. There is extensive use of "discriminators" so a single collection has many different models.
I am trying to query a document three levels deep based on document ids (ObjectId())
Parent Group
{
_id: ObjectId(),
__type: "ParentGroup",
name: "group 1",
subgroups: [
...ObjectIds,
],
}
Sub Group
{
_id: ObjectId(),
__type: "SubGroup",
name: "a text name",
members: [
...ObjectIds,
],
}
Member
{
_id: ObjectId(),
__type: "Member",
name: "string",
email: "",
induction: Date,
}
Examples I've seen deal with nested documents NOT references
Is it possible to query the Member documents and return?
[
{
parentGroup,
subgroups: [
{sub group, members: [...members]},
{sub group, members: [...members]},
{sub group, members: [...members]},
]
},
]
After reading the comments and further reading i've got this. Its almost there but I think the limitation of MongoDB will prevent the solution being in a single query. The goal is to return ParentGroups->Subgroups->Members Where Members have an "induction" value of "whatever". I am either returning ALL ParentGroups or nothing at all
db.development.aggregate([
{
$match: {
__type: "ParentGroup", $expr: {
$gt: [
{ $size: "$subgroups" }, 0
]
}
}
},
{
$lookup: {
from: "development",
localField: "subgroups",
foreignField: "_id",
as: "subgroups"
}
},
{
$unwind: {
path: "$subgroups",
// preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "development",
localField: "subgroups.members",
foreignField: "_id",
as: "subgroups.members"
}
}
])
Solution that worked for me:
db.development.aggregate([
{
$match: {
__type: "ParentGroup",
},
},
{
$lookup: {
from: "development",
localField: "subgroups",
foreignField: "_id",
as: "subgroups",
},
},
{
$unwind: {
path: "$subgroups",
preserveNullAndEmptyArrays: true,
},
},
{
$lookup: {
from: "development",
localField: "subgroups.members",
foreignField: "_id",
as: "subgroups.activities_x",
},
},
{
$unwind: {
path: "$subgroups.members",
preserveNullAndEmptyArrays: true,
},
},
{
$match: { "subgroups.members.meta": { $exists: true } },
},
{
$project: {
_id: 1,
__type: 1,
name: 1,
subgroups: {
_id: 1,
__type: 1,
name: 1,
members: {
_id: 1,
__type: 1,
name: 1,
meta: 1,
},
},
},
},
]);
I am new to MongoDB and wanted to create a view after making all the aggregation stages and it seems to give me this error when I click on create view :
A pipeline stage specification object must contain exactly one field.
This is the Aggregation query generated by MongoDB Compass :
[{
$unwind: {
path: "$destinations",
preserveNullAndEmptyArrays: true
}
}, {
$set: {
"destinationId": {
$toInt: "$destinations.ref"
}
}
}, {
$lookup: {
from: 'destinations',
localField: 'destinationId',
foreignField: 'destinationId',
as: 'destination'
}
}, {
$group: {
_id: "$productCode",
destinations: {
"$push": {
$first: "$destination"
}
},
status: {
"$first": "$status"
},
productCode: {
"$first": "$productCode"
},
language: {
"$first": "$language"
},
createdAt: {
"$first": "$createdAt"
},
lastUpdatedAt: {
"$first": "$lastUpdatedAt"
},
title: {
"$first": "$title"
},
ticketInfo: {
"$first": "$ticketInfo"
},
pricingInfo: {
"$first": "$pricingInfo"
},
images: {
"$first": "$images"
},
logistics: {
"$first": "$logistics"
},
timeZone: {
"$first": "$timeZone"
},
description: {
"$first": "$description"
},
inclusions: {
"$first": "$inclusions"
},
exclusions: {
"$first": "$exclusions"
},
additionalInfo: {
"$first": "$additionalInfo"
},
cancellationPolicy: {
"$first": "$cancellationPolicy"
},
bookingConfirmationSettings: {
"$first": "$bookingConfirmationSettings"
},
bookingRequirements: {
"$first": "$bookingRequirements"
},
languageGuides: {
"$first": "$languageGuides"
},
bookingQuestions: {
"$first": "$bookingQuestions"
},
tags: {
"$first": "$tags"
},
itinerary: {
"$first": "$itinerary"
},
translationInfo: {
"$first": "$translationInfo"
},
supplier: {
"$first": "$supplier"
}
}
}, {
$lookup: {
from: 'schedules',
localField: 'productCode',
foreignField: 'productCode',
as: 'schedules'
}
}, {
$set: {
"schedules": {
"$first": "$schedules"
}
}
}, {
$project: {
_id: true,
destinations: true,
schedules: true,
status: true,
productCode: true,
language: true,
createdAt: true,
lastUpdatedAt: true,
title: true,
ticketInfo: true,
pricingInfo: true,
images: true,
logistics: true,
timeZone: true,
description: true,
inclusions: true,
exclusions: true,
additionalInfo: true,
cancellationPolicy: true,
bookingConfirmationSettings: true,
bookingRequirements: true,
languageGuides: true,
bookingQuestions: true,
tags: true,
itinerary: true,
translationInfo: true,
supplier: true
}
}
}]
I want to know I am not able to create the view and what the error really means.
I checked in multiple places but no luck.
Please help.
Thank you.
I'm working with the followings mongoose schemas:
Question schema:
var Question = new Schema({
title: String,
content: String,
createdBy: {
type: Schema.ObjectId,
ref: 'User',
required: true
},
answers: {
type: [ { type: Schema.Types.ObjectId, ref: 'Answer' } ]
}
});
Answer Shchema:
var Answer = new Schema({
content: {
type: String,
require: 'Content cannot be empty.'
},
createdBy: {
type: Schema.Types.ObjectId,
ref: 'User'
},
isBest: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: Date.now
},
votes: {
type: Number,
default: 0
},
comments: {
type: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
}
});
and Comment Schema:
var Comment = new Schema({
content: {
type: String,
required: [true, 'Content cannot be empty']
},
createdBy: {
type: Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now
}
});
Basically what I'm trying to do is doing a $lookup for answers and for comments array in every answer, and then in $project stage try to add an isOwner field that is going to be true if the user logged is the owner of the answer or comment. This is what I' trying:
Question.aggregate([
{
$match: { '_id': { $eq: questionId } }
},
{
$lookup: {
from: 'answers',
localField: 'answers',
foreignField: '_id',
as: 'answers'
}
},{
$lookup:{
from: 'comments',
localField: 'answers.comments',
foreignField: '_id',
as: 'comments'
}
}, {
$project: {
title: true,
content: true,
createdBy: true,
createdAt: true,
isOwner: { $eq : ['$createdBy', currentUser] },
answers: true,
answers: {
isOwner: { $eq : ['$createdBy', currentUser] },
content: true,
createdBy: true,
createdAt: true,
comments: {
content: true,
createdAt: true,
createdBy: true,
isOwner: { $eq : ['$createdBy', currentUser] }
}
}
}
}
])
This is the ouput that I'm expecting:
{
"_id": "58a7be2c98a28f18acaa4be4",
"title": "Some title",
"createdAt:": "2017-03-03T05:13:41.061Z",
"content": "some content",
"isOwner": true,
"createdBy": {
"_id": "58a3a66c088fe517b42775c9",
"name": "User name",
"image": "imgUrl"
},
"answers": [
{
"_id": "58a3a66c088fe517b42775c9",
"content": "an answer content",
"createdAt": "2017-03-03T05:13:41.061Z",
"isBest": false,
"isOwner": false,
"createdBy":{
"_id": "58a3a66c088fe517b42775c9",
"name": "another user",
"image": "another image"
},
"comments": [
{
"_id": "58aa104a4254221580832a8f",
"content": "some comment content",
"createdBy": {
"_id": "58a3a66c088fe517b42775c9",
"name": "another user",
"image": "another image"
},
}
]
}
]
}
I'm using mongodb 3.4.2
You can try addFields stage to add the isOwner field for all the relations.
Question.aggregate([{
$match: {
'_id': {
$eq: questionId
}
}
}, {
$addFields: {
"isOwner": {
$eq: ['$createdBy', currentUser]
}
}
}, { // No unwind needed as answers is scalar of array values.
$lookup: {
from: 'answers',
localField: 'answers',
foreignField: '_id',
as: 'answers'
}
}, {
$addFields: {
"answers.isOwner": {
$eq: ['$createdBy', currentUser]
}
}
}, {
$unwind: "$answers" //Need unwind here as comments is array of scalar array values
}, {
$lookup: {
from: 'comments',
localField: 'answers.comments',
foreignField: '_id',
as: 'comments'
}
}, {
$addFields: {
"comments.isOwner": {
$eq: ['$createdBy', currentUser]
}
}
}, {
$addFields: { // Move the comments back to answers document
"answers.comments": "$comments"
}
}, {
$project: { // Remove the comments embedded array.
"comments": 0
}
}, {
$group: {
_id: null,
isOwner: {
$first: "$isOwner"
},
answers: {
$push: "$answers"
}
}
}])
THe problem with your code is that you have not unwind the answeeres array before lookup
Please check below comment
Question.aggregate([
{
$match: { '_id': { $eq: questionId } }
},
{
$lookup: {
from: 'answers',
localField: 'answers',
foreignField: '_id',
as: 'answers'
}
},
{$unwind : "$answers"}, // <-- Check here
{
$lookup:{
from: 'comments',
localField: 'answers.comments',
foreignField: '_id',
as: 'comments'
}
},
{
$group : {
_id : null,
title: {$first : true},
content: {$first :true},
createdBy: {$first :true},
createdAt: {$first :true},
isOwner: { $eq : ['$createdBy', currentUser] },
answersStatus: {$first :true},
answers : {$push : $answer}
}
}
])