MongoDB - Remove object from SubArray - javascript

I'm trying to remove an object from a subarray with no luck getting updateOne() is not a function and remove() is not function.
I want to remove the 'subcat 1' object with id of '61cae5daf5bfbebd7cf748ef':
[
{
_id: '61cae5daf5bfbebd7cf748ee'
title: 'category 1',
SubCats: [
{
_id: '61cae5daf5bfbebd7cf748ef'
name: 'subcat 1',
image: '/assets/images/vr-box-6203301_1920.jpg',
},
{
_id: '61cae5daf5bfbebd7cf748fb'
name: 'subcat 2',
image: '/assets/images/galaxy-s20_highlights_kv_00.jpg',
},
]
},
]
Please help
Controller:
const deleteSubCategory = asyncHandler(async (req, res) => {
const subCategory = await Category.aggregate([
{ $unwind: "$SubCats" },
{ $replaceRoot: { newRoot: '$SubCats'} },
{ $match: { _id: ObjectId(req.params.id) }}
])
if (subCategory) {
await subCategory.updateOne({ $pull: {_id: ObjectId(req.params.id)}})
res.json({ message: 'sub-category removed' })
} else {
res.status(404)
throw new Error('sub-Category not found')
}
})

$update with $pull
db.collection.update({
"SubCats._id": "61cae5daf5bfbebd7cf748ef"
},
{
"$pull": {
SubCats: {
_id: "61cae5daf5bfbebd7cf748ef"
}
}
},
{
"multi": true
})
mongoplayground

Related

Mongo Aggregation pipeline update or push

I have a MongoDB Model which consist of array of members as obejcts.
const guestSchema = new mongoose.Schema({
salutation: {
type: String,
},
members: [membersSchema],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
},
});
Members Schema:
const membersSchema = new mongoose.Schema({
name: String,
status: {
type: String,
enum: ['regular', 'helper'],
default: 'regular',
},
});
I want to achieve of doing an update in case documet with given ID exist or push to an array in case ID with document in array does not exist. I use aggregation pipeline, however I am not able to achieve pushing new document to array. Why can't I use push after else statement like this.
const subDocumentToUpsert = { 'name': mem.name, 'status': mem.status, '_id': ObjectId(mem.id)}
const subDocumentNoID = { 'name': mem.name, 'status': mem.status}
await Guest.findOneAndUpdate(
{ "_id": req.params.id },
[
{
$set: {
members: {
$cond: {
if: { $in: [subDocumentToUpsert._id, '$members._id'] },
then: {
$map: {
input: '$members',
as: 'sub_document',
in: {
$cond: {
if: { $eq: ['$$sub_document._id', subDocumentToUpsert._id] },
then: subDocumentToUpsert,
else: '$$sub_document',
},
},
},
},
else: {
$push: {
subDocumentNoID
},
},
},
},
},
},
},
]);
What is the best way of doing so? Thank you
You can do as follow:
db.collection.update({
_id: {
$in: [
1,
2
]
}
},
[
{
$set: {
members: {
$cond: {
if: {
$in: [
5,
"$members._id"
]
},
then: {
$map: {
input: "$members",
as: "sub",
in: {
$cond: {
if: {
$eq: [
"$$sub._id",
5
]
},
then: {
_id: 5,
status: "regular_updated",
name: "Negan_updated"
},
else: "$$sub"
},
},
},
},
else: {
$concatArrays: [
"$members",
[
{
_id: 5,
status: "regular_upserted",
name: "Negan_upserted"
}
]
]
}
}
}
}
}
}
],
{
multi: true
})
Explained:
Check if _id:5 exist in the subobject and update via $map/$cond only the object that has the _id:5.
In case there is no _id:5 add the new object to the array with $concatArrays.
Playground

Add field on mongoose find

I have a mongoose find like this
Persona.find(query)
.sort({ order:1 }).exec(async function (err, data) {
if (err) {
return res.status(400).send(err);
}
let dataform = [];
await asyncForEach(data, async persona => {
persona.newField = "Test";
dataform.push(persona);
}
});
res.json(dataform);
});
With the debugger the value of dataform on res.json(dataform); is this
0:{
_id: 123,
name: 'persona1',
newField: 'Test'
},
1:{
_id: 1234,
name: 'persona2',
newField: 'Test'
}
But when my controller gets the api response, the "newField" doesn't exist
0:{
_id: 123,
name: 'persona1'
},
1:{
_id: 1234,
name: 'persona2'
}

Find number of objects in nested array with different query params

I have this Event array but can't figure out how to query into the 'guests' nested array and do two things.
count the number of guests
count the number of attended guests (marked 'Y')
{ _id: new ObjectId('1'),
name: event1,
guests: [
{
phone: +12222222222,
_id: new ObjectId,
attended: 'Y'
},
{
phone: +12344466666,
_id: new ObjectId,
attended: 'Y'
},
{ phone: +11234567890,
_id: new ObjectId,
attended:
},
{
phone: +14443332222,
_id: new ObjectId,
attended: 'Y'
},
{ phone: +19090909090,
_id: new ObjectId
attended:
}
],
},
{ _id: new ObjectId('2'),
name: event2,
guests: [
{
phone: +11111111111,
_id: new ObjectId,
attended:
},
{
phone: +12222222222,
_id: new ObjectId,
attended: 'Y'
},
{
phone: +133333333333,
_id: new ObjectId,
attended: 'Y'
}
],
},
My code below is on its 20th iteration without getting any closer.
const event = await Event.findById(req.params.id);
For MongoDB query, you can use $size for calculating the size of the array field and $filter to filter specific document(s) that matched the criteria in the projection.
db.collection.find({
_id: "1"
},
{
_id: 1,
name: 1,
guests: 1,
totalGuests: {
$size: "$guests"
},
attendedGuests: {
$size: {
"$filter": {
input: "$guests",
cond: {
$eq: [
"$$this.attended",
"Y"
]
}
}
}
}
})
Sample Mongo Playground
Yong Shun helped above but didn't get 100% of the way -- here is the full answer in case anyone encounters something like this in the future:
module.exports.showEvent = async (req, res) => {
const event = await Event.findById(req.params.id).populate('artist');
const { guest_id } = req.cookies;
let totalGuests = 0;
let attendedGuests = 0;
const eventData = await Event.aggregate([
{
"$match": {
"_id": objectId(req.params.id)
}
},
{
$project: {
_id: 1,
name: 1,
guests: 1,
totalGuests: { $cond: { if: { $isArray: "$guests" }, then: { $size: "$guests" }, else: "NA" } },
attendedGuests: {
$size: {
$filter: {
input: "$guests",
as: "guest",
cond: {
$and: [{
$eq: ["$$guest.attended", "Y"]
}]
}
}
}
}
}
}
])
if (eventData && Array.isArray(eventData) && eventData.length > 0) {
totalGuests = eventData[0].totalGuests;
attendedGuests = eventData[0].attendedGuests;
}
if (!event) {
req.flash('error', 'Cannot find that Event');
return res.redirect('/events');
}
res.render('events/show', { event, eventData });
console.log(totalGuests, attendedGuests);
};

MongoDB aggregation: flatten array property into root array

I'm working on a feature that recursively looks up a thread of comments using $graphLookUp, and I almost have it. (albeit in a somewhat convoluted way but it is working!)
The last step I need is the following:
instead of having the nested posteriorThread as a property of the root array ($$ROOT), just merge it onto the root itself.
AGGREGATION CODE:
const posteriorThread = await Comment.aggregate([
{
$match: {
_id: post.threadDescendant
}
},
{
$graphLookup: {
from: 'baseposts',
startWith: '$threadDescendant',
connectFromField: 'threadDescendant',
connectToField: '_id',
as: 'posteriorThread'
}
},
{
$unwind: '$posteriorThread'
},
{
$sort: { 'posteriorThread.depth': 1 }
},
{
$group: {
_id: '$_id',
posteriorThread: { $push: '$posteriorThread' },
root: { $first: '$$ROOT' }
}
},
{
$project: {
'root.posteriorThread': 0
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
{
posteriorThread: '$posteriorThread'
},
'$root'
]
}
}
}
]);
CURRENT OUTPUT
OUTPUT: posteriorThread
[
{
_id: '5f7eab40575e6fc56ee07604',
onModel: 'BasePost',
depth: 1,
user: '5f5da45245c07cc06e51b09f',
text: 'thread 0',
isThread: true,
threadDescendant: '5f7eabad575e6fc56ee07607',
posteriorThread: [
{
_id: '5f7eabad575e6fc56ee07607',
onModel: 'Comment',
depth: 2,
user: '5f5da45245c07cc06e51b09f',
text: 'thread 1',
isThread: true,
threadDescendant: '5f7eac82575e6fc56ee07609'
},
{
_id: '5f7eac82575e6fc56ee07609',
onModel: 'Comment',
depth: 3,
user: '5f5da45245c07cc06e51b09f',
text: 'thread 2',
isThread: true
}
]
}
];
DESIRED OUTPUT
OUTPUT: posteriorThread
[
{
_id: '5f7eab40575e6fc56ee07604',
onModel: 'BasePost',
depth: 1,
user: '5f5da45245c07cc06e51b09f',
text: 'thread 0',
isThread: true,
threadDescendant: '5f7eabad575e6fc56ee07607'
},
{
_id: '5f7eabad575e6fc56ee07607',
onModel: 'Comment',
depth: 2,
user: '5f5da45245c07cc06e51b09f',
text: 'thread 1',
isThread: true,
threadDescendant: '5f7eac82575e6fc56ee07609'
},
{
_id: '5f7eac82575e6fc56ee07609',
onModel: 'Comment',
depth: 3,
user: '5f5da45245c07cc06e51b09f',
text: 'thread 2',
isThread: true
}
];
I could accomplish this after the aggregation in regular js, but I would prefer to do it all in the aggregation.
The part that needs to be replaced is the mergeObjects bit and replaced with something else or the group aggregation and taking a different strategy, but I'm not sure what to put in it's place.
Also, if you have any other suggestions to make this cleaner, I'm all ears.
Thanks in advance.
Its really challenging. Atleast for me. And really very interesting case. Lets try my solution. Hope it works..
db.test.aggregate([
// PREVIOSU STEPS YOU ALREADY DID
{
$group: {
_id: "$_id",
items: {$push: "$$ROOT"},
subItems: {$first: "$posteriorThread"}
}
},
{
$project: {
"items.posteriorThread": 0
}
},
{
$addFields: {
allItems: {
$concatArrays: ["$items", "$subItems"]
}
}
},
{
$group: {
_id: null,
mergedItems: {$push: "$allItems"}
}
},
{
$unwind: "$mergedItems"
},
{
$unwind: "$mergedItems"
},
{
$replaceRoot: {
newRoot: "$mergedItems"
}
}
])
Thanks to #Sunil K Samanta for steering me in the right direction. It's not the prettiest solution, but it does give me the right solution.
const posteriorThread = await Comment.aggregate([
{
$match: {
_id: post.threadDescendant
}
},
{
$graphLookup: {
from: 'baseposts',
startWith: '$threadDescendant',
connectFromField: 'threadDescendant',
connectToField: '_id',
as: 'posteriorThread'
}
},
{
$unwind: '$posteriorThread'
},
{
$sort: { 'posteriorThread.depth': 1 }
},
{
$group: {
_id: '$_id',
items: { $push: '$$ROOT.posteriorThread' },
root: { $push: '$$ROOT' },
},
},
{
$project: {
items: 1,
root: { $slice: ['$$ROOT.root', 0, 1] },
},
},
{
$project: {
'root.posteriorThread': 0,
},
},
{
$addFields: {
allItems: {
$concatArrays: ['$root', '$items'],
},
},
},
{
$replaceRoot: {
newRoot: {
full_posterior: '$$ROOT.allItems',
},
},
},
])
)[0].full_posterior;

find() inside reduce() method returns undefined

I have two arrays of objects; districts and userCounts. I am trying to reduce districts and find userCounts inside reduce
const result = districts.reduce((acc, curr) => {
const findUser = userCounts.find(({ _id }) => _id === curr._id)
console.log(findUser)
})
all findUser is returning undefined
districts:
[
{
_id: '5efc41d74664920022b6c016',
name: 'name1'
},
{
_id: '5efc41a44664920022b6c015',
name: 'name2'
},
{
_id: '5efc2d84caa7964dcd843a7b',
name: 'name3'
},
{
_id: '5efc41794664920022b6c014',
name: 'name 4'
}
]
userCounts:
[
{ _id: '5efc2d84caa7964dcd843a7b', totalCount: 3 },
{ _id: '5efc41794664920022b6c014', totalCount: 1 }
]
well .filter() with .every() is better use case here
let districts = [
{
_id: "5efc41d74664920022b6c016",
name: "name1",
},
{
_id: "5efc41a44664920022b6c015",
name: "name2",
},
{
_id: "5efc2d84caa7964dcd843a7b",
name: "name3",
},
{
_id: "5efc41794664920022b6c014",
name: "name 4",
},
];
let userCounts = [
{ _id: "5efc2d84caa7964dcd843a7b", totalCount: 3 },
{ _id: "5efc41794664920022b6c014", totalCount: 1 },
];
const result = districts.filter((dist) => {
return userCounts.some(({ _id }) => _id === dist._id);
});
console.log(result);
const districs=[
{
_id: '5efc41d74664920022b6c016',
name: 'name1'
},
{
_id: '5efc41a44664920022b6c015',
name: 'name2'
},
{
_id: '5efc2d84caa7964dcd843a7b',
name: 'name3'
},
{
_id: '5efc41794664920022b6c014',
name: 'name 4'
}
]
const userCounts= [
{ _id: '5efc2d84caa7964dcd843a7b', totalCount: 3 },
{ _id: '5efc41794664920022b6c014', totalCount: 1 }
]
let filtered=userCounts.map(item=>{
return districs.find(elemnt=>elemnt._id===item._id)
})
console.log(filtered)
here you go, you can modify it however you want.
try this if you want to use reduce and modify your array:
const result = districts.reduce((acc, curr) => {
const findUser = userCounts.filter(({ _id }) => _id === curr._id)
return [...acc, {...curr , user:findUser.length > 0 ? findUser[0].totalCount :0 }]
},[])
const districts = [
{
_id: '5efc41d74664920022b6c016',
name: 'name1'
},
{
_id: '5efc41a44664920022b6c015',
name: 'name2'
},
{
_id: '5efc2d84caa7964dcd843a7b',
name: 'name3'
},
{
_id: '5efc41794664920022b6c014',
name: 'name 4'
}
]
const userCounts = [
{ _id: '5efc2d84caa7964dcd843a7b', totalCount: 3 },
{ _id: '5efc41794664920022b6c014', totalCount: 1 }
]
const result = districts.reduce((acc, curr) => {
const findUser = userCounts.filter(({ _id }) => _id === curr._id)
return [...acc, {...curr , user:findUser.length > 0 ? findUser[0].totalCount :0 }]
},[])
console.log(result)

Categories

Resources