I am trying to update a collection using Mongoose 3.10.8
let findQuery = {username: data.username};
let updateQuery ={
"$addToSet":{
"schemes":{
"$each":[data.schemes]
}
}
};
return new Promise((resolve, reject) => {
this.modelInstance.update(findQuery, updateQuery).exec((err, result) => {
if(err) {
console.log(err);
} else {
resolve(result);
}
})
})
But my code triggers the following error:
{ MongoError: '$set' is empty. You must specify a field like so: {$set: {: ...}}
If I enable debug mode with mongoose.set('debug',true), I see that there is an empty $set in the query.
Mongoose: studentnotifications.update(
{ username: 'username1' },
{
'$set': {},
'$setOnInsert': {
created_at: new Date("Fri, 15 Sep 2017 12:51:36 GMT")
}, '$addToSet': {
schemes: {
'$each': [ { schemeName: 'scheme1' } ]
}
}
},
{ overwrite: undefined }
)
Then am I supposed to disable set?
OK, after reading the following document
https://github.com/Automattic/mongoose/wiki/3.8-Release-Notes#modelupdate-now-supports-overwrite
I had to set an option {overwrite:true}
But how do I disable to the updatedAt: new Date("Fri, 15 Sep 2017 13:02:28 GMT"), created_at: new Date("Fri, 15 Sep 2017 13:02:28 GMT")
Because I am getting the error
{ MongoError: The dollar ($) prefixed field '$addToSet' in '$addToSet' is not valid for storage.
If I remove the updatedAt field the data is getting updated while running it in the command prompt
Related
I have a Mongo-DB with one record, the following:
{
"_id": "608c0f5ceee3254fcb0f9d1f",
"outDated": false,
"domain": "test.com",
"releaseDate": "Tue May 04 2021 02:00:00 GMT+0200 (Central European Summer Time)",
"__v": 0
}
My Node.js-code looks like this:
getDomainsFromTodaysDate: async () => {
await module.exports.connectToDb();
return Domains.find({
releaseDate: { $gte: new Date(dayjs().format('YYYY-MM-DD')) }
})
.then(res => {
return res;
})
.catch(error => {
console.log(`getDomainsFromTodaysDate error: ${error}`);
});
}
The dayjs-function output is:
const test = new Date(dayjs().format('YYYY-MM-DD'));
console.log(test) // 2021-04-30T14:30:11.000Z
console.log(typeof test) // object
Mongoose-debug gives me this:
Mongoose: domains.find({ releaseDate: { '$gte': 'Fri Apr 30 2021 16:06:06 GMT+0200 (Central European Summer Time)' }}, { projection: {} })
Still I dont get the record stated above from the DB. What am I doing wrong? Without the releaseDate-filter I receive the record.
Edit:
Here is part of the Schema where releaseDate appears:
return new mongoose.Schema({
createdAt: String,
outDated: Boolean,
domain: String,
releaseDate: Date
});
From Robo3t:
I need to use both $set and $push within one update call. I have done it as mentioned in $push and $set in same MongoDB update
Now my code is
await this.collection.updateOne(
{ id },
[
{
$set: {
...changes,
"metadata.updated": new Date(),
"metadata.updatedBy": { id: updatedBy }
}
},
{
$push: {
history: {
user: { id: updatedBy },
type: HistoryType.TIME_ENTRY_UPDATED,
date: new Date(),
args: changes
}
}
}
]
);
But it is throwing MongoError: Unrecognized pipeline stage name: '$push'
Whats wrong with my code.
My mongodb server version - 4.2.9
node package client version - ^2.2.36
I'm using mongodb node native driver, and
My installed mongodb version is v4.4.5, and my node version is v14.15.4
I faced this problem too and I change update body command to below and it's worked for me.
await this.collection.updateOne(
{ id },
{
$set: {
...changes,
"metadata.updated": new Date(),
"metadata.updatedBy": { id: updatedBy }
},
$push: {
history: {
user: { id: updatedBy },
type: HistoryType.TIME_ENTRY_UPDATED,
date: new Date(),
args: changes
}
}
}
);
How can I update a field with new properties that is initially set to be an empty object?
For example, I have the following schema:
import mongoose from 'mongoose';
var RunSchema = mongoose.Schema(
{
runId: { type: String },
reports: {
cookieSummary: {
name: String,
path: String
}
}
}
)
export default mongoose.model('Run', RunSchema);
And I'm trying to update the following document:
{
"_id": {
"$oid": "5a0565c2537e0b5d9d08ee6b"
},
"__v": 0,
"reports": {},
"runId": "8r4LNN3fRqd3qNgdW"
}
But when I run this code, it returns undefined:
Run.findOneAndUpdate({runId: '8r4LNN3fRqd3qNgdW'},
{
$set: {'reports.cookieSummary': { 'name': 'test' }},
}, (err, doc) => { console.log(doc) })
The object notation works after adding type to fields, like this: name: { type: String }
Try to use dot notation, as you're setting just one field:
Run.findOneAndUpdate(
{ runId: '8r4LNN3fRqd3qNgdW' },
{ $set: {'reports.cookieSummary.name': 'test' } },
(err, doc) => { console.log(doc) })
According to the docs, the command you're using should work but you write it wrongly. Try like this:
Run.findOneAndUpdate(
{ runId: '8r4LNN3fRqd3qNgdW' },
{ $set: { 'reports.cookieSummary': {'name': 'test'} } },
(err, doc) => { console.log(doc) })
if it does not work, maybe mongo expect that the object matches its schema when you use the command like this. But I don't think so.
Let me know.
Your query for update a document is good only the mistake is at the end of curly braces of $set. You entered un-necessary comma at the end that is actually creating problem in this case. So I suggest you to remove it and run this :
Run.findOneAndUpdate({runId: '8r4LNN3fRqd3qNgdW'},
{
$set: {'reports.cookieSummary': { 'name': 'test' }}
}, (err, doc) => { console.log(doc) });
and then see. Rest of your query is fine.
Hope It will work for you.
Thanks.
Try using below code, it will update the document and return the updated document.
var Q = require('q');
var deferred = Q.defer();
Run.findOneAndUpdate({ runId: '8r4LNN3fRqd3qNgdW' }, { $set: { 'reports.cookieSummary.name': 'test' } }, { new: true },
(err, doc) => {
console.log(doc);
deferred.resolve(doc);
});
return deferred.promise;
I made a small change. Test this solution.
Run.findOneAndUpdate({runId: '8r4LNN3fRqd3qNgdW'},
{
$set: {"reports": {'cookieSummary':{'name': 'test'}}},
}, (err, doc) => { console.log(doc) })
Here is a schema that I am working on.
var testSchema = mongoose.Schema({
userCreated : {
type : mongoose.Schema.Types.ObjectId,
ref : "User"
},
points : {type: Number, default: 0},
numVotes : {type: Number, default: 0},
createdAt : Date,
updatedAt : Date,
}, { timestamps : true });
Now, I am trying to write a function that will increment two fields on this document (points and numVotes, as well as an additional points field that exists on the user schema.
Here is my attempt.
testSchema.statics.incrementTest = function(id, ...) {
this.findByIdAndUpdate(id, {$inc : {
points : 5,
numVotes : 1,
'userCreated.points' : 5
}}).exec();
}
Now, this code that I have written does not work. However, when I comment out the 'userCreated.points' : 5 line, the other two fields do increment as expected. My question is, what is the best way using mongoose to update the fields on a document and the fields on a subdocument at the same time?
The data here is contained in different collections, so no single update statement is able to increment counters in both at the same time.
In order to get a consistent view you are going to need to "chain" your update statements and use the return results of each to build the response.
Depending on your needs you can either use a Promise with this:
testSchema.statics.incrementTest = function(id) {
var self = this;
return new Promise(function(resolve,reject) {
self.findByIdAndUpdate(
id,
{
"$inc": {
"points": 5,
"numVotes": 1
}
},
{ "new": true }
).then(function(test) {
var userModel = test.schema.path("userCreated").options.ref;
mongoose.model(userModel).findByIdAndUpdate(
test.userCreated,
{ "$inc": { "points": 5 } },
{ "new": true }
).then(function(user) {
test.userCreated = user;
resolve(test);
})
}).catch(reject)
})
};
Which you can then invoke on your model:
Test.incrementTest("56fe279d363ce91765d9e39e").then(function(test) {
console.log(JSON.stringify(test,undefined,2));
}).catch(function(err) {
throw err;
})
Or you can use async.waterfall from the async library if that suits you better:
testSchema.statics.incrementTest = function(id,callback) {
var self = this;
async.waterfall(
[
function(callback) {
self.findByIdAndUpdate(
id,
{
"$inc": {
"points": 5,
"numVotes": 1
}
},
{ "new": true },
callback
)
},
function(err,test) {
if (err) callback(err);
var userModel = test.schema.path("userCreated").options.ref;
mongoose.model(userModel).findByIdAndUpdate(
test.userCreated,
{ "$inc": { "points": 5 } },
{ "new": true },
function(err,user) {
if ( typeof(user) !== "undefined" )
test.userCreated = user;
callback(err,test);
}
);
}
],
callback
);
};
Which has a similar usage:
Test.incrementTest("56fe279d363ce91765d9e39e",function(err,test) {
if (err) throw err;
console.log(JSON.stringify(test,undefined,2));
})
Both should be giving you a result back that shows the incremented data in both objects for both collections:
{ points: 5,
numVotes: 1,
__v: 0,
userCreated: { points: 5, __v: 0, _id: 56ff1aa6dba6d13e798fc894 },
createdAt: Sat Apr 02 2016 12:04:38 GMT+1100 (AEDT),
updatedAt: Sat Apr 02 2016 12:04:38 GMT+1100 (AEDT),
_id: 56fe279d363ce91765d9e39e }
Here is one document from my mongoose collection:
{ _id: 55ae7be99e772a7c025a0a7b,
id: 'foo',
isBoolean: true,
kind: 'bar',
values:
[
{ y: 0,
x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit),
_id: 55ae7ae05596dd740eb8a204 },
{ y: 0,
x: Wed Aug 26 2015 11:12:57 GMT+0200 (Mitteleuropäische Sommerzeit),
_id: 55ae7ae05596dd740eb8a203 },
{ y: 1,
x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit);
_id: 55ae7be91fa1511c1795c5ae }
]
}
So, I need to find all documents, that have specific value.x. After that I need to return that document with all fields and found value elements.
Wenn I try it with
.find({'values.x': mTime1})
.select({
'_id' : 1 ,
'id' : 1 ,
'kind' : 1 ,
'isBoolean' : 1 ,
'values' : {$elemMatch: {x: time1}}
})
I receive just the firsTt found value:
{ ...
values:
[ { exceeds: null,
y: 0,
x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit),
_id: 55ae7d86870b92b8056bed4c } ]
}
Next version
.aggregate({"$unwind" : "$values"}, {"$match" : {"values.x": time1}},
{"$group" : {
'_id' : '$_id',
'values' : {$addToSet: "$values"}
});
returns all matched values except other fields...
My goal is:
{ _id: 55ae7be99e772a7c025a0a7b,
id: 'foo',
isBoolean: true,
kind: 'bar',
values:
[
{ y: 0,
x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit),
_id: 55ae7ae05596dd740eb8a204 },
{ y: 1,
x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit);
_id: 55ae7be91fa1511c1795c5ae }
]
}
Have you any idea, how to achieve that with mongoose?? :)
Update:
Thanks to tsturzl, I solved it with next function (without changing the model):
self.aggregate(
{'$unwind' : '$values'},
{'$match' : { 'values.x': mTime1} },
{'$group' : {
'_id' : '$_id',
'values' : {$push: '$values'}
}}
)
.exec(
function(err, results) {
if(err) return done(err);
var values = {}; // hashMap to group values
results.forEach(function(item) {
if(item.values) // prevent empty results
values[item._id] = item.values;
});
self.find({_id:{$in: _.keys(values)}})
.exec(function(err, items) {
if(err) return done(err);
var results = items.map(function(item) {
item.values = values[item._id];
return item;
});
done(err, results); // callback results
});
});
The problem with using elemMatch in a projection is that it accesses a single item. Similar to an array arr[1], elemMatch fetches the index of the item in the array and then projects that array item at that index. So you can only retrieve one sub-document using this method.
You can use an aggregation similar to this
[
{$match: {'values.x': mTime1}}, //match before to reduce size of unwound
{$unwind: '$values'},
{$match: {'values.x': mTime1}},
{$group: {
_id: '$_id',
id: {$first: '$id'},
kind: {$first: '$kind'},
isBoolean: {$first: '$isBoolean'},
values: {$push: '$values'}
}
]
I've tested this to work fine locally on an array of subdocuments.
It's possible that your approach is best suited to being restructured. You should remodel your data so that your values have their own collection and reference by _id. In this cause I would store the reference in the values collection.
Remove values field from this collection
{ _id: 55ae7be99e772a7c025a0a7b,
id: 'foo',
isBoolean: true,
kind: 'bar'
}
Values Model:
{
y: Number,
x: Date,
parentId: {type: ObjectId, ref: "myColl"} //make sure you require ObjectId and rename the reference
}
You can then do something like this
ValuesModel.find({
x: mTime1
}).exec(function(err, results) {
var ids = {}; //hashMap to group values
var idsArr = []; //array of ids
results.forEach(function(item) {
if(!ids.hasOwnProperty(items.parentId.toString())) {
ids[items.parentId.toString()] = [];
idArr.push(item.parentId);
}
ids[items._id.toString()].push(item);
});
myColl.find({_id:{$in: idsArr}})
.exec(function(err, items) {
var results = items.map(function(item) {
item.values = ids[item._id.toString()];
return item;
});
done(results); //callback results
});
});
This will grab all values that you queried for, then group them in a hashMap and push all the parentIds to an array. Then I query for that array of parentIds. I take the hashMap, reference it by the id in the hashMap and create a new field for .values in the parent document. This will prevent you from having to use aggregations, which aren't as scalable, and will allow you to easily query the values table. If you want to find only one value you can simply use the mongoose populate method. The downfall to this approach is that you need to do more work in your code, and you have 2 round trips. However, this should still be more efficient than an aggregation.
This can be used to create a reusable method to simplify your code if you query into values a lot
function queryValues(query, done) {
ValuesModel.find(query).exec(function(err, results) {
if(err) return done(err);
var ids = {}; //hashMap to group values
var idsArr = []; //array of ids
results.forEach(function(item) {
if(!ids.hasOwnProperty(items.parentId.toString())) {
ids[items.parentId.toString()] = [];
idArr.push(item.parentId);
}
ids[items._id.toString()].push(item);
});
myColl.find({_id:{$in: idsArr}})
.exec(function(err, items) {
if(err) return done(err);
var results = items.map(function(item) {
item.values = ids[item._id.toString()];
return item;
});
done(null, results); //callback results
});
});
}
Then you can just call queryValues({x: mTime1}, function(err, results){...});, and you can pass any query you want and the function will handle populating the parent document without fetching duplicate data for maximum efficiency.
One thing I might also recommend is that you define this method as a schema static method in your model definition so you can tuck this code away and never have to worry. See: http://mongoosejs.com/docs/api.html#schema_Schema-static