how to aggregate mongodb collection data in laravel - php

i have collection like this
{
"wl_total" : 380,
"player_id" : 1241,
"username" : "Robin",
"hand_id" : 292656,
"time" : 1429871584
}
{
"wl_total" : -400,
"player_id" : 1243,
"username" : "a",
"hand_id" : 292656,
"time" : 1429871584
}
as both collection have same hand_id i want to aggregate both these collection on the basis of hand_id
i want result as combine of
data=array(
'hand_id'=>292656,
'wl_total'=>
{
0=>380,
1=>-400
},
'username'=>
{
0=>"Robin",
1=>"a"
},
"time"=>1429871584
)

You basically want a $group by the "hand_id" common to all players, and then $push to different arrays in the document and then also do something with "time", I took $max. Nees to be an accumulator of some sort at any rate.
Also not sure what your underlying collection name is, but you can call this in laravel with a construct like this:
$result = DB::collection('collection_name')->raw(function($collection)
{
return $collection->aggregate(array(
array(
'$group' => array(
'_id' => '$hand_id',
'wl_total' => array(
'$push' => '$wl_total'
),
'username' => array(
'$push' => '$username'
),
'time' => array(
'$max' => '$time'
)
)
)
));
});
Which returns output ( shown in json ) like this:
{
"_id" : 292656,
"wl_total" : [
380,
-400
],
"username" : [
"Robin",
"a"
],
"time" : 1429871584
}
Personally I would have gone for a single array with all the infomation in it for the grouped "hand", but I supose you have your reasons why you want it this way.

Related

Parse Server Aggregation using PHP SDK

I'm trying to build an aggregation query in Parse's PHP SDK, and I'm stuck in the "lookup" area, I saw a JS example regarding this but it doesn't work in my case.
I have a table of users, which contains a "Tags" field of type Array, the array is actually an array of pointers, that point to a separate Tag class.
What I'm trying to achieve is to list most popular Tags based on their usage, so basically I need to query the users class and group the Tags that exist in the array, I already achieved this, but I'm stuck with the lookup part, the query currently returns an array of Tags pointers, what I want is to pull the object of those pointers.
Here's what I have currently:
$query = new ParseQuery('_User');
$pipeline = [
'project' => ['tags' => 1],
'unwind' => '$tags',
'group' => [
'objectId' => '$tags.objectId',
'count' => ['$sum' => 1]
],
'sort' => [ 'count' => -1],
'limit' => 10,
];
try {
return $query->aggregate($pipeline);
} catch (ParseException $ex) {
return $ex->getMessage();
}
And here's a snippet of what the _User collection looks like:
{
"_id" : "5BuBVo2GD0",
"email" : "test#test.com",
"username" : "test#test.com",
"lastname" : "Doe",
"firstname" : "John",
"_created_at" : ISODate("2017-01-23T09:20:11.483+0000"),
"_updated_at" : ISODate("2019-02-15T02:48:30.684+0000"),
"tags" : [
{
"__type" : "Pointer",
"className" : "Tag",
"objectId" : "St2gzaFnTr"
},
{
"__type" : "Pointer",
"className" : "Tag",
"objectId" : "LSVxAy2o74"
}
],
"_p_country" : "Country$4SE8J4HRBi",
}
And the Tag collection looks like this:
{
"_id" : "St2gzaFnTr",
"name" : "Music",
"_created_at" : ISODate("2018-10-22T20:00:10.481+0000"),
"_updated_at" : ISODate("2018-10-22T20:00:10.481+0000")
}
Any help would be appreciated!
Thanks in advance
Not sure if this is a direct answer, but here's a working aggregation on tags sorting for freq...
public function tagHistogram(Request $request, Response $response, array $args): Response {
$pipeline = [
'unwind' => '$tags' ,
'sortByCount' => '$tags',
'limit' => 1000,
];
$query = new ParseQuery('Product');
$result = $query->aggregate($pipeline);
$result = array_map(
function ($e) {
$e['name'] = $e['objectId'];
unset($e['objectId']);
return $e;
},
$result
);
return $response->withJson($result);
}

Mongodb string to date conversion

Below is my sample mongodb collection
{
"_id" : ObjectId("57ed32f4070577ec56a56b9f"),
"log_id" : "180308",
"issue_id" : "108850",
"author_key" : "priyadarshinim_contus",
"timespent" : NumberLong(18000),
"comment" : "Added charts in the dashboard page of the application.",
"created_on" : "2017-08-16T18:22:04.816+0530",
"updated_on" : "2017-08-16T18:22:04.816+0530",
"started_on" : "2017-08-16T18:21:39.000+0530",
"started_date" : "2017-08-02",
"updated_date" : "2017-08-02",
"role" : "PHP",
"updated_at" : ISODate("2017-09-29T15:27:48.069Z"),
"created_at" : ISODate("2017-09-29T15:27:48.069Z"),
"status" : 1.0
}
I need to get records with help of started_date , by default I will give two dates in that i will check $gt and $lt of started date .
$current_date = '2017-08-31';
$sixmonthfromcurrent ='2017-08-01';
$worklogs = Worklog::raw ( function ($collection) use ($issue_jira_id, $current_date, $sixmonthfromcurrent) {
return $collection->aggregate ( [
['$match' => ['issue_id' => ['$in' => $issue_jira_id],
'started_date' => ['$lte' => $current_date,'$gte' => $sixmonthfromcurrent]
]
],
['$group' => ['issue_id' => ['$push' => '$issue_id'],
'_id' => ['year' => ['$year' => '$started_date'],
'week' => ['$week' => '$started_date'],'resource_key' => '$author_key'],
'sum' => array ('$sum' => '$timespent')]
],
[ '$sort' => ['_id' => 1]
]
] );
} );
If I run this query I am getting this type of error:
Can't convert from BSON type string to Date
How to rectify this error?
The only field in your $group that I see as troubling is the field week.
The year you could extract by doing a $project before your $group aggregation:
$project: {
year: { $substr: [ "$started_date", 0, 4 ] },
issue_id: 1,
author_key: 1,
timespent: 1
}
if you know that the date string will always come at this format. Of course you cannot do a substr operation for finding out the week.
It would be easy though if your field started_date would be an actual ISODate(), then you could use exactly what you wrote as you probably already saw in the documentation.
If you need the field week very bad, which I imagine you do, then I'd suggest you convert your field started_date to an ISODate().
You can do that with a bulkWrite:
db = db.getSiblingDB('yourDatabaseName');
var requests = [];
db.yourCollectionName.find().forEach(doc => {
var date = yourFunctionThatConvertsStringToDate(doc.started_date);
requests.push( {
'updateOne': {
'filter': { '_id': doc._id },
'update': { '$set': {
"started_date": date
} }
}
});
if (requests.length === 500) {
db.yourCollectionName.bulkWrite(requests);
requests = [];
}
});
if(requests.length > 0) {
db.yourCollectionName.bulkWrite(requests);
}
Load this script directly on your mongodb server and execute there.
Hope this helps.

PHP MongoDB Driver and Aggregations

I am at my first steps with mongoDB and php, trying to figure out how aggregations works. I have an approximate idea on how to use them from the command line but I am trying to translate this for the php driver. I am using the restaurants dexample DB, a list of records like this
{
"_id" : ObjectId("59a5211e107765480896f3f8"),
"address" : {
"building" : "284",
"coord" : [
-73.9829239,
40.6580753
],
"street" : "Prospect Park West",
"zipcode" : "11215"
},
"borough" : "Brooklyn",
"cuisine" : "American",
"grades" : [
{
"date" : ISODate("2014-11-19T00:00:00Z"),
"grade" : "A",
"score" : 11
},
{
"date" : ISODate("2013-11-14T00:00:00Z"),
"grade" : "A",
"score" : 2
},
{
"date" : ISODate("2012-12-05T00:00:00Z"),
"grade" : "A",
"score" : 13
},
{
"date" : ISODate("2012-05-17T00:00:00Z"),
"grade" : "A",
"score" : 11
}
],
"name" : "The Movable Feast",
"restaurant_id" : "40361606"
}
I just want to count how many restaurants for location, what I am doing is
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->myNewDb->restaurants;
$results = $collection->aggregate(
[
'name' => '$name'
],
[
'$group' => [
'cuisine' => ['sum' => '$sum']
]
]
);
and I am getting this error
Fatal error: Uncaught exception 'MongoDB\Exception\InvalidArgumentException'
with message '$pipeline is not a list (unexpected index: "name")'
any idea? I can't find any good documentation on php.net.
thanks
M
Just take a look into documentation, and you will see, that the pipelines must be passed as an array.
The aggregate method accepts two parameters $pipelines and $options (public function aggregate(array $pipeline, array $options = [])).
Also as was mentioned before, the $group must have the _id element.
Groups documents by some specified expression and outputs to the next
stage a document for each distinct grouping. The output documents
contain an _id field which contains the distinct group by key. The
output documents can also contain computed fields that hold the values
of some accumulator expression grouped by the $groupā€˜s _id field.
$group does not order its output documents.
https://docs.mongodb.com/manual/reference/operator/aggregation/group/
So your code must look like this:
$results = $collection->aggregate([
[
'$group' => [
'_id' => '$cuisine',
'sum' => ['$sum' => 1],
'names' => ['$push' => '$name']
]
]
]);
This code groups documents by cuisine element, counts the items and collects all name values into array.

Get matching elements in mongodb array

I want to use aggregation to get this array only with those tickets, which have start field after 2015-06-16. Can someone help me with the pipeline?
{
"name" : "array",
"tickets" : [
{
"id" : 1,
"sort" : true,
"start" : ISODate("2015-06-15T22:00:00.000Z")
},
{
"id" : 2,
"sort" : true,
"start" : ISODate("2015-06-16T22:00:00.000Z")
},
{
"id" : 3,
"sort" : true,
"start" : ISODate("2015-06-17T22:00:00.000Z")
}
]
}
It's true that the "standard projection" operations available to MongoDB methods such as .find() will only return at most a "single matching element" from the array to that is queried by either the positional $ operator form in the "query" portion or the $elemMatch in the "projection" portion.
In order to do this sort of "ranged" operation, you need the aggregation framework which has greater "manipulation" and "filtering" capabilities on arrays:
collection.aggregate(
array(
# First match the "document" to reduce the pipeline
array(
'$match' => array(
array(
'tickets.start' => array(
'$gte' => new MongoDate(strtotime('2015-06-16 00:00:00'))
)
)
)
),
# Then unwind the array
array( '$unwind' => '$tickets' ),
# Match again on the "unwound" elements to filter
array(
'$match' => array(
array(
'tickets.start' => array(
'$gte' => new MongoDate(strtotime('2015-06-16 00:00:00'))
)
)
)
),
# Group back to original structure per document
array(
'$group' => array(
'_id' => '$_id',
'name' => array( '$first' => '$name' ),
'tickets' => array(
'$push' => '$tickets'
)
)
)
)
)
Or you can possibly use the $redact operator to simplify with MongoDB 2.6 or greater which basically uses the $cond operator syntax as it's input:
collection.aggregate(
array(
# First match the "document" to reduce the pipeline
array(
'$match' => array(
array(
'tickets.start' => array(
'$gte' => new MongoDate(strtotime('2015-06-16 00:00:00'))
)
)
)
),
# Redact entries from the array
array(
'$redact' => array(
'if' => array(
'$gte' => array(
array( '$ifNull' => array(
'$start',
new MongoDate(strtotime('2015-06-16 00:00:00'))
)),
new MongoDate(strtotime('2015-06-16 00:00:00:00'))
)
),
'then' => '$$DESCEND',
'else' => '$$PRUNE'
)
)
)
)
So both examples do the "same thing" in "filtering" the elements from the array that "do not" match the conditions specified and return "more than one" element, which is something basic projection cannot do.
You should use Aggregation to get output.
You should use following query:
db.collection.aggregate({
$match: {
name: "array"
}
}, {
$unwind: "$tickets"
}, {
$match: {
"tickets.start": {
$gt: ISODate("2015-06-16")
}
}
}, {
$group: {
"_id": "name",
"tickets": {
$push: "$tickets"
}
}
})

PHP & MongoDB show results grouped by date

I have an mongodb collection with following documents:
{
"_id" : ObjectId("547af6aea3f0eba7148b4567"),
"check_id" : "f5d654e7-257d-4a93-ae50-2d59dfeeb451",
"chunks" : NumberLong(200),
"num_hosts" : NumberLong(1000),
"num_rbls" : NumberLong(163),
"owner" : NumberLong(7901),
"created" : ISODate("2014-11-30T10:51:26.924Z"),
"started" : ISODate("2014-11-30T10:51:31.558Z"),
"finished" : ISODate("2014-11-30T10:57:08.512Z")
}
{
"_id" : ObjectId("54db19a858a5d395a18b4567"),
"check_id" : "9660e510-1349-43f3-9d5e-8bf4b06179be",
"chunks" : NumberLong(2),
"num_hosts" : NumberLong(10),
"num_rbls" : NumberLong(166),
"owner" : NumberLong(7901),
"created" : ISODate("2015-02-11T08:58:17.118Z"),
"started" : ISODate("2015-02-11T08:58:18.78Z"),
"finished" : ISODate("2015-02-11T08:58:47.486Z")
}
{
"_id" : ObjectId("54db267758a5d30eab8b4567"),
"check_id" : "9660e510-1349-43f3-9d5e-8bf4b06179be",
"chunks" : NumberLong(2),
"num_hosts" : NumberLong(10),
"num_rbls" : NumberLong(166),
"owner" : NumberLong(7901),
"created" : ISODate("2015-02-11T09:52:55.388Z"),
"started" : ISODate("2015-02-11T09:52:56.109Z"),
"finished" : ISODate("2015-02-11T09:53:22.095Z")
}
What I need is to get the result and produce an array similar to this:
Array
(
[2015-02-11] => array
(
//array with results from 2015-02-11
)
[2014-11-30] => array
(
//array with results from 2014-11-30
)
)
I know that it's possible to just perform simply collection->find and then loop through results and use php logic to achieve my goal but is it possible to make it using mongo? Maybe using aggregation framework?
EDIT: I want to group results by "created" date
Any help will be highly appreciated.
Monogo aggregation mongo aggregation group used for this, so below query may solve your problem
db.collectionName.aggregate({
"$group": {
"_id": "$created",
"data": {
"$push": {
"check_id": "$check_id",
"chunks": "$chunks",
"num_hosts": "$num_hosts",
"num_rbls": "$num_rbls",
"owner": "$owner",
"started": "$started",
"finished": "$finished"
}
}
}
}).pretty()
Or
db.collectionName.aggregate({
"$group": {
"_id": "$created",
"data": {
"$push": "$$ROOT"
}
}
}).pretty()
Also in mongo 2.8 $dateToString provide facility to convert ISO date to string format so below query also work
db.collectionName.aggregate([
{
"$project": {
"yearMonthDay": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$created"
}
},
"check_id": "$check_id",
"chunks": "$chunks",
"num_hosts": "$num_hosts",
"num_rbls": "$num_rbls",
"owner": "$owner",
"started": "$started",
"finished": "$finished"
}
},
{
"$group": {
"_id": "$yearMonthDay",
"data": {
"$push": "$$ROOT"
}
}
}
]).pretty()
I have managed to solve this using the aggregation framework. Here is the answer, in case anyone need it.
$op = array(
array(
'$project' => array(
'data' => array(
'check_id' => '$check_id',
'chunks' => '$chunks',
'num_hosts' => '$num_hosts',
'num_rbls' => '$num_rbls',
'owner' => '$owner',
'started' => '$started',
'finished' => '$finished',
),
'year' => array('$year' => '$created' ),
'month' => array('$month' => '$created' ),
'day' => array('$dayOfMonth' => '$created'),
)
),
array(
'$group' => array(
'_id' => array('year' => '$year', 'month' => '$month', 'day' => '$day'),
'reports_data' => array('$push' => '$data'),
)
),
);
$c = $collection->aggregate($op);

Categories