Stucking with nested array lookup in MongoDB - mongodb

I'm trying to lookup query from nested array in mongodb and I'm getting stuck.
I have total threee collections.
(1) Channel (Parent)
(2) ChannelThreads (Children)
(3) Users
Channel Collection:
{
"_id" : ObjectId("61efcbdc1aa27f83da47c93f"),
"tags" : [],
"slug_history" : [
"iny1Xik"
],
"title" : "Pirate Chat",
"settingId" : ObjectId("61408586b719c8ce89f08674"),
"status" : "published",
"lockedPageContent" : "",
"slug" : "iny1Xik",
"createdAt" : ISODate("2022-01-25T10:07:24.144Z"),
"updatedAt" : ISODate("2022-01-25T10:07:24.144Z"),
"__v" : 0
}
Channel Thread Collection:
{
"_id" : ObjectId("61efcd5df82318884746eb80"),
"threadImage" : [],
"parentId" : null,
"channelId" : ObjectId("61efcbdc1aa27f83da47c93f"),
"authorId" : ObjectId("6177de8f8a5fd72a4f37b7db"),
"threadText" : "New Message",
"reactions" : [
{
"authors" : [
ObjectId("3687de8f8a5fd72a4f37b7bg")
],
"_id" : ObjectId("61ef856432753c196382c37d"),
"icon" : "&#128528"
}
],
"createdAt" : ISODate("2022-01-25T10:13:49.033Z"),
"updatedAt" : ISODate("2022-01-25T10:13:49.033Z"),
"__v" : 0
}
User Collection:
{
"_id" : ObjectId("6177de8f8a5fd72a4f37b7db"),
"image" : "",
"tags" : [],
"pushTokens" : [],
"lastLogin" : ISODate("2022-01-25T10:08:19.055Z"),
"firstName" : "dinesh",
"lastName" : "patel",
"email" : "dineshpatel#example.com",
"infusionSoftId" : "784589",
"role" : "user",
"__v" : 0,
"settings" : {
"commentNotification" : false,
"commentReplyNotification" : true
}
}
I'm trying to implement lookup for authors of thread reactions.
Expected Output:
{
"_id": ObjectId("61efcbdc1aa27f83da47c93f"),
"tags": [],
"slug_history": [
"iny1Xik"
],
"title": "Pirate Chat",
"settingId": ObjectId("61408586b719c8ce89f08674"),
"status": "published",
"lockedPageContent": "",
"slug": "iny1Xik",
"createdAt": ISODate("2022-01-25T10:07:24.144Z"),
"updatedAt": ISODate("2022-01-25T10:07:24.144Z"),
"__v": 0,
"threads": [
{
"_id": ObjectId("61efcd5df82318884746eb80"),
"threadImage": [],
"parentId": null,
"channelId": ObjectId("61efcbdc1aa27f83da47c93f"),
"authorId": {
"_id": ObjectId("6177de8f8a5fd72a4f37b7db"),
"image": "",
"tags": [],
"pushTokens": [],
"lastLogin": ISODate("2022-01-25T10:08:19.055Z"),
"firstName": "dinesh",
"lastName": "patel",
"email": "dineshpatel#example.com",
"infusionSoftId": "something",
"role": "user",
"__v": 0,
"settings": {
"commentNotification": false,
"commentReplyNotification": true
}
},
"threadText": "New Message",
"reactions": [
{
"authors": [
{
"_id": ObjectId("3687de8f8a5fd72a4f37b7bg"),
"image": "",
"tags": [],
"pushTokens": [],
"lastLogin": ISODate("2022-01-25T10:08:19.055Z"),
"firstName": "kayle",
"lastName": "hell",
"email": "kylehell#example.com",
"infusionSoftId": "8475151",
"role": "user",
"__v": 0,
"settings": {
"commentNotification": false,
"commentReplyNotification": true
}
}
],
"_id": ObjectId("61ef856432753c196382c37d"),
"icon": "&#128528"
}
],
"createdAt": ISODate("2022-01-25T10:13:49.033Z"),
"updatedAt": ISODate("2022-01-25T10:13:49.033Z"),
"__v": 0
}
]
}
How can write lookup query for reaction authors.
Thanks in advance!!

You can try nested lookup,
$lookup with channel thread collection, pass channel id in let
$match to match channelId condition
$lookup with user collection to get author info for authorId
$lookup with user collection to get reactions's authors info
$arrayElemAt to get first element from authorId
$map to iterate loop of reactions array, $filter to iterate loop of users and get matching author user info from users array,
$mergeObjects to merge authors and current object properties
$$REMOVE to remove users field because it is not needed now
db.channel.aggregate([
{
$lookup: {
from: "channelThread",
let: { channelId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$$channelId", "$channelId"] } } },
{
$lookup: {
from: "user",
localField: "authorId",
foreignField: "_id",
as: "authorId"
}
},
{
$lookup: {
from: "user",
localField: "reactions.authors",
foreignField: "_id",
as: "users"
}
},
{
$addFields: {
authorId: { $arrayElemAt: ["$authorId", 0] },
reactions: {
$map: {
input: "$reactions",
as: "r",
in: {
$mergeObjects: [
"$$r",
{
authors: {
$filter: {
input: "$users",
cond: { $in: ["$$this._id", "$$r.authors"] }
}
}
}
]
}
}
},
users: "$$REMOVE"
}
}
],
as: "threads"
}
}
])
Playground

Related

Mongodb aggregate lookup three collections

Learning MongoDB for the past two days and I am trying to aggregate three collections but unable to achieve it
Below are the three collection maintaining in the database
t_credentials
{
"_id" : "619ca68b624c41e408348406",
"title" : "Company ID"
}
t_groups
{
"_id" : "61a253da88ca12a37218898d",
"group_name" : "Gold"
}
t_user_credentials
{
"_id" : "619ca88a624c41e408348424",
"credential_id" : "619ca68b624c41e408348406",
"group_id" : "61a253da88ca12a37218898d",
"identifiers" : {
"first_name" : "Lee",
"middle_name" : "Min",
"last_name" : "Ho"
},
"created_at" : "2021-12-01T17:20:49.000Z"
}
Here I am trying to achieve the output in the below format:
Expected Output
[{
"_id" : "619ca88a624c41e408348424",
"first_name" : ,
"middle_name" : ,
"last_name" : ,
"credential" : {
"_id:" : "619ca68b624c41e408348406",
"title" : "Company ID"
},
"group" : {
"_id" : "61a253da88ca12a37218898d",
"group_name" : "Gold"
},
"created_at" : "2021-12-01T17:20:49.000Z"
}]
But, I am getting the fields only from t_user_credentials but not able to get like in the above format
Query
db.t_user_credentials.aggregate([
{
$lookup: {
from: "t_credentials",
localField: "_id",
foreignField: "credential_id",
as: "credentials"
}
},
{
$unwind: {
path:'$credentials',
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "t_groups",
localField: "_id",
foreignField: "group_id",
as: "groups"
}
},
{
$unwind: {
path: '$groups',
preserveNullAndEmptyArrays: true
}
},
{
$project: {
last_name: "$identifiers.last_name",
first_name: "$identifiers.first_name",
middle_name: "$identifiers.middle_name",
"credentials.title": 1,
created_at: 1,
group_id: 1
}
}
])
Can any one help me to solve this issue, it will be very helpful for me.
This query uses $replaceWith to merge the identifiers sub-document into the $$ROOT document. We also use $unset to remove fields we are no longer interested in. Before all of that we make sure to unwind our credential and group fields.
You can check out a live demo of this query here
Consider the following:
Database
db={
"t_credentials": [
{
"_id": "619ca68b624c41e408348406",
"title": "Company ID"
}
],
"t_groups": [
{
"_id": "61a253da88ca12a37218898d",
"group_name": "Gold"
}
],
"t_user_credentials": [
{
"_id": "619ca88a624c41e408348424",
"credential_id": "619ca68b624c41e408348406",
"group_id": "61a253da88ca12a37218898d",
"identifiers": {
"first_name": "Lee",
"middle_name": "Min",
"last_name": "Ho"
},
"created_at": "2021-12-01T17:20:49.000Z"
}
]
}
Query
db.t_user_credentials.aggregate([
{
"$lookup": {
"from": "t_credentials",
"localField": "credential_id",
"foreignField": "_id",
"as": "credential"
}
},
{
"$lookup": {
"from": "t_groups",
"localField": "group_id",
"foreignField": "_id",
"as": "group"
}
},
{
$unwind: "$group",
},
{
$unwind: "$credential"
},
{
$replaceWith: {
$mergeObjects: [
"$$ROOT",
"$identifiers"
]
}
},
{
$unset: [
"group_id",
"credential_id",
"identifiers"
]
}
])
Result
[
{
"_id": "619ca88a624c41e408348424",
"created_at": "2021-12-01T17:20:49.000Z",
"credential": {
"_id": "619ca68b624c41e408348406",
"title": "Company ID"
},
"first_name": "Lee",
"group": {
"_id": "61a253da88ca12a37218898d",
"group_name": "Gold"
},
"last_name": "Ho",
"middle_name": "Min"
}
]

Join two collections by matching value present inside array of object using MongoDB

I want to join two collection using MongoDB but the local filed matching key is present inside array. I am explaining my two document below.
users:
{ "_id" : ObjectId("5ee8c77330e6a86c5e5ce69b"),
"IsDeleted" : false,
"Name" : "savitha",
"Number" : "9848868000",
"Email" : "savitha.k#edqart.com",
"Password" : "savitha1",
"RoleId" : ObjectId("5ee1d885de9b6a5ae2bae165"),
"RoleName" : "KIOSK",
"UserType" : "POS",
"IsActive" : true,
"SalesAgentName" : "",
"salesAgentEmail" : "",
"SalesAgentMobile" : "",
"SalesAgentAlternateMobile" : "",
"SalesAgentRole" : "",
"MerchantID" : "",
"MachineName" : "",
"MachineBank" : "",
"StoreDetails" : [
{
"StoreCode" : "DKAA",
"Counter" : 3,
"TerminalID" : "12345",
"CounterName" : "Counter3"
}
]
}
This is my primary collection and i want to join with below collection.
storeinfo:
{
"_id" : ObjectId("5e447571f034c748ab11bd15"),
"IsActive" : true,
"IsDeleted" : false,
"StoreCode" : "DKAA",
"StoreName" : "Deeksha Group",
"StoreDescription" : "Deeksha Store is a place where Parents can purchase all the school merchandise in one place at reasonable prices.",
"StoreBranch" : "Bengaluru",
}
Here I need to join both collection as per StoreDetails.StoreCode(users) = StoreCode(storeinfo) and then I want to add only StoreName(from storeinfo) with respective record in StoreDetails. I am explaining my query below.
db.getCollection('users').aggregate([
{
$match: {"_id" : ObjectId("5ee8c77330e6a86c5e5ce69b")}
},
{
$addFields: {
"StoreDetails": {
$ifNull : [ "$StoreDetails", [ ] ]
}
}
},
{
$lookup: {
"from": "storeinfo",
"localField": "StoreDetails.StoreCode",
"foreignField": "StoreCode",
"as": "StoreDetails.StoreCode"
}
}
])
But as per this query I am not getting the expected output. My expected output should like below.
{ "_id" : ObjectId("5ee8c77330e6a86c5e5ce69b"),
"IsDeleted" : false,
"Name" : "savitha",
"Number" : "9848868000",
"Email" : "savitha.k#edqart.com",
"Password" : "savitha1",
"RoleId" : ObjectId("5ee1d885de9b6a5ae2bae165"),
"RoleName" : "KIOSK",
"UserType" : "POS",
"IsActive" : true,
"SalesAgentName" : "",
"salesAgentEmail" : "",
"SalesAgentMobile" : "",
"SalesAgentAlternateMobile" : "",
"SalesAgentRole" : "",
"MerchantID" : "",
"MachineName" : "",
"MachineBank" : "",
"StoreDetails" : [
{
"StoreCode" : "DKAA",
"StoreName" : "Deeksha Group"
"Counter" : 3,
"TerminalID" : "12345",
"CounterName" : "Counter3"
}
]
}
Here I need only store Name will add into the respective object as per storecode. But as per my query I am getting all fields value from storeinfo. Can any body help me to resolve this issue.
Here goes the full query
db.users.aggregate([
{
$match: {
"_id": ObjectId("5ee8c77330e6a86c5e5ce69b")
}
},
{
$addFields: {
"StoreDetails": {
$ifNull: [
"$StoreDetails",
[]
]
}
}
},
{
$unwind: "$StoreDetails"
},
{
$lookup: {
"from": "storeinfo",
"localField": "StoreDetails.StoreCode",
"foreignField": "StoreCode",
"as": "StoreDetails.StoreCode"
}
},
{
$unwind: "$StoreDetails.StoreCode"
},
{
$project: {
"Email": 1,
"IsActive": 1,
"IsDeleted": 1,
"MachineBank": 1,
"MachineName": 1,
"MerchantID": 1,
"Name": 1,
"Number": 1,
"Password": 1,
"RoleId": 1,
"RoleName": 1,
"SalesAgentAlternateMobile": 1,
"SalesAgentMobile": 1,
"SalesAgentName": 1,
"SalesAgentRole": 1,
"UserType": 1,
"_id": 1,
"salesAgentEmail": 1,
"StoreDetails": {
"StoreCode": "$StoreDetails.StoreCode.StoreCode",
"Counter": 1,
"CounterName": 1,
"StoreName": "$StoreDetails.StoreCode.StoreName",
"TerminalID": 1
}
}
},
{
$group: {
_id: "$_id",
"Email": {
$first: "$Email"
},
"IsActive": {
$first: "$IsActive"
},
"IsDeleted": {
$first: "$IsDeleted"
},
"MachineBank": {
$first: "$MachineBank"
},
"MachineName": {
$first: "$MachineName"
},
"MerchantID": {
$first: "$MerchantID"
},
"Name": {
$first: "$Name"
},
"Number": {
$first: "$Number"
},
"Password": {
$first: "$Password"
},
"RoleId": {
$first: "$RoleId"
},
"RoleName": {
$first: "$RoleName"
},
"SalesAgentAlternateMobile": {
$first: "$SalesAgentAlternateMobile"
},
"SalesAgentMobile": {
$first: "$SalesAgentMobile"
},
"SalesAgentName": {
$first: "$SalesAgentName"
},
"SalesAgentRole": {
$first: "$SalesAgentRole"
},
"UserType": {
$first: "$UserType"
},
"salesAgentEmail": {
$first: "$salesAgentEmail"
},
"StoreDetails": {
$push: {
StoreCode: "$StoreDetails.StoreCode",
Counter: "$StoreDetails.Counter",
CounterName: "$StoreDetails.CounterName",
StoreName: "$StoreDetails.StoreName",
TerminalID: "$StoreDetails.TerminalID"
}
}
}
}
])
You can see the full working example here:
https://mongoplayground.net/p/6KqECy-o1U8
Thanks

MongoDB aggregate field in array of objects

I'm trying to solve a problem for some time now but with no luck, unfortunately.
So I'm refactoring some old code (which used the all known get each doc query and for loop over it) and I'm trying to aggregate the results to remove the thousands of calls the BE is making.
The current doc looks like this
{
"_id" : ObjectId("5c176fc65f543200019f8d66"),
"category" : "New client",
"description" : "",
"createdById" : ObjectId("5c0a858da9c0f000018382bb"),
"createdAt" : ISODate("2018-12-17T09:43:34.642Z"),
"sentAt" : ISODate("2018-12-17T09:44:25.902Z"),
"scheduleToBeSentAt" : ISODate("2018-01-17T11:43:00.000Z"),
"recipients" : [
{
"user" : ObjectId("5c0a858da9c0f000018382b5"),
"status" : {
"approved" : true,
"lastUpdated" : ISODate("2018-01-17T11:43:00.000Z")
}
},
{
"user" : ObjectId("5c0a858da9c0f000018382b6"),
"status" : {
"approved" : true,
"lastUpdated" : ISODate("2018-01-17T11:43:00.000Z")
}
},
],
"recipientsGroup" : "All",
"isActive" : false,
"notificationSent" : true
}
The field recipients.user is an objectID of a user from the Users collection.
What is the correct way to modify this so the result will be
{
"_id": ObjectId("5c176fc65f543200019f8d66"),
"category": "New client",
"description": "",
"createdById": ObjectId("5c0a858da9c0f000018382bb"),
"createdAt": ISODate("2018-12-17T09:43:34.642Z"),
"sentAt": ISODate("2018-12-17T09:44:25.902Z"),
"scheduleToBeSentAt": ISODate("2018-01-17T11:43:00.000Z"),
"recipients": [{
"user": {
"_id": ObjectId("5c0a858da9c0f000018382b5"),
"title": "",
"firstName": "Monique",
"lastName": "Heinrich",
"qualification": "Management",
"isActive": true
},
"status": {
"approved": true,
"lastUpdated": ISODate("2018-01-17T11:43:00.000Z")
}
},
{
"user": {
"_id": ObjectId("5c0a858da9c0f000018382b6"),
"title": "",
"firstName": "Marek",
"lastName": "Pucelik",
"qualification": "Management",
"isActive": true
},
"status": {
"approved": true,
"lastUpdated": ISODate("2018-01-17T11:43:00.000Z")
}
},
],
"recipientsGroup": "All",
"isActive": false,
"notificationSent": true
}
An aggregation is a powerful tool but sometimes the simple solution makes your brain hurt.....
I tried something like this but with no luck also.
db.getCollection('Protocols').aggregate([
{
$lookup: {
from: "Users",
localField: "recipients.user",
foreignField: "_id",
as: "users"
}
},
{
$project: {
"recipients": {
"status": 1,
"user": {
$filter: {
input: "$users",
cond: { $eq: ["$$this._id", "$user"] }
}
},
}
}
}
])
You can use the $lookup operator in your aggregation pipeline
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/
But for performance reason you'd rather duplicate user object in your recipents array to avoid such complex queries.

$lookup and get count under certain conditions MongoDB

Have 2 collections for handling chat
For chat rooms
For chat Messages
Sample data for chatRooms is as follows
{
"data": [
{
"_id": "5a606ab0116e2c164b25ef33",
"topic": "akhil Ben chat",
"topicDesc": "question 1",
"roomName": "benakhil777akhil",
"createdOn": "2018-01-18T09:36:48.231Z",
"participants": [
"ben",
"akhil777"
],
"__v": 0
},
{
"_id": "5a4dbdaab46b426863e7ead3",
"topic": "test",
"topicDesc": "test123",
"roomName": "benakhil777test",
"createdOn": "2018-01-04T05:37:46.088Z",
"participants": [
"ben",
"akhil777"
],
"__v": 0
}
]}
Sample Data for chatMessages is as follows
{"data": [
{
"_id": "5a62281ea0652120a6668bae",
"topic": "akhil Ben chat",
"roomName": "benakhil777akhil",
"message": "test 1",
"__v": 0,
"readStatus": [
{
"recipient": "ben",
"_id": "5a62281ea0652120a6668bb0",
"status": true
},
{
"recipient": "akhil777",
"_id": "5a62281ea0652120a6668baf",
"status": true
}
],
"createdOn": "2018-01-19T17:17:18.456Z"
},
{
"_id": "5a622866a0652120a6668bb1",
"topic": "akhil Ben chat",
"roomName": "benakhil777akhil",
"message": "Test 2",
"__v": 0,
"readStatus": [
{
"recipient": "ben",
"_id": "5a622866a0652120a6668bb3",
"status": false
},
{
"recipient": "akhil777",
"_id": "5a622866a0652120a6668bb2",
"status": true
}
],
"createdOn": "2018-01-19T17:18:30.396Z"
},
{
"_id": "5a62287ca0652120a6668bb4",
"topic": "akhil Ben chat",
"roomName": "benakhil777akhil",
"message": "test 3",
"__v": 0,
"readStatus": [
{
"recipient": "ben",
"_id": "5a62287ca0652120a6668bb6",
"status": false
},
{
"recipient": "akhil777",
"_id": "5a62287ca0652120a6668bb5",
"status": true
}
],
"createdOn": "2018-01-19T17:18:52.018Z"
}
]}
In the above JSON readStatus store the status, which the user read the message or not. so that i can count the unread messages by a user for each chat room.
The status inside the readStatus holds the read status of message, true for message is read.
There are two rooms benakhil777akhil and benakhil777test.
What i want to get is the number of unread messages for each room by a user say ben
Also there is userDetails collection
say,
[{
"_id": "59e6d6ba02e11e1814481022",
"username": "ben",
"name": "Ben S",
"email": "qwerty#123.com",
},{
"_id": "59e6d6ba02e11e1814481022",
"username": "akhil777",
"name": "Akhil Clement",
"email": "qwerty#123.com",
}]
this will be the user details collection
and output JSON i need is like.
{
"data": [
{
"_id": "5a606ab0116e2c164b25ef33",
"topic": "akhil Ben chat",
"topicDesc": "question 1",
"roomName": "benakhil777akhil",
"createdOn": "2018-01-18T09:36:48.231Z",
"participants": [
"ben",
"akhil777"
],
"participantDetails":[{
"_id": "59e6d6ba02e11e1814481022",
"username": "ben",
"name": "Ben S",
"email": "qwerty#123.com",
},{
"_id": "59e6d6ba02e11e1814481022",
"username": "akhil777",
"name": "Akhil Clement",
"email": "qwerty#123.com",
}],
"unreadCount": 2,
"__v": 0
},
{
"_id": "5a4dbdaab46b426863e7ead3",
"topic": "test",
"topicDesc": "test123",
"roomName": "benakhil777test",
"createdOn": "2018-01-04T05:37:46.088Z",
"participants": [
"ben",
"akhil777"
],
"participantDetails":[{
"_id": "59e6d6ba02e11e1814481022",
"username": "ben",
"name": "Ben S",
"email": "qwerty#123.com",
},{
"_id": "59e6d6ba02e11e1814481022",
"username": "akhil777",
"name": "Akhil Clement",
"email": "qwerty#123.com",
}],
"unreadCount": 0,
"__v": 0
}
]}
Please try this aggregation pipeline
db.rooms.aggregate(
[
{$match : {participants : 'ben'}},
{$lookup : {
from : "chats",
localField : "roomName",
foreignField:"roomName",
as :"out"
}
},
{$unwind : {
path: "$out",
preserveNullAndEmptyArrays: true
}
},
{$unwind : {
path: "$out.readStatus",
preserveNullAndEmptyArrays: true
}
},
{$addFields : {
isMatch : { $and : [
{ $eq : ["$out.readStatus.recipient" , "ben" ] } , { $eq : [ "$out.readStatus.status" , false ] } ]
}
}
},
{$group : {
_id : {
_id : "$_id" ,
topic : "$topic",
topicDesc : "$topicDesc",
createdOn : "$createdOn",
participants : "$participants",
roomName : "$roomName"
},
unreadCount : { $sum : { $cond : [ "$isMatch" , 1, 0 ] } }
}
},
{$sort : {unreadCount : -1}}
]
).pretty()
result
{
"_id" : {
"_id" : "5a606ab0116e2c164b25ef33",
"topic" : "akhil Ben chat",
"topicDesc" : "question 1",
"createdOn" : "2018-01-18T09:36:48.231Z",
"participants" : [
"ben",
"akhil777"
],
"roomName" : "benakhil777akhil"
},
"unreadCount" : 2
}
{
"_id" : {
"_id" : "5a4dbdaab46b426863e7ead3",
"topic" : "test",
"topicDesc" : "test123",
"createdOn" : "2018-01-04T05:37:46.088Z",
"participants" : [
"ben",
"akhil777"
],
"roomName" : "benakhil777test"
},
"unreadCount" : 0
}
EDIT since addFields is not available in 3.2.17
{$group : {
_id : {
_id : "$_id" ,
topic : "$topic",
topicDesc : "$topicDesc",
createdOn : "$createdOn",
participants : "$participants",
roomName : "$roomName"
},
unreadCount : { $sum : { $cond : [ { $and : [
{ $eq : ["$out.readStatus.recipient" , "ben" ] } , { $eq : [ "$out.readStatus.status" , false ] } ]
} , 1, 0 ] } }
}
}
EDIT-2 added $project
{$project :
{
"_id" : "$_id._id",
"topic" : "$_id.topic",
"topicDesc" : "$_id.topicDesc",
"createdOn" : "$_id.createdOn",
"participants" : "$_id.participants",
"roomName" : "$_id.roomName",
"unreadCount" : "$unreadCount"
}
}
You can simplify your code to use below aggregation.
$cond with input criteria to check for read status flag, output 1 when false 0 when true.
inner $sum to count unread values in each chat message with outer $sum to sum the unread values across all matching chat messages.
db.chatRooms.aggregate(
[{
"$match":{"participants":"ben"}},
{"$lookup":{
"from":"chatMessages",
"localField":"roomName",
"foreignField":"roomName",
"as":"chatMessages"
}},
{"$project":{
"topic":1,
"topicDesc":1,
"roomName":1,
"createdOn":1,
"participants":1,
"unreadCount":{
"$sum":{
"$map":{
"input":"$chatMessages",
"as":"chatMessage",
"in":{
"$sum":{
"$map":{
"input":"$$chatMessage.readStatus",
"as":"mChatMessage",
"in":{"$cond":[{"$eq":["$$mChatMessage.status",false]},1,0]}
}
}
}
}
}
}
}}
])
result JSON with user details.
db.chatRooms.aggregate(
[
{$match : {participants : 'ben'}},
{ $unwind : {
path: "$participants",
preserveNullAndEmptyArrays: true
}
},
{ $lookup: {
from:"users",
localField:"participants",
foreignField:"username",
as:"userData"
}
},
{ $lookup: {
from:"chatmessages",
localField:"roomName",
foreignField:"roomName",
as:"out"
}
},
{ $unwind : {
path: "$out",
preserveNullAndEmptyArrays: true
}
},
{ $unwind : {
path: "$out.readStatus",
preserveNullAndEmptyArrays: true
}
},
{ $group : {
_id : {
_id : "$_id" ,
topic : "$topic",
topicDesc : "$topicDesc",
createdOn : "$createdOn",
roomName : "$roomName"
},
participants : {$addToSet : "$participants" } ,
participantDetails : {$addToSet : {$arrayElemAt : ["$userData", 0]}},
unreadCount : {
$sum : {
$cond : [ {
$and : [
{ $eq : ["$out.readStatus.recipient" , "ben" ] } ,
{ $eq : [ "$out.readStatus.status" , false ] }
]
} , 1, 0
]
}
}
}
}
,
{ $project :
{
_id : "$_id._id",
topic : "$_id.topic",
topicDesc : "$_id.topicDesc",
createdOn : "$_id.createdOn",
participants : "$_id.participants",
roomName : "$_id.roomName",
unreadCount : "$unreadCount",
participants : 1 ,
participantDetails : 1
}
}
])

MongoDB nested lookup with 3 levels

I need to retrieve the entire single object hierarchy from the database as a JSON. Actually, the proposal about any other solution to achieve this result would be highly appreciated. I decided to use MongoDB with its $lookup support.
So I have three collections:
party
{ "_id" : "2", "name" : "party2" }
{ "_id" : "5", "name" : "party5" }
{ "_id" : "4", "name" : "party4" }
{ "_id" : "1", "name" : "party1" }
{ "_id" : "3", "name" : "party3" }
address
{ "_id" : "a3", "street" : "Address3", "party_id" : "2" }
{ "_id" : "a6", "street" : "Address6", "party_id" : "5" }
{ "_id" : "a1", "street" : "Address1", "party_id" : "1" }
{ "_id" : "a5", "street" : "Address5", "party_id" : "5" }
{ "_id" : "a2", "street" : "Address2", "party_id" : "1" }
{ "_id" : "a4", "street" : "Address4", "party_id" : "3" }
addressComment
{ "_id" : "ac2", "address_id" : "a1", "comment" : "Comment2" }
{ "_id" : "ac1", "address_id" : "a1", "comment" : "Comment1" }
{ "_id" : "ac5", "address_id" : "a5", "comment" : "Comment6" }
{ "_id" : "ac4", "address_id" : "a3", "comment" : "Comment4" }
{ "_id" : "ac3", "address_id" : "a2", "comment" : "Comment3" }
I need to retrieve all parties with all corresponding addresses and address comments as part of the record. My aggregation:
db.party.aggregate([{
$lookup: {
from: "address",
localField: "_id",
foreignField: "party_id",
as: "address"
}
},
{
$unwind: "$address"
},
{
$lookup: {
from: "addressComment",
localField: "address._id",
foreignField: "address_id",
as: "address.addressComment"
}
}])
The result is pretty weird. Some records are ok. But Party with _id: 4 is missing (there is no address for it). Also, there are two Party _id: 1 in the result set (but with different addresses):
{
"_id": "1",
"name": "party1",
"address": {
"_id": "2",
"street": "Address2",
"party_id": "1",
"addressComment": [{
"_id": "3",
"address_id": "2",
"comment": "Comment3"
}]
}
}{
"_id": "1",
"name": "party1",
"address": {
"_id": "1",
"street": "Address1",
"party_id": "1",
"addressComment": [{
"_id": "1",
"address_id": "1",
"comment": "Comment1"
},
{
"_id": "2",
"address_id": "1",
"comment": "Comment2"
}]
}
}{
"_id": "3",
"name": "party3",
"address": {
"_id": "4",
"street": "Address4",
"party_id": "3",
"addressComment": []
}
}{
"_id": "5",
"name": "party5",
"address": {
"_id": "5",
"street": "Address5",
"party_id": "5",
"addressComment": [{
"_id": "5",
"address_id": "5",
"comment": "Comment5"
}]
}
}{
"_id": "2",
"name": "party2",
"address": {
"_id": "3",
"street": "Address3",
"party_id": "2",
"addressComment": [{
"_id": "4",
"address_id": "3",
"comment": "Comment4"
}]
}
}
Please help me with this. I'm pretty new to MongoDB but I feel it can do what I need from it.
The cause of your 'troubles' is the second aggregation stage - { $unwind: "$address" }. It removes record for party with _id: 4 (because its address array is empty, as you mention) and produces two records for parties _id: 1 and _id: 5 (because each of them has two addresses).
To prevent removing of parties without addresses you should set preserveNullAndEmptyArrays option of $unwind stage to true.
To prevent duplicating of parties for its different addresses you should add $group aggregation stage to your pipeline. Also, use $project stage with $filter operator to exclude empty address records in output.
db.party.aggregate([{
$lookup: {
from: "address",
localField: "_id",
foreignField: "party_id",
as: "address"
}
}, {
$unwind: {
path: "$address",
preserveNullAndEmptyArrays: true
}
}, {
$lookup: {
from: "addressComment",
localField: "address._id",
foreignField: "address_id",
as: "address.addressComment",
}
}, {
$group: {
_id : "$_id",
name: { $first: "$name" },
address: { $push: "$address" }
}
}, {
$project: {
_id: 1,
name: 1,
address: {
$filter: { input: "$address", as: "a", cond: { $ifNull: ["$$a._id", false] } }
}
}
}]);
With the mongodb 3.6 and above $lookup syntax it is quite simple to join nested fields without using $unwind.
db.party.aggregate([
{ "$lookup": {
"from": "address",
"let": { "partyId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$party_id", "$$partyId"] }}},
{ "$lookup": {
"from": "addressComment",
"let": { "addressId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$address_id", "$$addressId"] }}}
],
"as": "address"
}}
],
"as": "address"
}},
{ "$unwind": "$address" }
])