I create a platform in PHP/MYsql and I am now migrating to mongo
My old query for mysql :
select sum(game_won) as game_won,count(id) as total,position
from games_player_stats
where position < 6 and position > 0 and user_id = :pa_id
group by position
order by total desc
The new json format looks like this:
{
"region" : "EUW",
"players" : [
{
"position" : 2,
"summoner_id" : 123456,
"game_won": 1
},
{
"position" : 1,
"summoner_id" : 123459,
"game_won": 0
},
{
"position" : 3,
"summoner_id" : 123458,
"game_won": 1
},
{
"position" : 4,
"summoner_id" : 123457,
"game_won": 0
}
]
}
Having multiple documents like this, I need to find howmany times summoner_id 123456 has had position 2 or any of the other positions 1-6 and howmany times did he win in that position
The Index needs to be queryable on region and summoner_id
Outcome would look like
{
"positions" :
[
{ "position" : 1,
"total" : 123,
"won" : 65
},
{ "position" : 2,
"total" : 37,
"won" : 10
}
]
}
Would I need to use Map/Reduce for this?
The best results for this are obtained by the aggregation framework for MongoDB. It differs from mapReduce in that all operations are performed using "natively coded operators" as opposed to the JavaScript evaluation that is used by mapReduce.
This means "faster", and significantly so. Not to mention there are also certain parts of what you are looking for in a result that actually favour the "multiple group" concept that is inherently available to a "pipeline" of operations, that would otherwise be a fairly ugly accumulator using mapReduce.
Aggregation Pipeline Formats
The best approach will differ depending on the MongoDB "server" version you have available.
Ideally with MongoDB 3.2 you use $filter to "pre-filter" the array content before processing with $unwind:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
// Filter the array content for matched conditions
{ "$project": {
"players": {
"$filter": {
"input": "$players",
"as": "player"
"cond": {
"$and": [
{ "$eq": [ "$$player.summoner_id", 123456 ] },
{ "$gte": [ "$$player.position", 1 ] },
{ "$lte": [ "$$player.position", 6 ] }
]
}
}
}
}},
// Unwind the array contents to de-normalize
{ "$unwind": "$players" },
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
db.collection.aggregate(pipeline);
For MongoDB 2.6.x releases, still "pre-filter" but using $map and $setDifference:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
// Filter the array content for matched conditions
{ "$project": {
"players": {
"$setDifference": [
{ "$map": {
"input": "$players",
"as": "player",
"in": {
"$cond": {
"if": {
"$and": [
{ "$eq": [ "$$player.summoner_id", 123456 ] },
{ "$gte": [ "$$player.position", 1 ] },
{ "$lte": [ "$$player.position", 6 ] }
]
},
"then": "$$player",
"else": false
}
}
}},
[false]
]
}
}},
// Unwind the array contents to de-normalize
{ "$unwind": "$players" },
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
And for earlier versions with the aggregation framework from MongoDB 2.2, "post filter" with $match "after" the $unwind:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
{ "$unwind": "$players" },
// Post filter the denormalized content
{ "$match": {
"players.summoner_id": 123456,
"players.position": { "$gte": 1, "$lte": 6 }
}},
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
Walkthrough
Matching the Document: This is primarily done using $elemMatch since you are looking for "multiple" conditions within the array elements. With a "single" condition on an array element it is fine to use "dot notation":
"players.summoner_id": 12345
But for anything more than "one" condition you need to use $elemMatch, otherwise all the statement is really asking is "does this match something within the array?", and that does not contain to "all" within the element. So even the $gte and $lte combination alone is actually "two" conditions, and therefore requires $elemMatch:
"players": {
"$elemMatch": {
"position": { "$gte": 1, "$lte": 6 }
}
}
Also noting here that from "1 to 6 inclusive" means "greater than or equal to" and vice versa for the "less than" condition.
-
"Pre-filtering": Noting here that the eventual goal is to "group" by an element within the array, being "position". This means that eventually you are going to need to $unwind the content to do that.
However, the $unwind pipeline operation is going to be quite costly, considering that it "takes apart" the array and creates a new document to process for each array member. Since you only want "some" of the members that actually match the conditions, it's desirable to "remove" any un-matched content from the array "before" you de-normalize this content.
MongoDB 3.2 has a good method for this with the $filter operator. It performs exactly as named by "filtering" the content of the array to only elements that match a particular set of conditions.
In an aggregation pipeline stage we use it's "logical variants" of the operators such as $gte and $lte. These return a true/false value depending on where the condition matched. Also within the array, these can actually be referred to using the member fields using "dot notation" to the alias argument in "as" which points to the current processed member.
The $and here is also another "logical operator" which does the same true/false response. So this means "all" the arguments in it's array of arguments must be met in order to return true. For the $filter itself, the true/false evaluated in "cond" determines whether to return the array element or not.
For MongoDB 2.6 which does not have the $filter operator, the same is represented with the combination of $map and $setDifference Simply put the $map looks at each element and applies an expression within "in". In this case we use $cond which as a "ternary" operator evaluates an 'if/then/else` form.
So here where the "if" returns true the expression in "then" is returned as the current array member. Where it is false, the expression in else returns, and in this case we are returning the value of false ( PHP False ).
Since all members are actually being returned by the result of $map we then emulate $filter by applying the $setDifference operator. This does a comparison to the members of the array and effectively "removes" any members where the element was returned as false from the result. So with distinct array members such as you have, the resulting "set" ( being a "set" of "unique" elements) just contains those elements where the condition was true and a non-false value was returned.
"Post" filtering: The alternate approach which is mandatory for server versions below MongoDB 2.6 is to "post" filter the array content. Since there are no operators in these versions that allow such actions on array content before $unwind, the simple process here to applying another $match to the content "after" the $unwind is processed:
{ "$match": {
"players.summoner_id": 123456,
"players.position": { "$gte": 1, "$lte": 6 }
}}
Here you use "dot notation" since each array element is now actually it's own document, and there is nothing else to compare to other than looking at the conditions on the specified path.
This is not ideal, since when you process $unwind all of the elements that actually don't match the conditions are still present. This ultimately means "more documents to process" and has the double cost of:
Had to create a new document for every member despite it not matching the conditions
Now you have to to apply the condition across every "document" emitted as a result of $unwind
This has a potentially huge impact on performance, and for that reason the modern MongoDB releases introduce ways to act on arrays without resorting to $unwind in order to process. You still need it for the remaining processing since you are "grouping" on a property contained within the array. But it is of course desirably to "get rid of un-matched elements first".
Remainging Grouping: Now the elements are filtered and de-normalized, it only remains to do the actual $group condition that will total things by the "position" within each element. This is a simple matter of providing the grouping key to "_id" and using the appropriate data accumulation.
In this case you have two constructs, being:
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
The basic { "$sum": 1 } is just "counting" the elements matched for each group and the { "$sum": "$players.won" } actually uses the "won" value to accumulate a total. This is pretty standard usage for the $sum accumulator.
Of course your output shows the content within an "array", so the following stages are really "optional" since the real work of actually "grouping" is already done. So you could actually just use the results in the form provided up to this first $group, and the remaining just puts everything into a single document response rather than "one document per 'position' value", which would be the return at this point.
The first note is output from $group is not ordered. So if you want a specific order of results ( i.e by position ascending ) then you must $sort after that $group stage. This will order the resulting documents of the pipeline as of the point where it is applied.
In your case you are actually asking for a sort on "total" anyway, so you would of course apply this with -1 meaning "descending" in this case. But whatever the case, you still should not presume that the output from $group is ordered in any way.
The "second" $group here is basically cosmetic in that this is what makes a "single document" response. Using null ( PHP NULL ) in the grouping key basically says "group everything" and will produce a single document in response. The $push accumulator here is what actually makes the "array" from the documents in the pipeline preceding this.
Wrap-Up
So that's the general process in accumulating data like this:
Match the documents required to the conditions, since after all it would be a waste to apply conditions later to every document when they don't even contain array elements that would match the conditions you eventually want.
Filter the array content and de-normalize. Ideally done as a "pre-filter" where possible. This gets the documents into a form for grouping, from there original array form.
Accumulate the content using appropriate operators for the task, either $sum or $avg or $push or any other available according to needs. Nothing also that depending on structure and conditions you can always use "more than one" $group pipeline stage.
PHP Translation
The initial example in PHP notation:
pipeline = array(
array(
'$match' => array(
'players' => array(
'$elemMatch' => array(
'summoner_id' => 123456,
'position' => array( '$gte' => 0, '$lte' => 6 )
)
)
)
),
array(
'$project' => array(
'$filter' => array(
'input' => '$players',
'as' => 'player',
'cond' => (
'$and' => array(
array( '$eq' => array( '$$player.summoner_id' => 123456 ) ),
array( '$gte' => array( '$$player.position' => 1 ) ),
array( '$lte' => array( '$$player.position' => 6 ) )
)
)
)
)
),
array( '$unwind' => '$players' ),
array(
'$group' => array(
'_id' => '$players.position',
'total' => array( '$sum' => 1 ),
'won' => array( '$sum' => '$players.won' )
)
),
array( '$sort' => array( 'total' => -1 ) ),
array(
'$group' => array(
'_id' => NULL,
'positions' => array(
'$push' => array(
'position' => '$_id',
'total' => '$total',
'won' => '$won'
)
)
)
)
)
$result = $collection->aggregate($pipeline);
When making data structures in PHP that you are comparing to JSON, it is is often useful to check your structure with something like:
echo json_encode($pipeline, JSON_PRETTY_PRINT)
Then you can see that what you are doing in PHP notation is the same as the JSON example you are following. It's a helpful tip so that you cannot really go wrong. If it looks different then you are not doing the "same" thing.
Related
I have an array of objects, and want to update an attribute of one of the objects.
$objs = [
['value' => 2, 'key' => 'a'],
['value' => 3, 'key' => 'b'] ,
];
Let's say I want to set the 'value' of the object with 'key'=>'a' to 5.
Aside from iterating over the array searching for the key, is there any quicker/efficient way of doing this?
Thanks.
EDIT: There is debate as to why I can't use an associative array. It is because this array is obtained from a JSON value.
If my JSON object is this:
"obj": {
"a": {
"key": "a",
"value": 2
},
"b": {
"key": "b",
"value": 3
}
}
There is no guarantee that the order of the objects will be retained, which is required.
Hence I need an index in each object to be able to sort it using usort(). So my JSON needs to be:
"obj": {
"a": {
"key": "a",
"value": 2,
"index": 1
},
"b": {
"key": "b",
"value": 3,
"index": 2
}
}
But I cannot use usort() on an object, only on arrays. So my JSON needs to be
"obj": [
{
"key": "a",
"value": 2,
"index": 1
}, {
"key": "b",
"value": 3,
"index":2
}
]
Which brings us to the original question.
By using array_column(), you can pull all the values with the index key in the arrays. Then you can find the first occurrence of the value a by using array_search(). This will only return the first index where it finds a value. Then you can simply replace that value, as you now have the index of that value.
$keys = array_column($objs, 'key');
$index = array_search('a', $keys);
if ($index !== false) {
$objs[$index]['value'] = 5;
}
See this live demo.
http://php.net/array_search
http://php.net/array_column
You can make the array associative with array column. That way you can directly assign the value.
$objs = [ ['value'=>2, 'key'=>'a'], ['value'=>3, 'key'=>'b'] ];
$objs = array_column($objs, null, "key");
$objs['a']['value'] = 5;
https://3v4l.org/7tJl0
I want to recommend you reorginize your array lake that:
$objs = [
'a' => ['value'=>2, 'key'=>'a'],
'b' => ['value'=>3, 'key'=>'b']
];
And now
if( array_key_exists( 'a', $objs )) {
$objs ['a'] ['value'] = 5;
}
I had it like that initially. But I need for the objects to have an
index value in them, so I can run usort() on the main array. This is
because the array comes from JSON where the original order isn't
respected
Then create an index array:
// When fill `$objs` array
$objs = [];
$arrIndex = [];
$idx = 0;
foreach( $json as $item ) {
$arrIndex [ $item ['key']] = $idx;
$objs [$idx ++] = $item;
}
// And your task:
if( array_key_exists( 'a', $arrIndex )) {
$objs [ $arrIndex ['a']] ['value'] = 5;
}
Aside from iterating over the array searching for the key, is there
any quicker/efficient way of doing this?
You have to pay the price of iteration either way.
You can search your collection for the interesting object (takes linear time), or you form some kind of dictionary data structure, e.g. hash table (takes linear time) and then find the interesting object in constant time.
No free lunches here.
i'm working on a project where i have a structured object like this:
$main_array= [
[
"key"=> "home",
"value":=> "Go Home!"
],
[
"key"=> "business",
"value"=> "Go to Work!"
],
[
"key"=> "other",
"value"=> "Go where you want!"
]
]
i'd like to know if there is a way to retrieve the object based on the "key" parameter.
What i want to do is "extract" the nested array like
$home_array=["key"=> "home","value":=> "Go Home!"]
and so on for "business" and "others".
in javascript, i can use jquery or underscore to get what i want, is there a php method to achieve this, or something to simulate a "where" clause in a multidimensional array/object?
thak you in advance
You can easily convert the array you have to have the 'key' column to be the main index using array_column()...
$main_array= [
[
"key"=> "home",
"value"=> "Go Home!"
],
[
"key"=> "business",
"value"=> "Go to Work!"
],
[
"key"=> "other",
"value"=> "Go where you want!"
]
];
$out = array_column($main_array, null, "key");
print_r($out['business']);
Outputs...
Array
(
[key] => business
[value] => Go to Work!
)
A few possibilities to get a single item matching a specific key:
Iterate the main array and stop when you get to a child that has the proper key:
$object = (function($key) use($main_array) {
foreach ($main_array as $object) {
if ($object['key'] == $key) return $object;
}
})('business');
(This example uses an anonymous function, but you can just use a simple foreach loop and break when you find the key.)
Reindex the main array and look up the child by key:
$indexed = array_column($main_array, null, 'key');
$object = $indexed['business'];
Construct the array using the key as the index to begin with. Using string keys doesn't preclude the array elements being other arrays that can contain multiple values.
$main_array= [
'home' => ["value"=> "Go Home!"],
'business' => ["value"=> "Go to Work!"],
'other' => ["value"=> "Go where you want!"]
];
Methods 2 and 3 require that they key is unique. Method 1 doesn't, but it will just return the first instance it finds.
If you do have multiple instances of a key, you probably want array_filter. This will work more like the "where clause" you referred to.
$key = 'home';
$filtered = array_filter($main_array, function($item) use ($key) {
return $item['key'] == $key;
});
This will return multiple items instead of just one.
I have two JSON objects and I would like to compare their structure. How can I do it?
Those object are being generated on-the-fly and depending on dynamic content.
Which means that the objects are always different but most of the time have the same structure. I want to be able to catch the changes once they occur.
Example: These two objects should be considered as equal, because both have the same structure: index var and tags array.
{
"index": 0,
"tags": [
"abc"
]
}
{
"index": 1,
"tags": [
"xyz"
]
}
Thoughts?
## You can use this library TreeWalker php .##
TreeWalker is a simple and smal API in php
(I developed this library, i hope it helps you)
It offers two methods
1- Get json difference
2- Edit json value (Recursively)
this method will return the diference between json1 and json2
$struct1 = array("casa"=>1, "b"=>"5", "cafeina"=>array("ss"=>"ddd"), "oi"=>5);
$struct2 = array("casa"=>2, "cafeina"=>array("ss"=>"dddd"), "oi2"=>5);
//P.s
print_r($treeWalker->getdiff($struct1, $struct2))
{
new: {
b: "5",
oi: 5
},
removed: {
oi2: 5
},
edited: {
casa: {
oldvalue: 2,
newvalue: 1
},
cafeina/ss: {
oldvalue: "dddd",
newvalue: "ddd"
}
},
time: 0
}
It's a bit rough, but you get the picture;
$json = '[
{
"index": 0,
"tags": [
"abc"
]
},
{
"index": 1,
"tags": [
"xyz"
]
},
{
"foo": 2,
"bar": [
"xyz"
]
}]';
$array = json_decode($json, true);
$default = array_keys($array[0]);
$error = false;
$errors = array();
foreach ($array as $index => $result):
foreach ($default as $search):
if (!isset($result[$search])):
$error = true;
$errors[] = "Property '{$search}' at entry '{$index}' not found. ";
endif;
endforeach;
endforeach;
if ($error):
echo 'Objects are not the same. ';
foreach ($errors as $message):
echo $message;
endforeach;
endif;
returns:
Objects are not the same. Property 'index' at entry '2' not found. Property 'tags' at entry '2' not found.
You can try to use package https://github.com/coduo/php-matcher
Example: These two objects should be considered as equal, because both have the same structure: index var and tags array.
You can create a "php-matcher pattern" like this:
{
"index": "#integer#",
"tags": "#array#.repeat(\"#string#\")"
}
Then you match your JSONs against this pattern. If you have 2 JSONs and both match this pattern then it means that they are "equal" according to your definition of equality above.
Please see results in "php-matcher sandbox" for the example JSONs you gave:
Example 1 in sandbox
Example 2 in sandbox
Additionally you can use package https://github.com/sebastianbergmann/diff (which you should already have if you have phpunit) to generate a diff when the pattern doesnt match the value.
For example:
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
...
$valueToCheck = '{
"foo": 0,
"bar": {"one": 1, "two": "2"}
}';
$expectedValuePattern = '{
"foo": "#integer#",
"bar": {"one": 1, "two": 2}
}';
if (!$matcher->match($valueToCheck, $expectedValuePattern)) {
$differ = new Differ(
new UnifiedDiffOutputBuilder(
"Json value is not matching expected format:\n",
true
)
);
$diffOutput = $differ->diff(
\json_encode(\json_decode($expectedValuePattern, true), JSON_PRETTY_PRINT),
\json_encode(\json_decode($valueToCheck, true), JSON_PRETTY_PRINT)
);
var_dump(
$diffOutput
. "\n".$matcher->getError()."\n"
);
} else {
var_dump('OK');
}
it will print:
Json value is not matching expected format:
## -1,7 +1,7 ##
{
- "foo": "#integer#",
+ "foo": 0,
"bar": {
"one": 1,
- "two": 2
+ "two": "2"
}
}
That message with diff is especially helpfull for bigger JSON's to quickly see which element is not matching.
See more ways of usage in README of that package - especially:
https://github.com/coduo/php-matcher#json-matching
https://github.com/coduo/php-matcher#json-matching-with-unbounded-arrays-and-objects
This package is very good to use in automatic tests (for example: phpunit) to assert if JSON from API responses is correct etc - considering that in integration tests there are often many id's, uuid's, datetime's etc which change on each test execution - like database generated id's etc.
I hope it helps :)
You mean by structure, like model array like:
array ( 'index' => int, 'tags' => array() )
If that's what you are trying to get, try this...
$arr1 = array (
array (
'index' => 0,
'tags' => ['abc']
),
array (
'index' => 1,
'tags' => ['xyz']
),
array (
'index' => 2,
'tags' => ['xyz'],
'boom' => 'granade'
),
array (
'index' => 3,
'tags' => 'xyz'
)
);
$holder = array();
$model = array ('index' => 0, 'tags' => array());
for ($i = 0;$i < count($arr1); $i++)
{
$holder = array_diff(array_merge_recursive($arr1[$i], $model), $model);
if (!empty($holder))
{
echo "different structure<br>";
}
else
{
echo "same structure<br>";
// for further validation
/*
$keys = array_keys($model);
if (is_int($arr1[$i][$keys[0]]) && is_array($arr1[$i][$keys[1]]))
echo "same structure<br>";
else
echo "different structure<br>";
*/
}
}
Sample output:
same structure
same structure
different structure
different structure
You can convert the json string to a php array then use the array_diff($arr1,$arr2) function to compare the newly created array with another array
the result is an array containing the elements of the first array that doesn't exist in the other array
example :
<?php
$array1 = '{"name":"myname","age":"40"}';
//convert the obtained stdclass object to an array
$array1 = (array) json_decode($array1);
$array2 = array("name"=>"myname123","age"=>10);
print_r($array2);
$result_array = array_diff($array1,$array2);
if(empty($result_array[0])){
echo "they have the same structure ";
}
?>
I have this data in MongoDB:
{
"_id": ObjectId("542bxxxxxxxxxxxx"),
"TEMP_C": 13,
"time": ISODate("2014-08-21T05:30:00Z")
}
I want to group it by day and feed it to Highcharts, to display Temp average per day.
Like this: http://jsfiddle.net/tw7n6wxb/3/
Using MongoDB Aggregation Pipeline, I was able to do the grouping, based on some other examples and this great post: http://www.kamsky.org/stupid-tricks-with-mongodb/stupid-date-tricks-with-aggregation-framework
Side question: Why is it so complicated to group by date in MongoDB??? The most annoying part is having to re-compose the date object after splitting it into '$dayOfMonth', '$month', and '$year'.
Is there any simpler way of doing this?
In any case, I got this part working (I think). This is the result:
{
"_id" : {"sec":1409346800,"usec":0},
"avg" : 12
},
{
"_id" : {"sec":1409356800,"usec":0},
"avg" : 15
},
But, Highcharts series take arrays of value pairs as input:
Example: data: [[5, 2], [6, 3], [8, 2]].
The first value on each pair is the X value, and this value has to be a number (when X axis is configured as datetime, X values are in milliseconds).
The PROBLEM I'm having is that MongoDB returns the date as a MongoDate Object with two values inside, 'sec' and 'usec', while Highcharts is expecting one number.
Is there anyway to convert a MongoDate Object to integer in the pipeline? using a $project for example?
I'm using PHP, but I would like to avoid post-pocessing in the application (like PHP date formatting).
Or, any other ideas on how to solve this?
Thanks,
You seem to just want the timestamp values returned from the result. There is indeed a simple way to do this in the aggregation framework without using the date aggregation operators. You can use basic "date math" instead, and with a trick that can be used to extract the "timestamp" value from the date object and manipulate it:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$time", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$time", new Date("1970-01-01") ] },
1000 * 60 * 60 * 24
]}
]
},
"avg": { "$avg": "$TEMP_C" }
}}
])
So the basic "trick" there is that when subtract one date object from another ( or similar operation ) the result returned is a number for "milliseconds" of the time difference between the two. So by using the "epoch" date of "1970-01-01", you get the "epoch timestamp" value for the date object as a number.
Then the basic date math is applied by subtracting from this value the modulo ( or remainder ) from the milliseconds in a day. This "rounds" the value to represent the "day" on which the entry is recorded.
I like posting the JSON because it parses everywhere, but in a more PHP way, then like this:
$collection->aggregate(array(
array( '$group' => array(
'_id' => array(
'$subtract' => array(
array( '$subtract' => array(
'$time', new MongoDate(strtotime("1970-01-01 00:00:00"))
) ),
array( '$mod' => array(
array( '$subtract' => array(
'$time', new MongoDate(strtotime("1970-01-01 00:00:00"))
) ),
1000 * 60 * 60 * 24
))
)
),
"avg" => array( '$avg' => '$TEMP_C' )
))
))
So that is a little cleaner than using the date aggregation operators to get to your intended result. Of course this is still not "all the way" to how you want the data to be presented where you can use it in the client.
The real thing to do here is manipulate the result so that you get the output format you want. This is probably better suited to your server code doing the manipulation before you return the response, but if you have MongoDB 2.6 or greater then it is "possible" to do this within the aggregation pipeline itself:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$time", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$time", new Date("1970-01-01") ] },
1000 * 60 * 60 * 24
]}
]
},
"avg": { "$avg": "$TEMP_C" }
}},
{ "$group": {
"_id": null,
"data": {
"$push": {
"$map": {
"input": { "$literal": [ 1,2 ] },
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el", 1 ] },
"$$_id",
"$avg"
]
}
}
}
}
}}
])
So this is pretty sneaky really. After the initial "grouping" is done to determine the averages for each day you get two fields in your result documents per day for _id and avg. What the $map operator does here is takes and array as input ( in this case, just a numbered template with a pair of values to identify position ) and processes each element to return an array equal to the elements present in the original.
The $cond operator here allows you to look at the value of the current element of that array and "swap it" with another value present in the current document. So for each document, the results contain something that is a paired array like:
[ 1409346800000, 12 ]
Then all that happens is all results are pushed into a single document with a "data" array that appears as follows:
{ "_id": null, "data": [ [..,..], [..,..], (...) ] }
Now your data element in that one result is an array of array pairs representing the points you want.
Of course though, operators like $map are only available from MongoDB 2.6 and onwards, so if you have that available then you can use them but otherwise just process the results in code with a similar "map" operation:
function my_combine($v) {
return array($v["_id"],$v["avg"])
}
$newresult = array_map( "my_combine", $result )
So this really comes down to array manipulation from whichever way you approach it, but the date manipulation trick should also save you some work in obtaining the results as the expected timestamp values as well.
I have a multidimensional array that looks like this:
{
"groups": [
{
"__v": 0,
"_create_date": "2014-08-20T23:00:12.901Z",
"_id": "53f5287ca78473a969001827",
"_last_message_date": "2014-08-20T23:04:36.347Z",
"activity": 0,
"blocked_users": [],
"created_by": {
"_id": "53e84b0eba84943c6d0003f8",
"_last_modified": "2014-08-20T00:11:05.399Z",
"first_name": "Jegg",
"last_name": "V"
},
"curated": false,
"diversity": 0,
"featured": false,
"flagged": false,
"last_message": {
"text": "let's talk beo",
"created_by": {
"_id": "53e84b0eba84943c6d0003f8",
"first_name": "Jegg",
"last_name": "V"
},
"_id": "53f52984a78473a969001833",
"_create_date": "2014-08-20T23:04:36.347Z"
},
"member_count": 1,
"messages_count": 1,
"name": "Test",
"public": true,
"recency": 52182276.347,
"score": 52182276.347,
"tags": []
},
This structure repeats over 3000 times creating a very large multidimensional array. I think I can use array_chunk($array, 300) to break the array into smaller chunks. But I can't figure out how to access them exactly.
What I want to do is independently loop through the newly chunked arrays. So I'd like to end up with something like:
$array1 = {...}
$array2 = {...}
$array3 = {...}
$array4 = {...}
... and so on
THen I could loop through each of the newly created arrays, which are essentially smaller groups of the original array, but of 3000 arrays in one multidimensional array as I have in the first place, I end up with these smaller ones of 300 arrays each.
I hope this makes sense, I'm kinda out of my league. Help is always appreciated.
I think your array is in json format.
First decode it and then pass to array_chunk method.
array_chunk($input_array, 300));
then access them as $input_array[0][0], $input_array[0][1]....... $input_array[0][299], $input_array[1][0], $input_array[1][1].....
EDIT: oh, somehow I entirely misread the question. array_chunk is something worth looking into.
You could try using extract to fetch array values to the "global" variable namespace.
extract takes three arguments: the array you wish to extract, flags, and prefix if needed.
I'm not sure how non-associative arrays are extracted, but you could try
$full_array = array(
array( ... ),
array( ... ),
array( ... ),
array( ... ),
...
);
// EXTR_PREFIX_ALL prefixes all extracted keys with wanted prefix (the third param).
$extract_amount = extract( $full_array, EXTR_PREFIX_ALL, 'prefix' );
Now you should have the array extracted and available for use with variable names $prefix0, $prefix1, $prefix2 and so on.
I'm not sure how smart it is to extract an array with hundreds of available values.