In MongoDB how do you use $set to update a nested value?
For example, consider a collection people with the following document:
{
_id: ObjectId("5a7e395e20a31e44e0e7e284"),
name: "foo",
address: { street: "123", town: "bar" }
}
How do I update the street field embedded in the address document from "123" to "Main Street"?
Using the dot notation:
db.people.update({ }, { $set: { "address.street": "Main Street" } })
In addition to Niels' answer, also do verify the "type" of the nested value. In my case, it was a "string" formed from json. Though this might be unlikely, but do ensure that the value has the right type.
Related
Is there a way in PHP Mongodb to write an object in an already existing document and only overwrite or add the values contained in the object.
The object structure is not known to me.
Here is a sample:
existing document:
{
"package": {
"parameter": "value",
"one": "two"
}
}
php object or array:
$obj[package][parameter] = "value2"
$obj[package][new] = "test"`
result schould be
{
"package": {
"parameter": "value2",
"one": "two",
"new": "test"
}
}
I need something like array_merge()
I tried the $merge aggerator but it does not seem to work.
Unknown modifier: $merge. Expected a valid update modifier or pipeline-style update specified as an array
$merge is used to insert/update document to collection, like the UPSERT command in SQL. Have a look at $mergeObjects. Use it in combination with $replaceWith (or $replaceRoot which is just an alias)
Would be something like
{ $replaceWith: { $mergeObjects: [ "$$ROOT", {"new" : "test"} ] } }
$$ROOT is the existing object. If the existing object has any fields with the same name as your new object, then it will be overwritten with new field values. If you like to prefer the existing fields, then flip the arguments in the array.
The sample data you provided is not valid JSON, thus I cannot provide a full solution.
Incoming JSON:
[
{
"name": "Name1"
},
{
"name": "Name2"
},
{
"name": "Name3",
"surname": "Surname3"
}
]
If we use JSONPath like $[:].name, we will receive:
[
0: "Name1",
1: "Name2",
2: "Name3"
]
But if will use the same to get surname ($[:].surname), we will receive:
[
0: "Surname3"
]
Is this possible to get surname values with empty string (or nulls) to keep right indexes? E.g.
[
0: "",
1: "",
2: "Surname3"
]
P.S.: at the moment I'm using this library.
JSON Path doesn't support returning placeholder values like that. It's a query language for JSON documents, much like SQL is for relational databases. Could you imagine if a SQL query returned placeholder values for every record in a database that didn't match your query?
I expect that the reason you want this is to determine where in the original document the value appears. To that end, JSON Path implementations should support returning the paths to the values instead of the values themselves:
[
"$[2]['surname']"
]
But I can't see in the README of that library where such a feature is supported (though it still might).
I have a document in mongodb with 2 level deep nested array of objects that I need to update, something like this:
{
id: 1,
items: [
{
id: 2,
blocks: [
{
id: 3
txt: 'hello'
}
]
}
]
}
If there was only one level deep array I could use positional operator to update objects in it but for second level the only option I've came up is to use positional operator with nested object's index, like this:
db.objects.update({'items.id': 2}, {'$set': {'items.$.blocks.0.txt': 'hi'}})
This approach works but it seems dangerous to me since I'm building a web service and index number should come from client which can send say 100000 as index and this will force mongodb to create an array with 100000 indexes with null value.
Are there any other ways to update such nested objects where I can refer to object's ID instead of it's position or maybe ways to check if supplied index is out of bounds before using it in query?
Here's the big question, do you need to leverage Mongo's "addToSet" and "push" operations? If you really plan to modify just individual items in the array, then you should probably build these arrays as objects.
Here's how I would structure this:
{
id: 1,
items:
{
"2" : { "blocks" : { "3" : { txt : 'hello' } } },
"5" : { "blocks" : { "1" : { txt : 'foo'}, "2" : { txt : 'bar'} } }
}
}
This basically transforms everything in to JSON objects instead of arrays. You lose the ability to use $push and $addToSet but I think this makes everything easier. For example, your query would look like this:
db.objects.update({'items.2': {$exists:true} }, {'$set': {'items.2.blocks.0.txt': 'hi'}})
You'll also notice that I've dumped the "IDs". When you're nesting things like this you can generally replace "ID" with simply using that number as an index. The "ID" concept is now implied.
This feature has been added in 3.6 with expressive updates.
db.objects.update( {id: 1 }, { $set: { 'items.$[itm].blocks.$[blk].txt': "hi", } }, { multi: false, arrayFilters: [ { 'itm.id': 2 }, { 'blk.id': 3} ] } )
The ids which you are using are linear number and it has to come from somewhere like an additional field such 'max_idx' or something similar.
This means one lookup for the id and then update. UUID/ObjectId can be used for ids which will ensure that you can use Distributed CRUD as well.
Building on Gates' answer, I came up with this solution which works with nested object arrays:
db.objects.updateOne({
["items.id"]: 2
}, {
$set: {
"items.$.blocks.$[block].txt": "hi",
},
}, {
arrayFilters: [{
"block.id": 3,
}],
});
MongoDB 3.6 added all positional operator $[] so if you know the id of block that need update, you can do something like:
db.objects.update({'items.blocks.id': id_here}, {'$set': {'items.$[].blocks.$.txt': 'hi'}})
db.col.update({"items.blocks.id": 3},
{ $set: {"items.$[].blocks.$[b].txt": "bonjour"}},
{ arrayFilters: [{"b.id": 3}] }
)
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#update-nested-arrays-in-conjunction-with
This is pymongo function for find_one_and_update. I searched a lot to find the pymongo function. Hope this will be useful
find_one_and_update(filter, update, projection=None, sort=None, return_document=ReturnDocument.BEFORE, array_filters=None, hint=None, session=None, **kwargs)
Added reference and pymongo documentation in comments
hi,
I have a working model and I want to be able to just create an array from it but with only the key names and just have them empty.
To illustrate, my model Client comes from the clients table, and for example when I do this:
$this->Client->find( 'first' );
I get the following array (json encoded):
{
Client: {
id: "39",
name: "andrux",
phone: "1234567890",
email: "me#andrux.com",
city_id: "2"
},
City: {
id: "2",
city_name: "andruxville"
}
}
As you can see, I set this model to have a relationship with the City model, so I get both Client and City arrays as a result of my find method.
Now I need to get the same array but without values, and I just can't find an answer for this, I have tried some solutions but none have worked the way I want, for example I tried using array_map function and the schema method of the model but that just gives me the column names of the clients table, which I can set to null if I want but what about the City model?
The end result I want is the following:
{
Client: {
id: "",
name: "",
phone: "",
email: "",
city_id: ""
},
City: {
id: "",
city_name: ""
}
}
Anyone knows how to accomplish this? I rather find a way of doing this the cakephp way - if there is one - but any solution that gets me to my desired result will be greatly appreciated.
Thanks guys!
Well, if its ok, you could create a custom function in your model and pass in the result to it, like:
//can be added to your model
function reset_values($array, $replace_with) {
foreach ($array as $key => $arr) {
if(is_array($arr)) $res[$key] = reset_values($arr,$replace_with);
else $res[$key] = $replace_with;
}
return $res;
}
$resetedArr = reset_values($arr, null); //replace values with null
echo "<pre>"; print_r($resetedArr);
I'm using a JSON file to populate some drop down menus for various conversions. The converter has grown somewhat and I'd like to have the option of including a search bar.
So a user can search for Milliampere instead of navigating to the 'Current' category within "Electricity" etc etc. I'd just like to make it a bit easier for them.
My JSON only has two fields, the name and value of the drop down, example below.
The value is a string in the fashion that I understand so that Milliampere is milliAmpere and various others all using camel case. I don't think it would be suitable to run the search on this field as the values may differ from the name.
The name field in this instance is Milliampere(mA) so I need for the search to be able to look at part of the string and not do a full match ignoring case as the liklihood is the search string would be milliamp or milliampere or even milliamperes.
From there the next step is either to populate the drop downs whilst on that page or to return a list of possible options if there are many.
Is this possible at all and if so can you please guide me in the right direction?
Many thanks!
"current":[
{
"value" : "ampere",
"name" : "Ampere(A)"
},
{
"value" : "kiloAmpere",
"name" : "Kiloampere(kA)"
}]
You can use this JS lib - DefiantJS (defiantjs.com), which extends the global object JSON with a new method: "search". With this method, you can search a JSON structure with XPath expressions, like this:
var data = {
"current": [
{ "value": "ampere", "name": "Ampere(A)" },
{ "value": "kiloAmpere", "name": "Kiloampere(kA)" },
{ "value": "milliAmpere", "name": "milliAmpere" },
{ "value": "Milliampere", "name": "Milliampere" }
]
},
res = JSON.search( data, '//name[contains(translate(., "MA", "ma"), "millia")]/..' );
console.log( res[0].name );
console.log( res[1].name );
The "translate" method in the expression extends the search to include uppercase and lowercase versions of the letters "ma". If you want to cover all the alphabetic letter, then add all of them.
Here is a working fiddle;
http://jsfiddle.net/hbi99/6ssS9/