Remove all array element in mongodb which has id match in array - node.js

I have an array of the id which I get from post request in nodejs. I want to remove all the elements in the orders array which match receive id array.
eg remove_id = ['60f1ab20891ced4818b5ea88','60f1ab20891ced4818b5ea87']
so I want to remove all elements from orders array whoose id match match remove_id array.
orderdetail = {
_id: 60f1ab20891ced4818b5ea86,
totalPrice: 400,
orders: [
{
_id: 60f1ab20891ced4818b5ea87,
quantity: 1,
price: 200,
name: 'Caramel Latte',
category: 'Steaming Cups'
},
{
_id: 60f1ab20891ced4818b5ea88,
quantity: 1,
price: 200,
name: 'Cinnamon Latte',
category: 'Steaming Cups'
},
{
_id: 60f1ab20891ced4818b5ea89,
quantity: 1,
price: 200,
name: 'Cinnamon Latte',
category: 'Steaming Cups'
}
],
timestamp: 1626450720332,
name: 'xxx',
email: 'xxx',
}
what I have tried but not working,
Orderdetail.updateOne( { _id: '60f1ab20891ced4818b5ea86' }, {
$pull: { order: { _id: { $in: remove_id } } } } )

Rename object name order to orders !
Try this code !
let remove_id = ['60f1ab20891ced4818b5ea87','60f1ab20891ced4818b5ea88']
db.getCollection('Orderdetail').updateOne({ _id: '60f1ab20891ced4818b5ea86' }, {
$pull: {
orders: { // rename this to orders
_id: { $in: remove_id }
}
}
})

Related

Delete a single document from nested document array by its id

I have this model for a user, the user can also be a driver and the driver have an array of cars. I want to delete a specific car from all the cars of drivers, but my query deletes all the cars.
This is the schema:
const UserSchema = new mongoose.Schema({
name: {
type: String,
},
driverData: {
ExperienceLevel: {
type: String,
},
rides: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "ride.model",
},
],
cars: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
},
licensePlateNumber: {
type: String,
},
},
],
},
});
Code I am using to pull a car by id.
user = await User.findByIdAndUpdate(
{ _id: userId },
{ driverData: { cars: { $pull: { id: carId } } } }
);
Only a single car object and a single car id is left after making this query, all other data is removed. How to only remove only a single car.
Your syntax is incorrect. Try this:
user = await User.findByIdAndUpdate( { _id: userId }, { $pull: { driverData: { cars: { $elemMatch: { id: carId } } } } );

findOneAndUpdate doesn't update object's field (array of objects)

I'm trying to update multiple fields of a object in an array but it doesn't work.
what am i doing wrong?
Data Sample:
{
_id: 'mongodbid',
name: 'something',
employees: [
{
age: 25,
name: 'name',
salary: 500
},
{
age: 28,
name: 'name2',
salary: 700
}
],
}
Query:
await this.somethingModel
.findOneAndUpdate(
{
_id: id,
'employees.age': 25,
},
{
$set: {
'employees.$.salary': 600,
'employees.$.name': 'name4',
}
},
)
.exec();
I've added a arrayFilter in findOneAndUpdate. Try with this one, I hope, it works.
await this.somethingModel.findOneAndUpdate(
{
_id: id,
},
{
$set: {
"employees.$[elem].salary": 600,
"employees.$[elem].name": "name4"
}
},
{
$arrayFilters: [
{
"elem.age": 25,
},
],
});
Turns out problem wasn't with the query itself, rather the schema.
I forgot to add [ ] to the employees field in the schema.
I was using 'raw' from '#nestjs/mongoose' package, I mistakenly wrote this:
#Prop(raw({ age: Number, salary: Number, name: String }))
employees: { age: number; salary: number; name: string}[]
Instead of this:
#Prop(raw([{ age: Number, salary: Number, name: String }]))
employees: { age: number; salary: number; name: string}[]

Mongoose: find and aggregate together in db.collection()

I have a collection in mongodb, called recipe, where I have a document, called comments, which is an array, and in each recipe is saving the comments. Inside the comments array I have a ratings, which type is Number. So I want to calculate the average the ratings, but don't know, how can I use the db.collection().aggregate code to work in the recipe collection, in the comments document with the ratings variable.
Here is the recipe collection in mongodb:
const { Double } = require('bson');
const { timeStamp } = require('console');
const mongoose = require('mongoose');
const recipeSchema = new mongoose.Schema({
name: {
type: String,
required: 'This field is required.'
},
description: {
type: String,
required: 'This field is required.'
},
quantity: {
type: Array,
required: 'This field is required.'
},
ingredients: {
type: Array,
required: 'This field is required.'
},
categoryByServing: {
type: String,
enum: ['Reggeli', 'Ebéd', 'Vacsora', 'Desszert', 'Levesek', 'Egyéb'],
required: 'This field is required.'
},
categoryByNationality: {
type: String,
enum: ['Thai', 'Kínai', 'Indiai', 'Olasz', 'Angol', 'Magyar', 'Egyéb'],
required: 'This field is required.'
},
image: {
type: Array,
required: 'This field is required.'
},
comments: [
{
username: String,
comment: String,
date: {
type: Date,
default: Date.now
},
rating: Number
},{
timestamps: true
}
],
count: {
type: Number
},
likes: {
type: Number
},
recipe_id: {
type: String
}
});
recipeSchema.index({ name: 'text', description: 'text' });
const Recipe = module.exports = mongoose.model('Recipe', recipeSchema);
Here is the code, where I implemented the rating avg calculation, which is inside the commenting post method:
/**
* POST /comment-recipe
* Comment Recipe
*/
module.exports.CommentRecipeOnPost = async(req, res) => {
let recipeId = req.params.id
const comment = new Comment({
username: req.body.username,
comment: req.body.comment,
date: req.body.date,
rating: req.body.rating
});
comment.save((err, result) => {
if (err){
console.log(err)
}else {
Recipe.findById(req.params.id, (err, post) =>{
if(err){
console.log(err);
}else{
post.comments.push(result);
post.save();
db.collection('recipes').aggregate([
{
$unwind: "$comments"
},
{
$group: {
_id: null,
avgrating: {
$avg: "$rating"
}
}
}
]).toArray()
.then(results => {
console.log({ rating: results[0].avgrating })
})
.catch(error => console.error(error))
console.log('====comments=====')
console.log(post.comments);
res.redirect('/recipe/' + recipeId);
}
})
}
})
}
UPDATE
There is a simpler way pointed out by chridam in the comments using only a $project which I didn't figure out at first demo
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
avgRating: {
$avg: "$comments.rating"
}
}
}
])
..or using $addFields which will give the average of ratings as a new field avgRating for each record demo . you can use a project step after if need to get only certain fields
db.collection.aggregate([
{
$addFields: {
avgRating: {
$avg: "$comments.rating"
}
}
}
])
You have done the $unwind step correctly and now you will get a record for each comment.
{
"_id": "1",
"comments": {
"comment": "commment1-1",
"rating": 4
},
"name": "recipe 1"
},
{
"_id": "1",
"comments": {
"comment": "comment1-2",
"rating": 3
},
"name": "recipe 1"
},
...
In the $group stage group by something unique like the _id or the name and the $avg should be of $comments.rating instead of $rating.
In the end the pipeline should look something like this. demo
db.collection.aggregate([
{
$unwind: "$comments"
},
{
$group: {
_id: "$name", //group by something unique for that document containing comments
avgRating: {
$avg: "$comments.rating"
}
}
}
])

find array of objects with only matched object in subarray mongoose?

I'm trying to get an array of objects with the matching id's and with only sub-array product_price object with matching attributes size and model?
product_name: {
type: String,
required: true,
},
service_hourly_price: {
type: Number,
required: true
},
product_price: [{
model:{
type: String,
enum:['Euro','Japanese']
},
size:{
type: String,
enum: ['S','M','L','XL']
},
price:{
type: Number,
required: true,
}
}],
Trying to query like this:
ProductSchema.aggregate( [
{$match: { _id: {
$in: _id.map(function(_id){ return new mongoose.Types.ObjectId(_id) })
}}},
{ $match : { product_price : {model : 'Euro' , size: 'S'}}}
])
how can I achieve result like this:
products:{
_id: new ObjectId("61b3ab3ceba5bc724d754929"),
product_name: 'Basic Service',
service_hourly_price: 25,
product_price: [
{
_id: new ObjectId("61b3ab3ceba5bc724d75492a"),
size: 'S',
model: 'Euro',
price: 100
}
]
},
{
_id: new ObjectId("61b3aa88eba5bc724d7548fb"),
product_name: 'Horn',
service_hourly_price: 5,
product_price: [
{
_id: new ObjectId("61b3aa88eba5bc724d7548fc"),
size: 'S',
model: 'Euro',
price: 110
}
]
}
product_price must contain only one matching object in it.
I am uncertain why your product_prize is an array with only one object, you could remove the array there, and thus remove the $unwind in the aggregation, but nontheless this works for you right now:
ProductSchema.aggregate( [
{$match: {
_id: {
$in: _id.map(function(_id){ return new mongoose.Types.ObjectId(_id) })
}}}, {
'$unwind': {
'path': '$product_price'
}
}, {
'$match': {
'product_price.model': 'Euro',
'product_price.size': 'S'
}
}
])
Here try it out
Here the proof with the mongo Compass:

How to update a document having an array of arrays in MongoDB with Mongoose?

Given the following schema:
const item = {
_id: false,
amount: { type: Number, required: true },
};
const item_schema = new Schema({ item_history: [item] });
const parent_schema = new Schema({
...
items: [item_schema],
...
})
and this document in the database
{
...
items: [{ _id: 1, item_history: [{ amount: 10 }] }]
...
}
Let's say I want to update this document with these items:
const changed_or_new_items = [{ _id: 1, amount: 20 }, { amount: 30 }];
Which should result in this object in the database:
{
...
items: [{ _id: 1, item_history: [{ amount: 10 }, { amount: 20}] },
{ _id: 2, item_history: [{ amount: 30 }] }]
...
}
This is how I currently update the document:
const parent = await Parent.findOne(some_query).exec();
changed_or_new_items.forEach(item => {
if (!item._id) {
parent.items.push({ item_history: [item] });
}
else {
const item_doc = parent.items.id(item._id);
item_doc.item_history.push(_.omit(item, '_id'));
}
});
await parent.save();
Would the above be possible to achieve using an update operation e.g. findOneAndUpdate and if so, how?
You can use findOneAndUpdate with arrayFilters as:
Parent.findOneAndUpdate(
{ 'items._id': 1 },
{ '$set': { 'items.$.item_history.$[element].amount': 30 } },
{
'arrayFilters': [ {'element.amount': 20} ],
'new': true,
'upsert': true
}, (err, updatedParent ) => {
if (err) res.status(400).json(err);
res.status(200).json(updatedParent);
}
);

Resources