How to select only few values from lookup table - node.js

I am trying to using aggregate function for fetching data from two documents. I am able to do it but i am finding a solution how can i apply $project in lookup table only
below is my approach
app.get('/getAllDetailById',(req,res)=>{
if(db){
// lookup
db.collection("points").aggregate(
[
{ "$addFields": { "enquiry_by": { "$toObjectId": "$enquiry_by" }}},
{
"$lookup" : {
from: "user",
localField: "enquiry_by",
foreignField: "_id",
as: "userDetails"
}
},
{ $unwind: "$userDetails"},
]
).toArray()
.then(result=>{
console.log(result[0])
}).catch(err=>{
res.send(err)
})
}
})
What i want is get all fields from points table and from user table i just want name and username. I have used $project but than its return only fields defined in this.
{ $project: {"userDetails.name":1, "userDetails.username":1,"_id":0} }
Is there any way that $project can be applied separately for user table

You can use pipeline in the lookup if you are using mongodb >= 3.6: https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
So your code will look like:
app.get('/getAllDetailById',(req,res)=>{
if(db){
// lookup
db.collection("points").aggregate(
[
{ "$addFields": { "enquiry_by": { "$toObjectId": "$enquiry_by" }}},
{
"$lookup" : {
from: "user",
let: { "enquiry_by": "$enquiry_by" },
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$_id", "$$enquiry_by"]
}
},
"$project": {
"$name": 1,
"$username": 1,
}
},
],
as: "userDetails"
}
},
{ $unwind: "$userDetails"},
]
).toArray()
.then(result=>{
console.log(result[0])
}).catch(err=>{
res.send(err)
})
}
})

Related

How to lookup in another collection with objectID of main collection in mongodb

I am trying to get the data from another collection via lookup
collection "users"
{
"_id":{
"$oid":"60bf4bb31f45d98903d1851f"
},
"name":"Dave",
"center":"THGJ556",
}
collection "addresses"
{
"_id":{
"$oid":"60bf4bb31f45d98903d1851f"
},
"userId":"60bf4bb31f45d98903d1851f",
}
collection "applications"
{
"_id":{
"$oid":"60bf4bb31f45d98903d1851f"
},
"userId":"60bf4bb31f45d98903d1851f",
"centerId":"THGJ556",
},
{
"_id":{
"$oid":"60bf4bb31f45d98903d3647j"
},
"userId":"60bf4bb31f45d98903d1851f",
"centerId":"JHGJ5476",
}
Now I need data from all the tables.
here is my code:
users.aggregate([
{
$lookup: {
from: "addresses",
localField: "_id",
foreignField: "userId",
as: "addressData"
}
},
{
$lookup: {
from: "applications",
pipeline: [
{ $match:
{ userId:"$_id", centerId: "JHGJ5476"}
},
],
as: "applicationData"
}
},
] ,function(err, result) {
if (err) {
console.log(err)
} else {
console.log(result)
}
});
I am doing something wrong while using aggregate and match in pipeline.
I am getting addressData correctly, but I get nothing [] in applicationData because I suspect something is wrong with userId:"$_id"
As docs explain:
A $match stage requires the use of an $expr operator to access the variables. $expr allows the use of aggregation expressions inside of the $match syntax.
So you have to use $expr into $match stage and also a let stage.
let stage is to define variable to use into $expr: id: $_id.
$expr used with $and and $eq is to get both conditions.
db.users.aggregate([
{
$lookup: {
from: "addresses",
localField: "_id",
foreignField: "userId",
as: "addressData"
}
},
{
$lookup: {
from: "applications",
let: {"id": "$_id"},
pipeline: [
{
$match: {
"$expr": {
"$and": [
{"$eq": ["$userId","$$id"]},
{"$eq": ["$centerId","JHGJ5476"]}
]
}
}
}
],
as: "applicationData"
}
}
])
Check this example.

I want to display only one product image

This is Code in node js
const result = await OrderDB.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(id) } },
{
$lookup: {
from: 'products',
localField: 'product',
foreignField: '_id',
as: 'productDetail',
},
},
{
$project: {
productDetail: {
name: 1,
price: 1,
productImage: 1,
},
},
},
])
This is the response of code
{
"message": "Get Order Successfully",
"result": [
{
"_id": "5ff47348db5f5917f81871aa",
"productDetail": [
{
"name": "Camera",
"productImage": [
{
"_id": "5fe9b8a26720f728b814e246",
"img": "uploads\\product\\7Rq1v-app-7.jpg"
},
{
"_id": "5fe9b8a26720f728b814e247",
"img": "uploads\\product\\FRuVb-app-8.jpg"
}
],
"price": 550
}
]
}
]
}
I want to display only one productImage from the response using nodejs and mongoose
This is using in aggregate projection
I was use $arrayElemAt but it is don't work
I also use $first but it is don't work
so projection method I use to display only one *productImage*
Data looks like multi level nested.
You have array of results, each result contains array of productDetails
play
You need to unwind the data to get the first productImage
db.collection.aggregate([
{
"$unwind": "$result"
},
{
"$unwind": "$result.productDetail"
},
{
$project: {
pImage: {
"$first": "$result.productDetail.productImage"
}
}
}
])
With the above response
db.collection.aggregate([
{
"$project": {
productDetails: {
$map: {
input: "$productDetail",
in: {
"$mergeObjects": [
"$$this",
{
productImage: {
"$arrayElemAt": [
"$$this.productImage",
0
]
}
}
]
}
}
}
}
}
])
Working Mongo playground

Mongodb $lookup using with multiple criteria mongodb

{
$lookup: {
from: "Comment",
let: {
p_id: "$_id",
d_id: "$data_id",
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$_id",
"$$p_id"
]
},
{
$eq: [
"$data_id",
"$$d_id"
]
}
]
}
}
}
],
as: "subComment"
}
}
https://mongoplayground.net/p/GbEgnVn3JSv
I am good at mongoplayground but tried to put there my thought
I want to fetch the comment of posts based on doc_id and post_id for mainComment query looks good to me but subcommand is not good. Please guide on this
Its simple as a post can have multiple comment need comment count base on Post.data._id which is equal to Comment.doc_id and Post._id is in Comment.post_id
Not sure what "mainComment" and "subComment" are, I believe you missed the dollar sign before them
{
$project: {
_id: 1,
main_comments_count: {
$size: "$mainComment"
},
sub_comments_count: {
$size: "$subComment"
},
}
}
Update
What you did wrong in the playground is that you used $data in the lookup.let stage. $data is a document and the field you actually want to lookup is $data._id.
sidenote: if you are looking up using just one field, you can simply use the localField and foreign in the lookup stage. Using let and pipeline is not necessary there.
db.setting.aggregate([
{
$lookup: {
from: "site",
"let": {
"pid": "$data._id" //here
},
"pipeline": [
{
"$match": {
"$expr": {
"$in": [
"$doc_id",
"$$pid"
]
}
}
}
],
"as": "subComment"
}
},
{
$addFields: {
countRecord: "$subComment"
}
}
])
i.e. this gives the same output
db.setting.aggregate([
{
$lookup: {
from: "site",
localField: "data._id",
foreignField: "doc_id",
as: "subComment"
}
},
{
$addFields: {
countRecord: "$subComment"
}
}
])

mongo use $lookup after $group

I have a visits collection where I successfully count the number of visits per location
Visits model:
{
"_id": {
"$oid": "5a3969e2f4ea3e33ac5a523d"
},
"locationId": "5a395ccf210a1d35d0df4a58"
}
locationId above is of type 'Object' as I learned lookup localField and foreignField must be of same type
nodejs code =>
let sort = { "count": -1, "locationId": 1 };
Visit.aggregate([
{
$match:{
$and: [
{ accountId: req.session.passport.user },
{
'details.dateTimeIn': {
$gte: new Date(dateFrom), $lte: new Date(dateTo)
}
}
]
}
},
{
"$group": {
//_id: name,
_id: "$locationId",
count: { $sum: 1 }
}
},
{ $sort: sort }
])
Output is half ok:
[
{
"_id":"5a395ccf210a1d35d0df4a58",
"count":20
}
]
Instead of showing location id id like to show location name. Schema for locations collection is:
{
"_id": {
"$oid": "5a395ccf210a1d35d0df4a58"
"name": "Tower A",
"__v": 0
}
}
Research suggests I need to use $lookup to get that JOIN effect
So I tried
{
"$lookup": {
from: "locations",
localField: "_id",
foreignField: "_id",
as: "locationdetails"
}
}
but the match seems broken. The closest I got was a list of all locations in 'locationdetails'
But with code above here is the empty locationdetails
[
{
"_id":"5a395ddf1d221918d0041313",
"count":20,
"locationdetails":[
]
}
]
What am I missing ?

$lookup on ObjectId's in an array of objects (Mongoose)

I have this two schema:
module.exports = mongoose.model('Article', {
title : String,
text : String,
lang : { type: String, default: 'en'},
user : { type : mongoose.Schema.Types.ObjectId, ref: 'User' },
});
var userSchema = mongoose.Schema({
email : String,
name : String,
rating : [{
_id: false,
articleID: {type: mongoose.Schema.Types.ObjectId, ref: 'Article'},
rate: Number
}]
});
module.exports = mongoose.model('User', userSchema);
and i want to calculate the average rating of an user (the average on all rating on its articles).
I tried this:
User.aggregate([
{ $unwind: "$rating" },
{
"$lookup": {
"from": "Article",
"localField": "rating.articleID",
"foreignField": "_id",
"as": "article-origin"
}
}//,
//{ $match: { "article-origin.user" : mongoose.Types.ObjectId(article.user) } }//,
//{ $group : {_id : "$rating.articleID", avgRate : { $avg : "$rating.rate" } } }
]).exec(function (err,result) {
console.log(err);
console.log(JSON.stringify(result));
});
but without success, the lockup always return the field article-origin null.
result:{"_id":"590747e1af02570769c875dc","name":"name","email":"email","rating":{"rate":5,"articleID":"59074a357fe6a307981e7925"},"__v":0,"article-origin":[]}]
Why this is not working ?
Certainly no need for the $lookup operator since the group aggregation operation does not make use of the documents from the articles collection, it only needs a single field i.e. articleID for grouping.
There are two ways you can go about this. If your MongoDB server version is 3.4 or greater, then the $divide, $reduce and $size operators can be applied here to calculate the average without resorting
to flatten the rating array first which can have some performance ramifications if the array is large.
Consider running the following pipeline:
User.aggregate([
{ "$match": { "_id" : mongoose.Types.ObjectId(article.user) } },
{
"$addFields": {
"avgRate": {
"$divide": [
{
"$reduce": {
"input": "$rating",
"initialValue": 0,
"in": { "$sum": ["$$value", "$$this.rate"] }
}
},
{
"$cond": [
{ "$ne": [{ "$size": "$rating" }, 0] },
{ "$size": "$rating" },
1
]
}
]
}
}
}
]).exec(function (err, result) {
console.log(err);
console.log(JSON.stringify(result));
});
If using MongoDB version 3.2 then you would need to $unwind the rating array first:
User.aggregate([
{ "$match": { "_id" : mongoose.Types.ObjectId(article.user) } },
{ "$unwind": "$rating" },
{
"$group": {
"_id": "$_id",
"avgRate": { "$avg": "$rating.rate" }
}
}
]).exec(function (err, result) {
console.log(err);
console.log(JSON.stringify(result));
});
If for some reason you need the $lookup operation, you need to reference the collection name, not the model name, thus the correct aggregate operation should be
User.aggregate([
{ "$unwind": "$rating" },
{
"$lookup": {
"from": "articles", /* collection name here, not model name */
"localField": "rating.articleID",
"foreignField": "_id",
"as": "article-origin"
}
},
{ "$match": { "article-origin.user" : mongoose.Types.ObjectId(article.user) } },
{
"$group": {
"_id": "$_id",
"avgRate": { "$avg": "$rating.rate" }
}
}
]).exec(function (err, result) {
console.log(err);
console.log(JSON.stringify(result));
});

Resources