MongoDB upsert with $push and $not - mongodb

I've recently updated to Mongodb 2.2.0 and found that the following query now no longer works.
The database is empty and I run the following:
db.Sessions.update({_id:"test",sessions:{$not:{$elemMatch:{type:"Web"}}}},{$push:{sessions:{type:"Web",dateAdded:new Date}}},true)
returns the error:
Cannot apply $push/$pushAll modifier to non-array
Prior to the update the following document would have been created:
{ "_id" : "test", "sessions" : [ { "type" : "Web", "dateAdded" : ISODate("2012-09-12T15:11:11.942Z") } ] }
Any ideas?
Edit:
I forgot to mention, it's the addition of the $not that breaks in this version as the following works fine, so it's not a issue that the array/field doesn't exist.
db.Sessions.update({_id:"test"},{$push:{sessions:{type:"Web",dateAdded:new Date}}},true)

The behavior of $elemMatch has changed slightly in 2.2, which is why this no longer works in combination with $not, which is only a meta-operator for negating other operators and can not actually be used to search fields.
Instead, you can use $nin (not in) to inspect the values inside your sessions array.
For example,
> db.Sessions.update( { _id:"test", sessions: { "$nin": {type: "Web"} } }, {"$push":{sessions:{type:"Web",dateAdded:new Date}}},true);
> db.Sessions.findOne();
{
"_id" : "test",
"sessions" : [
{
"type" : "Web",
"dateAdded" : ISODate("2012-09-12T16:03:18.271Z")
}
]
}

I filed a bug "a query field with a $not operator should not be used to populate an upsert document" which will be backported into 2.2.x that fixes this situation.

Related

find() return the latest value only on MongoDB

I have this collection in MongoDB that contains the following entries. I'm using Robo3T to run the query.
{
"_id" : ObjectId("xxx1"),
"Evaluation Date" : "2021-09-09",
"Results" : [
{
"Name" : "ABCD",
"Version" : "3.2.x"
}
]
"_id" : ObjectId("xxx2"),
"Evaluation Date" : "2022-09-09",
"Results" : [
{
"Name" : "ABxD",
"Version" : "5.2.x"
}
]
}
This document contains multiple entries of similar format. Now, I need to extract the latest value for "Version".
Expected output:
5.2.x
Measures I've taken so far:
(1) I've only tried findOne() and while I was able to extract the value of "Version": db.getCollection('TestCollectionName').findOne().Results[0].Version
...only the oldest entry was returned.
3.2.x
(2) Using the find().sort().limit() like below, returns the entire document for the latest entry and not just the data value that I wanted; db.getCollection('TestCollectionName').find({}).sort({"Results.Version":-1}).limit(1)
Results below:
"_id" : ObjectId("xxx2"),
"Evaluation Date" : "2022-09-09",
"Results" : [
{
"Name" : "ABxD",
"Version" : "5.2.x"
}
]
(3) I've tried to use sort() and limit() alongside findOne() but I've read that findOne is maybe deprecated and also not compatible with sort. And thus, resulting to an error.
(4) Finally, if I try to use sort and limit on find like this: db.getCollection('LD_exit_Evaluation_Result_MFC525').find({"Results.New"}).sort({_id:-1}).limit(1) I would get an unexpected token error.
What would be a good measure for this?
Did I simply mistake to/remove a bracket or need to reorder the syntax?
Thanks in advance.
I'm not sure if I understood well, but maybe this could be what are you looking for:
db.collection.aggregate([
{
"$project": {
lastResult: {
"$last": "$Results"
},
},
},
{
"$project": {
version: "$lastResult.Version",
_id: 0
}
}
])
It uses aggregate with some operators: the first $project calculate a new field called lastResult with the last element of each array using $last operator. The second $project is just to clean the output. If you need the _id reference, just remove _id: 0 or change its value to 1.
You can check how it works here: https://mongoplayground.net/p/jwqulFtCh6b
Hope I helped

Mongo aggregate is not updating the actual document

As can be seen from the below example when I do aggregation it spits
out the required result but the actual result is not getting replaces.
Could some tell me how to persist aggregate o/p?
> db.demo95.find();
{ "_id" : ObjectId("5eed924ae3fc5c755e1198a2"), "Id" : "5ab9cbe531c2ab715d42129a" }
> db.demo95.aggregate([ { "$addFields": { "Id" : { "$toObjectId": "$Id" } }} ])
{ "_id" : ObjectId("5eed924ae3fc5c755e1198a2"), "Id" : ObjectId("5ab9cbe531c2ab715d42129a") }
> db.demo95.find();
{ "_id" : ObjectId("5eed924ae3fc5c755e1198a2"), "Id" : "5ab9cbe531c2ab715d42129a" }
Aggregate is supposed to read the data from a collection. You can write the output to another collection by using a $out or $merge stage.
Only from v4.4 (not generally available yet as of June 20th, 2020), you can use a $merge stage to output to the same collection.
However, starting from version 4.2, you can use "updates with aggregation pipeline". The syntax for the pipeline is the same, but you can use only selected stages.
Your query can be translated to:
db.demo95.updateMany({}, [ { "$addFields": { "Id" : { "$toObjectId": "$Id" } }} ])
Refer to updateMany with aggregation pipeline for more information.
If you have an issue with updateMany, you can refer to another answer by #whoami on a different question:
As of now, aggregation-pipeline in .updateMany() is not supported by
many clients even few mongo shell versions - back then my ticket to
them got resolved by using .update(), if it doesn't work then try to
use update + { multi : true }.

MongoDB 4.0 aggregation addFields not saving documents after using toDate

I have the following documents,
{
"_id" : ObjectId("5b85312981c1634f59751604"),
"date" : "0"
},
{
"_id" : ObjectId("5b85312981c1634f59751604"),
"date" : "20180330"
},
{
"_id" : ObjectId("5b85312981c1634f59751604"),
"date" : "20180402"
},
{
"_id" : ObjectId("5b85312981c1634f59751604"),
"date" : "20180323"
},
I tried to convert date to ISODate using $toDate in aggregation,
db.documents.aggregate( [ { "$addFields": { "received_date": { "$cond": [ {"$ne": ["$date", "0"] }, {"$toDate": "$date"}, new Date("1970-01-01") ] } } } ] )
the query executed fine, but when I
db.documents.find({})
to examine all the documents, nothing changed, I am wondering how to fix it. I am using MongoDB 4.0.6 on Linux Mint 19.1 X64.
As they mentioned in the comments, aggregate doesn't update documents in the database directly (just an output of them).
If you'd like to permanently add a new field to documents via aggregation (aka update the documents in the database), use the following .forEach/.updateOne method:
Your example:
db.documents
.aggregate([{"$addFields":{"received_date":{"$cond":[{"$ne":["$date","0"]}, {"$toDate": "$date"}, new Date("1970-01-01")]}}}])
.forEach(function (x){db.documents.updateOne({_id: x._id}, {$set: {"received_date": x.received_date}})})
Since _id's value is an ObjectID(), there may be a slight modification you need to do to {_id:x._id}. If there is, let me know and I'll update it!
Another example:
db.users.find().pretty()
{ "_id" : ObjectId("5acb81b53306361018814849"), "name" : "A", "age" : 1 }
{ "_id" : ObjectId("5acb81b5330636101881484a"), "name" : "B", "age" : 2 }
{ "_id" : ObjectId("5acb81b5330636101881484b"), "name" : "C", "age" : 3 }
db.users
.aggregate([{$addFields:{totalAge:{$sum:"$age"}}}])
.forEach(function (x){db.users.updateOne({name: x.name}, {$set: {totalAge: x.totalAge}})})
Being able to update collections via the aggregation pipeline seems to be quite valuable because of what you have the power to do with aggregation (e.g. what you did in your question, doing calculations based on other fields within the document, etc.). I'm newer to MongoDB so maybe updating collections via aggregation pipeline is "bad practice", but it works and it's been quite valuable for me. I wonder why it isn't more straight-forward to do?
Note: I came up with this method after discovering Nazo's now-deprecated .save() method. Shoutout to Nazo!

Yet another MongoDB findAndModify

Please consider the following document, a part of the Runtime collection:
{
"entity_id" : 10,
"features" : [
{
"10" : "Test System 2"
},
{
"20" : "System 2 Description"
},
{
"180" : ISODate("2013-12-25T18:19:40.589Z")
},
{
"190" : ISODate("2013-12-25T18:19:40.589Z")
}
],
"_id" : ObjectId("52bb21bc8a2ebdc01c000001")
}
My goal is to update the value of the element of the "features" array having the key "20".
Here are the things I've tried (in mongo shell):
db.Runtime.findAndModify({ "query" : {"_id": "52bb21bc8a2ebdc01c000001"}, "update" : {$set : {"features.$.20":"Updated Description"}}} );
db.Runtime.findAndModify({ "query" : {"_id": "52bb21bc8a2ebdc01c000001"}, "update" : {$set : {"features['20']":"Updated Description"}}} );
db.Runtime.findAndModify({ "query" : {"_id": "52bb21bc8a2ebdc01c000001"}, "update" : {$set : {"features[1]":"Updated Description"}}} );
In all instances the shell prints
null
and nothing happens to the data. So, the main question is, of course, what is wrong with my code snippets. Also, how that "null" is supposed to get interpreted? And is there such a thing as mongo shell's log where one could find any clues? Many thanks for your help!
When using the $ positional operator in an update, your query needs to include a term that matches the element of the array you're updating:
db.Runtime.findAndModify({
query: {_id: ObjectId("52bb21bc8a2ebdc01c000001"),
'features.20': {$exists: true}},
update: {$set: {"features.$.20":"Updated Description"}}
})
Note that you also need to call ObjectId on your _id string to turn it into an actual ObjectId or it won't match the doc.

$elemMatch differences in mongo

I'm seeing some differences between 2.0.7 and 2.2.0 when it comes to the $elemMatch operation.
In 2.2.0, I do get results back with this query:
db.testColl.find( { "metadata" : {$elemMatch : {$gt : {age:23}, $lt : {age:99}} }});
In 2.0.7, I don't get any results back.
For testing purposes, I have only one document in my testColl collection:
{
"_id" : ObjectId("4fb2974cbedb4a626109b002"),
"metadata" : [
{
"age" : 59
},
{
"gender" : "FEMALE"
}
]
}
Does anyone know why this works in 2.2.0, but not 2.0.7?
According to this:
http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24elemMatch
elemMatch is supported for v1.4+
Thanks,
Galen
If you're looking for a way that works in both versions, you don't need to use $elemMatch here because you're only comparing against a single field so you can use a simpler query. Try this instead:
db.testColl.find({ 'metadata.age': { $gt: 23, $lt: 99 }});