mongodb upsert in updating an array element - mongodb

Want to upsert in object properties in a array of a document
Consider a document in collection m
{ "_id" : ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : [
{ "id":0.0, "name":"aaa"},{ "id":1.0, "name":"bbb"}
]
}
Want to add age:100 to { "id":0.0, "name":"aaa"} .
Not just age .. But but provision for upsert in the array element {}. So it can contain {age:100,"city":"amd"} (since i am getting this from the application service)
Was trying this... But did not worked as it replaced the entire array element
db.m.update({_id:ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : {
"$elemMatch" : {
"id" : 0.0
}
}},
{
$set : {
"x.$" : {
"age": 100
}
}
},
{upsert: true}
)
Changed the document to (which i did not wanted)
{ "_id" : ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : [
{ "age":100},{ "id":1.0, "name":"bbb"}
]
}
Is this possible without changing schema.

$set : {"x.$" : {"age": 100}}
x.$ sets the entire matched array element to {age: 100}
This should work:
db.m.update({_id:ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x.id": 0.0}, {$set: {"x.$.age": 100 }});
Using elemMatch:
db.test.update({x: {$elemMatch: {id: 1}}},{$set: {"x.$.age": 44}})
Note that the upsert option here, is redundant and wouldn't work if the id isn't present in x because the positional operator $ doesn't support upserting.

This is not possible without changing schema. If you can change schema to use an object to store your items (rather than an array), you can follow the approach I outlined in this answer.

Related

Mongodb: update an object inside an array

I have the current mongo collection and need to find and specific id inside an array and update its value.
{
"_id" : ObjectId("111fe6813abdeb1505f5111"),
"objectIdUser" : ObjectId("111fe6813abdeb1505f5111"),
"objectIdClasses" : [
{
"objectIdClass" : ObjectId("111fe6813abdeb1505f5111")
}
],
}
I tried to use the following query but It never updates.
db.classes.findOneAndUpdate(
{
objectIdUser: ObjectId("111fe6813abdeb1505f5111"),
"objectIdClasses.objectIdClass": ObjectId('111fe6813abdeb1505f5111')
},
{
$set:
{
"objectIdClasses.$.objectIdClass": ObjectId('111fe6813abdeb1505f5111')
}
}
);
The records exists, but I always get a null value and the value is never updated.
Follow the below steps carefully;
db.<collectionName>.updateOne(filter, updateDoc, options);
the filter go defines the document you want to change, just like the find.
the updateDoc defines the new changes
the options instruct the method to create a document if no documents match the filter
Example:
I have a database of shop collection with the following data in a products collection
{ "_id" : 1, "name" : "Pen", "price" : 1.2 }
If I want to change the "price" to of "Pen" to 5.0 then:
db.products.updateOne({name: "Pen"}, {$set: {price: 5.0}}, {upsert: true});
Note: all your changes should be inside the <$set> object

MongoDB Aggregation - Does $unwind order documents the same way as the nested array order

I am wandering whether using $unwind operator in aggregation pipeline for document with nested array will return the deconstructed documents in the same order as the order of the items in the array.
Example:
Suppose I have the following documents
{ "_id" : 1, "item" : "foo", values: [ "foo", "foo2", "foo3"] }
{ "_id" : 2, "item" : "bar", values: [ "bar", "bar2", "bar3"] }
{ "_id" : 3, "item" : "baz", values: [ "baz", "baz2", "baz3"] }
I would like to use paging for all values in all documents in my application code. So, my idea is to use mongo aggregation framework to:
sort the documents by _id
use $unwind on values attribute to deconstruct the documents
use $skip and $limit to simulate paging
So the question using the example described above is:
Is it guaranteed that the following aggregation pipeline:
[
{$sort: {"_id": 1}},
{$unwind: "$values"}
]
will always result to the following documents with exactly the same order?:
{ "_id" : 1, "item" : "foo", values: "foo" }
{ "_id" : 1, "item" : "foo", values: "foo2" }
{ "_id" : 1, "item" : "foo", values: "foo3" }
{ "_id" : 2, "item" : "bar", values: "bar" }
{ "_id" : 2, "item" : "bar", values: "bar2" }
{ "_id" : 2, "item" : "bar", values: "bar3" }
{ "_id" : 3, "item" : "baz", values: "baz" }
{ "_id" : 3, "item" : "baz", values: "baz2" }
{ "_id" : 3, "item" : "baz", values: "baz3" }
I also asked the same question in the MongoDB community forum . An answer that confirms my assumption was posted from a member of MongoDB stuff.
Briefly:
Yes, the order of the returned documents in the example above will always be the same. It follows the order from the array field.
In the case that you do run into issues with order. You could use includeArrayIndex to guarantee order.
[
{$unwind: {
path: 'values',
includeArrayIndex: 'arrayIndex'
}},
{$sort: {
_id: 1,
arrayIndex: 1
}},
{ $project: {
index: 0
}}
]
From what I see at https://github.com/mongodb/mongo/blob/0cee67ce6909ca653462d4609e47edcc4ac5c1a9/src/mongo/db/pipeline/document_source_unwind.cpp
The cursor iterator uses getNext() method to unwind an array:
DocumentSource::GetNextResult DocumentSourceUnwind::doGetNext() {
auto nextOut = _unwinder->getNext();
while (nextOut.isEOF()) {
.....
// Try to extract an output document from the new input document.
_unwinder->resetDocument(nextInput.releaseDocument());
nextOut = _unwinder->getNext();
}
return nextOut;
}
And the getNext() implemenation relies on array's index:
DocumentSource::GetNextResult DocumentSourceUnwind::Unwinder::getNext() {
....
// Set field to be the next element in the array. If needed, this will automatically
// clone all the documents along the field path so that the end values are not shared
// across documents that have come out of this pipeline operator. This is a partial deep
// clone. Because the value at the end will be replaced, everything along the path
// leading to that will be replaced in order not to share that change with any other
// clones (or the original).
_output.setNestedField(_unwindPathFieldIndexes, _inputArray[_index]);
indexForOutput = _index;
_index++;
_haveNext = _index < length;
.....
return _haveNext ? _output.peek() : _output.freeze();
}
So unless there is anything upstream that messes with document's order the cursor should have unwound docs in the same order as subdocs were stored in the array.
I don't recall how merger works for sharded collections and I imagine there might be a case when documents from other shards are returned from between 2 consecutive unwound documents. What the snippet of the code guarantees is that unwound document with next item from the array will never be returned before unwound document with previous item from the array.
As a side note, having million items in an array is quite an extreme design. Even 20-bytes items in the array will exceed 16Mb doc limit.

How to remove property of nested object from MongoDB document?

I have a MongoDB document like this :
{
"_id": ObjectId("5589044a7019e802d3e9dbc5"),
"sessionId": LUUID("f49d4280-ced0-9246-a3c9-a63e68e1ed45"),
"teamId": LUUID("6ef7d1a8-f842-a54c-bd8c-daf6481f9cfc"),
"variableId": LUUID("59d1b512-eee2-6c4b-a5b5-dda546872f55"),
"values": {
"725400": 691.0000000000000000,
"725760": 686.0000000000000000,
"726120": 683.0000000000000000,
"726480": 681.0000000000000000,
"726840": 679.0000000000000000,
"727200": 678.0000000000000000,
"727560": 677.0000000000000000,
"727920": 676.0000000000000000
},
"variableType": 2,
"isSet": false,
"teamNumber": 2,
"simPageIds": []
}
I have a scenario that I have to delete a particular property from the "values" property of my document. for example, I want to delete value "727920" from the "values" property.
Since "Values" is not an array, I can't use $pull here. What I need is to remove
"727920" : 676.0000000000000000 from "values".
What is the right way to do that?
Use $unset as below :
db.collectionName.update({},{"$unset":{"values.727920":""}})
EDIT
For updating multiple documents use update options like :
db.collectionName.update({},{"$unset":{"values.727920":""}},{"multi":true})
You may try the following query using $unset
For single document update,
db.collectionName.update({ /* filter condition */ }, { $unset : { "ParentKey.ChildKey" : 1} })
For multiple documents update,
db.collectionName.updateMany({ /* filter condition */ }, { $unset : { "ParentKey.ChildKey" : 1} })

Select the value from an array of hashes based on key from a single document in mongoid

I have a document in items collection like
// Document One
{
"_id" : ObjectId("556411af73616d0d822f0000"),
"visibility" : [
{
"user_id" : ObjectId("556412bb73616d0d82310000"),
"visible" : false
},
{
"user_id" : ObjectId("556412c973616d0d82320000"),
"visible" : true
}
]
},
// Some other documents
{...}
{...}
I want to get the value of visible only for "Document One" based on user_id I provide (eg. ObjectId("556412bb73616d0d82310000")).
How?
I am using mongoid 4.0.0.
Thanks.
You can do this in two ways :
1> Using $elemMatch and $ in projection as below
db.collectionName.find({"visibility":{"$elemMatch":{"user_id":ObjectId("556412bb73616d0d82310000")}}},
{"visibility.$visible":1,"_id":0})
it retunrns results as
"visibility" : [ { "user_id" : ObjectId("556412bb73616d0d82310000"), "visible" : false } ]
this return whole matching array in visibility
2> Using aggregation as below :
db.collectionName.aggregate({
"$unwind": "$visibility"
}, {
"$match": {
"visibility.user_id": ObjectId("556412bb73616d0d82310000")
}
}, {
"$project": {
"_id": 0,
"visible": "$visibility.visible"
}
})
return results as { "visible" : false }
Maybe you can try this:
db.one.find(
{"visibility.user_id": ObjectId("556412bb73616d0d82310000")},
{_id: 0, "visibility.$": 1})
In the query statementsdb.collection.find(query, projection), {"visibility.user_id": ObjectId("")} is used to select the required item, and {_id: 0, "visibility.$": 1} is used to show the specified field.
What's more, $ operator (projection) is used to limit the output to be the matched one of array.
Official Doc: http://docs.mongodb.org/manual/reference/operator/projection/positional/#projection
$
The positional $ operator limits the contents of an from the query results to contain only the first element matching the query document. To specify an array element to update, see the positional $ operator for updates.
Use $ in the projection document of the find() method or the findOne() method when you only need one particular array element in selected documents.

Append a string to the end of an existing field in MongoDB

I have a document with a field containing a very long string. I need to concatenate another string to the end of the string already contained in the field.
The way I do it now is that, from Java, I fetch the document, extract the string in the field, append the string to the end and finally update the document with the new string.
The problem: The string contained in the field is very long, which means that it takes time and resources to retrieve and work with this string in Java. Furthermore, this is an operation that is done several times per second.
My question: Is there a way to concatenate a string to an existing field, without having to fetch (db.<doc>.find()) the contents of the field first? In reality all I want is (field.contents += new_string).
I already made this work using Javascript and eval, but as I found out, MongoDB locks the database when it executes javascript, which makes the overall application even slower.
Starting Mongo 4.2, db.collection.updateMany() can accept an aggregation pipeline, finally allowing the update of a field based on its current value:
// { a: "Hello" }
db.collection.updateMany(
{},
[{ $set: { a: { $concat: [ "$a", "World" ] } } }]
)
// { a: "HelloWorld" }
The first part {} is the match query, filtering which documents to update (in this case all documents).
The second part [{ $set: { a: { $concat: [ "$a", "World" ] } } }] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline). $set (alias of $addFields) is a new aggregation operator which in this case replaces the field's value (by concatenating a itself with the suffix "World"). Note how a is modified directly based on its own value ($a).
For example (it's append to the start, the same story ):
before
{ "_id" : ObjectId("56993251e843bb7e0447829d"), "name" : "London
City", "city" : "London" }
db.airports
.find( { $text: { $search: "City" } })
.forEach(
function(e, i){
e.name='Big ' + e.name;
db.airports.save(e);
}
)
after:
{ "_id" : ObjectId("56993251e843bb7e0447829d"), "name" : "Big London
City", "city" : "London" }
Old topic but i had the same problem.
Since mongo 2.4, you can use $concat from aggregation framework.
Example
Consider these documents :
{
"_id" : ObjectId("5941003d5e785b5c0b2ac78d"),
"title" : "cov"
}
{
"_id" : ObjectId("594109b45e785b5c0b2ac97d"),
"title" : "fefe"
}
Append fefe to title field :
db.getCollection('test_append_string').aggregate(
[
{ $project: { title: { $concat: [ "$title", "fefe"] } } }
]
)
The result of aggregation will be :
{
"_id" : ObjectId("5941003d5e785b5c0b2ac78d"),
"title" : "covfefe"
}
{
"_id" : ObjectId("594109b45e785b5c0b2ac97d"),
"title" : "fefefefe"
}
You can then save the results with a bulk, see this answer for that.
this is a sample of one document i have :
{
"_id" : 1,
"s" : 1,
"ser" : 2,
"p" : "9919871172",
"d" : ISODate("2018-05-30T05:00:38.057Z"),
"per" : "10"
}
to append a string to any feild you can run a forEach loop throught all documents and then update desired field:
db.getCollection('jafar').find({}).forEach(function(el){
db.getCollection('jafar').update(
{p:el.p},
{$set:{p:'98'+el.p}})
})
This would not be possible.
One optimization you can do is create batches of updates.
i.e. fetch 10K documents, append relevant strings to each of their keys,
and then save them as single batch.
Most mongodb drivers support batch operations.
db.getCollection('<collection>').update(
// query
{},
// update
{
$set: {<field>:this.<field>+"<new string>"}
},
// options
{
"multi" : true, // update only one document
"upsert" : false // insert a new document, if no existing document match the query
});