i am working on node js project, i have a mongodb collection of orders in which i want to add a field to every document , the problem is that i want to pass a function as the value of this new field and the arguments of the function are two other fields of the document , this is an example that will make you understand .
my collection :
[
{
_id:"eyxwapfhiezfe664ec",
orderPrice : 20,
createdAt : 2021-01-15T17:16:25.844Z
endedAt : 2021-01-15T17:20:25.844Z
}
{
_id:"eyxwlcfeojrfeoc",
orderPrice : 50,
createdAt : 2021-01-15T17:16:25.844Z
endedAt : 2021-01-15T17:20:25.844Z
}
{
_id:"eyxwapfhiseflflpsssc",
orderPrice : 20,
createdAt : 2021-01-15T17:16:25.844Z
endedAt : 2021-01-15T17:20:25.844Z
}
the field i want to add is completedTime which is the difference between createdAt and endedAt i have a function differenceBetweenDates which takes two dates as argument createdAt and ended at but i don't know how to pass this function to $set in update .
So to be more clear i want to add this field to every document :
CompletedTime: differenceBetweenDates(createdAt, endedAt)
thank you .
If you are using MongoDB 4.2, you can pass aggregation expressions as part of the update.
db.collection.updateMany({},[{ $set:{ CompletedTime: {$subtract:["$endedAt", "$createdAt" ]}}}])
In MongoDB 4.4 you might also use an aggregation pipeline with a $merge stage to merge the result set back into the original collection:
db.collection.aggregate([
{ $set: { CompletedTime: {$subtract:["$endedAt", "$createdAt" ]}}},
{ $merge: "collection" }
])
Related
I have lists of data which I want to save and if already exist update.
I can do that using loop. But Is there any other way like insertMany which only supports insert but I want to insert and update too in bulk.
You can use the bulk update feature that Mongo driver provides. Instead of invoking the transactions in a loop, you may add them to a bulk transaction and execute as a batch.
First you need to initialize the bulk operation, ordered / unordered:
var bulk = db.collection.initializeUnorderedBulkOp();
or
var bulk = db.collection.initializeOrderedBulkOp();
Then you can go on adding transactions to the bulk object.
bulk.insert( {
// attributes
} ); // insert operation
or
bulk.find( {
// query attributes
} ).update( {
$set: {
// set attributes
} } ); // update operation
In the end you need to call
bulk.execute();
I don't know if this would serve your purpose.
Please refer this link:
https://docs.mongodb.com/manual/reference/method/Bulk/
Use updateMany with option { upsert: true } which update the document if it is already exists otherwise insert the new document.
Find below example with restaurant collection
{ "_id" : 1, "name" : "Central Perk Cafe", "violations" : 3 }
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "violations" : 2 }
{ "_id" : 3, "name" : "Empire State Sub", "violations" : 5 }
{ "_id" : 4, "name" : "Pizza Rat's Pizzaria", "violations" : 8 }
The query below update the documents with violations equal to 4 and if the document not exists insert new document.
db.restaurant.updateMany(
{ violations: 4 },
{ $set: { "name" : "Eat and Treat" } },
{ upsert: true }
);
Find more details here:
https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
I am trying to count the number of models in a collection based on a property:
I have an upvote model, that has: post (objectId) and a few other properties.
First, is this good design? Posts could get many upvotes, so I didn’t want to store them in the Post model.
Regardless, I want to count the number of upvotes on posts with a specific property with the following and it’s not working. Any suggestions?
upvote.count({‘post.specialProperty’: mongoose.Types.ObjectId(“id”), function (err, count) {
console.log(count);
});
Post Schema Design
In regards to design. I would design the posts collection for documents to be structured as such:
{
"_id" : ObjectId(),
"proprerty1" : "some value",
"property2" : "some value",
"voteCount" : 1,
"votes": [
{
"voter": ObjectId()// voter Id,
other properties...
}
]
}
You will have an array that will hold objects that can contain info such as voter id and other properties.
Updating
When a posts is updated you could simply increment or decrement the voteCountaccordingly. You can increment by 1 like this:
db.posts.update(
{"_id" : postId},
{
$inc: { voteCount: 1},
$push : {
"votes" : {"voter":ObjectId, "otherproperty": "some value"}
}
}
)
The $inc modifier can be used to change the value for an existing key or to create a new key if it does not already exist. Its very useful for updating votes.
Totaling votes of particular Post Criteria
If you want to total the amount for posts fitting a certain criteria, you must use the Aggregation Framework.
You can get the total like this:
db.posts.aggregate(
[
{
$match : {property1: "some value"}
},
{
$group : {
_id : null,
totalNumberOfVotes : {$sum : "$voteCount" }
}
}
]
)
I have a straightforward mongo collection with an array of subdocuments. I'm trying to do the oft asked "upsert a subdocument in an array". I have read all questions on this topic, but can't seem to get it to work.
Data structure for game_managers:
{
"_id" : ObjectId("555cf465715ff974fb09221f"),
"game_id" : "123456789",
"players" : [
{
"request_email" : "thebigcheese#foobar.com",
"request_notes" : "I love mongo!",
"user_id" : ObjectId("551eb55f555b404d68b88063")
},
{
"request_email" : "morecowbell#example.com",
"request_notes" : "I love oysters!",
"user_id" : ObjectId("551eb55f555b404d68b88063")
}
]
}
When I try to Create / Update with the following code, it always overwrites the first element. I can't get it to even
var col = db.mongo.collection('game_managers');
// Upsert a game manager record for the game
col.update( {game_id:game.place_id}, {$setOnInsert:{game_id:game.game_id}}, { upsert: true }, function(err, result, upserted) {
// Append or update game manager record.
col.update(
{game_id:game.place_id},
{$addToSet: {"players":fields}},
function(err, result) {
next();
}
);
});
I modelled the code from this similar question however it doesn't apply to arrays of subdocuments. I do not want to $pull, and then $push a new element, as the subdocument will ultimately have timestamps and some comments[{},{},{}] subdocs on them.
This is my code:
this.aggregate(
{
$match: {
measurer_code : parseInt(_measurer_code),
user_code :parseInt(_user_code),
time : {$gte : iDate, $lte : eDate}
}
},
{
$group:{
sum: {$sum: "$value"},
cant: {$sum: 1}
}
},cb);
The collection has the following structure:
user_code : Number,
measurer_code : Number,
value : Number,
time : Date
Returns undefined. But if i run only $match or only $group returns documents.
Someone can help me?
Thanks!
When you group you have to specify how you want to group the data.
If you want count and sum across all the documents, you have to add "_id":null or "_id":1 (any constant) in the $group document. If you want to group by field "foo" you have to add "_id":"$foo" to the document.
In a new version on MongoDB we can use an $elemMatch projection operator to limit the response of a query to a single matching element of an array. http://docs.mongodb.org/manual/reference/projection/elemMatch/
But it seems doesn't work yet in mongoose 3 here is the example:
{
_id: ObjectId(5),
items: [1,2,3,45,4,67,9,4]
}
Folder.findOne({_id: Object(5)}, {$elemMatch: {$in: [1,67,9]}})
.exec(function (err, doc) {
});
I'm expected to get the follows doc:
{
_id: ObjectId(5),
items: [1,67,9]
}
But unfortunately what I'm getting is document with all items:
{
_id: ObjectId(5),
items: [1,2,3,45,4,67,9,4]
}
The mongodb docs here are misleading, we'll get them updated.
What its saying is that you can now use $elemMatch in your projection, that is, your field selection:
https://gist.github.com/3640687
See also: https://github.com/learnboost/mongoose/issues/1085
[Edit] pull request for docs sent: https://github.com/mongodb/docs/pull/185
Firstly, you are missing the items field name in front of the $elemMatch operator. Your query should read
Folder.findOne({_id: Object(5)}, {items: {$elemMatch: {$in: [1,67,9]}}})
.exec(function (err, doc) { });
But this would still not return the desired result, because as stated in the documentation:
The $elemMatch projection will only match one array element per source
document.
So you would only get back something like:
{
_id: ObjectId(5),
items: [1]
}
I haven't got mongoose set up to do this with node, but you can also get the result you want using the new aggregation framework in 2.2 - here's an example that gets you the result you wanted. First, my sample doc looks like this:
> db.foo.findOne()
{
"_id" : ObjectId("50472eb566caf6af6108de02"),
"items" : [
1,
2,
3,
45,
4,
67,
9,
4
]
}
To get to what you want I did this:
> db.foo.aggregate(
{$match : {"_id": ObjectId("50472eb566caf6af6108de02")}},
{$unwind : "$items"},
{$match : {"items": {$in : [1, 67, 9]}}},
{$group : {_id : "$_id", items : { $push : "$items"}}},
{$project : {_id : 0, items : 1}}
)
{
"result" : [
{
"_id" : ObjectId("50472eb566caf6af6108de02"),
"items" : [
1,
67,
9
]
}
],
"ok" : 1
}
To explain, in detail I will take it line by line:
{$match : {"_id": ObjectId("50472eb566caf6af6108de02")}}
This is fairly obvious - it is basically the equivalent to the find criteria on a regular query, the results are passed to the next step in the pipeline to be processed. This is the piece that can use indexes etc.
{$unwind : "$items"}
This will explode the array, creating a stream of documents, one for each element of the array.
{$match : {"items": {$in : [1, 67, 9]}}}
This second match will return only the documents in the list, basically reducing the stream of docs to a result set of three.
{$group : {_id : "$_id", items : { $push : "$items"}}}
We want our output to be an array, so we have to undo the unwind above now that we have selected the items we want, using the _id as the key to group. Note: this will have repeating values if there is more than one match, if you wanted a unique list you would use $addToSet instead of $push
{$project : {_id : 1, items : 1}}
Then finally, this projection is not really needed, but I included it to illustrate the functionality - you could choose to not return the _id if you wished etc.
Neither $elemMatch nor MongoDB in general will filter data from an array. $elemMatch can be used to match a document but it won't affect the data to be returned. You can only include/exclude fields from a documented by using the filter parameter (second parameter of a find() findOne() call) but you can not filter the result based on some query input.