mongodb lookup (aggregation) - node.js

I have this function and I can't get it to work.
function GetDataTeacher(err){
Data_teacher.aggregate([
{
"$project": {
"user": {
"$toString": "$user"
}
}
},
{
$lookup:
{
from: "Teacher",
localField: "user",
foreignField: "user",
as: "info"
}
}
]).exec(function(err, results){
console.log(results);
});
}
my models have in common
user: { type: Schema.ObjectId, ref: 'User' },
I don't understand what I can be doing wrong

Possible issues here are:
1, You are converting the user id to string(which was already of type id)
user: { type: Schema.ObjectId, ref: 'User' },
so no need of converting to string and you can do this directly.
Data_teacher.aggregate([
{
$lookup:
{
from: "users",
localField: "user",
foreignField: "user",
as: "info"
}
}
])
2, Please check if you have the user field in both the collections (Data_teacher and Teacher).
3, check if localfield and foreignfield are correct

Related

Aggregate Function Mongoose - Node

I have a schema
const membershipsSchema = new Schema({
spaceId: {
type: Schema.Types.ObjectId,
ref: 'Space',
},
member: {
type: Schema.Types.ObjectId,
ref: 'User',
},
....
);
mongoose.model('Membership', membershipsSchema);
I want to use join statement like
Select * from membershipPlans as plans join User as users on plans.member=users._id
where plans.spaceId=id and users.status <> 'archived'; // id is coming from function arguments
I tried the aggregate pipeline like
const memberships = await Memberships.aggregate([
{
$match: {
spaceId: id
}
},
{
$lookup: {
from: 'User',
localField: 'member',
foreignField: '_id',
as: 'users',
},
},
{
$match: {
'users.status': {$ne: 'archived'}
}
},
]);
But on console.log(memberships); I am getting an empty array. If I try return Memberships.find({ spaceId: id }) it returns populated memberships of that space. But when I try
const memberships = await Memberships.aggregate([
{
$match: {
spaceId: id
}
},
]}
It still returns an empty array. Not sure if I know how to use an aggregate pipeline.
There are two things that you need to do:
Cast id to ObjectId.
Instead of using $match, just filter the contents of the users array using $filter.
Try this:
const memberships = await Memberships.aggregate([
{
$match: {
spaceId: new mongoose.Types.ObjectId(id)
}
},
{
$lookup: {
from: 'User',
localField: 'member',
foreignField: '_id',
as: 'users',
},
},
{
$project: {
users: {$filter: {
input: "$users",
as: "user",
cond: {
$ne: ["$$user.status", "archived"]
}
}}
}
},
]);

How to get the first element from a child lookup in aggregation - Mongoose

I'm trying to find all the docs from groupUserRoleSchema with a specific $match condition in the child. I'm getting the expected result, but the child application inside groupSchema is coming as an array.
I just need the first element from the application array as an object. How to convert this application into a single object.
These are my models
const groupUserRoleSchema = new mongoose.Schema({
group: {
type: mongoose.Schema.Types.ObjectId,
ref: 'group'
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
});
const groupSchema = new mongoose.Schema({
application: {
type: mongoose.Schema.Types.ObjectId,
ref: 'application'
}
});
Here is my aggregate condition.
groupUserRoleModel.aggregate([
{
$lookup: {
from: "groups", //must be PHYSICAL collection name
localField: "group",
foreignField: "_id",
as: "group",
}
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "user",
}
},
{
$lookup: {
from: "applications",
localField: "group.application",
foreignField: "_id",
as: "group.application",
}
},
{
$addFields: {
group: {
$arrayElemAt: ["$group", 0],
},
user: {
$arrayElemAt: ["$user", 0],
}
},
},
{
$match: {
"user.email_id": requestHeaders.user_email
},
}
]);
Here the $lookup group.application is coming as an array. Instead i need it as an object.
Below added is the current output screen-shot
Here is the expected output screen-shot
Any suggestions ?
A alternative is re-writing the main object return, and merge the fields.
The lookup field, gonna use the $arrayElemAt attribute at position 0, with the combination of the root element '$$ROOT'.
const result = await groupUserRoleModel.aggregate([
{
$match: {
"user.email_id": requestHeaders.user_email //The Match Query
},
},
{
$lookup: {
from: 'groups', // The collection name
localField: 'group',
foreignField: '_id',
as: 'group', // Gonna be a group
},
},
{
// Lookup returns a array, so get the first once it is a _id search
$replaceRoot: {
newRoot: {
$mergeObjects: [ // merge the object
'$$ROOT',// Get base object
{
store: {
$arrayElemAt: ['$group', 0], //Get first elemnt
},
},
],
},
},
},
]);

Combining $lookup aggregation inside updateMany?

I have a collection of users like this
[
{ _id: ObjectId("61a6d586e56ea12d6b63b68e"), fullName: "Mr A" },
{ _id: ObjectId("6231a89b009d3a86c788bf39"), fullName: "Mr B" },
{ _id: ObjectId("6231a89b009d3a86c788bf3a"), fullName: "Mr C" }
]
And a collection of complains like this
[
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("61a6d586e56ea12d6b63b68e" },
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("6231a89b009d3a86c788bf3c" },
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("6231a89b009d3a86c788bf3b" },
]
I want to check if the postedBy fields of complains are not existed in users, then update by using the updateMany query
By the way, I have an optional way to achieve the goal but must use 2 steps:
const complains = await Complain.aggregate()
.lookup({
from: "users",
localField: "postedBy",
foreignField: "_id",
as: "postedBy",
})
.match({
$expr: {
$eq: [{ $size: "$postedBy" }, 0],
},
});
complains.forEach(async (complain) => {
complain.type = "other";
await complain.save();
});
Therefore, can I combine 2 steps into a single updateMany query? Like $match and $lookup inside updateMany query?
With MongoDB v4.2+, you can use $merge to perform update at last stage of aggregation.
db.complains.aggregate([
{
"$lookup": {
from: "users",
localField: "postedBy",
foreignField: "_id",
as: "postedByLookup"
}
},
{
$match: {
postedByLookup: []
}
},
{
"$addFields": {
"type": "other"
}
},
{
"$project": {
postedByLookup: false
}
},
{
"$merge": {
"into": "complains",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

Facing issues in aggregation lookup. Need lookup result in same structure as defined

Schema structure
categories: [{
category: { type: Schema.Types.ObjectId, ref: 'Category' },
subCategory: [{ type: Schema.Types.ObjectId, ref: 'Category' }]
}]
Query
{
$lookup: {
from: 'categories',
localField: "categories.subCategory",
foreignField: "_id",
as: "categories.subCategory"
}
},
{
$lookup: {
from: 'categories',
localField: "categories.category",
foreignField: "_id",
as: "categories.category"
}
},
Facing issues in aggregation lookup. Need lookup result in same structure as defined.
You need to use $unwind for decoding an array, explanation is added in query comment below:
unwind your categories array
lookup for category with Category collection
unwind again because lookup will return an array and unwind will convert into object
db.collection.aggregate([
{ $unwind: "$categories" },
{
$lookup: {
from: "Category",
localField: "categories.category",
foreignField: "_id",
as: "categories.category"
}
},
{ $unwind: "$categories.category" },
lookup for subCategory with Category collection
{
$lookup: {
from: "Category",
localField: "categories.subCategory",
foreignField: "_id",
as: "categories.subCategory"
}
},
group by _id because without group lookup will multiple documents
store $$ROOT in root var, because we need to use it for replace root
push categories object that we got from lookup
{
$group: {
_id: "$_id",
root: { $mergeObjects: "$$ROOT" },
categories: { $push: "$categories" }
}
},
replace new root after merging with root and $$ROOT
{
$replaceRoot: {
newRoot: {
$mergeObjects: ["$root", "$$ROOT"]
}
}
},
remove root var because no longer needed
{
$project: { root: 0 }
}
])
For the explanation purpose i have divided in to parts, you can merge easily as all are in sequence.
Working Playground: https://mongoplayground.net/p/lKd5-MjcT1K

Aggregate and lookup

I am trying to group a mongoose query and populate a few sub-documents in mongoose using $lookup but my result document arrays are returning empty.
Can anyone tell me why?
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var answerSchema = new Schema({
_user : { type: Schema.Types.ObjectId, ref: 'User', required: true },
_poll : { type: Schema.Types.ObjectId, ref: 'Poll', required: true },
_question : [{ type: Schema.Types.ObjectId, ref: 'Question' }],
answer : { type : String },
},
{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
});
mongoose.model('Answer', answerSchema);
Here's my code:
module.exports = {
index: function(req, res, next){
Poll.findOne({_id: req.params.poll_id}, function(err, poll){
if(err) return next(err);
console.log(poll);
Answer.aggregate([
{$unwind: "$_question"},
{$match: {_poll: poll._id}},
{$group: {
_id: '$_question',
}},
{
$lookup : {
from : 'polls',
localField : '_poll',
foreignField: '_id',
as: 'poll'
},
},
{
$lookup : {
from : 'questions',
localField : '_question',
foreignField: '_id',
as: 'questions'
},
},
{
$lookup : {
from : 'users',
localField : '_user',
foreignField: '_id',
as: 'user'
},
},
], function (err, result) {
res.status(200).json(result);
});
});
},
The new subdocuments are returned empty for some reason.
Please note that each answer contains the reference to ONE poll, ONE user and MANY questions.
[
{
_id: "57ce8927ea5a4f090774215d",
poll: [ ],
questions: [ ],
user: [ ]
}
]
Can anyone spot my mistake?
Should I be using $project instead? I heard $lookup is the new and better way. I am using mongo 3.2.0 and mongoose 4.5.8.
Mongdb aggregate query is a pipeline operation. So the result of subsequent queries is passed on to the next query. For more info on Mongodb aggregate refer this. The mistake you have done is that when you used the $group query only _id is being passed on to the next $lookup query. You can fix this by using the following query.
Answer.aggregate([
{$unwind: "$_question"},
{$match: {_poll: poll._id}},
{$group: {
_id: '$_question',
_poll: { $first: '$_poll'},
_user: { $first: '$_user'},
_question : { $first: "$_question "}
}},
{
$lookup : {
from : 'polls',
localField : '_poll',
foreignField: '_id',
as: 'poll'
},
},
{
$lookup : {
from : 'questions',
localField : '_question',
foreignField: '_id',
as: 'questions'
},
},
{
$lookup : {
from : 'users',
localField : '_user',
foreignField: '_id',
as: 'user'
},
},
], function (err, result) {
res.status(200).json(result);
});

Resources