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.
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.
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.
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
I have the following related tables:
tableA
- id
- value
tableB
- id
- tableA_id
- value
tableC
- id
- tableB_id
- value
tableD
- id
- tableC_id
- value
I normally use a nested eager loading to get the object of tableaA from tableD, for example:
$table_d = TableD::with('TableC.TableB.TableA')->find($id);
And I get an object like this:
{
"id": 1,
"value": "value",
"tableC_id": 1,
"tablec": {
"id": 1,
"value": "value",
"tableB_id": 1,
"tableb": {
"id": 1,
"value": "value",
"tableA_id": 1,
"tablea": {
"id": 1,
"value": "value"
}
}
}
}
What I want to achieve is to obtain only the object of table D, with its object from table A related, without having table C and table B in the final object, something like this:
{
"id": 1,
"value": "value",
"tablea": {
"id": 1,
"value": "value"
}
}
}
I tried adding this function in the model file of Table D:
public function TableA()
{
return $this->belongsTo('App\Models\TableC', 'tableC_id')
->join('tableB','tableC.tableB_id','=','tableB.id')
->join('tableA','tableB.tableA_id','=','tableA.id')
->select('tableA.id', 'tableA.value');
}
but it does not work because when I do the following query, it returns some good objects and others with tableA = null:
$tables_d = TableD::with('TableA')->get()
Am I doing something wrong or is there another way to achieve what I want?
You may be able to skip a table with
this->hasManyThrough() but depending on what you really want as 'future features', you may want to have multiple relations with whatever code you desire according to your needs. QueryScopes aswell.
One can generally use a has many through relationship for mapping tables when it is just two tables and a linking table between. You have yet another join beyond that so it won't really be much better than what you have currently.
Have you considered another mapping table from D to A directly or a bit of denormalization? If you always need to load it like that you might benefit from having a bit of duplicated fks to save on the joins.
This will really depend on your needs and it is not 3NF (third normal form), maybe it's not even 2NF, but that's why denormalization is like comma use...follow the rules generally but break them for specific reasons; in this case to reduce the number of required joins by duplicating a FK reference in a table.
https://laravel.com/docs/5.6/eloquent-relationships#has-many-through
You can try to do this:
- add a method in TableD Model:
public function table_a()
{
return $this->TableC->TableB->TableA();
}
then use: TableD::with(table_a);
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/