Querying a referenced document in MongoDB using Mongoose - mongodb

This is a document in the collection BlogPosts:
{
_id: ObjectId("..."),
post_title: "Hello World!",
post_body: "",
comments: [
{ user_id: ObjectId("123"), body: "nice post!" },
{ user_id: ObjectId("456"), body: "awesome!" },
]
}
I would like to display comments with the user's first name, which is found in the referenced document in the Users collection:
{
_id: ObjectId("123"),
first_name: "Marion",
last_name: "Smith",
email_address: "marion#example.com",
password: "..."
}
Is there a way to retrieve the BlogPosts document while including first_name from this referenced data?
For example, I'm looking for an output like this (each comment has a first name):
{
_id: ObjectId("..."),
post_title: "Hello World!",
post_body: "",
comments: [
{ user_id: ObjectId("..."), first_name: "Marion", body: "nice post!" },
{ user_id: ObjectId("..."), first_name: "Margaret", body: "awesome!" },
]
}
I'm using Mongoose.

You can use below aggregation
db.collection.aggregate([
{ "$unwind": "$comments" },
{ "$lookup": {
"from": "users",
"let": { "userId": "$comments.user_id" },
"pipeline": [{ "$match": { "$expr": { "$eq": ["$$userId", "$_id"] } } }],
"as": "user"
}},
{ "$addFields": {
"comments.first_name": { "$arrayElemAt": ["$user.first_name", 0] }
}},
{ "$group": {
"_id": "$_id",
"comments": { "$push": "$comments" },
"post_title": { "$first": "$post_title" },
"post_body": { "$first": "$post_body" }
}}
])

I've since found a more straightforward approach, using just Populate.
BlogPosts
.findOne({_id: req.params.id})
.populate('comments.user_id', ['first_name', 'last_name'])
.then(post => console.log(post))
In the schema for BlogPosts, a ref should be defined for the comments.user_id field:
const User = require('./User.model.js');
const blogPostSchema = new Schema({
post_title: { type: String },
post_body: { type: String },
comments: [{
user_id: {
type: Schema.ObjectId,
ref: 'User' <-------- here
}
}]
});
const BlogPost = mongoose.model('BlogPost', blogPostSchema);
module.exports = BlogPost;

Related

MongoDB lookup - using $lookup

so i have document for users with this structure in JSON format:
[
{
"_id": {
"$oid": "6369aeb83ce0f8168520f42f"
},
"fullname": "Jokona",
"password": "$2b$10$MUAe7XIc/xtJTGVh/y1DeuShCARbwxCSejUbHaqIPZfjekNrn0.Yy",
"NIK": "MT220047",
"status": "active",
"department": "Logistic",
"position": "Management Trainee",
"Group_Shift": "Non Shift",
"role": "admin",
"createdAt": 1667870392,
"updatedAt": 1668564835,
"__v": 0
},
{
"_id": {
"$oid": "6369b17b11e02557349d8de5"
},
"fullname": "Warana",
"password": "$2b$10$0xaqz5V8bar/osWmsCiofet5bY10.ORn8Vme3QC7Dh0HwLHwYOm3a",
"NIK": "17000691",
"status": "active",
"department": "Production",
"position": "Foreman",
"Group_Shift": "R1",
"role": "user",
"__v": 0,
"createdAt": 1667871099,
"updatedAt": 1668496775
},
]
it try to lookitup using mongodb $lookup to get the fullname by joining using the NIK as the foreignnkey,here is what i have try:
const dataAnaylitics = await Answer.aggregate([
// $match stage
{
$group: {
_id: {
username: "$username",
title: "$title",
date: "$date",
},
count: {
$sum: 1,
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$lookup: {
from: "users",
localField: "username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { fullname: 0 } }],
},
},
{
$group: {
_id: {
username: "$_id.username",
title: "$_id.title",
},
dates: {
$push: {
k: "$_id.date",
v: "$count",
},
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$project: {
_id: 0,
username: "$_id.username",
title: "$_id.title",
position: 1,
department: 1,
dates: 1,
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{
$arrayToObject: "$dates",
},
],
},
},
},
{
$unset: "dates",
},
]);
but the result doesnt returning the fullname field, is there is something wrong with my code? i seek for documentation and already follow the step
In your group stage, since you are grouping based on username, the resulting document will have _id.username as the field. Use this field as localField in your lookup.
{
$lookup: {
from: "users",
localField: "_id.username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { fullname: 0 } }],
}
i have fix it, hope it will helps other..
const dataAnaylitics = await Answer.aggregate([
// $match stage
{
$group: {
_id: {
username: "$username",
title: "$title",
date: "$date",
},
count: {
$sum: 1,
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$lookup: {
from: "users",
localField: "_id.username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { _id: 0, fullname: 1 } }],
},
},
{
$group: {
_id: {
username: "$_id.username",
title: "$_id.title",
},
dates: {
$push: {
k: "$_id.date",
v: "$count",
},
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
fullname: {
$first: { $arrayElemAt: ["$fullname.fullname", 0] },
},
},
},
{
$project: {
_id: 0,
username: "$_id.username",
title: "$_id.title",
position: 1,
department: 1,
dates: 1,
fullname: 1,
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{
$arrayToObject: "$dates",
},
],
},
},
},
{
$unset: "dates",
},
]);

Unable to populate and structure document with aggregate

So I have this schema which have foreign keys to other collections in the database. The document has around 60k posts and each post can have multiple categories and there are around 200 categories. So I'm trying to fetch and structure data based on the category's foreign key and populate the category details and count.
Here's how the main schema and category schema looks like:
const postSchema = new mongoose.Schema( {
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'PostDetails'
},
categories: [ {
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
subCategories: [ {
subCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Subcategory'
}
} ]
} ]
} );
const categorySchema = new mongoose.Schema( {
category: {
type: String,
},
categorySlug: {
type: String,
}
} );
I was successful in making the count but the returned data is not what I expected. The returned data shows the id of the categories and the count but no name and slug. Here's how it looks like:
[
{
"_id": [
"617acfd232c766589c23a90c"
],
"count": 876,
"category": []
}
]
I got the above output with the following query:
const aggregateStages = [
{
$group: {
_id: '$categories.category',
count: { $sum: 1 }
}
},
{
$lookup: {
from: "Category",
localField: "categories.category",
foreignField: "_id",
as: "category"
}
}
];
const categories = await Post.aggregate( aggregateStages ).exec();
I'm hoping to get the data as follows:
[
{
"_id": "617acfd232c766589c23a90c",
"count": 876,
"category": 'SomeCategory',
"categorySlug": 'some-category'
}
]
Where am I going wrong and how can I fix it?
SAMPLE DATA FROM DB AS REQUESTED BY MATT OESTREICH
POST DATA
{
"_id": "617adad39054bae2c983c34f",
"post": "617ad1c80597c78ed4cc151e",
"author": "617acc689b309fdbbbdfdfe0",
"categories": [{
"category": "617acfd232c766589c23a8d1",
"subCategories":[]
}]
}
CATEGORY DATA
{
"_id": "617acfd232c766589c23a8d1",
"category": "Lorem Ipsum",
"categorySlug": "lorem-ipsum"
}
Ok so, it looks like you can resolve this by using the $size operator. The $size operator will give you the length (or count) of elements in an array.
Live demo here
Database
db={
"post": [
{
"_id": ObjectId("617adad39054bae2c983c34f"),
"post": ObjectId("617ad1c80597c78ed4cc151e"),
"author": ObjectId("617acc689b309fdbbbdfdfe0"),
"categories": [
{
"category": ObjectId("617acfd232c766589c23a8d1"),
"subCategories": []
}
]
}
],
"categories": [
{
"_id": ObjectId("617acfd232c766589c23a8d1"),
"category": "Lorem Ipsum",
"categorySlug": "lorem-ipsum"
}
]
}
Query
db.post.aggregate([
{
"$lookup": {
"from": "categories",
"localField": "categories.category",
"foreignField": "_id",
"as": "found_categories"
}
},
{
"$project": {
_id: "$_id",
count: {
"$size": "$found_categories"
},
"category": {
"$first": "$found_categories.category"
},
"categorySlug": {
"$first": "$found_categories.categorySlug"
}
}
}
])
Result
[
{
"_id": ObjectId("617adad39054bae2c983c34f"),
"category": "Lorem Ipsum",
"categorySlug": "lorem-ipsum",
"count": 1
}
]
Although, I do not believe this will give you what you are looking for if more than one category is found. Please let me know if it doesn't work and I will try to help fix it.

Join document inside nested array MongoDB

My first collection, called Scenes looks like this:
{
sceneId: "id1",
contentId: "contentId1",
comments: [{
comment: "abc",
userId: "test1"
},
{
comment: "def",
userId: "test2"
}]
},
{
sceneId: "id2",
contentId: "contentId2",
comments: [{
comment: "abc",
userId: "test1"
},
{
comment: "def",
userId: "test2"
}]
}
Any my second collection, called Userdatas, looks like this:
{
userId: "test1",
profilPicture: "def.jpg"
},
{
userId: "test2",
profilPicture: "abc.jpg"
}
And I want to join them in a way, that I get the following:
{
sceneId: "id1",
contentId: "contentId1",
comments: [{
comment: "abc",
userId: "test1",
profilPicture: "def.jpg"
},
{
comment: "def",
userId: "test2",
profilPicture: "abc.jpg"
}]
},
{
sceneId: "id2",
contentId: "contentId2",
comments: [{
comment: "abc",
userId: "test1",
profilPicture: "def.jpg"
},
{
comment: "def",
userId: "test2",
profilPicture: "abc.jpg"
}]
}
and I have no idea how to do this, all my previous attempts failed. Help is appreciated! The key problem is, that the $group operator will not show the contentId, when grouping via the sceneId
You can use this aggregation query:
First $unwind comments array to destructure and join by comments.userId.
Then $lookup that is like a JOIN, to merge userIds between collections generating a object called data.
Destructure $data to get values.
Add profilPicture to the object comments using $set.
$group by sceneId adding all comments into an array.
And use $projection to show only fields you want.
db.Scenes.aggregate([
{
"$unwind": "$comments"
},
{
"$lookup": {
"from": "Userdatas",
"localField": "comments.userId",
"foreignField": "userId",
"as": "data"
}
},
{
"$unwind": "$data"
},
{
"$set": {
"comments.profilPicture": "$data.profilPicture"
}
},
{
"$group": {
"_id": "$sceneId",
"comments": {
"$push": "$comments"
}
}
},
{
"$project": {
"_id": 0,
"sceneId": "$_id",
"comments": 1
}
}
])
Example here

Mongoose, aggregate query with $lookup return null?

varyantalt Model:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const varyantaltSchema = new Schema({
_id: {
type: Schema.ObjectId
},
varyantid: {
type: String
},
altvaryantname: {
type: String
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model("varyantalt", varyantaltSchema, "varyantalt");
varyant model:
const varyantSchema = new Schema({
_id: {
type: Schema.ObjectId
},
stokid: {
type: Schema.ObjectId
},
varyantname: {
type: String
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model("varyant", varyantSchema, "varyant");
varyant collection:
{
"_id": ObjectId("5e32286c34fb7322bdd566ed"),
"stokid": ObjectId("5e28b4a2a1d9692b29a65b24"),
"varyantname": "RENK"
}
altvaryant collection:
{
"_id": ObjectId("5e3228df34fb7322bdd566f5"),
"varyantid": "5e32286c34fb7322bdd566ed",
"altvaryantname": "KIRMIZI",
"vars": [
{
"_id": ObjectId("5e35b1e410fce83f3370cd0a"),
"images": {
"imageurl": "http://",
"_id": ObjectId("5e35b1e410fce83f3370cd0b"),
"filename": "5320_7d93",
"path": "https://res",
"publicid": "panel"
}
}
]
},
{
"_id": ObjectId("5e3359e6fa4c5e4bd9112fb6"),
"varyantid": "5e32286c34fb7322bdd566ed",
"altvaryantname": "SARI",
"vars": [
{
"_id": ObjectId("5e35b1f610fce83f3370cd0d"),
"images": {
"imageurl": "http://",
"_id": ObjectId("5e35b1f610fce83f3370cd0e"),
"filename": "veli-fidan-1LT-2-450x450",
"path": "https://",
"publicid": "panel"
}
}
]
},
{
"_id": ObjectId("5e335b64fa4c5e4bd9112fc9"),
"varyantid": "5e32286c34fb7322bdd566ed",
"altvaryantname": "YEŞİL",
"vars": [
{
"_id": ObjectId("5e35b20010fce83f3370cd10"),
"images": {
"imageurl": "http://",
"_id": ObjectId("5e35b20010fce83f3370cd11"),
"filename": "maxresdefault-29-450x450",
"path": "https://",
"publicid": ""
}
}
]
}
Query:
varyant
.aggregate([
{
$match: {
stokid: mongoose.Types.ObjectId("5e28b4a2a1d9692b29a65b24")
}
},
{
$lookup: {
from: "varyantalt",
localField: "_id",
foreignField: "varyantid",
as: "vars"
}
},
{ $unwind: "$vars" }
])
.exec((err, locations) => {
if (err) throw err;
console.log("res::", locations);
});
I am trying to get results with $aggregate and $lookup query with mongoose
Why is my query returning empty? I couldn't find the problem.
Under $lookup operator from: 'varyantalt' I do not understand exactly whether it takes a blank value. Thanks in advance for your help.
In your varyantaltSchema schema, varyantid is defined as String, but it must be Schema.ObjectId.
They must be same because $lookup performs an equality match on the foreignField to the localField from the input documents
If you save your varyandid as Object id aggregate will work.

How to do lookup on an aggregated collection in mongodb that is being grouped?

For some reason, I can't retrieve the author name from another collection on my aggregate query.
db.getCollection('books').aggregate([
{
$match: {
authorId: { $nin: [ObjectId('5b9a008575c50f1e6b02b27b'), ObjectId('5ba0fb3275c50f1e6b02b2f5'), ObjectId('5bc058b6ae9a2a4d6df330b1')]},
isBorrowed: { $in: [null, false] },
status: 'ACTIVE',
},
},
{
$lookup: {
from: "authors",
localField: "authorId", // key of author id in "books" collection
foreignField: "_id", // key of author id in "authors" collection
as: "bookAuthor",
}
},
{
$group: {
_id: {
author: '$authorId',
},
totalSalePrice: {
$sum: '$sale.amount',
},
},
},
{
$project: {
author: '$_id.author',
totalSalePrice: '$totalSalePrice',
authorName: '$bookAuthor.name', // I can't make this appear
_id: 0,
},
},
{ $sort: { totalSalePrice: -1 } },
])
Any advice on where I had it wrong? Thanks for the help.
Two things that are missing here: you need $unwind to convert bookAuthor from an array into single object and then you need to add that object to your $group stage (so that it will be available in next stages), try:
db.getCollection('books').aggregate([
{
$match: {
authorId: { $nin: [ObjectId('5b9a008575c50f1e6b02b27b'), ObjectId('5ba0fb3275c50f1e6b02b2f5'), ObjectId('5bc058b6ae9a2a4d6df330b1')]},
isBorrowed: { $in: [null, false] },
status: 'ACTIVE',
},
},
{
$lookup: {
from: "authors",
localField: "authorId",
foreignField: "_id",
as: "bookAuthor", // this will be an array
}
},
{
$unwind: "$bookAuthor"
},
{
$group: {
_id: {
author: '$authorId',
},
bookAuthor: { $first: "$bookAuthor" },
totalSalePrice: {
$sum: '$sale.amount',
},
},
},
{
$project: {
author: '$_id.author',
totalSalePrice: '$totalSalePrice',
authorName: '$bookAuthor.name',
_id: 0,
},
},
{ $sort: { totalSalePrice: -1 } },
])
Actually you have lost the bookAuthor field in the $group stage. You have to use $first accumulator to get it in the next $project stage.
{ "$group": {
"_id": { "author": "$authorId" },
"totalSalePrice": { "$sum": "$sale.amount" },
"authorName": { "$first": "$bookAuthor" }
}},
{ "$project": {
"author": "$_id.author",
"totalSalePrice": "$totalSalePrice",
"authorName": { "$arrayElemAt": ["$bookAuthor.name", 0] }
"_id": 0,
}}