How to group by in mongodb and keep all fields? - database

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

Related

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

Can't $unwind documents after $group

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

How to get transform from object to specific key value pair using lodash?

Products: [
{_id: 'xx1', name: 'p1', sku: 's1'},
{_id: 'xx2', name: 'p2', sku: 's2'},
{_id: 'xx3', name: 'p3', sku: 's3'}
]
I want to replace word '_id' with 'product', and to map to below result:
productArray = [ {product: 'xx1'}, {product: 'xx2'}, {product: 'xx3'} ];
I tried lodash code something like below but it just doesn't seem to work correctly:
let productArray = _.map(Products, '_id');
Can anyone help me on this? Thank you
Why would you even need to use lodash? Map will easily do the job.
const products = [{
_id: 'xx1',
name: 'p1',
sku: 's1'
},
{
_id: 'xx2',
name: 'p2',
sku: 's2'
},
{
_id: 'xx3',
name: 'p3',
sku: 's3'
}
];
const outProducts = products.map(x => {
return {
product: x._id
}
});
console.log(outProducts);

In a one to many relationship, is there a way to filter the parent objects via an attribute of the child?

I have a rails backend with the following relationships: a USER has many MOVES. a Move has many boxes. A Box has many items.
I have page that lists all of the boxes inside of a specific move and this page ALSO lists all of the items for that specific move. I have a search bar on this page that enables you to search for specific items. I am able to filter my items display, however, i cannot figure out how to filter my boxes BY the searching for the name of the items WITHIN them.
I have tried iterating over the array of Box objects, and then iterating over the key within each box that points to its array of items. I am able to get the filtered ITEMS, but I dont know how to translate that back to reflect the BOXES with those items.
For instance, in the console I tried:
var filteredBoxes = boxes.map((box) => {
return box.items.filter((i) => {
return i.name.includes(this.state.searchTerm)
})
})
But it keeps returning items, not the boxes im trying to filter.
This is how the JSON looks when I fetch my boxes. I used a serializer to list the items as well:
{
id: 1,
name: "Bedding",
category: "Bedroom",
move_id: 1,
move: {
id: 1,
name: "Leaving for College",
date: "2019-08-12",
user_id: 1
},
items: [
{
id: 1,
name: "Comforter",
image: "https://www.shopmarriott.com/images/products/v2/lrg/Marriott-down-duvet-comforter-MAR-112_1_lrg.jpg",
box_id: 1
},
{
id: 2,
name: "Throw Pillows",
image: "https://media.kohlsimg.com/is/image/kohls/3427815?wid=500&hei=500&op_sharpen=1",
box_id: 1
}
]
},
{
id: 2,
name: "Random Blankets",
category: "Den",
move_id: 1,
move: {
id: 1,
name: "Leaving for College",
date: "2019-08-12",
user_id: 1
},
items: [
{
id: 3,
name: "Pillows",
image: "https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id: 2
},
{
id: 4,
name: "Stuffed Animals",
image: "https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id: 2
}
]
},
{
id: 3,
name: "Cleaning Supplies",
category: "Kitchen",
move_id: 1,
move: {
id: 1,
name: "Leaving for College",
date: "2019-08-12",
user_id: 1
},
items: [
{
id: 5,
name: "Pillows",
image: "https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id: 3
},
{
id: 6,
name: "Stuffed Animals",
image: "https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id: 3
}
]
}
you just have to iterate boxes, and so filter items. Based on these filtered items you may choose to return or not a box to the list.
const data = [{
id:1,
name:"Bedding",
category:"Bedroom",
move_id:1,
move:{
id:1,
name:"Leaving for College",
date:"2019-08-12",
user_id:1
},
items:[
{
id:1,
name:"Comforter",
image:"https://www.shopmarriott.com/images/products/v2/lrg/Marriott-down-duvet-comforter-MAR-112_1_lrg.jpg",
box_id:1
},
{
id:2,
name:"Throw Pillows",
image:"https://media.kohlsimg.com/is/image/kohls/3427815?wid=500&hei=500&op_sharpen=1",
box_id:1
}
]
},
{
id:2,
name:"Random Blankets",
category:"Den",
move_id:1,
move:{
id:1,
name:"Leaving for College",
date:"2019-08-12",
user_id:1
},
items:[
{
id:3,
name:"Pillows",
image:"https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id:2
},
{
id:4,
name:"Stuffed Animals",
image:"https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id:2
}
]
},
{
id:3,
name:"Cleaning Supplies",
category:"Kitchen",
move_id:1,
move:{
id:1,
name:"Leaving for College",
date:"2019-08-12",
user_id:1
},
items:[
{
id:5,
name:"Pillows",
image:"https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id:3
},
{
id:6,
name:"Stuffed Animals",
image:"https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id:3
}
]
}];
const searchTerm = "Animals"
// function to filter sub-items
const filterItems = items => items.filter((i) => searchTerm ? i.name.includes(searchTerm) : i.name);
const filteredBoxes = data.map(boxes => {
//filter sub-items
const items = filterItems(boxes.items);
//in case there is any item, return that boxes
if (items.length) {
return Object.assign({}, boxes, { items })
}
// in case there is nothing, return false
return false;
}).filter(Boolean); // filter the boxes list removing the false values
console.log('filteredBoxes', filteredBoxes);

Angular 2 pipe to filter grouped arrays

I have a group of arrays on my Angular2 app that I use to build a grouped list with *ngFor in my view:
[
{
category: 1,
items: [{ id: 1, name: "helloworld1" }, { id: 2, name: "helloworld2" }]
},
{
category: 2,
items: [{ id: 3, name: "helloworld3" }, { id: 4 }]
},
{
category: 3,
items:[{ id: 5 }, { id: 6 }]
}
]
I also have a boolean that when it's true should filter only the items that have the name property. If a group does not have any item that matches this condition it should not pass. So the result would be the following if the boolean is true:
[
{
category: 1,
items: [{ id: 1, name: "helloworld1" }, { id: 2, name: "helloworld2" }]
},
{
category: 2,
items: [{ id: 3, name: "helloworld3" }]
}
]
How can I implement a pipe to achieve this kind of result?
http://plnkr.co/edit/je2RioK9pfKxiZg7ljVg?p=preview
#Pipe({name: 'filterName'})
export class FilterNamePipe implements PipeTransform {
transform(items: any[], checkName: boolean): number {
if(items === null) return [];
let ret = [];
items.forEach(function (item) {
let ret1 = item.items.filter(function (e) {
return !checkName || (checkName && (e.name !== undefined));
});
if(ret1.length > 0) {
item.items = ret1;
ret.push(item);
}
});
return ret;
}
}

Resources