Can't $unwind documents after $group - database

I'm learning Mongo and I'm not understanding something about the aggregate function. I have a User that can make many different kinds of posts, I match the user by blogName and then $lookup all posts that have the matching user ID.
After that I'm stumped. If I unwind and don't group then I get the mess of joined documents, but all I want are the posts.
If I group after $unwind then the documents are turned back into objects.
This code:
return User
.aggregate([
{
$match: {
blogName: blogName
}
},
{
$lookup: {
from: 'posts',
localField: '_id',
foreignField: 'user',
as: 'posts'
}
},
{
$unwind: "$posts"
},
{
$group: {
_id: "$_id",
'posts': { $push: { fields: '$posts' } }
}
},
Gives me this:
[
{
_id: 6065e579bf709d81274cc51e,
posts: [ [Object], [Object], [Object], [Object] ]
}
]
Adding a second $unwind after $group just gives me this:
[
{ _id: 6065e579bf709d81274cc51e, posts: { fields: [Object] } },
{ _id: 6065e579bf709d81274cc51e, posts: { fields: [Object] } },
{ _id: 6065e579bf709d81274cc51e, posts: { fields: [Object] } },
{ _id: 6065e579bf709d81274cc51e, posts: { fields: [Object] } }
]
Which is further from what I want.
I just want the posts. If I can $unwind the posts in this array then I can handle everything from there. What am I not understanding about this?
Update 1:
If I just use $project like so:
return User
.aggregate([
{
$match: {
blogName: blogName
}
},
{
$lookup: {
from: 'posts',
localField: '_id',
foreignField: 'user',
as: 'posts'
}
},
{
$unwind: "$posts"
},
{
$project: {
"_id": 0,
"posts": "$posts"
}
},
I can get this array:
[
{
posts: {
_id: 60664856447970128fee597b,
descriptionImages: [],
tags: [],
likes: [],
kind: 'TextPost',
createdAt: 2021-04-01T22:25:26.531Z,
updatedAt: 2021-04-01T22:25:26.531Z,
title: '',
body: '',
user: 6065e579bf709d81274cc51e,
__v: 0
}
},
{
posts: {
_id: 60664925d2548912dc960e05,
mainImages: [],
descriptionImages: [],
tags: [],
likes: [],
kind: 'PhotoPost',
createdAt: 2021-04-01T22:28:53.179Z,
updatedAt: 2021-04-01T22:28:53.179Z,
user: 6065e579bf709d81274cc51e,
description: '',
__v: 0
}
},
{
posts: {
_id: 6066495f347bb812fd6dc703,
mainImages: [],
descriptionImages: [],
tags: [],
likes: [],
kind: 'PhotoPost',
createdAt: 2021-04-01T22:29:51.815Z,
updatedAt: 2021-04-01T22:29:51.815Z,
user: 6065e579bf709d81274cc51e,
description: '',
__v: 0
}
},
{
posts: {
_id: 60664961347bb812fd6dc704,
descriptionImages: [],
tags: [],
likes: [],
kind: 'TextPost',
createdAt: 2021-04-01T22:29:53.385Z,
updatedAt: 2021-04-01T22:29:53.385Z,
title: '',
body: '',
user: 6065e579bf709d81274cc51e,
__v: 0
}
}
]
But now I'm stumped as to how to get rid of the extra level of nesting. I just need each post object without the additional nesting under posts.

Demo - https://mongoplayground.net/p/bCwBJipyYYZ
db.collection.aggregate([
{ $match: { blogName: "abc" } },
{ $unwind: "$posts" },
{ $replaceRoot: { "newRoot": "$posts" } }
])
Use $replaceRoot
Replaces the input document with the specified document. The operation replaces all existing fields in the input document, including the _id field. You can promote an existing embedded document to the top level, or create a new document for promotion (see example).
User.aggregate([
{ $match: { blogName: blogName } },
{ $lookup: { from: 'posts', localField: '_id', foreignField: 'user', as: 'posts' } },
{ $unwind: "$posts" },
{ $replaceRoot: { "newRoot": "$posts" } }
]

Related

How do I update individual fields in nested objects that are in arrays in MongoDB using Mongoose?

This is my first post so please bear with me. I am building a LinkedIn clone and I am trying to keep track of the work experience, projects and courses of the users, and those will be kept in an array of objects inside of the User schema. Now let's say a user will try to add or update one of the elements in one of those arrays, I have the user ID and I am passing it to findOneAndUpdate() as the filter.
Here is my User schema:
const userSchema = new mongoose.Schema({
user_id: {
type: String,
required: [true, 'User ID required.'],
unique: true,
immutable: true,
},
name: {
type: String,
required: [true, 'Name required.'],
},
email: {
type: String,
required: [true, 'Email required.'],
unique: true,
lowercase: true,
immutable: true,
},
title: {
type: String,
},
location: {
type: String,
},
phone_number: {
type: String,
},
contact_email: {
type: String,
},
photo: {
type: String,
},
website: {
type: String,
},
backdrop: {
type: String,
},
summary: {
type: String,
},
work: {
type: String,
},
connections: {
type: Number,
},
projects: [
{
title: {
type: String,
},
description: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
technologies: {
type: String,
},
picture: {
type: String,
},
},
],
skills: [{
skill: {
name: {
type: String,
},
level: {
type: String,
},
},
}],
experience: [
{
company: {
type: String,
},
logo: {
type: String,
},
title: {
type: String,
},
location: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
description: {
type: String,
},
},
],
education: [
{
school: {
type: String,
},
logo: {
type: String,
},
degree: {
type: String,
},
location: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
description: {
type: String,
},
},
],
languages: [
{
name: {
type: String,
},
level: {
type: String,
},
},
],
awards: [
{
title: {
type: String,
},
date: {
type: Date,
},
awarder: {
type: String,
},
summary: {
type: String,
},
},
],
courses: [
{
title: {
type: String,
},
number: {
type: String,
},
school: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
description: {
type: String,
},
},
],
});
And in my UserController.ts file, I tried using this:
const updateUser = async (req: Request, res: Response) => {
try {
const filter = { user_id: req.body.user_id };
const update = req.body;
const updatedUser = await User.findOneAndUpdate(filter, update, {
new: true,
upsert: true,
});
res.status(201).json({
status: 'success',
data: {
user: updatedUser,
},
});
} catch (err) {
res.status(400).json({
status: `ERROR: ${err}`,
message: 'error updating user',
});
}
};
And in my request using the format of the schema but that didn't work out as expected. I know mongoose will automatically give it an _id field to each of the individual objects in the array, but again, I have had no luck updating them. I tried sending a PATCH request with this as the body to add a skill like so:
{
"user_id": "xxxxxxxxxxxxxxxx",
"title": "Mr.",
"skills" : {
"name": "Flute",
"level": "Novice"
}
}
And this was the response I got. It created a skill but didnt add the data in the skill object:
{
"status": "success",
"data": {
"user": {
"_id": "63d3715f2ef9698667230a53",
"user_id": "xxxxxxxxxxxxxxxx",
"name": "Jonathan Abitbol",
"email": "yoniabitbol1#gmail.com",
"projects": [],
"skills": [
{
"_id": "63d4068d2df30c9e943e4608"
}
],
"experience": [],
"education": [],
"languages": [],
"awards": [],
"courses": [],
"__v": 0,
"title": "Mr."
}
}
}
Any help on how to add/edit the nested objects would be appreciated.

Error in array on aggregation query with lookup & nest array of objects

I have a problem with a query that I can't solve 100%
The fact is that when a user does not have any comment within the post. As inside the comments there is a "createdBy" and I need to make a lookup of that user inside the array. If there are no comments, it returns an array with an empty object, but it must return an empty array, not with empty objects.
Who can help me? Thank you very much in advance!
HERE MY USER collection (data)
[
{
_id: ObjectId("619d0f5df3f74665aff1a551"),
name: "Test Name",
surname: "Test Surname2",
createdAt: ISODate("2021-11-11T17:21:58.624+01:00"),
updatedAt: ISODate("2021-11-25T10:35:25.842+01:00"),
posts: [
{
_id: ObjectId("619d0f5df3f74575aff1a551"),
updatedAt: ISODate("2021-11-23T16:57:17.816+01:00"),
createdAt: ISODate("2021-11-23T16:57:17.816+01:00"),
content: "Test content....",
comments: [
{
createdBy: ObjectId("618d4326f1668007b3b98404"),
comment: "test comment...",
_id: ObjectId("619dfaaaa88266dc91b9489c"),
},
{
createdBy: ObjectId("618d4326f1668007b3b98404"),
comment: "test comment...",
_id: ObjectId("619dfc60a88266dc91b95741"),
},
],
date: ISODate("2021-11-23T16:57:17.820+01:00"),
},
{
_id: ObjectId("619d0f5df3f74575aff1a551"),
updatedAt: ISODate("2021-11-23T16:57:17.816+01:00"),
createdAt: ISODate("2021-11-23T16:57:17.816+01:00"),
content: "Test content....",
comments: [],
date: ISODate("2021-11-23T16:57:17.820+01:00"),
},
],
},
{
_id: ObjectId("619d0f5df3f74665aff1a551"),
name: "Test Name",
surname: "test surname",
createdAt: ISODate("2021-11-11T17:21:58.624+01:00"),
updatedAt: ISODate("2021-11-25T10:35:25.842+01:00"),
posts: [
{
_id: ObjectId("619d0f5df3f74575aff1a551"),
updatedAt: ISODate("2021-11-23T16:57:17.816+01:00"),
createdAt: ISODate("2021-11-23T16:57:17.816+01:00"),
content: "Test content....",
comments: [
{
createdBy: ObjectId("618d4326f1668007b3b98404"),
comment: "test comment...",
_id: ObjectId("619dfaaaa88266dc91b9489c"),
},
{
createdBy: ObjectId("618d4326f1668007b3b98404"),
comment: "test comment...",
_id: ObjectId("619dfe7ba88266dc91b961b6"),
},
],
date: ISODate("2021-11-23T16:57:17.820+01:00"),
},
{
_id: ObjectId("619d0f5df3f74575aff1a551"),
updatedAt: ISODate("2021-11-23T16:57:17.816+01:00"),
createdAt: ISODate("2021-11-23T16:57:17.816+01:00"),
content: "Test content....",
comments: [
{
createdBy: ObjectId("618d4326f1668007b3b98404"),
comment: "test comment...",
_id: ObjectId("619dfaaaa88266dc91b9489c"),
},
{
createdBy: ObjectId("618d4326f1668007b3b98404"),
comment: "test comment...",
_id: ObjectId("619dfc60a88266dc91b95741"),
},
],
date: ISODate("2021-11-23T16:57:17.820+01:00"),
},
],
},
];
HERE MY AGGREGATE QUERY
db.users.aggregate([
{ $unwind: { path: '$posts', preserveNullAndEmptyArrays: true } },
{ $unwind: { path: '$posts.comments', preserveNullAndEmptyArrays: true } },
{
$lookup: {
from: 'users',
localField: 'posts.comments.createdBy',
foreignField: '_id',
as: 'posts.comments.createdBy'
}
},
{ $unwind: { path: '$posts.comments.createdBy', preserveNullAndEmptyArrays: true } },
{
$group: {
_id: { _id: '$_id', post_id: '$posts._id' },
name: { $first: '$name' },
posts: { $push: '$posts' },
comments: { $push: '$posts.comments' },
}
},
{
$group: {
_id: '$_id._id',
name: { $first: '$name' },
posts: {
$push: {
_id: '$_id.post_id',
date: { $first: '$posts.date' },
content: { $first: '$posts.content' },
comments: '$comments'
}
}
}
},
])
Here an image with the fail array:
you have to remove preserveNullAndEmptyArrays field from unwind to don't have empity objects

Normalize data redux and using

I have the following data coming from an API:
const feed = {
id: "feedId",
content: "...",
comments: [
{
id: "commentId",
content: "...",
// reply comments
comments: [{
id: "commentId",
content: "..."
}]
}
],
}
I want to normalize the data for use in Redux as presented here. But my data has reply comments nested. I dont know where to place comment reply. Should I use normalize data and normalizr lib ?. Thank a lot If you answer and explain for me. Sorry about my bad english, but I really want know.
// where place comments reply ???
{
feeds: {
byId: {
"feed1": {
id: "feed1",
content: "...",
comments: ["comment1", "comment2"],
},
"feed2": {
id: "feed2",
content: "...",
comments: ["comment3", "comment4"],
},
},
allIds: ["feed1", "feed2"],
},
comments: {
byId: {
"comment1": {
id: "comment1",
content: "...",
},
"comment2": {
id: "comment2",
content: "...",
},
"comment3": {
id: "comment3",
content: "...",
},
"comment4": {
id: "comment4",
content: "...",
},
},
allIds: ["comment1", "comment2", "comment3", "comment4"],
},
};
This is my current init feedReducer for handle feed and comments in reducer.
const initialState = {
feed_id_1: {
id: "feed_id_1",
content: "...",
comments: [
{
id: "comment_id_1",
content: "...",
comments: [
{ id: "comment_reply_id_1", content: "..." },
{ id: "comment_reply_id_2", content: "..." },
],
},
{},
],
},
// ...
};

How to group by in mongodb and keep all fields?

I have 2 collections: products and categories:
products:
{
_id:1
name:"product1"
category: 1
price:2399
},
{
_id:2
name:"product2"
category: 2
price:566
}
categories:
{
_id:1
name:"category1"
},
{
_id:2
name:"category2"
}
I've tried to use $group and $push but with no luck,
How can i group the products by their categories and achieve this query result:
categories: [
{
_id:1
name:"category1"
products: [{
_id:1
name:"product1"
category: 1
price:2399
}]
},
{
_id:2
name:"category2"
products: [{
_id:1
name:"product2"
category: 2
price:566
}]
}]

Object is undefined while have data

I have an array contains object look like below
[
{
_id: "12",
data: { _id: "123", isDelete: false, name: "afd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
},
{
_id: "12",
data: { _id: "123", isDelete: false, name: "ffd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
},
]
This is when I filter in it
brands.filter(brand => {
console.log(brand);// like above data
console.log(brand.data);// show brand.data
console.log(brand.data._id);//error brand.data is not defined
})
I already try
brands.filter(brand => {
let a = brand.data;
console.log(a._id);// error a is null
})
I need to get brand.data.name like when I loop it need to print afd and ffd.
As per your question if all you want to do is to log the value of brand.data.name then using filter doesn't make any sense.
You can just use forEach for that.
const brands = [
{
_id: "12",
data: { _id: "123", isDelete: false, name: "afd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
},
{
_id: "12",
data: { _id: "123", isDelete: false, name: "ffd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
}
];
brands.forEach(brand => { console.log(brand.data.name) });
If you want to store the names in an array instead of just logging them then you can use map for that.
const brands = [
{
_id: "12",
data: { _id: "123", isDelete: false, name: "afd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
},
{
_id: "12",
data: { _id: "123", isDelete: false, name: "ffd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
}
];
const brandNames = brands.map(brand => brand.data.name);
console.log(brandNames);
Javascript filter works on array. So considering you want to filter your nested object(based on id) which is data, your code should be written like below-
const brands = [
{
_id: "12",
data: { _id: "1", isDelete: false, name: "afd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
},
{
_id: "13",
data: { _id: "2", isDelete: false, name: "ffd" },
createdAt: "2020-04-11T08:38:15.966Z",
shop_id: "sfd",
updatedAt: "2020-04-27T02:07:12.271Z"
},
]
const updatedBrands = brands.map(brand => {(
...brand, brand.data.filter(key => key._id === '1')
}))
You have to first use javascript map method to iterate over the array and then you can filter your data object by id. Hope this helps.

Resources