I have a working MongoDB aggregate query which I can run via the MongoDB shell. However, I am trying to convert it to work with the official PHP Mongo driver (http://php.net/manual/en/mongocollection.aggregate.php).
Here is the working raw MongoDB query:
db.executions.aggregate( [
{ $project : { day : { $dayOfYear : "$executed" } } },
{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }
] )
Here is my attempt (not working) in PHP using the Mongo driver:
$result = $c->aggregate(array(
'$project' => array(
'day' => array('$dayOfYear' => '$executed')
),
'$group' => array(
'_id' => array('day' => '$day'),
'n' => array('$sum' => 1)
),
'$sort' => array(
'_id' => 1
),
'$limit' => 14
));
The error from the above PHP code is:
{"errmsg":"exception: wrong type for field (pipeline) 3 != 4","code":13111,"ok":0}
Any ideas? Thanks.
The parameter in your Javascript is an array of 4 objects with one element each, in your PHP it's an associative array (object) with 4 elements. This would represent your Javascript:
$result = $c->aggregate(array(
array(
'$project' => array(
'day' => array('$dayOfYear' => '$executed')
),
),
array(
'$group' => array(
'_id' => array('day' => '$day'),
'n' => array('$sum' => 1)
),
),
array(
'$sort' => array(
'_id' => 1
),
),
array(
'$limit' => 14
)
));
In addition, if you have at least PHP5.4, you can use simpler array syntax. Transformation to PHP is then trivial, you simply replace curly braces with square brackets and colons with arrows:
$result = $c->aggregate([
[ '$project' => [ 'day' => ['$dayOfYear' => '$executed'] ] ],
[ '$group' => ['_id' => ['day' => '$day'], 'n' => ['$sum' => 1] ] ],
[ '$sort' => ['_id' => 1] ],
[ '$limit' => 14 ]
]);
You can also use the JSON array directly:
$json = [
'{ $project : { day : { $dayOfYear : "$executed" } } },
{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }'
];
$pipeline = [];
foreach ($json as $stage) {
array_push($pipeline, toPHP(fromJSON($stage)));
}
$result = $c->aggregate($pipeline);
You can also use combination like this:
use function MongoDB\BSON\toRelaxedExtendedJSON;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toPHP;
use function MongoDB\BSON\fromJSON;
$operator = '$dayOfYear';
$json = [];
array_push($json, toRelaxedExtendedJSON(fromPHP(
array('$project ' => array('day' => array($operator => '$executed')))
)));
array_push($json,
'{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }'
);
$pipeline = [];
foreach ($json as $stage) {
array_push($pipeline, toPHP(fromJSON($stage)));
}
$result = $c->aggregate($pipeline);
or
$operator = '$dayOfYear';
$json = [];
array_push($json,
'{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }'
);
$pipeline = [];
array_push($pipeline, array('$project ' => array('day' => array($operator => '$executed'))));
foreach ($json as $stage) {
array_push($pipeline, toPHP(fromJSON($stage)));
}
$result = $c->aggregate($pipeline);
If your aggregation query is dynamic or based on user-input be aware of NoSQL-Injection!
https://www.acunetix.com/blog/web-security-zone/nosql-injections/
Related
This question already has answers here:
Group subarrays by one column, make comma-separated values from other column within groups
(2 answers)
Closed last month.
I have a two dimensional array which needs to be restructured. Rows must be grouped by date values, and within each group, the name values should be formed into a single comma-delimited string.
My input:
$missedFridgeLog = [
[
"date" => "01/01/18",
"name" => "Medicine"
],
[
"date" => "01/01/18",
"name" => "Drugs"
],
[
"date" => "02/01/18",
"name" => "Medicine"
],
[
"date" => "02/01/18",
"name" => "Drugs"
]
];
I have tried implementing a solution from Implode or join multidimentional array with comma, but it did not work as desired.
Desired output:
[
[
'date' => '01/01/18',
'name' => 'Medicine,Drugs',
],
[
'date' => '02/01/18',
'name' => 'Medicine,Drugs',
]
]
$missedFridgeLog = [
[
"date" => "01/01/18",
"name" => "Medicine"
],[
"date" => "01/01/18",
"name" => "Drugs"
]
[
"date" => "02/01/18",
"name" => "Medicine"
],
[
"date" => "02/01/18",
"name" => "Drugs"
]
];
$byDates = [];
foreach ($missedFridgeLog as $mfg) {
$byDates[$mfg['date']][] = $mfg['name'];
}
$res = [];
foreach ($byDates as $date => $name) {
$res[] = [
'name' => join(',',$name),
'date' => $date
];
}
var_dump($res);
You need to search key, value pair everytime you make a insert or update to result array. use this function() to search associate array. If match then update with additional name info else make a new insert to result array.
function filter_array($array){
///$array your previous array data
$result = array();
foreach($array["missedFridgeLog"] as $m){
$flag = true;
foreach($result as $k=>$r) {
if (is_in_array($r, "date", $m["date"]) == "yes") {
$result[$k]["name"] = $r["name"] . ',' . $m["name"];
$flag = false;
break;
}
}
if($flag==true){
$result[] = $m;
}
}
return array("missedFridgeLog"=>$result);
}
function is_in_array($array, $key, $key_value){
$within_array = 'no';
foreach( $array as $k=>$v ){
if( is_array($v) ){
$within_array = is_in_array($v, $key, $key_value);
if( $within_array == 'yes' ){
break;
}
} else {
if( $v == $key_value && $k == $key ){
$within_array = 'yes';
break;
}
}
}
return $within_array;
}
May be this is not the best way to do this, but it will help
$arr['missedFridgeLog'] = [
[
'date' => '01/01/18',
'name' => 'Medicine1'
],
[
'date' => '02/01/18',
'name' => 'New Medicine2'
],
[
'date' => '01/01/18',
'name' => 'Drugs1'
],
[
'date' => '02/01/18',
'name' => 'Medicine2'
],
[
'date' => '01/01/18',
'name' => 'New Drugs1'
],
[
'date' => '02/01/18',
'name' => 'Drugs2'
]
];
echo "<pre>";
$new_arr = array();
$date = array();
foreach ($arr['missedFridgeLog'] as $k => $a) {
if (in_array($a['date'], $date))
continue;
$new_arr[$k]['name'] = $a['name'];
$new_arr[$k]['date'] = "";
foreach ($arr[missedFridgeLog] as $key => $val) {
if ($key != $k) {
if ($a['date'] == $val['date']) {
$date[] = $new_arr[$k]['date'] = $a['date'];
$new_arr[$k]['name'] .= ", " . $val['name'];
}
}
}
if (empty($new_arr[$k]['date']))
unset($new_arr[$k]);
}
print_r($new_arr);
You can try below :-
Input array :-
$chkArray = [
'missedFridgeLog' => [
'0' => [
'date' => '01/01/18',
'name' => 'Medicine'
],
'1' => [
'date' => '01/01/18',
'name' => 'Drugs'
],
'2' => [
'date' => '02/01/18',
'name' => 'Medicine'
],
'3' => [
'date' => '02/01/18',
'name' => 'Drugs'
],
'4' => [
'date' => '02/01/18',
'name' => 'My Drugs'
]
]
];
$i = 0;
$finalarray = array();
foreach($chkArray['missedFridgeLog'] as $key=>$value) {
$checkExist = array_search($value['date'], array_column($finalarray, 'date'), true);
if($checkExist !== false) {
$finalarray[$checkExist]['name'] = $finalarray[$checkExist]['name'].','.$value['name'];
}
else {
$finalarray[$i]['date'] = $value['date'];
$finalarray[$i]['name'] = $value['name'];
$i++;
}
}
print_r($finalarray);
OutPut :-
Array
(
[0] => Array
(
[date] => 01/01/18
[name] => Medicine,Drugs
)
[1] => Array
(
[date] => 02/01/18
[name] => Medicine,Drugs,My Drugs
)
)
Do not use:
More than one loop,
Nested loops,
in_array(), or
array_search().
You only need one loop to apply temporary grouping keys. When a date/group is encountered after the first time, append its name value to the group's name value. When finished iterating, re-index the array.
Code: (Demo)
$result = [];
foreach ($missedFridgeLog as $row) {
if (!isset($result[$row['date']])) {
$result[$row['date']] = $row;
} else {
$result[$row['date']]['name'] .= ",{$row['name']}";
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'date' => '01/01/18',
'name' => 'Medicine,Drugs',
),
1 =>
array (
'date' => '02/01/18',
'name' => 'Medicine,Drugs',
),
)
I wrote a mongodb query that I am having a hard time converting to php code:
var geoips = db.geoip.find().map(function(like){ return like.ip; });
var result = db.audit.aggregate([
{ $match: { ip: { $nin: geoips } } },
{ $group: {
_id: "$ip",
count: { $sum: 1 }
}}
]);
UPDATE:
The above query is the equivalent of the following Relation Database Query
Select ip,count(*)
from audit
where ip not in (select ip from geoip)
group by ip
Since I had to make this query in mongodb version 3.0, I was unable to take advantage of $lookup as suggested in an answer.
The below PHP code accomplishes the above objective and works as expected. It gets the distinct ips from geoip collection. It passes that result and does an aggregate on the audit collection to get the desired result.
$geoipcolln = $this->dbConn->selectCollection('geoip');
$geoips = $geoipcolln->distinct('ip');
$match = array('ip' => array('$nin' => $geoips));
$result = $this->collection->aggregate(
array(
'$match' => $match
),
array('$group' => array(
'_id' => '$ip',
'count' => array('$sum' => 1.0),
))
);
This can be done in one aggregation query using the $lookup operator as follows:
var result = db.audit.aggregate([
{
"$lookup": {
"from": "geoip",
"localField": "ip",
"foreignField": "ip",
"as": "geoips"
}
},
{ "$match": { "geoips.0": { "$exists": false } } },
{ "$group": {
"_id": "$ip",
"count": { "$sum": 1 }
}}
])
which can then be translated to PHP as:
<?php
$m = new MongoClient("localhost");
$c = $m->selectDB("yourDB")->selectCollection("audit");
$ops = array(
array(
"$lookup" => array(
"from" => "geoip",
"localField" => "ip",
"foreignField" => "ip",
"as" => "geoips"
)
),
array( "$match" => array( "geoips.0" => array( "$exists" => false ) ) ),
array( "$group" => array(
"_id" => "$ip",
"count" => array( "$sum" => 1 )
))
);
$results = $c->aggregate($ops);
var_dump($results);
?>
I am having trouble converting it to PHP. My query is working in mongodb and get output well. But same query i tried to convert into php shown error
Array
(
[ok] => 0
[errmsg] => A pipeline stage specification object must contain exactly one field.
[code] => 16435
)
My collection sample as follows
{
"_id" : ObjectId("5790f9407ffbe031fa2049e3"),
"EventTS" : ISODate("2016-07-01T04:30:00.000+0530"),
"KeyValues" : [
{
"InputVolt" : null
},
{
"InputVolt" : 250
},
{
"InputVolt" : 230
},
{
"InputVolt" : 230
},
{
"InputVolt" : 240
}
]
},
{
"_id" : ObjectId("5790f9407ffbe031fa2049e4"),
"EventTS" : ISODate("2016-07-01T04:35:00.000+0530"),
"KeyValues" : [
{
"InputVolt" : null
},
{
"InputVolt" : 260
},
{
"InputVolt" : null
},
{
"InputVolt" : null
},
{
"InputVolt" : null
}
]
}
My mongodb query is
db.sample_coll.aggregate(
{
$unwind: {
path:"$KeyValues",
includeArrayIndex:"arrayIndex",
preserveNullAndEmptyArrays:true
}
},
{
$project: {
timestamp:{
"$add":["$EventTS",{"$multiply":[60000,"$arrayIndex"]}]
} ,
"InputVolt":"$KeyValues.InputVolt",
arrayIndex:1
}
},
{
$match: {
$and: [
{InputVolt: {$ne: null}}
]
}
}
);
My mongo query convert into php as follows
$pipeline = array(
array('$unwind' =>
array( 'path' => '$KeyValues'),
array( 'includeArrayIndex' => 'arrayIndex' ),
array( 'preserveNullAndEmptyArrays' => 'true' )
),
array(
'$project' => array(
'timestamp' => array(
'$add' => array(
'$EventTS',
array('$multiply' => array( 60000, '$arrayIndex' ))
)
),
array( 'InputVolt' => array( '$KeyValues', 'InputVolt' ) ) ,
array('arrayIndex' => 1)
)
),
array(
'$match' => array(
'$and' => array(
array('InputVolt' => array('$ne' => null )),
)
)
)
);
$result = $collection->aggregate($pipeline);
I don't know what happen. Please suggest me.
I have the following collection structure
{
"_id": {
"d_timestamp": NumberLong(1429949699),
"d_isostamp": ISODate("2015-04-25T08:14:59.0Z")
},
"XBT-USD-cpx-okc": [
{
"buySpread": -1.80081
}
I run the following aggregation
$spreadName ='XBT-USD-stp-nex';
$pipe = array(
array(
'$match' => array(
'_id.d_isostamp' => array(
'$gt' => $start, '$lt' => $end
)
)
),
array(
'$project' => array(
'sellSpread' =>'$'.$spreadName.'.sellSpread',
)
),
array(
'$group' => array(
'_id' => array(
'isodate' => array(
'$minute' => '$_id.d_isostamp'
)
),
'rsell_spread' => array(
'$avg' => '$sellSpread'
),
)
),
);
$out = $collection->aggregate($pipe ,$options);
and I get as a result the value 0 for rsell_spread whereas if I run a $max for instance instead of an $avg in the $group , I get an accurate value for rsell_spread , w/ the following structure
{
"_id": {
"isodate": ISODate("2015-04-25T08:00:58.0Z")
},
"rsell_spread": [
-4.49996▼
]
}
So I have two questions :
1/ How come does the $avg function does not work?
2/ How can I can a result not in an array when I use $max for example (just a regular number)?
The $avg group accumulator operator does work, it's only that in your case it is being applied to an element in an array and thus gives the "incorrect" result.
When you use the $max group accumulator operator, it returns the the highest value that results from applying an expression to each document in a group of documents, thus in your example it returned the maximum array.
To demonstrate this, consider adding a few sample documents to a test collection in mongoshell:
db.test.insert([
{
"_id" : {
"d_timestamp" : NumberLong(1429949699),
"d_isostamp" : ISODate("2015-04-25T08:14:59.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80081
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949710),
"d_isostamp" : ISODate("2015-04-25T08:15:10.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80079
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949720),
"d_isostamp" : ISODate("2015-04-25T08:15:20.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80083
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949730),
"d_isostamp" : ISODate("2015-04-25T08:15:30.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80087
}
]
}
])
Now, replicating the same operation above in mongoshell:
var spreadName = "XBT-USD-stp-nex",
start = new Date(2015, 3, 25),
end = new Date(2015, 3, 26);
db.test.aggregate([
{
"$match": {
"_id.d_isostamp": { "$gte": start, "$lte": end }
}
},
{
"$project": {
"sellSpread": "$"+spreadName+".sellSpread"
}
}/*,<--- deliberately omitted the $unwind stage from the pipeline to replicate the current pipeline
{
"$unwind": "$sellSpread"
}*/,
{
"$group": {
"_id": {
"isodate": { "$minute": "$_id.d_isostamp"}
},
"rsell_spread": {
"$avg": "$sellSpread"
}
}
}
])
Output:
/* 0 */
{
"result" : [
{
"_id" : {
"isodate" : 15
},
"rsell_spread" : 0
},
{
"_id" : {
"isodate" : 14
},
"rsell_spread" : 0
}
],
"ok" : 1
}
The solution is to include an $unwind operator pipeline stage after the $project step, this will deconstruct the XBT-USD-stp-nex array field from the input documents and outputs a document for each element. Each output document replaces the array with an element value. This will then make it possible for the $avg group accumulator operator to work.
Including this will give the aggregation result:
/* 0 */
{
"result" : [
{
"_id" : {
"isodate" : 15
},
"rsell_spread" : -1.80083
},
{
"_id" : {
"isodate" : 14
},
"rsell_spread" : -1.80081
}
],
"ok" : 1
}
So your final working aggregation in PHP should be:
$spreadName ='XBT-USD-stp-nex';
$pipe = array(
array(
'$match' => array(
'_id.d_isostamp' => array(
'$gt' => $start, '$lt' => $end
)
)
),
array(
'$project' => array(
'sellSpread' =>'$'.$spreadName.'.sellSpread',
)
),
array('$unwind' => '$sellSpread'),
array(
'$group' => array(
'_id' => array(
'isodate' => array(
'$minute' => '$_id.d_isostamp'
)
),
'rsell_spread' => array(
'$avg' => '$sellSpread'
),
)
),
);
$out = $collection->aggregate($pipe ,$options);
I have this collection :
...
"votes": [
{
"track1": [
{
"facebook": NumberLong(1)
}
],
"track2": [
{
"google": NumberLong(1),
"twitter": NumberLong(1)
}
]
}
],
...
I want to get the sum of the votes of track1 or track2 so what i did is :
$match = array(
'app_id' => (int)$appId,
'campaign_id' => (int)$campaign_id
);
$group = array(
'_id' => 'votes.0.'.$_t.'.0.facebook', //$_t => track id
'total' => array(
'$sum' => '$votes.0.'.$_t.'.0.facebook'
)
);
$res = $collection->aggregate(array(
array(
'$match' => $match
),
array(
'$group' => $group
)
)
);
$res_facebook = (int) $res['result'][0]['total'];
Result : 0
Where is the problem ?
Not sure if it will work, I didn't check it, but I had something similar.
This is how I would solve it:
$votes_array = json_decode($votes_array_json,TRUE);
$total_votes = 0;
foreach($votes_array as $track) {
foreach($track as $social_network => $votes) {
$total_votes = $total_votes + $votes;
};
};