MongoDB simply merge two unknown Documents - php

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.

Related

How can I Iterate through PHP multi-dimension array that has label -> value structure

New to PHP (from C#). I have an array ($metaarray), which if I json_encode() to the screen, it has this value:
[
{
"measure":"Walking","record":"steps","latestres":"6870","datet":"2022-08-31"
},{
"measure":"","record":"kilograms","latestres":"117","datet":"2022-09-12"
},{
"measure":"","record":"","latestres":null,"datet":"2022-09-12"
},{
"measure":"Walking","record":"steps","latestres":"6840","datet":"2022-09-12"
},{
"measure":"Bodyweight","record":"kilograms","latestres":"92","datet":"2022-09-12"
},{
"measure":"Benchpress","record":"kilograms","latestres":"90","datet":"2022-09-12"
}
]
Is there an easy way for me to iterate through the metaarray - or to easily reference a record - eg. I would normally do something like:
$latestres = $metaarray[0][2];
...which should be "6870" - however it doesn't return any result when I do that.
Is there a way I can easily reference a particular value (eg. first record, "latestres" or 3rd value) in the above array?
I don't know if this helps you, but $data[2] does not represent third item in an array, unless the array happens to be created linearly (called a list). In PHP, 2 is actually the key to a map (name/value pair). So unless there is actually a key with that index, you can't access it. You can see a demo of what I'm talking about here.
You can get around the feature/limitation by using one of the tricks from this answer: https://stackoverflow.com/a/24825397/231316
function getNthItemFromArray(array $array, int $idx)
{
return $array[array_keys($array)[$idx]];
}
Obviously you'd add some guards.
As everyone notes, you should really start with your data before the encode. However, assuming that for whatever you have a JSON string, you can tell the decoder to give you an associative array instead of an object. Putting that all together you could do something like:
$json = <<<EOT
[
{
"measure":"Walking","record":"steps","latestres":"6870","datet":"2022-08-31"
},{
"measure":"","record":"kilograms","latestres":"117","datet":"2022-09-12"
},{
"measure":"","record":"","latestres":null,"datet":"2022-09-12"
},{
"measure":"Walking","record":"steps","latestres":"6840","datet":"2022-09-12"
},{
"measure":"Bodyweight","record":"kilograms","latestres":"92","datet":"2022-09-12"
},{
"measure":"Benchpress","record":"kilograms","latestres":"90","datet":"2022-09-12"
}
]
EOT;
$decoded = json_decode($json, true);
echo getNthItemFromArray($decoded[0], 2);
function getNthItemFromArray(array $array, int $idx)
{
return $array[array_keys($array)[$idx]];
}
Demo here: https://3v4l.org/POdma

How to search through unknown property name in database with Laravel

I'm trying to search database with json contains method of laravel. Here is my JSON of one database line:
{
"row": {
"1": {
"ID":"110555175667"
},
"2": {
"ID":"11023235667"
},
"3": {
"ID":"11001414141667"
},
"4": {
"ID":"11023235667"
},
"5": {
"ID":"1100012222225667"
},
}
}
I want to search ID, but as you see there are numbers as properties.
In example I want to find 11023235667. I've tried it like that:
->whereJsonContains('json', [['row' => ['1' => ['ID' => '11023235667']]]])
But it didn't worked. How can I do it?
EDIT:
I have also tried this:
->whereRaw('JSON_CONTAINS(json, "$.row.*.ID", "11023235667")')
I know the property of row must be JSON array to accomplish to match the id, but it has been set as JSON object
The usage of JSON_CONTAINS() accepts a JSON document as its second argument, not a path.
You could use that path to extract the ID's into an array:
SELECT JSON_EXTRACT(json, '$.row.*.ID') FROM ...
+--------------------------------------------------------------------------------------+
| ["110555175667", "11023235667", "11001414141667", "11023235667", "1100012222225667"] |
+--------------------------------------------------------------------------------------+
Using this, you can search the resulting array:
SELECT ... FROM mytable
WHERE JSON_SEARCH(JSON_EXTRACT(json, '$.row.*.ID'), 'one', '11023235667') IS NOT NULL;
You would need to do this using whereRaw() in Laravel, because Laravel doesn't have a builtin query builder function for this expression.
Tip: As soon as you reference a JSON column in the WHERE clause of an SQL query, your query becomes harder to write, and harder to optimize. This should be a red flag indicating your design is wrong. You would be better off storing data in normal rows and columns, not JSON.

update value using nested element match in mongo [duplicate]

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

Retaining array keys after using slice in MongoDB with PHP

I have a MongoDB object of following structure:
{
"_id": ObjectId("4f5ed7d33c9059a00d000002"),
"data":
{
"0": "data1",
"1": "data2",
"2": "data3",
"3": "data4",
}
}
I am using the following code to retrieve sliced result:
$obj1 = $collection->findOne(array('_id' => new MongoId('4f5ed7d33c9059a00d000002')),array('_id'=>1,'data'=>array('$slice' =>2 )));
But the result of this query does not retain array indexes of array data.
You can use the $slice operator to retrieve a subrange of elements in an array.
What you are trying to slice is a document (subdocument). Arrays use fixed positions for the elements, [0..(numelements-1)] so there's no way to keep the "indexes". Unfortunately php's equivalent of a document is an associative array hence the confusion.

Can I query twice to get more specific value in mongoDB?

For an example, there is a group called "A" which is an array.
And there is another group called "B" which is inside of group "A" also an array.
I want to find and update group "B" elements.
I tried to query chain-like query like in jQuery.
db.collection.findOne({"group":"A"}).findOne({"society":"B"})
something like this..
but this does not work. But main point is that I want to query group elements in group.
Any suggestion on doing this?
If you give me advice especially with PHP implementation, it will be really helpful
Maybe I am misunderstanding something, but what is wrong with:
db.collection.findOne({"group":"A", "society":"B"})
Also note that findOne only returns one document.
Assuming your document looks something like this:
db.mycol.insert( {
"_id": 1,
"group": "A",
"societies": [
{"society": "A", "name": "Alpha" },
{"society": "B", "name": "Beta" }
]
} );
Then in the Mongo shell, you can retrieve the document you want using a query:
var group = db.mycol.findOne( { "group": "A" } );
And then further filter down on its fields using some client-side JavaScript:
var societyB = group.societies.filter(function (val) {
return (val.society == "B");
} );
printjson(societyB);
You'd be able to do something similar with the PHP driver. The key is to perform the action in separate steps: first grab the document you're interested in; then filter and manipulate its fields; then save it back to the database.

Categories