Need a map reduce function by mongo in php
This my mongo structure
[_id] => MongoId Object (
[$id] => 4fcf2f2313cfcd2454500000d
)
[id] => 454
[table] => people
[news] => Array (
[03-06-2012] => 2
[04-06-2012] => 3
[05-06-2012] => 5
[06-06-2012] => 4
)
Here I try to sum the array news with below code,
$map = new MongoCode('function() { emit(this.news, 1); }');
$reduce = new MongoCode('function(previous, current) {
var count = 0;
for (index in current) {
count = count + current[index];
}
return count;
}');
$sales = $db->command(array(
'mapreduce' => 'mycollection',
'map' => $map,
'reduce' => $reduce,
'query' => array('table' => 'people'),
'out' => 'news'
));
//pr($sales);exit;
$users = $db->selectCollection($sales['result'])->find();
foreach ($users as $user) {
//echo "{$user['_id']} had {$user['value']} sale(s).\n";
pr($user);
}
When pr($user)
Array
(
[_id] => Array
(
[04-06-2012] => 0
[08-06-2012] => 2
[11-06-2012] => 6
)
[value] => 39540
)
Where I expected a value will be 8 instead of 39540.
How I can correct this function and how to the add a field sum as array sum of 'news' to original collection(mycollection) ?
I am not familar with map reduce functions in mongo.
When calling emit(), the first parameter is the key you'll be reducing on (or grouping, for this example). The second parameter is the value being emitted for that key, which can be anything. For your example, you probably mean to emit the sum of all values in the news field, using the document's ID as your key:
var map = function() {
var total = 0;
for (count in this.news) {
total += count;
}
emit(this._id, total);
}
In this case, a placeholder reduce function can be used (since each emitted key will be unique, there's very little reduction to be done):
var reduce = function(key, values) {
var total = 0;
values.forEach(function(v) { total += v; });
return total;
}
However, as I mentioned in the Google Group post, you may be better off doing this with pure PHP:
$cursor = $collection->find(array(), array('news' => 1));
$cursor->snapshot();
foreach ($cursor as $document) {
$collection->update(
array('_id' => $document['_id']),
array('$set' => array('sum' => array_sum($document['news']))),
array('multiple' => false)
);
}
With map/reduce, you'd still have to examine its results and update your records. This would avoid the need to execute JavaScript through Mongo, and should be more performant. And if you can utilize $inc to update the sums as the news field is modified on a per-document basis, that will be even better. The above snippet would still be useful for initializing sum fields across the collection, or correcting any drift if things get out of sync with per-document increments.
Note: see snapshot() in the documentation for the reasoning behind that method call in the example above.
While jmikola's answer gives me wright track to deal with mongo map reduce functions.
I am adding this answer in order to help future visitors.
The following map-reduce function works perfectly to my requirement.
This will sum all values in the news field to new collection called news created in command by adding ("out" => "news").
Map-Reduce Function
$map = new MongoCode('function() {
var total = 0;
for (count in this.news) {
total += this.news[count];
}
emit(this._id, {id: this.id, total: total});
}');
$reduce = new MongoCode('function(key, values) {
var result = {id: null, total: 0};
values.forEach(function(v) {
result.id = v.id;
result.total = v.total;
});
return result;
}');
$sales = $db->command(array(
'mapreduce' => 'mycollection', // collection name
'map' => $map,
'reduce' => $reduce,
'query' => array('table' => 'people'),
"out" => "news" // new collection name
));
The result will be news collection with sum as total and id of actual document
Output
[_id] => MongoId Object (
[$id] => 4fd8993a13cfcd4e42000000
)
[value] => Array (
[id] => 454
[total] => 14
)
Related
So I'm somewhat confused and need some assistance from the community, so I have a piece of code that I'm working on and I would like to compare two arrays and then if IDs don't match, to put the post in pending state.
I have the following method:
/**
* Update Office titles
*/
function update_office_titles() {
$office_data = build_offices_array();
$compare_ids = [];
foreach ($office_data as $office) {
$compare_ids[] = [
'wp_office_id' => $office['office_id']
];
}
foreach (api_offices_raw() as $api_office_id) {
$compare_ids[] = [
'api_id' => $api_office_id->id
];
}
print_r($compare_ids);
echo 'All of the office titles have been updated';
}
Here, I'm grabbing all the office_id post_meta and getting all the post_meta values in the DB:
foreach ($office_data as $office) {
$compare_ids[] = [
'wp_office_id' => $office['office_id']
];
}
I get the following return:
[243] => Array
(
[wp_office_id] => 3
)
[244] => Array
(
[wp_office_id] => 4
)
Then I have this foreach that loops through an API with IDs:
foreach (api_offices_raw() as $api_office_id) {
$compare_ids[] = [
'api_id' => $api_office_id->id
];
}
I get this output:
[246] => Array
(
[api_id] => 1
)
[247] => Array
(
[api_id] => 3
)
Here is what I was attempting to achieve:
Check api_id's and match them to the wp_office_id key values, if there are matching IDs, don't do nothing, otherwise call a wp_update_post and set the status to pending on $office['post_id'] on all IDs from wp_office_id that don't match.
All help is appreciated!
Im getting an array within array of 'Cylinders' data from POST:
Array
(
[serie] => Array
(
[0] => 1234
[1] => 3545
)
[seriesap] => Array
(
[0] => 1234234
[1] => 345345
)
[type] => Array
(
[0] => 4546
[1] => csdfwe
)
[admission] => Array
(
[0] => 04-05-2015
[1] => 04-05-2015
)
[invoice] => Array
(
[0] => fei76867
[1] => feiasodjf
)
)
Now, the fields inside the keys: serie, type, admission, etc dont change, but the info inside those key do change, i mean there could be even 15 items in there.
At the end i need to save to the database:
$cylinder = new Cylinder();
$cylinder->serie = ??;
$cylinder->seriesap = ??;
$cylinder->type = ??;
$cylinder->admission = ??;
$cylinder->invoice = ??;
$cylinder->save
How can i accomplish this task and save all the cylinders?
I have tried all the foreach's that i could think of nothing seems to work.
/edit/
This is what Im doing so far:
$cyldata = $_POST['cylinder']; //this is the post from top.
$num_elements = 0;
while($num_elements < count($cyldata['serie'])){
$cylinder = new Cylinder();
$cylinder->serie = $cyldata['serie'][$num_elements];
$cylinder->type = $cyldata['type'][$num_elements];
$cylinder->admission = $cyldata['admission'][$num_elements];
$cylinder->seriesap = $cyldata['seriesap'][$num_elements];
$cylinder->save
$num_elements++;
}
But it feels ugly, all those saves doesnt feel right. Dirty solution if you ask me.
First, you need to convert you input data to another format:
$cyldata = $_POST['cylinder']; //this is the post from top.
$num_elements = 0;
$sqlData = array();
while($num_elements < count($cyldata['serie'])){
$sqlData[] = array(
'serie' => $cyldata['serie'][$num_elements],
'type' => $cyldata['type'][$num_elements],
'admission' => $cyldata['admission'][$num_elements],
'seriesap' => $cyldata['seriesap'][$num_elements],
'invoice' => $cyldata['invoice'][$num_elements], // you miss this field, aren't you?
'created_at' => Carbon\Carbon::now(), // only if your table has this column
'updated_at' => Carbon\Carbon::now(), // only if your table has this column
);
$num_elements++;
}
Second, use the Fluent query builder to do a batch insert:
DB::table('table_name')->insert($sqlData);
Note: the created_at and updated_at appear here if your table has these field. When working with Eloquent model, these field is updated automatically. However, we do not use Eloquent, so that we have to assign the value to these field manually.
I don't know what forced you to use $_POST global array in order to receive the data from the user.
Perhaps, this is what you want.
/**
* Store the form inputs in the table
*
* #param Request $request
*/
public function store( Request $request ) {
$data = Input::get();
for($i = 0; $i < count($data['serie']); $i++) {
$c = new Cylinder();
$c->serie = $data['serie'][$i];
$c->type = $data['type'][$i];
$c->admission = $data['admission'][$i];
$c->seriesap = $data['seriesap'][$i];
$c->save(); // fixed typo
}
}
Because you are having an array to insert the data to database, you can try the create method from the model:
Cylinder::create($array);
but it actually needs the key of the array to be the field_name in your database. Or you can do this with the query builder:
DB::table('table_name')->insert($array);
and again it is required to set the key of the array to be the field_name in your database.
I have a two collections one of all the people I am following and another of what they have been posting on social networking sites like Twitter and Facebook.
The following collection has a subarray of the _id of the feed collection of each user which each status has the word owner and that has the ObjectId that the owner which is the same as the following key. Here is an example.
'_id' => new MongoId("REMOVED"),
'following' =>
array (
'0' => 'ObjectId("53bf464ee7fda8780c8b4568")',
'1' => 'ObjectId("53b00ab5e7fda8304b8b4567")',
),
'owner' => new MongoId("53b9ea3ae7fda8863c8b4123"),
and in the feed you will see that the following.0 status below
array (
'_id' => new MongoId("REMOVED"),
'owner' => new MongoId("53bf464ee7fda8780c8b4568"),
'status' => ' love this video - Pedigree Shelter dogs http://youtube.com/watch?v=5v5Ui8HUuN8',
'timestamp' => new MongoDate(1405044327, 565000),
)
While I can loop through one by one, I can't for some reason do an $or search. I am not quite understanding how I loop through the following array and add it to the search query before I ran the query.
collection = static::db()->feed;
$where=array( '$or' => array(array('owner' => new MongoId($following.0)))));
$feed = $collection->find($where);
return $feed;
now I understand I will somehow have to loop the $where=array( '$or' => array(array('owner' => new MongoId($following.0))))); But I am just not 100% sure how to do this.
Update
As per the answer below I had to edit the array that was returned - now I have only got this working manually and can't seem to get the PHP script to do it.
Answer Returns
Array ( [owner] => Array ( [$in] => Array ( [0] => new MongoId("53bf464ee7fda8780c8b4568") [1] => new MongoId("53b00ab5e7fda8304b8b4567") ) ) )
Correct:
Array ( "owner" => Array ( '$in' => Array ( "0" => new MongoId("53bf464ee7fda8780c8b4568"), "1" => new MongoId("53b00ab5e7fda8304b8b4567") ) ) )
I am not sure how else to get this to work.
current PHP
$collection = static::db()->following;
$following = $collection->findOne(array ('owner' => new MongoId($_SESSION['user_information'][0]['_id'])));
$follow = $following['following'];
$collection = static::db()->feed;
$where=array("owner" => array( '$in' =>$follow));
print_r($where);
$feed = $collection->find($where);
print_r($feed);
return $feed;
I have fixed a small issue with the collection and now the return array shows
Array ( [owner] => Array ( [$in] => Array ( [0] => MongoId Object ( [$id] => 53bf464ee7fda8780c8b4568 ) [1] => MongoId Object ( [$id] => 53b00ab5e7fda8304b8b4567 ) ) ) )
However, I still can't get it to return the feed like this one:
array (
'_id' => new MongoId("53bf4667e7fda8700e8b4567"),
'owner' => new MongoId("53bf464ee7fda8780c8b4568"),
'status' => ' love this video - Pedigree Shelter dogs http://youtube.com/watch?v=5v5Ui8HUuN8',
'timestamp' => new MongoDate(1405044327, 565000),
)
I am presuming here that this is just a PHPism in the way things are displayed and that your following array is an actual array and not a hash/map, which would generally look like this in a JSON representation:
{
"following": [
ObjectId("53bf464ee7fda8780c8b4568"),
ObjectId("53b00ab5e7fda8304b8b4567"),
],
"owner": ObjectId("53b9ea3ae7fda8863c8b4123"),
}
In which case the "following" is already an actual array, and if you just want to .find() all the "feed" items for the people you are following, then you just pass that to the $in operator for your query selection:
$where = array( "owner" => array( '$in' => $following ) );
$feed = $collection->find($where);
return $feed;
The returned cursor will only contain results from the feed where the "owner" is present in your "following" array from the other collection item.
Watch this code:
$list = array(new MongoId(), new MongoId, new MongoId());
$doc = array( "owner" => array( '$in' => $list ));
echo json_encode( $doc, JSON_PRETTY_PRINT );
Despite how this serializes for JSON by this method the equivalent JSON is:
{
"owner": {
"$in": [
ObjectId("53bf8157c8b5e635068b4567"),
ObjectId("53bf8157c8b5e635068b4568"),
ObjectId("53bf8157c8b5e635068b4569")
]
}
}
That is how the BSON will serialize and is the correct query.
(Answer added on behalf the question author to move it to the answer space).
The issue was fixed when I used the following:
var_dump(iterator_to_array($feed));
So I have a function that merges two arrays replacing "variables" that are in the $template array with values from the $marketArray. It's nice tight bit of code from some kind contributor's here. Now due to a new requirement I need to switch things a bit.
First I need to make it an array of arrays that essentially groups the stuff by market instead of one giant list in a single large array. Secondly I need the keys in the new arrays to be of the format market-counter
e.g. gb/en-1, gb/en-2 etc etc (this is so a JQuery gets an id it can use to determine where the results go later.
So I have entered a couple of new entries (marked //NEW) that would get the value for the market and started a counter. It's twisting my brain around the next step that hurts!
$marketArray is a multidimensional associative array of the markets like this (but a lot more markets!)
$markets = array(
array(market => 'se/sv', storeid => 'storeId=66', langid => 'langId=-14', storenumber => '109', prodid => '741586', artid => '22112334'),
array(market => 'at/de', storeid => 'storeId=82', langid => 'langId=-11', storenumber => '234', prodid => '374637', artid => '45678214')
);
$template is a bunch of url templates that need to be manipulated on a market by market basis (again shortened)
$template = array (
'/$market',
'/$market/catalog/',
'/$marketproducts/$artid',
'StockSearchForm?&productId=$prodid'
);
Here is the function
function urlBuilder($marketArray,$template) {
$urlstohit=array();
foreach ($marketArray as $m) {
$market = $m['market']; //NEW
$counter = 1; //NEW
foreach ($template as $t) {
$tt=$t;
foreach ($m as $k=>$v)
$tt=str_replace('$'.$k, $v, $tt);
$urlstohit[]=$tt;
}
}
return ($urlstohit);
}
so what I am trying to achieve is instead of one giant array like
$urlstohit (
[0] => '/se/sv/catalog/categories/',
[1] => '/se/sv/catalog/news/',
[2] => '/se/sv/catalog/categories/departments/',
[3] => '/se/sv/search/?query=giant'
[4] => '/at/de/catalog/categories/',
[5] => '/at/de/catalog/news/',
[6] => '/at/de/catalog/categories/departments/',
[7] => '/at/de/search/?query=giant'
)
a md-array grouped by market with the market-counter as keys
$urlstohit (
['se/sv'] => array(
['se/sv-1'] => '/se/sv/catalog/categories/',
['se/sv-2'] => '/se/sv/catalog/news/',
['se/sv-3'] => '/se/sv/catalog/categories/departments/',
['se/sv-4'] => '/se/sv/search/?query=giant'
),
['at/de'] => array(
['at/de-1'] => '/at/de/catalog/categories/',
['at/de-2'] => '/at/de/catalog/news/',
['at/de-3'] => '/at/de/catalog/categories/departments/',
['at/de-4'] => '/at/de/search/?query=giant'
)
)
Try this
function urlBuilder($marketArray,$template) {
$urlstohit=array();
foreach ($marketArray as $m) {
$market = $m['market'];
$counter = 1;
$urlstohit[$market] = array(); / ADDED
foreach ($template as $t) {
$tt=$t;
foreach ($m as $k=>$v)
$tt=str_replace('$'.$k, $v, $tt);
$urlstohit[$market][$market.'-'.$counter]=$tt; // EDITED
$counter++; // ADDED
}
}
} // ADDED
return ($urlstohit);
}
I've marked the lines I've added and edited (I think you were also missing a curly brace).
Given this multidimensional array, I'm trying to retrieve the value of one of the child keys:
$movieCast = Array(
'1280741692' => Array(
...
, 'userid' => 62
, 'country_id' => '00002'
...
)
, '1280744592' => Array(
...
, 'userid' => 62
, 'country_id' => '00002'
...
)
)
How can I retrieve the value of country_id?
The top-level array key could be anything and the value of country_id will always be the same for a specific user. In this example, user #62's country_id will always be 00002.
You have to iterate through the outer array:
foreach ($outer as $inner) {
//do something with $inner["country_id"]
}
Another option is to build an array with the contry_ids (example uses PHP >=5.3 functionality, but that can be worked around easily in earlier versions):
array_map(function ($inner) { return $inner["country_id"]; }, $outer);
EDIT If the ids are all the same, even easier. Do:
$inner = reset($outer); //gives first element (and resets array pointer)
$id = $inner["country_id"];
a more general-purpose solution using php 5.3:
function pick($array,$column) {
return array_map(
function($record) use($column) {
return $record[$column];
},
$array
);
}
You need to use this:
array_column($movieCast, 'country_id')
The result will be:
array (
0 => '00002',
1 => '00002',
)