Updating a MongoDB array subelement field - php

I can't seem to figure out how to update a single element within a subarray. I'd like to update images > 59db1c3654819952005897 > sort to be 5
"_id" : 34,
"images": [
{
"59db1c3654819952005897": {
"name": "1024x1024.png",
"size": "19421",
"sort": 2
}
},
{
"59db1c3652cda581935479": {
"name": "200x200.png",
"size": "52100",
"sort": 3
}
}
]
Here's what I've tried but neither work:
updateOne(['_id' => 34], ['$set' => ["images.59db1c3654819952005897.sort" => 5]])
updateOne(['_id' => 34], ['$set' => ["images.$.59db1c3654819952005897.sort" => 5]])

When using the positional $ operator and the dot notation to update the embedded documents field, you need to include the array in the query otherwise it won't work. In the above case, the revised update operation would be
db.collection.updateOne(
{
"_id": 34,
"images.59db1c3654819952005897": { "$exists": true } // <-- include array in query
},
{
"$set": {
"images.$.59db1c3654819952005897.sort": 5
}
}
)

Related

PHP - Slice nested array

I have two nested arrays with different length. I want to make length of second array as per first array, see below examples to get idea. Just remove all those items which don't exist in first array. Sometime second array has more values then first array, in this case my tree structure breaks.
These arrays are nested array so simple array_slice not working.
Here are the structure of array.
First Array
"1": {
"id": "1",
"username": "username",
"children": [
{
"id": "-1",
"username": "NULL",
"children": [
{
"id": "-1",
"username": "NULL",
"children": [
{
"id": "-1",
"username": "NULL",
"children": []
}
]
}
]
}
]
}
Second Array
"157": {
"id": "157",
"username": "test1",
"children": [
{
"id": "158",
"username": "test1",
"children": [
{
"id": "159",
"username": "test2",
"children": [
{
"id": "160",
"username": "test3",
"children": []
},
{
"id": "160",
"username": "KICK ME BECAUSE I M NOT EXIST IN FIRST ARRAY",
"children": []
}
]
}
]
},
{
"id": "160",
"username": "KICK ME BECAUSE I M NOT EXIST IN FIRST ARRAY",
"children": [
{
"id": "159",,
"username": "KICK ME BECAUSE I M NOT EXIST IN FIRST ARRAY",
"children": [
{
"id": "161",
"username": "KICK ME BECAUSE I M NOT EXIST IN FIRST ARRAY",
"children": []
}
]
}
]
}
]
}
Expected Output
"157": {
"id": "157",
"username": "test1",
"children": [
{
"id": "158",
"username": "test1",
"children": [
{
"id": "159",
"username": "test2",
"children": [
{
"id": "160",
"username": "test3",
"children": []
},
]
}
]
},
]
}
I am trying this method, but it is not working.
$firstCount = (array_map('count', $actualTree));
$secondCount = (array_map('count', $emptyTree));
$chunk = array_slice($actualTree, 0 , $second[$this->userId], true);
Use Case
The thing which I want to do is that remove those array childrens completely which are not exists in first array. I am building a binary tree upto three levels. First array already has a binary tree with empty values. The second array is data that is coming from the database, and I am simply replacing empty data with the actual data using array_replace. This is working good until second array has more values then first array. So to make it working I have to remove those extra elements.
Could anyone please help me to make there length same. Any help will be appreciated.
Thanks in advance.
A Stack Overflow miracle has occurred... I got a recursive snippet to work on the first pass! Usually it takes me a good hour or two to write something that works.
I don't know if I can make it any tighter/better OR if it will fail on any fringe cases, but:
it works for your sample input
it is midnight for me, I'm tired, and I have to work in the morning
Effectively, it synchronously & recursively iterates each array and stores each level of the entry array to the output array so long as the same level keys exists in the structure array.
Code: (Demo)
function truncateRecursive($structure, $entry) {
$output = [];
while (($structureKey = key($structure)) !== null && ($entryKey = key($entry)) !== null) {
$output[$entryKey] = !is_array($entry[$entryKey])
? $entry[$entryKey]
: truncateRecursive($structure[$structureKey], $entry[$entryKey]);
unset($structure[$structureKey], $entry[$entryKey]);
}
return $output;
}
var_export(truncateRecursive($structure, $entry));
Output:
array (
157 =>
array (
'id' => '157',
'username' => 'test1',
'children' =>
array (
0 =>
array (
'id' => '158',
'username' => 'test1',
'children' =>
array (
0 =>
array (
'id' => '159',
'username' => 'test2',
'children' =>
array (
0 =>
array (
'id' => '160',
'username' => 'test3',
'children' =>
array (
),
),
),
),
),
),
),
),
)

Elasticsearch -- count number of keyword occurences in a document

Database: Elasticsearch v7.2
Application: Laravel v5.7
Using Elasticsearch/Elasticsearch (https://github.com/elastic/elasticsearch-php) Official PHP Library
I have a query_string query for Elasticsearch with this code to retrieve documents that have a certain phrase as I search throughout my index
[
"query_string" => [
"default_field" => $content,
"query" => $keywords
]
],
and the $keywords variable contains:
("MCU" OR "Marvel" OR "Spiderman")
Now, I want to count the NUMBER OF OCCURENCES of these words in the documents that I'm about to retrieve
I used the aggs query with this:
'aggs' => [
'count' => [
'terms' => [
'field' => 'content.keyword'
]
]
]
However, I have no idea how to associate these doc_count and display it in a matched manner with the hits -- because the key itself is the content, instead of the IDs
Im planning to display the whole document and pertain how many times the $keywords above have occurred in each document as Mentions
Is there other way to do the counting of occurrences without using the aggs in Elasticsearch?
If you only wants to count the occurrences of keywords, then you don't have to enable fielddata, try the filters aggs along with your query
GET my_index/_search
{
"query": {
"query_string": {
"default_field": "content",
"query": "MCU OR Marvel OR Spiderman"
}
},
"aggs": {
"count": {
"filters": {
"filters": {
"mcu": {
"match": {
"content": "MCU"
}
},
"marvel": {
"match": {
"content": "Marvel"
}
},
"spiderman": {
"match": {
"content": "Spiderman"
}
}
}
}
}
}
}
Result with be like below :
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 1.219939,
"hits": [
....
....
]
},
"aggregations": {
"count": {
"buckets": {
"marvel": {
"doc_count": 2
},
"mcu": {
"doc_count": 2
},
"spiderman": {
"doc_count": 1
}
}
}
}
}
Source : https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filters-aggregation.html
Thanks to sir #AshrafulIslam, I was able to come up with Elasticsearch's feature called highlights. Though highlights literally emphasizes keywords that occur, I resorted to PHP's substr_count() function to count the <em> tags
I added this code as a sibling of the ['body']['query'] element:
"highlight" => [
"fields" => [
"content" => ["number_of_fragments" => 0]
],
'require_field_match' => false
]
Then as I loop through the ['hits']['hits'] array element, I performed something like this:
$articles = $client->search($params);
$hits = $articles['hits']['hits'];
for($i=0; $i<count($hits); $i++){
$hits[$i]['_source']['count_mentions'] = substr_count($hits[$i]['highlight']['content'][0],"<em>");
}
Enabling Fieldata may not be the best way to enable text search.
https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html#before-enabling-fielddata
Before you enable fielddata, consider why you are using a text field for aggregations, sorting, or in a script. It usually doesn’t make sense to do so.
A text field is analyzed before indexing so that a value like New York can be found by searching for new or for york. A terms aggregation on this field will return a new bucket and a york bucket, when you probably want a single bucket called New York.
Instead, you should have a text field for full text searches, and an unanalyzed keyword field with doc_values enabled for aggregations, as follows:
PUT my_index
{
"mappings": {
"properties": {
"my_field": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}

How to format the JSON output to the desired format

I am trying to create a custom controller for the WordPress JSON API plugin and so far everything is working except the JSON Data I have is not in the correct format.
This is my current JSON output:
{
"status": "ok",
"all_tags": {
"tag-1": {
"term_name": "Tag 1",
"category_details": {
"0": {
"category_ID": 8,
"category_name": "category 1",
"category_count": 2
},
"2": {
"category_ID": 13,
"category_name": "category 2",
"category_count": 1
}
}
},
"tag-2": {
"term_name": "Tag 2",
"category_details": [
{
"category_ID": 8,
"category_name": "category 1",
"category_count": 2
}
]
}
}
}
However, in order to parse the data I must have the json data in a specific format. The correct format should be like this:
{
"status": "ok",
"all_tags": [
{
"id": 1,
"term_name": "Tag 1",
"category_details": [
{
"id": 2,
"category_ID": 8,
"category_name": "category 1",
"category_count": 2
},
{
"id": 3,
"category_ID": 13,
"category_name": "category 2",
"category_count": 1
}
]
},
{
"id": 2,
"term_name": "Tag 2",
"category_details": [
{
"id": 2,
"category_ID": 8,
"category_name": "category 1",
"category_count": 2
}
]
}
]
}
This is how I am creating the array for the json:
<?php
...
$cats_all = array(); // the array
if (!isset($cats_all[$custom_term->slug])) {
// create the array
$cats_all[$custom_term->slug] = array(
'term_name' => $custom_term->name,
'category_details' => array(
array(
'category_ID' => $categories[0]->term_id,
'category_name' => $categories[0]->name,
'category_count' => $mycats[0]->category_count
)
)
);
} else {
$cats_all[$custom_term->slug]['category_details'][] = array(
'category_ID' => $categories[0]->term_id,
'category_name' => $categories[0]->name,
'category_count' => $mycats[0]->category_count
);
}
...
// remove duplicates
$input = $this->super_unique( $cats_all );
// return the array for json output by the plugin
return array(
'all_tags' => $input,
);
Any help will be greatly appreciated. Also the entire controller can be viewed here.
There are two things you need to achieve:
The all_tags value must be a sequential array, not an associative one. This you can achieve by taking the array_values at the last statement:
return array(
'all_tags' => array_values($input)
);
The category_details values must be sequential arrays, not associative ones. This one is more tricky, as you actually do create them as sequential arrays, but the function super_unique will sometimes turn them into associative arrays, when it eliminates at least one duplicate. I suggest this fix to the function super_unique by adding two statements, around this one:
$result = array_map( 'unserialize', array_unique( array_map( 'serialize', $array ) ) );
To get this:
$is_seq = end(array_keys($array)) == count($array)-1;
$result = array_map( 'unserialize', array_unique( array_map( 'serialize', $array ) ) );
if ($is_seq) $result = array_values($result);
If you get an error on end, then you can use this for that line instead:
end($array); $is_seq = key($array) == count($array)-1;
The $is_seq variable checks that $array is sequential, and if so, calls array_values after the removal of the duplicates, which always returns a sequential array.
I think that the problem here is this:
"0": {
"category_ID": 8,
"category_name": "category 1",
"category_count": 2
},
"2": {
"category_ID": 13,
"category_name": "category 2",
"category_count": 1
}
if you want an array to be a json encoded array, indexes must be numeric and correlatives (0,1,2,3...)
Probably, after using your $this->super_unique( $cats_all ), or inside this function, you should call array_values on every array that has been reindexed; it reset the values of the array to 0, 1, ... etc... and when encoded, it will be an array isntead of an object.
Generally speaking, it´s a good practice to use $array = array_values($array) after using array_filter(...) to correctly reindex the array, elsewhere, you can get indexes like 0,2,7... etc...
Let me know if you need more details

How do we sum individual array elements in MongoDB aggregation query?

Document :
{
"version": "1.0.0",
"actor": {
"objectType": "Agent",
"name": "Test user",
"account": {
"homePage": "http://testing.com/",
"name": "67"
}
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/completed",
"display": {
"en-US": "completed"
}
},
"object": {
"objectType": "Activity",
"id": "http://localhost/action?id=cji",
"definition": {
"type": "http://adlnet.gov/expapi/activities/lesson",
"name": {
"en-US": "ps3"
},
"description": {
"en-US": "ps3"
}
}
},
"timestamp": "2016-10-25T11:21:25.917Z",
"context": {
"extensions": {
"http://localhost/eventinfo": {
"sessionId": "1477393533327",
"starttm": "1477394351210",
"eventtm": "1477394485917",
"course": "cji"
}
},
"contextActivities": {
"parent": [
{
"objectType": "Activity",
"id": "http://localhost/id=cji"
}
]
}
},
"result": {
"duration": "PT2M14.71S",
"score": {
"raw": 6,
"max": 21
}
},
"authority": {
"objectType": "Agent",
"name": "New Client",
"mbox": "mailto:hello#lhd.net"
},
"stored": "2016-10-25T11:20:29.666700+00:00",
"id": "c7039783-371f-4f59-a665-65a9d09a2b7f"
}
We've got this PHP + MongoDB aggregation query:
$condition = array(
array(
'$match' => array(
'client_id' => $CFG->mongo_clientid,
'statement.actor.account.name' => array('$in'=> array('67','192','213')),
'statement.verb.id' => 'http://adlnet.gov/expapi/verbs/completed',
'statement.object.id' => 'http://localhost/action?id=cji'
)),
array(
'$group' => array(
'_id' => '$statement.actor.account.name' ,
//'totalpoints' =>array( '$sum' => array('$last' => '$statement.result.score.raw'))
'laststatement' => array('$last' => '$statement.result.score.raw'),
//'sumtest' => array('$add' => ['$laststatement'])
)
)
);
$cursor = $collection->aggregate($condition);
echo "";
print_r($cursor);
echo "";
which returns this result:
Array
(
[result] => Array
(
[0] => Array
(
[_id] => 192
[laststatement] => MongoInt64 Object
(
[value] => 4
)
)
[1] => Array
(
[_id] => 67
[laststatement] => MongoInt64 Object
(
[value] => 6
)
)
)
[ok] => 1
)
How do we sum [laststatement].[value] of these individual array elements in MongoDB aggregation query?
[laststatement] => MongoInt64 Object
(
[value] => values goes here
)
Also, how do we use $last and $sum together in MongoDB aggregation query?
In my result there are 2 raw scores(last statement) for 2 different id (192,67). I want to sum this scores like 4 + 6 = 10 for all multiple id's but want only the last scores from the last statement. I am unable to use $last and $sum on the line. Please check
Looks like all you want is a single group. So the grouping id should be null. You may want to add a sort if you care for what last record should be. Not tested.
array(
'$group' => array(
'_id' => null ,
'totalpoints' => array( '$sum' => '$statement.result.score.raw')
'laststatement' => array('$last' => '$statement.result.score.raw')
)
)
Here is the mongo shell version.
aggregate([
{
$match :{
"actor.account.name":{$in:["67","192","213"]},
"verb.id":{$eq:"http://adlnet.gov/expapi/verbs/completed"},
"object.id":{$eq:"http://localhost/action?id=cji"}
}
},
{
$group: {
"_id": null,
"totalpoints" : {$sum:"$result.score.raw"},
"laststatement" :{$last:"$result.score.raw"}
}
}
])
Output:
{ "_id" : null, "totalpoints" : 10, "laststatement" : 4 }
Update Changed to include the sum for the last statement from each group. The first grouping is by actor name and returns the last statement from each group. The second grouping sums all the last statement.
aggregate([{
$match: {
"actor.account.name": {
$in: ["67", "192", "213"]
},
"verb.id": {
$eq: "http://adlnet.gov/expapi/verbs/completed"
},
"object.id": {
$eq: "http://localhost/action?id=cji"
}
}
}, {
$group: {
"_id": "$actor.account.name",
"laststatement": {
$last: "$result.score.raw"
}
}
}, {
$group: {
"_id": null,
"totalpoints": {
$sum: "$laststatement"
},
}
}])

I can not get data to aggregate It. PHP + mongoDB

It works like I need:
$out = $collection->aggregate(
array(
'$match' => array('type' => 'chair')
),
array(
'$project' => array(
'chairtype' => 1,
'mijczjeqeo'=>1
)
),
array(
'$group' => array(
'_id' => '$chairtype',
'MIDDLE_mijczjeqeo' => array('$avg' => '$mijczjeqeo'),
'SUMMA__mijczjeqeo' => array('$sum' => '$mijczjeqeo')
)
)
);
my_dump($out);
But i need to get true data for aggregation from array in the same documents: versions[0][content][mijczjeqeo]
Please correct my script. It does not work:
$out = $collection->aggregate(
array(
'$match' => array('type' => 'chair')
),
array(
'$project' => array(
'chairtype' => 1,
'versions.0.content.mijczjeqeo'=>1
)
),
array(
'$group' => array(
'_id' => '$chairtype',
'MIDDLEmijczjeqeo' => array('$avg' => '$versions.0.content.mijczjeqeo'),
'SUMMAmijczjeqeo' => array('$sum' => '$versions[0]["content"]["mijczjeqeo"]')
)
)
);
no one method does not work:
'MIDDLEmijczjeqeo' => array('$avg' => '$versions.0.content.mijczjeqeo')
'SUMMAmijczjeqeo' => array('$sum' => '$versions[0]["content"]["mijczjeqeo"]')
I think the problem near .0.
I try to do it in mongo console...
db.documents.aggregate({$match:{'type':'chair'}},{$project:{'chairtype': 1, 'mijczjeqeo':1}},{$group:{'_id':'$chairtype','MID':{$avg:'$mijczjeqeo'}}})
{
"result" : [
{
"_id" : "T",
"MID" : 6.615384615384615
},
{
"_id" : "G",
"MID" : 8.310344827586206
},
{
"_id" : "E",
"MID" : 6.9523809523809526
}
],
"ok" : 1
}
db.documents.aggregate({$match:{'type':'chair'}},{$project:{'chairtype': 1, 'versions.0.content.mijczjeqeo':1}},{$group:{'_id':'$chairtype','MID':{$avg:'$versions.0.content.mijczjeqeo'}}})
{
"result" : [
{
"_id" : "T",
"MID" : 0
},
{
"_id" : "G",
"MID" : 0
},
{
"_id" : "E",
"MID" : 0
}
],
"ok" : 1
}
Well you cannot project like that in the aggregation pipeline. If you want to act on array elements within an aggregation statement you first need to $unwind the array and then either $match the required element(s) or as in your case choose the $first item using an additional $group stage.
Your question does not show the structure of a document so I'll just use a sample, as my "chairs" collection:
{
"_id": 1,
"type": "chair",
"chairtype": "A",
"versions": [
{
"revision": 1,
"content": {
"name": "ABC",
"value": 10
}
},
{
"revision": 2,
"content": {
"name": "BBB",
"value": 15
}
}
]
}
{
"_id": 2,
"type": "chair",
"chairtype": "A",
"versions": [
{
"revision": 1,
"content": {
"name": "CCC",
"value": 20
}
},
{
"revision": 2,
"content": {
"name": "BAB",
"value": 12
}
}
]
}
Minimal, but enough to get the point. Now the aggregate statement:
db.chairs.aggregate([
// Normal query matching, which is good
{ "$match": { "type": "chair" } },
// Unwind the array to de-normalize
{ "$unwind": "$versions" },
// Group by the document in order to get the "first" array element
{ "$group": {
"_id": "$_id",
"chairtype": { "$first": "$chairtype" },
"versions": { "$first": "$versions" }
}},
// Then group by "chairtype" to get your average values
{ "$group": {
"_id": "$chairtype",
"MID": {"$avg": "$versions.content.value"}
}}
])
Of course if your actual document has nested arrays then you will be "unwinding" and "matching" the required elements. But that is the general process of "narrowing down" the array contents to the elements you need.

Categories