Cannot get parent data by mongoDB aggregate graphLookup - mongodb

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"
}
]

Related

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.

MongoDB Aggregate and Group by Subcategories of products

I have a MongoDB schema that looks like this
const ProductModel = new Schema({
subcategory: {
type : mongoose.Schema.Types.ObjectId,
ref : "Subcategory",
},
product_name: {
type: String
},
description: {
type: String
},
price: {
type: Number
},
});
And a subcategory schema:
const SubcategoryModel = new Schema({
subcategoryName: {
type: String,
}
});
The input query before aggregation looks like this:
[
{
"_id": "111",
"subcategory": {
"_id": "456",
"categoryName": "Sneakers",
},
"product_name": "Modern sneaker",
"description": "Stylish",
"price": 4400
},
{
"_id": "222",
"subcategory": {
"_id": "456",
"categoryName": "Sneakers",
},
"product_name": "Blue shoes",
"description": "Vived colors",
"price": 7500
},
{
"_id": "333",
"subcategory": {
"_id": "123",
"categoryName": "Jackets",
"__v": 0
},
"product_name": "Modern jacket",
"description": "Stylish",
"price": 4400
},
}
]
The final result of the query should look like this:
{
"Sneakers":[
{
"product_name":"Modern sneaker",
"description":"Stylish",
"price":"4400"
},
{
"product_name":"Blue shoes",
"description":"Vived colors",
"price":"7500"
},
"Jackets":{
"...."
}
]
}
Subcategory before aggregation:
"subcategories": [
{
"_id": "123",
"categoryName": "Jackets",
},
{
"_id": "456",
"categoryName": "Sneakers",
}
]
I'm trying to populate the subcategory, And then group the products by their subcategoryName field.
You can use this aggregation query:
First $lookup to do the join between Product and Subcategory creating the array subcategories.
Then deconstructs the array using $unwind.
$group by the name of subproduct adding the entire object using $$ROOT.
The passes the fields you want using $project.
And replaceRoot to get key value into arrays as Sneakers and Jackets.
db.Product.aggregate([
{
"$lookup": {
"from": "Subcategory",
"localField": "subcategory.categoryName",
"foreignField": "categoryName",
"as": "subcategories"
}
},
{
"$unwind": "$subcategories"
},
{
"$group": {
"_id": "$subcategories.categoryName",
"data": {
"$push": "$$ROOT"
}
}
},
{
"$project": {
"data": {
"product_name": 1,
"description": 1,
"price": 1
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$arrayToObject": [
[
{
"k": "$_id",
"v": "$data"
}
]
]
}
}
}
])
Example here
With your provided data, result is:
[
{
"Sneakers": [
{
"description": "Stylish",
"price": 4400,
"product_name": "Modern sneaker"
},
{
"description": "Vived colors",
"price": 7500,
"product_name": "Blue shoes"
}
]
},
{
"Jackets": [
{
"description": "Stylish",
"price": 4400,
"product_name": "Modern jacket"
}
]
}
]

MongoDB: How to merge original record back after lookup

I have the following collections which I am using a $lookup to bring together:
{
"organizations": [
{
"_id": 1,
"name": "foo",
"users": [1,2]
},
{
"_id": 2,
"name": "bar",
"users": [1]
}
],
"users": [
{
"_id": 1,
"name": "john1 smith"
},
{
"_id": 2,
"name": "bob johnson"
}
]
}
The query works fine:
[{
"$lookup": {
"from": "users",
"localField": "users",
"foreignField": "_id",
"as": "members"
}
},
{
"$unwind": "$members"
},
{
"$group": {
"_id": "$_id",
"original": { "$first": "$$ROOT" },
"members": {
"$push": "$members"
}
}
}
]
however, the resulting organizations records don't return all of their properties without adding the original prop which gives me back a nesting of the original organization:
{
"_id": 1,
"original": {
"_id": 1,
"name": "foo",
"users": [
1,
2
],
"members": {
"_id": 1,
"name": "john1 smith"
}
},
"members": [
{
"_id": 1,
"name": "john1 smith"
},
{
"_id": 2,
"name": "bob johnson"
}
]
}
I'm trying to get everything in the original prop back into the root along with the new members array.

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