mongodb update operation with aggregation pipeline results in error code 14 - mongodb

i'm trying to update the value of a field with the value of another field of a document. mongodb docs say it's possible by using an aggregation pipeline as described here.
even the sample code from the docs results in an TypeMismatch code 14 error.
command:
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ] } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
result:
WriteCommandError({
"operationTime" : Timestamp(1561779602, 1),
"ok" : 0,
"errmsg" : "BSON field 'update.updates.u' is the wrong type 'array', expected type 'object'",
"code" : 14,
"codeName" : "TypeMismatch",
"$clusterTime" : {
"clusterTime" : Timestamp(1561779602, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
})
is this an actual bug in mongodb or am i missing something?

I think what you are facing is a mongodb version issue.
According to the official documentation :
Update with Aggregation Pipeline
Starting in MongoDB 4.2, the db.collection.update() can use an
aggregation pipeline for the update. The pipeline can consist of the
following stages:
$addFields and its alias $set
$project and its alias $unset
$replaceRoot and its alias $replaceWith.
You can see that this support is available from mongodb version 4.2, And thats why it is throwing you that error.

Related

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!

MongoDB aggregation query

I am using mongoDb 2.6.4 and still getting an error:
uncaught exception: aggregate failed: {
"errmsg" : "exception: aggregation result exceeds maximum document size (16MB)",
"code" : 16389,
"ok" : 0,
"$gleStats" : {
"lastOpTime" : Timestamp(1422033698000, 105),
"electionId" : ObjectId("542c2900de1d817b13c8d339")
}
}
Reading different advices I came across of saving result in another collection using $out. My query looks like this now:
db.audit.aggregate([
{$match: { "date": { $gte : ISODate("2015-01-22T00:00:00.000Z"),
$lt : ISODate("2015-01-23T00:00:00.000Z")
}
}
},
{ $unwind : "$data.items" } ,
{
$out : "tmp"
}]
)
But I am getting different error:
uncaught exception: aggregate failed:
{"errmsg" : "exception: insert for $out failed: { lastOp: Timestamp 1422034172000|25, connectionId: 625789, err: \"insertDocument :: caused by :: 11000 E11000 duplicate key error index: duties_and_taxes.tmp.agg_out.5.$_id_ dup key: { : ObjectId('54c12d784c1b2a767b...\", code: 11000, n: 0, ok: 1.0, $gleStats: { lastOpTime: Timestamp 1422034172000|25, electionId: ObjectId('542c2900de1d817b13c8d339') } }",
"code" : 16996,
"ok" : 0,
"$gleStats" : {
"lastOpTime" : Timestamp(1422034172000, 26),
"electionId" : ObjectId("542c2900de1d817b13c8d339")
}
}
Can someone has a solution?
The error is due to the $unwind step in your pipeline.
When you unwind by a field having n elements, n copies of the same documents are produced with the same _id. Each copy having one of the elements from the array that was used to unwind. See the below demonstration of the records after an unwind operation.
Sample demo:
> db.t.insert({"a":[1,2,3,4]})
WriteResult({ "nInserted" : 1 })
> db.t.aggregate([{$unwind:"$a"}])
{ "_id" : ObjectId("54c28dbe8bc2dadf41e56011"), "a" : 1 }
{ "_id" : ObjectId("54c28dbe8bc2dadf41e56011"), "a" : 2 }
{ "_id" : ObjectId("54c28dbe8bc2dadf41e56011"), "a" : 3 }
{ "_id" : ObjectId("54c28dbe8bc2dadf41e56011"), "a" : 4 }
>
Since all these documents have the same _id, you get a duplicate key exception(due to the same value in the _id field for all the un-winded documents) on insert into a new collection named tmp.
The pipeline will fail to complete if the documents produced by the
pipeline would violate any unique indexes, including the index on the
_id field of the original output collection.
To solve your original problem, you could set the allowDiskUse option to true. It allows, using the disk space whenever it needs to.
Optional. Enables writing to temporary files. When set to true,
aggregation operations can write data to the _tmp subdirectory in the
dbPath directory. See Perform Large Sort Operation with External Sort
for an example.
as in:
db.audit.aggregate([
{$match: { "date": { $gte : ISODate("2015-01-22T00:00:00.000Z"),
$lt : ISODate("2015-01-23T00:00:00.000Z")
}
}
},
{ $unwind : "$data.items" }] , // note, the pipeline ends here
{
allowDiskUse : true
});

Update an item in an array that is in an array

I have a document in a mongodb collection like this :
{
sessions : [
{
issues : [
{
id : "6e184c73-2926-46e9-a6fd-357b55986a28",
text : "some text"
},
{
id : "588f4547-3169-4c39-ab94-8c77a02a1774",
text : "other text"
}
]
}
]
}
And I want to update the issue with the id 588f4547-3169-4c39-ab94-8c77a02a1774 in the first session.
The problem is that I only know that it's the first session and the issue id (NOT the index of the issue !)
So I try something like this :
db.mycollection.update({ "sessions.0.issues.id" : "588f4547-3169-4c39-ab94-8c77a02a1774"},
{ $set: { "sessions.0.issues.$.text" : "a new text" }})
But I got the following result :
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16837,
"errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: sessions.0.issues.$.text"
}
How can I do this ?
Thanks for help.
You have to use this (apparently equivalent) query:
db.mycollection.update({"sessions.0.issues": {$elemMatch: {id: <yourValue>}}}, {$set: {"sessions.0.issues.$.text": "newText"}})
Notice that your update expression was correct.
More information about $elemMatch.
Btw, MongoDB reference explicits that $ operator does not work "with queries that traverse nested arrays".
Important: $elemMatch only works with version 4 or more.

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.

MongoDB upsert with $push and $not

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.