MongoDB: Merge of two update requests possible? - javascript

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

Related

How to get last defined value in an aggregration in MongoDB?

I have a collection in which the documents sometimes contain a status field and sometimes don't. There is always a data field. I'm trying to form a query to get the latest value for both, but when using the $last operator, I get the values from the latest document and results in status sometimes being null. How can I get it to retrieve the latest defined status value, while still keeping the data value from the latest document?
Current aggregration:
const project = await collection.aggregate([
{
$match: {
projectId: id
}
},
{
$group: {
_id: '$projectId',
status: {
$last: '$status'
},
data: {
$last: '$data'
}
}
}
]).toArray();
You can use $facet and perform multiple query in the parallel on the same set of documents.
db.collection.aggregate([
{
$facet: {
last_status: [
{
"$match": {
status: {
$ne: null
}
}
},
{
"$sort": {
_id: -1
}
},
{
"$limit": 1
}
],
last_data: [
{
"$match": {
data: {
$ne: null
}
}
},
{
"$sort": {
_id: -1
}
},
{
"$limit": 1
}
]
}
},
{
"$project": {
other_fields: {
$first: "$last_data"
},
status: {
$first: "$last_status.status"
}
}
}
])
Working example

Mongodb Group and get other fields from documents

update so Mohammad Faisal has the best solution.However it breaks when a new document is added lol! so i learned a lot from his code and modified it and it Works! =) the code is all the way in the bottom.
But here's what i said..
So i have this document
{"_id":"5ddea2e44eb407059828d740",
"projectname":"wdym",
"username":"easy",
"likes":0,
"link":["ssss"]
}
{"_id":"5ddea2e44eb407059822d740",
"projectname":"thechosenone",
"username":"easy",
"likes":30,
"link":["ssss"]
}
{"_id":"5ddea2e44eb407059828d740",
"projectname":"thanos",
"username":"wiley",
"likes":10,
"link":["ssss"]
}
and basically what i want is the document that contains the highest
likes with it's associated project name
For example the output would be
"projectname":"thechosenone",
"username":"easy",
"likes":30
}
,
{
"projectname":"thanos",
"username":"wiley",
"likes":10,
}
the code i have for this is the following
db
.collection("projects")
.aggregate([
{
$group: {
_id: { username: "$username" },
likes: { $max: "$likes" }
}
},
{
$project:{projectname:1}
}
])
$project gives me a strange output. However,
the output was correct without the $project.
But i wanted to project the projectname, the user and the highest likes. Thanks for hearing me out :)
heres the solution =)
db
.collection("projects")
.aggregate([
{
$sort: {
likes: -1
}
},
{
$group: {
_id: {
username: "$username"
},
likes: {
$max: "$likes"
},
projectname: {
$push: "$projectname"
},
link: {
$push: "$link"
}
}
},
{
$project: {
username: "$_id.username",
projectname: {
$arrayElemAt: ["$projectname", 0]
},
link: {
$arrayElemAt: ["$link", 0]
}
}
}
])
.toArray()
If you don't have to use $group this will solve your problem:
db.projects.aggregate([
{$sort:{likes:-1}},
{$limit:1}
]).pretty()
the result would be
{
"_id" : ObjectId("5ddee7f63cee7cdf247059db"),
"projectname" : "thechosenone",
"username" : "easy",
"likes" : 30,
"links" : ["ssss"]
}
Try this:-
db.collection("projects").aggregate([
{
$group: {
_id: { username: "$username" },
likes: { $max: "$likes" },
projectname: { $push : { $cond: [ { $max: "$likes" }, "$projectname", "" ]}}
}
}
,
{
$project:{
username:"$_id.username",
projectname:{"$reduce": {
"input": "$projectname",
"initialValue": { "$arrayElemAt": ["$projectname", 0] },
"in": { "$cond": [{ "$ne": ["$$this", ""] }, "$$this", "$$value"] }
}},
likes:1
}
}
])

Return fields from different collection Mongoose

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.

findOneAndUpdate: using parent and subdocument id to update [duplicate]

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();

Remove object fields by updating the complete object

This is some data which is stored in a mongoDB document:
{
_id: "123"
order: 1
parent: "Dueqmd64nTxM3u9Cm"
type: "article"
unit: "kg"
}
While all the saved data will be calculated and validated first (that's why I don't use only data = { order: 2 }), I'm using the complete object to update the document:
var data = {
order: 2
parent: "Dueqmd64nTxM3u9Cm"
type: "article"
unit: "kg"
}
Collection.update(
{ _id: 123 },
{ $set: data }
);
This is working.
But if I want to remove some values, it doesn't work:
var data = {
order: 2
parent: "Dueqmd64nTxM3u9Cm"
type: "article"
unit: undefined
}
Collection.update(
{ _id: 123 },
{ $set: data }
);
I'm expecting the unit-field to get removed. But that isn't the case...
To remove fields, use the $unset operator:
data = {
unit: ""
};
Collection.update(
{ _id: 123 },
{ $set: data }
);

Categories