get nested model reference from mongodb - mongodb

I have code below to get bookmarked blog of user
Question -> I want to also need the user object with selected field
Bookmarkblog.aggregate([{
$lookup:
{
from: "blogs",
localField: "blog",
foreignField: "_id",
as: "blog"
}
}, {
$match: { "user": mongoose.Types.ObjectId(req.headers.userid) }
}])
current output
[
{
"_id": "5a82ce0d80f62b18bc5ac5bf",
"blog": [
{
"_id": "5a696a31f78861dd29bec5bd",
"user": "59bbbe071b4a358f43dc0cbf",
"category": "59c3b5022ad1854d496cfb8b",
"image": [
"57904805-imac_pro_large.jpg"
],
"tags": [
"Mac"
],
"content": "",
"title": "iMac Pro, the most powerful Mac ever",
"created": "2018-01-25T05:25:05.025Z",
"__v": 0
}
],
"user": "59e5a62427543c54603050de",
"created": "2018-02-13T11:37:49.071Z",
"__v": 0
}
]

Related

Cannot get parent data by mongoDB aggregate graphLookup

Following is the data
[
{
"_id": {
"$oid": "6364f2eee35fc06fa06afb5f"
},
"type": "subbranch",
"parent_id": "635116c18fe4294398842ebb",
"org_name": "Pune - Delhi"
},
{
"_id": {
"$oid": "635116c18fe4294398842ebb"
},
"type": "branch",
"org_name": "Delhi Branch",
"parent_id": "0"
}
]
query which i have written is as follows
// req.params.id is 6364f2eee35fc06fa06afb5f
let id = mongoose.Types.ObjectId(req.params.id);
let data = await organisation.aggregate([
{
$addFields: { "_idString": { "$toString": "$_id" }}
},
{
$graphLookup: {
from: "organisations",
startWith: "$parent_id",
connectFromField: "parent_id",
connectToField: "_idString",
as: "parent"
}
},
{
$match: {_id: id}
},
]);
but in output i get as follow
[
{
"_id": "6364f2eee35fc06fa06afb5f",
"type": "subbranch",
"parent_id": "635116c18fe4294398842ebb",
"org_name": "Pune - Delhi",
"_idString": "6364f2eee35fc06fa06afb5f",
"parent": [
]
}
]
i am getting empty parent array but expected output is array with parent data in it.
any suggestion would be appreciated.
Remember connectFromField expected or extracted from current aggregated collection while connectToField is connected to from orginal collection
DEMO ON https://mongoplayground.net/p/vYDdOgNt9bW
The aggregate query be like
db.collection.aggregate([
{
$addFields: {
"parent_id": {
$convert: {
input: "$parent_id",
to: "objectId",
onError: "$parent_id",
}
}
}
},
{
$graphLookup: {
from: "collection",
startWith: "$parent_id",
connectFromField: "parent_id",
connectToField: "_id",
as: "parent"
}
}
])
Outputs
[
{
"_id": ObjectId("6364f2eee35fc06fa06afb5f"),
"org_name": "Pune - Delhi",
"parent": [
{
"_id": ObjectId("635116c18fe4294398842ebb"),
"org_name": "Delhi Branch",
"parent_id": "0",
"type": "branch"
}
],
"parent_id": ObjectId("635116c18fe4294398842ebb"),
"type": "subbranch"
},
{
"_id": ObjectId("635116c18fe4294398842ebb"),
"org_name": "Delhi Branch",
"parent": [],
"parent_id": "0",
"type": "branch"
}
]

Join multiple collections in MongoDB

Greetings amigo i have one question related joining multiple collection in MongoDb
i have collection schema something like below
Posts Collection
{
"type": "POST_TYPE",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"post_id": "63241dffb0f6770c23663230",
"likes": 50
}
Post Types: 1. Event
{
"date": "2022-09-16T07:07:18.242+00:00",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"venue": "Some Place",
"lat": "null",
"long": "null",
}
Post Types: 2. Poll
{
"created_date": "2022-09-16T07:07:18.242+00:00",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"question": "Question??????",
"poll_opt1": "Yes",
"poll_opt2": "No",
"poll_opt1_count": "5",
"poll_opt2_count": "2"
}
now i have to join Post collection with respective collection e.g.
"post_id" to Event::_id or Poll::_id with condition to Post::type
i have tried aggregation but it does not gave expected output.
i am trying to get output something like below
[
{
"type": "event",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"post_id": {
"date": "2022-09-16T07:07:18.242+00:00",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"venue": "Some Place",
"lat": "null",
"long": "null"
},
"likes": 50
},
{
"type": "poll",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"post_id": {
"created_date": "2022-09-16T07:07:18.242+00:00",
"_id": "63241dffb0f6770c23663230",
"user_id": "63241dffb0f6770c23663230",
"question": "Question??????",
"poll_opt1": "Yes",
"poll_opt2": "No",
"poll_opt1_count": "5",
"poll_opt2_count": "2"
},
"likes": 50
}
]
is there any efficient way to achieve this or better MongoDb schema to manage these types of records?
You can try something like this, using $facet:
db.posts.aggregate([
{
"$facet": {
"eventPosts": [
{
"$match": {
type: "event"
},
},
{
"$lookup": {
"from": "events",
"localField": "post_id",
"foreignField": "_id",
"as": "post_id"
}
}
],
"pollPosts": [
{
"$match": {
type: "poll"
},
},
{
"$lookup": {
"from": "poll",
"localField": "post_id",
"foreignField": "_id",
"as": "post_id"
}
}
]
}
},
{
"$addFields": {
"doc": {
"$concatArrays": [
"$pollPosts",
"$eventPosts"
]
}
}
},
{
"$unwind": "$doc"
},
{
"$replaceRoot": {
"newRoot": "$doc"
}
},
{
"$addFields": {
"post_id": {
"$cond": {
"if": {
"$eq": [
{
"$size": "$post_id"
},
0
]
},
"then": {},
"else": {
"$arrayElemAt": [
"$post_id",
0
]
}
}
}
}
}
])
We do the following, in the query:
Perform two $lookups for the different post_type within $facet. This unfortunately will increase, with the different values of post_type.
Then we combine all the arrays obtained from $facet, using $concatArray.
Then we unwind the concatenated array, and bring the nested document to the root using $replaceRoot.
Finally, for post_id we pick the first array element if it exists, to match the desired output.
Playground link.

Mongo Aggregate Combine Two Documents

Once I've unwound a sub-document array, how do I put it back together with all the original root fields?
Consider the following Tasks data set:
[
{
"_id": "5e95bb1cf36c0ab3247036bd",
"name": "Task A",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": "5e117e5cd90de7187b000d87"
},
{
"_id": "5e95bb30f36c0ab3247036be",
"name": "Task B1",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": "5e117e5cd90de7187b000d87",
"parent": "5e95bb1cf36c0ab3247036bd"
},
{
"_id": "5e95bb35f36c0ab3247036bf",
"name": "Task B2",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": "5e117e5cd90de7187b000d87",
"parent": "5e95bb1cf36c0ab3247036bd"
}
]
So, then I run $graphLookup to get the parent task and populate it's children and then $unwind it and populate the creator field:
[
{
"$match": {
"parent": {
"$exists": false
}
}
},
{
"$graphLookup": {
"from": "tasks",
"startWith": "$_id",
"connectFromField": "_id",
"connectToField": "parent",
"as": "children"
}
},
{
"$unwind": {
"path": "$children"
}
},
{
"$lookup": {
"from": "users",
"localField": "children.creator",
"foreignField": "_id",
"as": "children.creator"
}
},
{
"$unwind": {
"path": "$children.creator"
}
}
]
Which returns the following documents:
[
{
"_id": "5e95bb1cf36c0ab3247036bd",
"name": "Task A",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": "5e117e5cd90de7187b000d87",
"children": [
{
"_id": "5e95bb30f36c0ab3247036be",
"name": "Task B1",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": {
"name": "Jack Frost"
},
"parent": "5e95bb1cf36c0ab3247036bd"
}
]
},
{
"_id": "5e95bb1cf36c0ab3247036bd",
"name": "Task A",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": "5e117e5cd90de7187b000d87",
"children": [
{
"_id": "5e95bb35f36c0ab3247036bf",
"name": "Task B2",
"org": "5e95b9894a0aa0b30dfcbc0b",
"creator": {
"name": "Bill Nye"
},
"parent": "5e95bb1cf36c0ab3247036bd"
}
]
},
]
Lastly, I need to merge all of these duplicate documents back together and join the $children. This is the part I can't figure out. Below is some junk I'm trying but it seems messy to have to specifically list every property.
Is there a better way to combine multiple (mostly) matching docs?
[
...
{
"$group": {
"_id": "$_id",
"name": {
"$mergeObjects": "$properties"
},
"watchers": {
"$addToSet": "$watchers"
},
"assignees": {
"$addToSet": "$assignees"
},
"org": {
"$addToSet": "$$ROOT.org"
},
"children": {
"$push": "$children"
}
}
}
]
Answering my own question here, the best solution I can find is to specify each property but pass it the $first operator. This will ensure that the original value will be passed through.
{
$group: {
_id: '$_id',
name: {$first: '$name'},
org: {$first: '$org'},
creator: {$first: '$creator'},
children: {$push: '$children'}
}
}

How to filter with aggregation and lookup in mongoose?

I am using the following code:
Appointment
.aggregate([
{
$lookup:
{
from: "users",
localField: "doctorId",
foreignField: "_id",
as: "appointment_book"
},
},
])
.then((task) => res.send(task))
.catch((error) => console.log(error));
});
and the output is:
[{
"_id": "5dfd17823a974b39d89857d0",
"userId": "5dfcff8d2c5be31b803313ee",
"doctorId": "5dfd00102c5be31b803313f0",
"dateOfAppointment": "22/12/2019",
"timeOfAppointment": "11",
"currentStatus": "Pending",
"remarks": "",
"__v": 0,
"appointment_book": [
{
"_id": "5dfd00102c5be31b803313f0",
"name": "doctor1",
"avatar": "http://localhost:4000/public/doctor-02.jpg",
"email": "doctor1#gmail.com",
"phone": "0234820",
"city": "Faridabad",
"password": "$2a$10$m/FZg5XG9tVt86FCRAc8fO0RWhcs9D.QLFIH7BQqP/wuOR7EX8OuG",
"problem": "Neurology",
"usertype": 1,
"saltSecret": "$2a$10$m/FZg5XG9tVt86FCRAc8fO",
"__v": 0
}
]
},
{
"_id": "5dfd17bf3a974b39d89857d2",
"userId": "5dfcffdb2c5be31b803313ef",
"doctorId": "5dfd00102c5be31b803313f0",
"dateOfAppointment": "23/12/2019",
"timeOfAppointment": "11",
"currentStatus": "Pending",
"remarks": "",
"__v": 0,
"appointment_book": [
{
"_id": "5dfd00102c5be31b803313f0",
"name": "doctor1",
"avatar": "http://localhost:4000/public/doctor-02.jpg",
"email": "doctor1#gmail.com",
"phone": "0234820",
"city": "Faridabad",
"password": "$2a$10$m/FZg5XG9tVt86FCRAc8fO0RWhcs9D.QLFIH7BQqP/wuOR7EX8OuG",
"problem": "Neurology",
"usertype": 1,
"saltSecret": "$2a$10$m/FZg5XG9tVt86FCRAc8fO",
"__v": 0
}
]
}
]
My requirement to get only "userid" : "5dfcffdb2c5be31b803313ef" in mongoose. How can I do this.
output should be:
{
"_id": "5dfd17bf3a974b39d89857d2",
"userId": "5dfcffdb2c5be31b803313ef",
"doctorId": "5dfd00102c5be31b803313f0",
....
"appointment_book": [
{
"_id": "5dfd00102c5be31b803313f0",
"name": "doctor1",
....
}
]
},
{
"_id": "5dfd17bf3a974b39d89857e3",
"userId": "5dfcffdb2c5be31b803313ef",
"doctorId": "5dfd00102c5be31b803313g1",
....
"appointment_book": [
{
"_id": "5dfd00102c5be31b803313ff",
"name": "doctor2",
....
}
]
},
]
You need to use $match as initial stage before $lookup stage in order to filter collection for the document needed & get referenced document/documents from other collection :
Appointment.aggregate([
{ $match: { "userId": "5dfcffdb2c5be31b803313ef" } },
{
$lookup:
{
from: "users",
localField: "doctorId",
foreignField: "_id",
as: "appointment_book"
},
}])
In case of userId being an ObjectId() :
const mongoose = require('mongoose');
const userId = mongoose.Types.ObjectId("5dfcffdb2c5be31b803313ef");
Appointment.aggregate([
{ $match: { "userId": userId } },
{
$lookup:
{
from: "users",
localField: "doctorId",
foreignField: "_id",
as: "appointment_book"
}
}])
Ref : $match

Populate one path of an object nested inside array for specific fileds

My Posts Schema has a "comments" array path, for each "comment", "user" is objectId to User Modal. How could I populate only "name", "_id", "thumbnailURI" for the user.
what the posts db looks like:
[
...
{
"_id": "5ce5b41222fa4937f8a495e5",
"owner": "5ce5b41123456789f8a495e5",
"uri": "uri.jpg",
"comments": [
{
"date": "2019-05-23T23:24:47.554Z",
"_id": "5ce72bbf88781b35fca696ce",
"user": "5cdda3a4f5a8a077a9352f38",
"content": "cute!!!"
},
{
"date": "2019-05-23T23:28:52.941Z",
"_id": "5ce72cb46fba6f1bece267db",
"user": "5cdda3a4f5a8a077a9352f38",
"content": "cute!!!"
}
],
"createAt": "2019-05-22T20:41:54.290Z",
"__v": 0
},
...
]
I want the "comments" looks like this:
"comments": [
{
"date": "2019-05-23T23:24:47.554Z",
"_id": "5ce72bbf88781b35fca696ce",
"user": {
"name": "name",
"_id": "5ce72cb46fb5ce72cb46fb"
"thumbnailURI": "thumbnailURI.jpg"
},
"content": "cute!!!"
},
{
"date": "2019-05-23T23:28:52.941Z",
"_id": "5ce72cb46fba6f1bece267db",
"user": {
"name": "name",
"_id": "5ce72cb46fb5ce72cb46fb"
"thumbnailURI": "thumbnailURI.jpg"
},
"content": "cute!!!"
}
],
Tried
posts
.getByOwners(owners)
.populate('owner', 'username thumbnail _id')
.populate({
path: "comments",
populate: {
path: "user",
model: "user",
}
});
get empty "comments" array
Thank you!
Thanks for the advises.
I have a "user" model with some paths
There is no "comments" model. "comments" is an array embedded in "post" model.
The query looks like this:
posts
.findById(id)
.populate('owner', 'username thumbnail _id')
.populate("comments.user", 'username thumbnail _id')
This is the final result I want:
"comments": [
{
"date": "2019-05-23T23:24:47.554Z",
"_id": "5ce72bbf88781b35fca696ce",
"user": {
"thumbnail": "pic1.jpg",
"_id": "5cdda3a4f5a8a077a9352f38",
"username": "a"
},
"content": "cute!!!"
},
{
"date": "2019-05-23T23:28:52.941Z",
"_id": "5ce72cb46fba6f1bece267db",
"user": {
"thumbnail": "pic2.jpg",
"_id": "5cdda3a4f5a8a077a9352f38",
"username": "a"
},
"content": "cute!!!"
}
],