Node JS, Mongoose How to query schema via aggregate? - node.js

I am new to using MongoDB and I am trying to update update my document using aggregate $set pipeline. However I have been trying this for a few days and there is still no success. I am trying to update by querying ObjectId and replacing matched result (singular) with the key value pair I specified. I tested the aggregate on Mongo Compass and it works. Does anyone know how to use aggregate for Node JS?
updateOne query I tried
let query = {"_id": ObjectId('theObjectIamUpdating')};
response = await newForm.updateOne(query, payload);
aggregate query I tried
response = await newForm.updateOne([
{$match: {"_id": ObjectId(update_id)}},
{$set: {
"data.velo": [
[1, 2, 3], [5, 7]
]
}}
]);
newForm Mongoose Schema
data: {
date: {
type: Array,
required: false,
trim: true,
},
speed: {
type: Array,
required: false,
trim: true,
},
velo: {
type: Array,
required: false,
trim: true,
}
},
calc: {
date: {
type: Array,
required: false,
trim: true,
},
speed: {
type: Array,
required: false,
trim: true,
},
velo: {
type: Array,
required: false,
trim: true,
}
}
UPDATE
I my updateOne() has succeeded, but my documents are not getting updated.
the result I got after logging response that I awaited
Data received {
acknowledged: true,
modifiedCount: 0,
upsertedId: null,
upsertedCount: 0,
matchedCount: 0
}
POST /api/compute/calculate 200 16 - 79.821 ms
Additionally, this is the MongoDB aggregate that worked when I tried on Mongo Compass
pipeline = $match > $set
**$match**
{
"_id": ObjectId('62e2295060280132dbcee4ae')
}
**$set**
{
"data.velo": [
[1, 2, 3], [5, 7]
]
}
where velo is one of the key value pairs in data, and the set result replaced only the data in data.velo.

As prasad_ mentioned in the comment section, I indeed found some mistakes with regards to my syntax of updateOne().
Correct Syntax for updateOne()
let response = await Form_csv_metadata2.updateOne({ '_id': [update_id] }, [
{//delete this line $match: {"_id": ObjectId(update_id)},
$set: {
"data.velo": [[1, 2, 3], [5, 7]]
}}
]);
As the official document has mentioned the parameters are: Query, UpdateData and Option. I made the mistake as my query was placed in my UpdateData() param (the $match). It should have been a param by itself as a query (query uses same syntax as find()). Another note is that if I were to use a pipeline aggregate, it should have been { $match: {...}, $set: {...} } instead of { {$match: {...}}, {$set: {...}} }

Related

MongoDB: executionTimeMillis increased when adding index

I am pretty confused to why making the searched fields to be "index" is making the query "theoretically" slower.
I have a not very big collection of items (6240) and all of them have the following structure.
const SomeSchema = new mongoose.Schema({
data: String,
from: {
type: Number,
},
to: {
type: Number,
},
timeStamp: {
type: Date,
default: new Date()
}
})
SomeSchema.set('toJSON', {
getters: true,
transform: (doc, ret) => {
delete ret.from
delete ret.to
return sanitizeSensitiveProperties(ret)
}
})
export const Some = mongoose.model('Some', SomeSchema, 'somethings')
The strange thing came when after trying to improve the query I changed the schema to be
...
from: {
type: Number,
index: true
},
to: {
type: Number,
index: true
},
...
With this schema I run the following query
db.rolls.find({from: {$lte: 1560858984}, to: {$gte: 1560858984}}).explain("executionStats")
This are the results NOTE THAT THE 1st ONE is the one without index
"executionTimeMillis" : 6,
"totalKeysExamined" : 0,
"totalDocsExamined" : 6240,
"executionTimeMillis" : 15,
"totalKeysExamined" : 2895,
"totalDocsExamined" : 2895,
Does this result make any sense, or is just the mongo .explain() function messing around?
As you can see I am using the Mongoose Driver in the version ^5.5.13 and I am using Mongo in the version 4.0.5

How do I create mongoose unique index with specific value?

I have collection named TradeRequest with the following schema
const TradeRequestSchema = mongoose.Schema({
status: {
type: String,
enum: ["opened", "rejected", "accepted"],
default: "opened"
},
_requester: {
required: true,
type: mongoose.Schema.Types.ObjectId
},
_requestedBook: {
required: true,
type: mongoose.Schema.Types.ObjectId
}
});
My database should have only one open request for a book by any person.
So I added a unique index like so -
TradeRequestSchema.index({_requester: 1, _requestedBook: 1, status: 1}, {unique: true});
Problem is this prevents some data to be entered in the database which should be allowed.
For example it prevents my database to have following documents which is fine for my use case -
{_id: 1, _requester: 12345, _requestedBook: 9871212, status: "opened"}
{_id: 2, _requester: 12345, _requestedBook: 9871212, status: "opened"}
But it also prevents the following which is wrong for my use case.
For example if my database already has following documents -
{_id: 3, _requester: 12345, _requestedBook: 9871212, status: "closed"}
{_id: 4, _requester: 12345, _requestedBook: 9871212, status: "opened"}
I cannot change request with id 4 to closed now which is wrong.
Basically what I want is to have a single opened request for a book but multiple closed requests.
How do I achieve this?
Mongodb has many types of indices like unique indices, sparse indices and partial indices. Your case doesn't need just the unique index, it needs unique partial index.
Try creating something like -
db.<<collectionName>>.createIndex(
{_requester: 1, _requestedBook: 1, status: 1},
{unique: true, partialFilterExpression: { "status" : "opened" }}
)
Hope it helps...

Unable to update the data in mongodb

I need to update some fields
i am using mongoose driver and express js
schema:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ProfilesSchema = new Schema({
presentRound: {
type: Number,
default: 1
},
scheduleInterviewStatus: {
type: Boolean,
default: false
},
interviewStatus: String,
ratings: [{
round: Number,
rating: Number,
feedback: String,
interviewer: String,
roundStatus: String
}]
});
module.exports = mongoose.model('Profiles', ProfilesSchema);
so in these i need to update by id presentRound scheduleInterviewStatus interviewStatus and roundStatus(in ratings array by matching round number)
Before updating:
presentRound: 1,
scheduleInterviewStatus: true,
interviewStatus: "on-hold",
ratings: [{
round: 1,
rating: 3,
feedback: "good communication skills",
interviewer: "Vishal",
roundStatus: "second opinion"
}]
After Updating:
presentRound: 2,
scheduleInterviewStatus: false,
interviewStatus: "in-process",
ratings: [{
round: 1,
rating: 3,
feedback: "good communicationskills",
interviewer: "Vishal",
roundStatus: "selected"
}]
i have tried to run the query in robomongo first but getting error
Error: Fourth argument must be empty when specifying upsert and multi with an object.
Query:
db.getCollection('profiles').update({
"_id": ObjectId("57a9aa24e93864e02d91283c")
}, {
$set: {
"presentRound": 2,
"interviewStatus":"in process",
"scheduleInterviewStatus": false
}
},{
"ratings.$.round": 1
},{
$set: {
"ratings.roundStatus":"selected"
}
},
{ upsert: true },{multi:true})
I have no idea where i am going wrong
Please help.
Your update statement is incorrect, it has misplaced arguments - you are putting multiple $set operations and options as different parameters to the update method; they should be under separate designated update parameters. The correct Node.js syntax is:
update(selector, document, options, callback)
where selector is an object which is the selector/query for the update operation, document is also an object which is the update document and finally an optionsobject which by default is null and has the optional update settings.
Here you are doing
update(selector, document, selector, document, options, options, callback)
In which mongo is updating the collection using the first two parameters as correct and it naturally throws the error
Error: Fourth argument must be empty when specifying upsert and multi
with an object.
because you have too many incorrect parameters specified.
Also, you have incorrect usage of the positional operator. It should be part of the document to be updated, not in the query.
For the correct implementation, follow this update
db.getCollection('profiles').update(
/* selector */
{
"_id": ObjectId("57a9aa24e93864e02d91283c"),
"ratings.round": 1
},
/* update document */
{
"$set": {
"presentRound": 2,
"interviewStatus": "in process",
"scheduleInterviewStatus": false,
"ratings.$.roundStatus": "selected"
}
},
/* optional settings */
{ upsert: true, multi: true }
)
replace {upsert:true} -> {upsert:true,strict: false}

Mongoose get document as an array of arrays based on a key

I don't really know how to frame the question but what I have is the following schema in mongoose
new Schema({
gatewayId: { type: String, index: true },
timestamp: { type: Date, index: true },
curr_property:Number,
curr_property_cost:Number,
day_property:Number,
day_property_cost: Number,
curr_solar_generating: Number,
curr_solar_export:Number,
day_solar_generated:Number,
day_solar_export:Number,
curr_chan1:Number,
curr_chan2:Number,
curr_chan3:Number,
day_chan1:Number,
day_chan2:Number,
day_chan3:Number
},{
collection: 'owlelecmonitor'
});
and I want to be able to query all the documents in the collection but the data should be arranged inside the array in the following format
[ [{
gatewayId: 1,
timestamp: time
....
},
{
gatewayId: 1,
timestamp: time2
....
}],
[{
gatewayId: 2,
timestamp: time
....
},
{
gatewayId: 2,
timestamp: time2
....
}],
[{
gatewayId: 3,
timestamp: time
....
},
{
gatewayId: 3,
timestamp: time2
....
}]
];
Is there a way that I can do this in mongoose instead of retrieving the documents and processing them again ?
Yes, it's possible. Consider the following aggregation pipeline in mongo shell. This uses a single pipeline stream comprising of just the $group operator, grouping all the documents by gatewayId and creating another array field that holds all the grouped documents. This extra field uses the accumulator operator $push on the system variable $$ROOT which returns the root document, i.e. the top-level document, currently being processed in the aggregation pipeline stage.
With the cursor returned from the aggregate() method, you can then use its map() method to create the desired final array. The following mongo shell demonstration describes the above concept:
var result = db.owlelecmonitor.aggregate([
{
"$group": {
"_id": "$gatewayId",
"doc": {
"$push": "$$ROOT"
}
}
}
]).map(function (res){ return res.doc; });
printjson(result);
This will output to shell the desired result.
To implement this in Mongoose, use the following aggregation pipeline builder:
OwlelecMonitorModel
.aggregate()
.group({
"_id": "$gatewayId",
"doc": {
"$push": "$$ROOT"
}
})
.exec(function (err, result) {
var res = result.map(function (r){return r.doc;});
console.log(res);
});

Mongo/Mongoose Invalid atomic update value error

I am trying to write to write an update to a Mongo document using the Mongoose findOneAndUpdate function. Essentially, I have a document that has an array of another Schema in it, and when I attempt to append more of those schema type, I get the following error:
[Error: Invalid atomic update value for $__. Expected an object, received object]
I'm having a hard time figuring out what this error even means, much less what its source is.
The data I'm attempting to update is as follows:
{ section_id: 51e427ac550dabbb0900000d,
version_id: 7,
last_editor_id: 51ca0c4b5b0669307000000e,
changelog: 'Added modules via merge function.',
committed: true,
_id: 51e45c559b85903d0f00000a,
__v: 0,
modules:
[ { orderId: 0,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 1,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 2,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 3,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 4,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 5,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] } ] }
The only difference is that when I retrieve it, there are three fewer modules, and I append some new ones to the array.
Would love to hear any thoughts, at least as to what the error means!
This is probably because the updated object is still a Mongoose object.
Try to convert it to a JS object before the findOneAndUpdate
object = object.toString()
And delete any potential ID attribute
delete object._id
Or simply
object = object.toObject();
I had the same problem and it turned out I was using $push incorrectly. I was doing
{$push:thing_to_push}
But it needed to be
{$push:{list_to_push_into:thing_to_push}}
#Magrelo and #plus led me to an answer that worked. Something like:
MyModel.findOneAndUpdate({ section_id: '51e427ac550dabbb0900000d' }, mongooseObject.toObject(), { upsert: true }, function(err, results) {
//...
});
Try passing update parameter value as string instead of mongoose model object. I was getting same error when I use to pass model object. Below is the code difference.
Code that was having issue:
updateUser: function(req, res) {
**var updatedUserModel = new Users(req.body);**
Users.findOneAndUpdate({tpx_id:req.params.id}, {$set:**updatedUserModel**}, function(err, User){
...
}
}
Working code:
updateUser: function(req, res) {
Users.findOneAndUpdate({tpx_id:req.params.id}, {$set:**req.body**}, function(err, User) {
...
}
I had the same issue. I ended up by using finOne() method
create a new one if no found, update the existing one if found.
I know there are two operations. but I just haven't find any way to do it in one step.
Another thing to check is if you are sending passing an array of changes to $set. The error message that I received when calling something like this:
db.products.update( { sku: "abc123" },
{ $set: [
{quantity: 500},
{instock: true}
]
}
)
Gave me the [Error: Invalid atomic update value for $set. Expected an object, received object]
Changing it to an object worked.
db.products.update( { sku: "abc123" },
{ $set: {
quantity: 500,
instock: true
}
}
)

Resources