Mongoose nested lookup with 3 child levels included in arrays - javascript

I am new to MongoDB and mongoose.
I am trying to retrieve the entire array of object hierarchy from the database as a JSON.
By searching, I learned how to group nested lookup with 3 child levels without array, but the problem is I cannot find a way to deal with the nested arrays.
Here is what I am struggling with.
user:
[
{
_id: ObjectId("a1"),
username: "user1",
name: "name1"
},
{
_id: ObjectId("a2"),
username: "user2",
name: "name2"
},
{
_id: ObjectId("a3"),
username: "user2",
name: "name2"
},
...
]
reply:
[
{
_id: ObjectId("a"),
author: {ObjectId("a3")},
title: {"Reply1"};
body: {"Hello World!"}
},
{
_id: ObjectId("b"),
author: {ObjectId("a1")},
title: {"Reply2"};
body: {"Hello World!"}
},
{
_id: ObjectId("c"),
author: {ObjectId("a2")},
title: {"Reply3"};
body: {"Hello World!"}
},
{
_id: ObjectId("d"),
author: {ObjectId("a2")},
title: {"Reply4"};
body: {"Hello World!"}
}
...
]
post:
[
//First post
{
_id: ObjectId("0"),
title: 'post title',
author: {ObjectId("a1")},
reply: [{ObjectId("a")}, {ObjectId("b")}, ...],
},
//Second Post
{
_id: ObjectId("1"),
title: 'post title2',
author: {ObjectId("a2")},
reply: [{ObjectId("c")}, {ObjectId("d")}, ...],
},
...
]
Expected example:
[
//First post
{
_id: ObjectId("0"),
title: 'post title',
author: {
_id: ObjectId("a1"),
username: "user1",
name: "name1"
},
reply: [{
_id: ObjectId("a"),
author: {
_id: ObjectId("a3"),
username: "user2",
name: "name2"
},
title: {"Reply1"},
body: {"Hello World!"}
},
{
_id: ObjectId("b"),
author: {
_id: ObjectId("a1"),
username: "user1",
name: "name1"
},
title: {"Reply2"};
body: {"Hello World!"}
},
...
],
},
{
//Second post
},
...
]
Here is the code that I used.
posts = await Post.aggregate([{
$match: searchQuery
},
{
$lookup: {
from: 'users',
localField: 'author',
foreignField: '_id',
as: 'author'
}
},
{
$lookup: {
from: 'replies',
localField: 'reply',
foreignField: '_id',
as: 'reply'
}
},
{
$unwind: '$author',
},
{
$lookup: {
from: 'users',
localField: 'reply.author',
foreignField: '_id',
as: 'reply.author'
}
},
{
$project: {
title: 1,
author: 1,
reply: 1
}
},
]).exec();
How should I change the code if I want to get the expected example?

Here's one way to do it. Lots of "$lookup" for author of post, reply to the post, author ids of reply, and then author document matching _id.
db.post.aggregate([
{
"$lookup": {
"from": "user",
"localField": "author",
"foreignField": "_id",
"as": "author"
}
},
{
"$set": {
"author": {
"$first": "$author"
}
}
},
{
"$lookup": {
"from": "reply",
"localField": "reply",
"foreignField": "_id",
"as": "reply"
}
},
{
"$lookup": {
"from": "user",
"localField": "reply.author",
"foreignField": "_id",
"as": "replyAuthors"
}
},
{
"$project": {
"title": 1,
"author": 1,
"reply": {
"$map": {
"input": "$reply",
"as": "repObj",
"in": {
"$mergeObjects": [
"$$repObj",
{
"author": {
"$first": {
"$filter": {
"input": "$replyAuthors",
"as": "repA",
"cond": {
"$eq": [
"$$repA._id",
"$$repObj.author"
]
}
}
}
}
}
]
}
}
}
}
}
])
Try it on mongoplayground.net.

Related

Get students for given list of classes in mongodb

As specified below, I have section and class schema in my MongoDB.
How can I query all the students of a given list of Class IDs?
const sectionsSchema = new Schema({
section: {
type: String,
required: false
},
students:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
const classSchema = new Schema({
name:{
type: String,
required: true
},
institution: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Institution'
},
divisions :[{
type: sectionsSchema,
required: true
}]
},{timestamps: true})
Below is the Example Data
Class: [{
_id: 'Class1',
institution: 'inst1',
divisions:[{
section: 'A',
students: ['Stud1','Stud2']
},
{
section: 'B',
students: ['Stud3','Stud4']
}]
},
{
_id: 'Class2',
institution: 'inst1',
divisions:[{
section: 'A',
students: ['Stud5','Stud6']
}]
}]
input: ['Class1','Class2'];
Expected Output: ['Stud1','Stud2','Stud3','Stud4','Stud5','Stud6'];
My suggestion is to use an aggregate query as following:
.aggregate([
{
$match: {
_id: {
$in: [
"Class1",
"Class2"
]
}
}
},
{
$unwind: "$divisions"
},
{
$unwind: "$divisions.students"
},
{
"$replaceRoot": {
"newRoot": "$divisions"
}
},
])
Example output:
[
{
"section": "A",
"students": "Stud1"
},
{
"section": "A",
"students": "Stud2"
},
{
"section": "B",
"students": "Stud3"
},
{
"section": "B",
"students": "Stud4"
},
{
"section": "A",
"students": "Stud5"
},
{
"section": "A",
"students": "Stud6"
}
]
You afterwards have to map the documents returned to simple strings, since mongoose is not able to return plain strings.

How do I get comments count while fetching posts

I have two collections Posts an comments. I am storing comments with postID. I want to show comments count field when fetching all the posts data.
How do I achieve this?
// posts
{
postID: '123',
title: 'abc'
}
// comments
{
postID: '123',
comments: [
{
commentID: 'comment123',
comment: 'my Comment'
}
]
}
// Looking for this
{
postID: '123',
title: 'abc',
commentCount: 1
}
Here's one way you could do it.
db.posts.aggregate([
{
"$lookup": {
"from": "comments",
"localField": "postID",
"foreignField": "postID",
"pipeline": [
{
"$project": {
"_id": 0,
"commentCount": {"$size": "$comments"}
}
}
],
"as": "commentCount"
}
},
{
"$project": {
"_id": 0,
"postID": 1,
"title": 1,
"commentCount": {"$first": "$commentCount.commentCount"}
}
}
])
Try it on mongoplayground.net.
Try This.
pipeline = [{
"$lookup": {
"from": "comments",
"let": {
"postId": "$postId",
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": ["$postId", "$$postId"]
},
}
},
{
"$group": {
"_id": "$postId",
"comments_count": {"$sum": 1}
}
}
],
"as": "comments"
}
},
{
"$project": {
"_id": 0,
"postId": 1,
"title":1,
"comments_count": "$comments.comments_count"
}
}]
db.posts.aggregate(pipeline)

How to join three (multipe) collections with $lookup in mongodb?

How to join three (multipe) collections with $lookup in mongodb?
Hi I am looking to join data from three collection
users collection:
[
{
_id:0,
name:"abc",
phone:999999999
},
{
_id:1,
name:"xyz",
phone:888888888
},
]
product collection:
[
{
_id:"p01",
name:"product-name",
price:1200
},
{
_id:"p02",
name:"product-name1",
price:100
}
]
productreviews collection:
[
{
_id:"pr0",
userId:0,
productId:"p01",
star:4
},
{
_id:"pr1",
userId:1,
productId:"p01",
star:3
}
]
mongodb query:
product.aggregate([
{
$lookup: {
from: "productreviews",
localField: "_id",
foreignField: "productId",
as: "review",
},
},
{
$lookup: {
from: "users",
localField: "review.userId",
foreignField: "_id",
as: "review.userInfo",
},
},
])
I am not able to get that output which i need.
How can i get this following output:
{
product: [
{
_id: "p01",
name: "product-name",
price: 1200,
review: [
{
_id: "pr0",
userId: 0,
productId: "p01",
star: 4,
"userInfo": {
name: "abc",
phone: 999999999
}
},
{
_id: "pr1",
userId: 1,
productId: "p01",
star: 3,
"userInfo": {
"name": "xyz",
"phone": 888888888,
}
},
]
},
{
_id: "p02",
name: "product-name1",
price: 100,
},
]
}
Any help appreciated!. Thank You...
db.product.aggregate([
{
$lookup: {
from: "review",
localField: "_id",
foreignField: "productId",
as: "review",
},
},
{
$lookup: {
from: "users",
localField: "review.userId", //See here
foreignField: "_id",
as: "review.userInfo",
},
},
])
Local field name is userId in the second lookup.
playground
EDIT:
To preserve reviews also
Add a unwind stage
db.product.aggregate([
{
$lookup: {
from: "review",
localField: "_id",
foreignField: "productId",
as: "review",
},
},
{
"$unwind": "$review"
},
{
$lookup: {
from: "users",
localField: "review.userId",
foreignField: "_id",
as: "review.userInfo",
},
},
])
Update
To keep the docs where there is no match, preserve null arrays at unwind stage as below
{
$unwind: {
path: "$review",
"preserveNullAndEmptyArrays": true
}
}

MongoDB Query Nested Schema

I have the next schemas:
Product Schema:
_id: String
name: String
description: String
company: Company
Company Schema:
_id: String
name: string
I'm trying to find all the products that match my query string with the name or company name.
I already tried:
const query: any[] = [
{
"company.name": {
$regex: name,
$options: "i",
},
},
{
"name": {
$regex: name,
$options: "i",
},
}
];
return Product.find()
.or(query)
.populate("company")
Data - Products:
{ _id: "6067b7d7b5913759d9bb8b39", "name": "Test 1", "description": "Desc", "company": "6067b809c78a4a39ebeae4d4" }
Data - Companies:
{ _id: "6067b809c78a4a39ebeae4d4", "name": "Facebook" },
{ _id: "59862209c78a4a39ebeae4d4", "name": "Apple" },
This query will filter only products, will not work in your case as you want to filter by company.name also
Product.find()
.or(query)
.populate("company")
Demo - https://mongoplayground.net/p/VqREj1vvkss
Use aggregation query to achieve this
db.companies.aggregate([
{ $lookup: { "from": "products", "localField": "_id", "foreignField": "company", "as": "products"} },
{ $unwind: "$products" },
{ $match: {
$or: [
{ "name": { $regex: "Test 1",$options: "i", }, },
{ "products.name": { $regex: "Test 1",$options: "i" } }
]}
}
])
https://mongoosejs.com/docs/api/aggregate.html#aggregate_Aggregate

Getting error, 'The field name '$acknowledged' cannot be an operator name' with aggregation query

I'm trying to fetch all latest messages between User A and any other user.
I keep running into the error ,
The field name '$acknowledged' cannot be an operator name
Not sure what I'm doing wrong here. Mongo playground.
The expected output should be the latest message exchanged between user with id 5a934e000102030405000001, and any other user.
[
{
"from": ObjectId("5a934e000102030405000002"),
"to": ObjectId("5a934e000102030405000001"),
"acknowledged": true,
date: "2020-04-17T18:26:34.353+00:00"
},
{
"from": ObjectId("5a934e000102030405000001"),
"to": ObjectId("5a934e000102030405000003"),
"acknowledged": false,
date: "2020-04-17T18:26:31.353+00:00"
},
{
"from": ObjectId("5a934e000102030405000004"),
"to": ObjectId("5a934e000102030405000001"),
"acknowledged": false,
date: "2020-04-17T18:26:29.353+00:00"
},
]
You had a typo here:
$acknowledged: { acknowledged: {
$first: "$acknowledged", --> $first: "$acknowledged"
}
},
and
then: "$responseTo", --> then: "$to",
db.Message.aggregate([
{
$match: {
$or: [
{
from: {
$in: [
ObjectId("5a934e000102030405000001")
]
}
},
{
to: {
$in: [
ObjectId("5a934e000102030405000001")
]
}
}
]
}
},
{
$sort: {
date: -1
}
},
{
$group: {
_id: {
userConcerned: {
$cond: [
{
$in: [
"$to",
[
ObjectId("5a934e000102030405000001")
]
]
},
"$to",
"$from"
]
},
interlocutor: {
$cond: [
{
$in: [
"$to",
[
ObjectId("5a934e000102030405000001")
]
]
},
"$from",
"$to"
]
}
},
id: {
$first: "$_id"
},
from: {
$first: "$from"
},
acknowledged: {
$first: "$acknowledged"
},
to: {
$first: "$to"
},
date: {
$first: "$date"
}
}
},
{
$lookup: {
from: "User",
localField: "to",
foreignField: "_id",
as: "to"
}
},
{
$unwind: "$to"
},
{
$lookup: {
from: "User",
localField: "from",
foreignField: "_id",
as: "from"
}
},
{
$unwind: "$from"
},
{
$project: {
_id: 0,
date: 1,
acknowledged: 1,
from: "$from._id",
to: "$to._id"
}
}
])
MongoPlayground

Categories