This question already has answers here:
How do I update Array Elements matching criteria in a MongoDB document?
(2 answers)
Update field in exact element array in MongoDB
(5 answers)
Closed 4 years ago.
I want to find correct subdocument and change a specific field.
Here are my attempts:
Book.findOneAndUpdate(
{ _id: req.params.id, chapters: { $elemMatch: { _id: req.params.subid } } },
{ $set: { chapters: { title: 'new title' } } }, { 'new': true }).exec();
});
Also tried:
Book.findOneAndUpdate(
{ _id: req.params.id, 'chapters._id': req.params.subid },
{ $set: { 'title': 'new title' } }, { 'upsert': true }).exec();
The first solution removes everything by overwriting it all with a single field, title.
How can I update it correctly?
EDIT:
//Book
{
"_id": {
"$oid": "5ae2f417fe46cf86900af82f"
},
"chapters": [
{
"_id": {
"$oid": "5af1b798be6bb05e4427ee65"
},
"title": "old title",
"something": "something"
},
//..some more
]
}
Something like:
const ObjectID = require('mongodb').ObjectID;
var bookId = new ObjectID(req.params.id),
subId = new ObjectID(req.params.subid);
Book.findOneAndUpdate(
{ _id: bookId, chapters: { $elemMatch: { _id: subId } } },
{ $set: { chapters: { title: 'new title' } } }, { 'new': true }).exec();
});
think you need to use positional operator $ info
Book.findOneAndUpdate(
{ _id: req.params.id, 'chapters._id': req.params.subid },
{ $set: 'chapters.$': { 'title': 'new title' } }).exec();
OR to update only title
Book.findOneAndUpdate(
{ _id: req.params.id, 'chapters._id': req.params.subid },
{ $set: 'chapters.$.title': 'new title'}).exec();
Related
In my application i have a collection called Blog and i run this query every 24h
await Blog.updateMany({}, [
{
$set: {
viewed: {
$add: [{ $size: "$visitorIps" }, "$viewed"]
},
visitorIps: []
}
}
]);
My problem is that i have a second collection called Users.
Inside of Users i have an array called posts and here are all posts from that user saved.
{
_id: 234klj2รถ34,
user: "Max",
posts: [
{
_id: 5dgewef323523,
name: "My first blogpost",
content: "...",
viewed: 0,
visitorIps: ["192.168.23.12"]
}
]
}
Now i need the same query on my second collection for each array. How do i do it? I tried something like this but it doesnt worked:
await User.updateMany({}, [
{
$set: {
"posts.$[].viewed: {
$add: [{ $size: "posts.$[].visitorIps" }, "posts.$[].viewed"]
},
"posts.$[].visitorIps": []
}
}
]);
But thats completely wrong. Could somebody help me here out?
You can try using $map,
your logic and code remain same for viewed and visitorIps
$mergeObjects will merge current cursor fields and viewed and visitorIps that we have calculated
await User.updateMany({},
[{
$set: {
posts: {
$map: {
input: "$posts",
as: "post",
in: {
$mergeObjects: [
"$$post",
{
"viewed": {
$add: [{ $size: "$$post.visitorIps" }, "$$post.viewed"]
},
"visitorIps": []
}
]
}
}
}
}
}]
)
Imagine a function that finds users by their name and returns them.
User.aggregate(
[
{ $sort: { userFirstName: 1, userLastName: 1 } },
{
$addFields: {
firstLastName: { $concat: ['$userFirstName', ' ', '$userLastName'] },
lastFirstName: { $concat: ['$userLastName', ' ', '$userFirstName'] }
}
},
{
$match: $match // Set from above with match crit
},
{
$group: {
_id: null,
total: { $sum: 1 },
data: {
$push: {
'_id': '$_id',
'userFirstName': '$userFirstName',
'userLastName': '$userLastName',
'userProfileImage': '$userProfileImage',
'userVihorCategory': '$userVihorCategory'
}
}
}
},
{
$project: {
total: 1,
data: { $slice: ['$data', start, limit] }
}
}
]
).exec((errAgg, results) => {...
This works, it splices them and returns them correctly.
There is another collection that tracks user connections.
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
userConnection: { type: Schema.Types.ObjectId, ref: 'User' },
userConnectionStatus: {
type: String,
enum: ['following', 'blocked', 'requested']
}
}
Eg User: me, userConnection: 'someone', userConnectionStatus: 'following'
What I am trying to achive is to return 2 more fields,
1. My userConnectionStatus to him
2. His userConnectionStatus to me
And not to return users who have blocked me.
What is the best approach when it comes to this DB structure.
Thank you for your time
Preventing blocked users was solved by selecting all blocked users, and adding $nin in match inside aggregate.
For connection status, I have resolved the problem by adding 2 virtual fields to User.
UserMongoSchema.virtual('userConnectionStatus', {
ref: 'UserConnection',
localField: '_id',
foreignField: 'user',
justOne: true
});
UserMongoSchema.virtual('connectionStatus', {
ref: 'UserConnection',
localField: '_id',
foreignField: 'userConnection',
justOne: true
});
And populating them on results
...
.exec((errAgg, results) => {
User.populate(results[0].data, [
{ path: 'userConnectionStatus', match: { userConnection: req.userCode }, select: 'userConnectionStatus' },
{ path: 'connectionStatus', match: { user: req.userCode }, select: 'userConnectionStatus' },
], (errPop, populateResponse) => {
if (errPop) { return next(errPop); }
populateResponse = populateResponse.map((row) => {
row['userConnectionStatus'] = row.userConnectionStatus ? row.userConnectionStatus.userConnectionStatus : null;
row['connectionStatus'] = row.connectionStatus ? row.connectionStatus.userConnectionStatus : null;
return row;
});
...
Looking at the order of actions, I think this won't affect performance since I am running populate only on those matched top X (max 100) results.
I won't mark this as Answer yet. If you have any opinion about if this is bad practice or if there is a better way of doing it, feel free to comment.
Do I have to do two updates, if I want to unset one value and set another value? Or is it possible to merge both together?
Collection.update(
{ _id: id },
{ $unset: { 'status.editing': '' } }
);
Collection.update(
{ _id: id },
{ $set: { 'status.version': timestamp } }
);
How about
Collection.update(
{ _id: id },
{
$unset: { 'status.editing': '' },
$set: { 'status.version': timestamp }
}
);
See $unset and $set
It is also possible to set or unset multiple fields at the time with
{ $unset: { quantity: "", instock: "" } }
or
{ $set:
{
quantity: 500,
details: { model: "14Q3", make: "xyz" },
tags: [ "coats", "outerwear", "clothing" ]
}
}
Sample data:
{
_id: '1234',
games: [{
name: 'World of Warcraft',
lastLogin: ISODate(),
subscriptions: ['56', '78']
}, {
name: 'Starcraft II',
lastLogin: ISODate()
}]
}
Essentially, I want to find everyone that does not have a "subscriptions" field for a given game. I can't quite figure out a good way to do it.
Players.update({
'games.name': 'World of Warcraft',
'games.$.subscriptions': { $exists: false }
}, {
$set: { 'games.$.subscriptions': GLOBAL_SUB }
});
How do you query an array of elements for an attribute and the existence of a field?
Use $elemMatch when you want to match multiple terms on the same array element:
Players.update({
games: { $elemMatch: {
name: 'World of Warcraft',
subscriptions: { $exists: false }
}}
}, {
$set: { 'games.$.subscriptions': GLOBAL_SUB }
});
I want to update an element of an array inside mongodb's document (I am using mongoose). Schema is something like:
{
..
arr : [{
foo: Number,
bar: [String],
name: String
}]
..
}
And my query is:
SomeModel.update({
_id: "id of the document",
arr: {
$elemMatch: {
_id: "_id assigned by mongoose to array element"
}
}
}, {
'arr.$': {
name: 'new name'
}
}).exec()
It just replaces whole array element say:
{
_id: "some objectId",
name: 'old name',
foo: 0,
}
to:
{
name: 'new name'
}
what I want:
{
_id: "some objectId",
name: 'new name',
foo: 0,
}
What I am curious to know if it is possible to achieve this in single update query ? (May be there is a silly mistake in my query :P or another approach)
I would also like to do update query like so:
{
$inc: { foo: 1},
$push: { bar: "abc"}
}
If you are still struggling with the whole implementation the full application of your statement is as follows:
SomeModel.update(
{
"arr._id": "123"
},
{
"$set": { "arr.$.name": "new name" },
"$inc": { "arr.$.foo": 1},
"$push": { "arr.$.bar": "abc" }
}
)
,function(err,numAffected) {
});
So each operation is performed in turn.