MongoDB update multiple items in an array of objects with corresponding data - node.js

I have an array in my MongoDB document as shown below:
{
...otherDocFields,
groupID: "group-id",
users: [
{id: "uid1", name: "User1"},
{id: "uid2", name: "User2"},
{id: "uid3", name: "User3"},
{id: "uid4", name: "User4"}
]
}
I'm trying to write a function that will update users' names based on that ID.
I tried something like:
async function updateNames(groupID: string, data: Array<{id: string, name: string}>) {
try {
// MongoDB Aggregation
await mongoDB.collection("users").aggregate([
{$match: {groupID}},
{$unwind: {
path: '$users',
includeArrayIndex: 'users.id',
preserveNullAndEmptyArrays: true
}
}
//....
])
} catch (e) {
console.log(e)
}
}
I'm stuck at the part to update the relevant names from the data param in the function.
A sample data would be:
[
{id: "uid1", name: "newName1"},
{id: "uid3", name: "newName3"}
]
I can for sure read, manually process it and update the document but I'm looking for a way to do it in single go using aggregation.

You can do this with an update statement using array filters (https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#---identifier--)
First, if we just declare some tests data which would be passed into your method:
const data = [
{id: "uid2", name: "ChangeName1"},
{id: "uid4", name: "ChangedName2"}
];
We can then create an update statement which updates all the users within that list within a map and a reduce:
const sets = data.map((element, index) => ({ [`users.$[f${index}].name`]: element.name })).reduce((accumulator, currentValue) => (Object.assign(currentValue, accumulator)), { });
const update = { $set: sets };
This will give us the following update statement:
{
"$set" : {
"users.$[f1].name" : "ChangedName2",
"users.$[f0].name" : "ChangeName1"
}
}
We can create a bunch of array filters with that data:
const arrayFilters = data.map((element, index) => ({ [`f${index}.id`]: element.id }));
This will give us the following which we can pass into the options of an update.
[ { "f0.id" : "uid2" }, { "f1.id" : "uid4" } ]
Last of all we can execute the following update command:
db.users.updateOne(
filter,
update,
{ arrayFilters }
);
Now if we check the output we'll get the following results
> db.users.find().pretty()
{
"_id" : ObjectId("60e20841351156603932c526"),
"groupID" : "123",
"users" : [
{
"id" : "uid1",
"name" : "User1"
},
{
"id" : "uid2",
"name" : "ChangeName1"
},
{
"id" : "uid3",
"name" : "User3"
},
{
"id" : "uid4",
"name" : "ChangedName2"
}
]
}

Related

writing filter in loopback

I'm trying to write a loopback 4 filter that returns the objects where creatorId = userId OR userId in sharedUsers = userId but I cant seem to formulate the filter correctly
(neither of these properties are unique id's)
this is what my object shape looks like:
{
"_id" : "20",
"configMetadata" : {
...
"creatorId" : "50",
"creatorName" : "Mark"
},
"sharedUsers" : [
{
"userId" : "15"
},
{
"userId" : "20"
}
],
"sharedRoles" : ....,
"tiles" : ...
}
here is what I tried
const filter2: Filter<ProductViewConfig> =
{
where: {
or: [
{ configMetadata : { creatorId : userId} },
{ sharedUsers: { [userId]: userId } },
],
},
};
but I'm getting 2 errors: one is : configMetadata is missing the rest of the properties, so just properties mismatch and for sharedUsers I'm getting a MongoError: unknown operator: $15 when I enter userId = 15
to provide context: this is what my endpoint looks like
#get('/product-view-configs/{userId}')
#response(200, {
description: 'ProductViewConfig model instance',
content: {
'application/json': {
schema: getModelSchemaRef(ProductViewConfig),
},
},
})
async findIt(
#param.path.string('userId') userId: string,
#param.filter(ProductViewConfig, { exclude: 'where' }) filter?: FilterExcludingWhere<ProductViewConfig>
): Promise<ProductViewConfig> {
//returns configs where creatorId = userId OR userId in sharedUsers = userId
const filter2: Filter<ProductViewConfig> =
{
where: {
or: [
{ configMetadata : { creatorId : userId} },
{ sharedUsers: { [userId]: userId } },
],
},
};
const records = this.productViewConfigRepository.find(filter2);
return this.productViewConfigRepository.findById(userId, filter);
}
can anyone point me in the right direction? I couldn't find examples on how to filter nested objects in the documentation, so any help would be appreciated!

Update nested object in array MongoDB

I need to find and update documents with category that corresponding to the query. Array could contain mo than one corresponding id.
Query:
{
"ids": ["61f1cda47018c60012b3dd01", "61f1cdb87018c60012b3dd07"],
"userId": "61eab3e57018c60012b3db3f"
}
I got collection with documents like:
`{
"_id":{"$oid":"61f1cdd07018c60012b3dd09"},
"expenses":[
{"category":"61eafc104b88e154caa58616","price":"1111.00"},
{"category":"61f1cdb87018c60012b3dd07","price":"2222.00"},
{"category":"61f1cda47018c60012b3dd01","price":"1241.00"},
{"category":"61f1cdb87018c60012b3dd07","price":"111.00"}
],
"userId":"61eab3e57018c60012b3db3f"
}`
my method:
async myMethod(ids: [string], userId: string) {
try {
const { ok } = await this.ExpensesModel.updateMany(
{"userId": userId, "expenses.category": { $in: ids }},
{$set: {"expenses.$.category": "newCategoryID"}}
);
return ok
} ........
I path array of ids ["61f1cda47018c60012b3dd01","61f1cdb87018c60012b3dd07","61f1cdb87018c60012b3dd07"] and userId, this code update only 1 category by document.
So can i made it with mongo build in methods? or i need to find matching document and update it it by my self and after that update or insert;
Update with arrayFilters
db.collection.update({
"expenses.category": {
$in: [
"61f1cda47018c60012b3dd01",
"61f1cdb87018c60012b3dd07"
]
}
},
{
$set: {
"expenses.$[elem].category": "61eab3e57018c60012b3db3f"
}
},
{
arrayFilters: [
{
"elem.category": {
$in: [
"61f1cda47018c60012b3dd01",
"61f1cdb87018c60012b3dd07"
]
}
}
]
})
mongoplayground

$pulling an object from an array based on _id in Mongoose [duplicate]

Doc:
{
_id: 5150a1199fac0e6910000002,
name: 'some name',
items: [{
id: 23,
name: 'item name 23'
},{
id: 24,
name: 'item name 24'
}]
}
Is there a way to pull a specific object from an array? I.E. how do I pull the entire item object with id 23 from the items array.
I have tried:
db.mycollection.update({'_id': ObjectId("5150a1199fac0e6910000002")}, {$pull: {id: 23}});
However I am pretty sure that I am not using 'pull' correctly. From what I understand pull will pull a field from an array but not an object.
Any ideas how to pull the entire object out of the array.
As a bonus I am trying to do this in mongoose/nodejs, as well not sure if this type of thing is in the mongoose API but I could not find it.
try..
db.mycollection.update(
{ '_id': ObjectId("5150a1199fac0e6910000002") },
{ $pull: { items: { id: 23 } } },
false, // Upsert
true, // Multi
);
I have a document like
I have to delete address from address array
After searching lots on internet I found the solution
Customer.findOneAndUpdate(query, { $pull: {address: addressId} }, (err, data) => {
if (err) {
return res.status(500).json({ error: 'error in deleting address' });
}
res.json(data);
});
my database:
{
"_id" : ObjectId("5806056dce046557874d3ab18"),
"data" : [
{ "id" : 1 },
{ "id" : 2 },
{ "id" : 3 }
]
}
my query:
db.getCollection('play_table').update({},{$pull:{"data":{"id":3}}},{multi:true}
output:
{
"_id" : ObjectId("5806056dce046557874d3ab18"),
"data" : [
{ "id" : 1 },
{ "id" : 2 }
]
}
You can try it also:
db.getCollection('docs').update({ },{'$pull':{ 'items':{'id': 3 }}},{multi:true})
For a single record in array:
db.getCollection('documents').update(
{ },
{'$pull':{ 'items':{'mobile': 1234567890 }}},
{new:true}
);
For a multiple records with same mobile number in array:
db.getCollection('documents').update(
{ },
{
$pull: {
items: { mobile: 1234567890 }
}
},
{ new:true, multi:true }
)
Use $pull to remove the data
return this.mobiledashboardModel
.update({"_id": args.dashboardId}, { $pull: {"viewData": { "_id": widgetId}}})
.exec()
.then(dashboardDoc => {
return {
result: dashboardDoc
}
});
Kishore Diyyana:
If you want to remove all elements including the key of the element attributes list.
Here is the example of mongoDB unset operator:
db.UM_PREAUTH_CASE.update(
{ 'Id' : 123}, { $unset: { dataElements: ""} } )
JSON Look like this:
{ "Id":123,"dataElements" : [ { "createdBy" : "Kishore Babu Diyyana", "createdByUserId" : 2020 }, { "createdBy" : "Diyyana Kishore", "createdByUserId" : 2021 } ] }

Skip one nested level of subdocument in Mongoose

I have a parent schema with a subdocument. The subdocument has a property with an array of embedded objects:
Child schema
var snippetSchema = new Schema({
snippet: [
{
language: String,
text: String,
_id: false
}
]
});
Parent schema
var itemSchema = new Schema({
lsin: Number,
identifier: {
isbn13: Number,
},
title: snippetSchema,
});
Which upon Item.find returns an object like so:
[
{
_id: (...),
lsin: 676765,
identifier: {
isbn13: 8797734598763
},
title: {
_id: (...),
snippet: [
{
language: 'se',
text: 'Pippi Långstrump'
}
]
}
}
]
I would like to skip one nested level of the subdocument when the object is returned to the client:
[
{
_id: (...),
lsin: 676765,
identifier: {
isbn13: 8797734598763
},
title: {
language: 'se',
text: 'Pippi Långstrump'
}
}
]
So far I have tried:
#1 using a getter
function getter() {
return this.title.snippet[0];
}
var itemSchema = new Schema({
...
title: { type: snippetSchema, get: getter }
});
But it creates an infinite loop resulting in RangeError: Maximum call stack size exceeded.
#2 using a virtual attribute
var itemSchema = new Schema({
..., {
toObject: {
virtuals: true
}
});
itemSchema
.virtual('title2')
.get(function () {
return this.title.snippet[0];
});
Which will generate the desired nested level but under a new attribute, which is not acceptable. To my knowledge there is no way of overriding an attribute with an virtual attribute.
The question is: is there any other way to go about in getting the desired output? There will be several references to the snippetSchema across the application and a DRY method is preferred.
I am new to MongoDB and Mongoose.
You'll need to use the $project within an mongodb aggregation pipeline.
Within my database I have the following:
> db.items.find().pretty()
{
"_id" : 123,
"lsin" : 676765,
"identifier" : {
"isbn13" : 8797734598763
},
"title" : {
"_id" : 456,
"snippet" : [
{
"language" : "se",
"text" : "Pippi Långstrump"
}
]
}
}
Then we just need to create a simple aggregation query:
db.items.aggregate([
{$project: { lsin: 1, identifier: 1, title: { $arrayElemAt: [ '$title.snippet', 0 ] }}}
])
This just uses a $project (https://docs.mongodb.com/v3.2/reference/operator/aggregation/project/) and a $arrayElemAt (https://docs.mongodb.com/v3.2/reference/operator/aggregation/arrayElemAt/) to project the first item out of the array. If we execute that we will get the following:
{
"_id" : 123,
"lsin" : 676765,
"identifier" : {
"isbn13" : 8797734598763
},
"title" : {
"language" : "se",
"text" : "Pippi Långstrump"
}
}

Issue: updating value inside the subdocument of a subdocument

I am having some trouble trying to update some fields of a subdocument inside a subdocument in mongodb.
First of all, let's find exactly the document I need to update to see how the document structure looks like:
// query:
db.getCollection('collection').find({
application: ObjectId("568b3a2feaa4171d03734776"),
_id: ObjectId("568b3a2feaa4171d03734779"),
status: 'sent',
'mailingList.recipients.email': 'example#example.com' // an index
},
{
'mailingList.$.recipients': true
});
// query result:
{
"_id" : ObjectId("568b3a2feaa4171d03734779"),
"mailingList" : [
{
"id" : 55,
"recipients" : [
{
"metadata" : {
"name" : "Example",
"surname" : "Example"
},
"email" : "example#example.com",
"message" : {
"events" : []
}
}
]
}
]
}
What I would like to achieve is exactly being able to update any of the fields in the object inside recipients[]: let's say, for example, email. I've tried the following so far, using the $set operator:
db.getCollection('collection').update({
application: ObjectId("568b3a2feaa4171d03734776"),
_id: ObjectId("568b3a2feaa4171d03734779"),
status: 'sent',
'mailingList.recipients.email': 'example#example.com'
},
{
$set: {
'mailingList.$.recipients.email': 'newmail#example.com'
}
});
but I get the following error:
cannot use the part (recipients of mailingList.1.recipients.email) to
traverse the element ({recipients: [ { metadata: { name: "Example",
surname: "Example" }, email: "example#example.com", message: { events:
[] } } ]})
What am I missing? I had already worked with embedded subdocuments before (not like this where there is a subdocument inside another subdocument) and using $set was enough to update any field inside a single subdocument, i.e:
$set: {
'mailingList.$.email': 'newmail#gmail.com'
}

Resources