Update a subdocument with one single query or multiple (mongoose)? - mongodb

This is a part of my document:
var actions = {
title: 'Change of windows'
energySavings:[
{
_id: 234324234,
mainCategory: 'Elektricitet',
subCategory: 'Belysning',
value: 1
},
{
_id: 5434534543,
mainCategory: 'Elektricitet',
subCategory: 'Utrustning',
value: 1
}
]
}
I want to be able to update energySavings, add items and remove items. This could be done in three separate queries. Or, I could all do it in just one
var givenAction = JSON.parse(req.body.action);
mongoose.model('Action').findOne({actionId: req.params.actionId}, function(err, action){
action.energySavings = givenAction.energySavings;
action.save(function(err, savedAction){
res.status(200).send(savedAction);
});
})
Way less code then three separate queries. However, if the client side programmer makes a mistake, for example empty the entire array and save it, we will have a problem. Is there a best practice to this, make in one query or several with more control?

You can also update the document with findOneAndUpdate of mongoose. For case the empty array added mistakenly, you need to check if the query is empty or not before updating or adding that.
var givenAction = JSON.parse(req.body.action);
mongoose.model('Action').findOneAndUpdate({
actionId: req.params.actionId
}, {
$set: {
energySavings: givenAction.energySavings
}
}, callback);
You can use $push to add as new items, and $pull to remove items instead of $set to updating it. I don't think it is not a good choice to update energySavings, add items and remove items in one query.

Related

Mongoose findOneAndUpdate an array within an array within a document

I'm trying to update an array that sits inside another array in a document. The schema is like this:
const projectSchema = new mongoose.Schema({
stakeholders: [{
stakeholderTitle: {
type: String,
},
...
subgroup: [{
subgroupTitle: {
type: String
},
subgroupPercent: {
type: Number,
}
}]
}],
and I'm trying to update the 'subgroup' array. I have got the query to work on its parent (the stakeholder array) with the positional $ operator, using the answer to this question I asked previously. So my query looks like this.....
await db.findOneAndUpdate({ find by the id }, { "stakeholders.$.stakeholderTitle": req.body.stakeholderTitle, ... "stakeholders.$.subgroup": req.body.subgroup })
However, this query doesn't work for the 'stakeholders subgroup' array, and makes it null. Looking through the mongo docs for the positional operator it states that 'The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value', which I guess might be my problem.
So how can I do this with a findOneAndUpdate query?
From what I see is you have to specify the object you want to update inside the subgroup array. Try this - (i.e I'm updating the subgroupTitle of the subgroup array);
await db.findOneAndUpdate(
{
_id: userId,
"stakeholders.stakeholderTitle": req.body.stakeholderTitle,
"stakeholders.stakeholderTitle.subgroup.subgroupTitle": req.body.subgroupTitle
},
{$set: {
"stakeholders.stakeholderTitle.subgroup.$.subgroupPercent": somePercentValue,
}
},
);
Also note, it's only the array that you find that you can update. It might not be exactly what you want, but its a step closer

MongoDB update array in multiple nested document

I have a collections like the following
{
_id: 1
rooms:[{
_id: 1,
doors: [{
_id: 1,
name: 'kitchen',
color: 'green'
},{
_id:2,
name: 'bathroom',
color: 'red'
}]
}]
}
I want to update the whole content of specific door by id.
I try this code but it not work.
db.collections.update({_id:1, "rooms._id":1, "rooms.doors._id":2}, {$set: {rooms.$.doors.$: {_id:2, name: 'balcony', color:'yellow'}}}
If I use "rooms.$.doors.0", it will always update the first door in array.
db.collections.update({_id:1, "rooms._id":1, "rooms.doors._id":2}, {$set: {rooms.$.doors.0: {_id:2, name: 'balcony', color:'yellow'}}}
Anyone can help?.
Thank you for your looking.
you should try to avoid nesting multiple array like that. it's too troublesome if you wish to update. In previous version of mongodb, this guide still works, apparently not in latest version of mongodb. Just for fun, if you read the guide, imagine, how many $$ we need to update another nested array.
you can try aggregation, but I think it is easier to bring the the whole document with {_id:1} to server, replace the whole rooms_array and save it.
db.collections.find({_id:1}).exec((err, house)=>{
var rooms = house.rooms.map((r)=> {
return r.doors.map((d) => {
if(r._id === 1 && d._id === 2){
return {_id:2, name: 'balcony', color:'yellow'}
}
else{
return d
}
})
})
house.rooms = rooms
house.save()
})
alternative, perhaps you can do object reference for doors. Instead another document, just include ObjectId that refers to the doors. This door will be another new collection.

Meteor collection get last document of each selection

Currently I use the following find query to get the latest document of a certain ID
Conditions.find({
caveId: caveId
},
{
sort: {diveDate:-1},
limit: 1,
fields: {caveId: 1, "visibility.visibility":1, diveDate: 1}
});
How can I use the same using multiple ids with $in for example
I tried it with the following query. The problem is that it will limit the documents to 1 for all the found caveIds. But it should set the limit for each different caveId.
Conditions.find({
caveId: {$in: caveIds}
},
{
sort: {diveDate:-1},
limit: 1,
fields: {caveId: 1, "visibility.visibility":1, diveDate: 1}
});
One solution I came up with is using the aggregate functionality.
var conditionIds = Conditions.aggregate(
[
{"$match": { caveId: {"$in": caveIds}}},
{
$group:
{
_id: "$caveId",
conditionId: {$last: "$_id"},
diveDate: { $last: "$diveDate" }
}
}
]
).map(function(child) { return child.conditionId});
var conditions = Conditions.find({
_id: {$in: conditionIds}
},
{
fields: {caveId: 1, "visibility.visibility":1, diveDate: 1}
});
You don't want to use $in here as noted. You could solve this problem by looping through the caveIds and running the query on each caveId individually.
you're basically looking at a join query here: you need all caveIds and then lookup last for each.
This is a problem of database schema/denormalization in my opinion: (but this is only an opinion!):
You could as mentioned here, lookup all caveIds and then run the single query for each, every single time you need to look up last dives.
However I think you are much better off recording/updating the last dive inside your cave document, and then lookup all caveIds of interest pulling only the lastDive field.
That will give you immediately what you need, rather than going through expensive search/sort queries. This is at the expense of maintaining that field in the document, but it sounds like it should be fairly trivial as you only need to update the one field when a new event occurs.

Publish all fields in document but just part of an array in the document

I have a mongo collection in which the documents have a field that is an array. I want to be able to publish everything in the documents except for the elements in the array that were created more than a day ago. I suspect the answer will be somewhat similar to this question.
Meteor publication: Hiding certain fields in an array document field?
Instead of limiting fields in the array, I just want to limit the elements in the array being published.
Thanks in advance for any responses!
EDIT
Here is an example document:
{
_id: 123456,
name: "Unit 1",
createdAt: (datetime object),
settings: *some stuff*,
packets: [
{
_id: 32412312,
temperature: 70,
createdAt: *datetime object from today*
},
{
_id: 32412312,
temperature: 70,
createdAt: *datetime from yesterday*
}
]
}
I want to get everything in this document except for the part of the array that was created more than 24 hours ago. I know I can accomplish this by moving the packets into their own collection and tying them together with keys as in a relational database but if what I am asking were possible, this would be simpler with less code.
You could do something like this in your publish method:
Meteor.publish("pubName", function() {
var collection = Collection.find().fetch(); //change this to return your data
_.each(collection, function(collectionItem) {
_.each(collectionItem.packets, function(packet, index) {
var deadline = Date.now() - 86400000 //should equal 24 hrs ago
if (packet.createdAt < deadline) {
collectionItem.packets.splice(index, 1);
}
}
}
return collection;
}
Though you might be better off storing the last 24 hours worth of packets as a separate array in your document. Would probably be less taxing on the server, not sure.
Also, code above is untested. Good luck.
you can use the $elemMatch projection
http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/
So in your case, it would be
var today = new Date();
var yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
collection.find({}, //find anything or specifc
{
fields: {
'packets': {
$elemMatch: {$gt : {'createdAt' : yesterday /* or some new Date() */}}
}
}
});
However, $elemMatch only returns the FIRST element matching your condition. To return more than 1 element, you need to use the aggregation framework, which will be more efficient than _.each or forEach, particularly if you have a large array to loop through.
collection.rawCollection().aggregate([
{
$match: {}
},
{
$redact: {
$cond: {
if : {$or: [{$gt: ["$createdAt",yesterday]},"$packets"]},
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}], function (error, result ){
});
You specify the $match in a way similar to find({}). Then all the documents that match your conditions get pipped into the $redact which is specified by the $cond.
$redact scans the document from top level to bottom. At the top level, you have _id, name, createdAt, settings, packets; hence {$or: [***,"$packets"]}
The presence of $packets in the $or allows the $redact to scan the second level which contain the _id, temperature and createdAt; hence {$gt: ["$createdAt",yesterday]}
This is async, you can use Meteor.wrapAsync to wrap around the function.
Hope this help

mongo: multiple queries or not?

I'm wondering the best way to query mongo db for many objects, where each one has an array of _id's that are attached to it. I want to grab the referenced objects as well. The objects' schemas looks like this:
var headlineSchema = new Schema({
title : String,
source : String,
edits : Array // list of edits, stored as an array of _id's
...
});
...and the one that's referenced, if needed:
var messageSchema = new Schema({
message : String,
user : String,
headlineID : ObjectId // also contains a ref. back to headline it's incl. in
...
});
One part of the problem (well, depending if I want to keep going this route) is that pushing the message id's is not working (edits remains an empty array [] afterwards) :
db.headline.update({_id : headlineid}, {$push: {edits : messageid} }, true);
When I do my query, I need to grab about 30 'headlines' at a time, and each one could contain references to up to 20 or 30 'messages'. My question is, what is the best way to fetch all of these things? I know mongo isn't a relational db, so what I'm intending is to first grab the headlines that I need, and then loop through all 30 of them to grab any attached messages.
db.headline.find({'date': {$gte: start, $lt: end} }, function (err, docs) {
if(err) { console.log(err.message); }
if(docs) {
docs.forEach(function(doc){
doc.edits.forEach(function(ed){
db.messages.find({_id:ed}, function (err, msg) {
// save stuff
});
});
});
}
});
This just seems wrong, but I'm unsure how else to proceed. Should I even bother with keeping an array of attached messages? I'm not married to the way I've set up my schema, either. If there is a better way to track relationships between them, or a better query to achieve this, please let me know.
Thanks
Does each message belong to only one headline? If so, you can store the headline id as part of each message. Then for each headline, do:
db.messages.find({headline_id: current-headline-id-here})
You could try using the $in operator for selecting a list of ObjectIds
http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24in