Multiple group by in (aggregation) Mongo - php

I trying to group by two different fields in an aggregation in order to avoid duplicating it. I have the below case:
'$project' => [
'type1' => '$field1',
'type2' => '$field2',
'category1' => [
'$cond' => [
['$gt' => ['field3', ' ']],
1,
0
]
],
'category2' => [
'$cond' => [
['$eq' => ['$field4', 1]],
1,
0
]
],
],
],
['$group' => [
'_id' => '$type1',
'total' => ['$sum' => 1],
'category1' => ['$sum' => '$category1'],
'category' => ['$sum' => '$category2']
]
],
['$sort' => ['_id.$type1' => 1, '_id.type1' => 1]]
In the above example I am grouping them by the $field1. Is it possible in the same aggregation to group the same data by $field 2 too?

Yes, its possible.
If you want to group two fields, then the query is as simple as:
db.collection.aggregate([
{$group:{_id:{field1:"$field1", field2:"$field2"}..other accumulators here..}}
])

Related

Is it possible to retrieve 2 eloquent-arrays with a single query in laravel

I have a table of sent emails history that stores (id, locale, timestamps, subject, content)
locale can be either 'en' or 'fr'
timestamps are standard laravel created_at, updated_at timestamps
I'm trying to get the list of emails with 'en' locale and 'fr' locale separately, ordered by oldest first, counting the number of sent emails each day
here's an example of the expected array
[
'en' => [
0 => [
'date' => '2022-08-01',
'number' => 2,
],
1 => [
'date' => '2022-08-02',
'number' => 1,
]
2 => [
'date' => '2022-08-03',
'number' => 1,
]
3 => [
'date' => '2022-08-04',
'number' => 3,
]
4 => [
'date' => '2022-08-05',
'number' => 1,
]
],
'fr' => [
0 => [
'date' => '2022-08-01',
'number' => 1,
],
1 => [
'date' => '2022-08-02',
'number' => 2,
]
2 => [
'date' => '2022-08-03',
'number' => 2,
]
3 => [
'date' => '2022-08-04',
'number' => 1,
]
4 => [
'date' => '2022-08-05',
'number' => 1,
]
]
]
I've tried to use the groupBy function but I'm struggling to separate locales
// $date is the date to start couting from
Email::where('created_at', '>=', $date)->orderBy('date')->groupBy('locale', 'date')->get(array(
DB::raw('locale'),
DB::raw('Date(created_at) as date'),
DB::raw('COUNT(*) as "number"'))
);
this gives the following output
[
0 => [
'locale' => 'en',
'date' => '2022-08-01',
'number' => 2,
],
1 => [
'locale' => 'fr',
'date' => '2022-08-01',
'number' => 1,
],
2 => [
'locale' => 'en'
'date' => '2022-08-02',
'number' => 1,
]
3 => [
'locale' => 'fr',
'date' => '2022-08-02',
'number' => 2,
]
...
]

Codeigniter: How to implement inner join in mongodb

I want to know how can I query inner joins in mongodb? I have this users and payments table. I only want to display records that users has payments if not, do not include it in the result. So far, I only got left join like results.
Here is my code:
$getData=[
[
'$match' => ['profile_status'=> 'buyer']
],
[
'$project'=>[
'_id' => ['$toString' => '$_id'],
'profile_image' => '$profile_image',
'full_name' => '$full_name',
'profile_status' => '$profile_status'
]
],
[
'$lookup' => [
'from' => 'payment_details',
'let' => [
'user_id' => ['$toString' => '$_id']
],
'pipeline' => [
[
'$match' => [
'$expr' => ['$eq' => ['$buyer_id','$$user_id']]
]
],
[
'$project' => [
'_id' => ['$toString' => '$_id'],
'price' => '$price'
]
],
],
'as' => 'paymentData'
]
]
];
$getresult= $db->users->aggregate($getData);
$getfinalresult= iterator_to_array($getresult);
This code returns users data even paymentData is empty, I only want users that has paymentData. Im using PHP/Codeigniter 3.
Thank you!

MongoDB filter query results by $lookup array

MongoDB 3.4
I have a project and project_permission collections. The project_permission collection contains permissions to the projects for some users. A single user can have multiple different permissions to the project.
[
'$lookup' => [
'from' => ProjectPermission::collectionName(),
'localField' => '_id',
'foreignField' => 'project_id',
'as' => 'project_permissions'
]
],
[
'$project' => [
// ... irrelevant fields here
'permissions' => '$project_permissions'
]
],
this is how the project query results looks like without filtering:
// other project results
// ... other fields
'permissions' => [
0 => [
'_id' => '5d2873aafa873b2b7c000fad'
'project_id' => '56a9e5c5d18cacc72a485839'
'user_id' => '562f6bfc05dfe9570fb6e427'
'permission' => 'read'
'created_at' => 1562932138
'updated_at' => 1562932139
]
1 => [
'_id' => '5d2879fdfa873b2b7c000fbd'
'project_id' => '56a9e5c5d18cacc72a485839'
'user_id' => '562f6bfc05dfe9570fb6e427'
'permission' => 'write'
'created_at' => 1562932139
'updated_at' => 1562932140
]
2 => [
'_id' => '5db960b5fa873b1604005e8e'
'project_id' => '56a9e5c5d18cacc72a485839'
'user_id' => '582b30dd1e634e6362e1b504'
'permission' => 'write'
'created_at' => 1572430005
'updated_at' => 1572430005
]
]
What I would like to achieve is to return with only those projects where the client - who requested the query - has a specific permission to the project, for example write.
The way I tried it:
pipeline: [
0 => [
'$match' => [
// not related to the problem
]
]
1 => [
'$match' => [
'$and' => [
0 => [
'shared_permissions' => [
'$eq' => true
]
]
1 => [
'$or' => [
0 => [
'project_permissions' => [
'$exists' => true
'$ne' => []
]
]
1 => [
'owner_id' => [
'$ne' => MongoDB\BSON\ObjectId#1
(
[oid] => '582b30dd1e634e6362e1b504'
)
]
]
]
]
]
]
]
2 => [
'$lookup' => [
'from' => 'project_permission'
'localField' => '_id'
'foreignField' => 'project_id'
'as' => 'project_permissions'
]
]
3 => [
'$project' => [
// more not important fields here
'shared_permissions' => 1
'permissions' => [
'$map' => [
'input' => [
'$filter' => [
'input' => '$project_permissions'
'as' => 'project_permission'
'cond' => [
'$and' => [
0 => [
'$eq' => ['$$project_permission.user_id', MongoDB\BSON\ObjectId#1
(
[oid] => '582b30dd1e634e6362e1b504'
)
]
1 => [
'$eq' => ['$$project_permission.permission', 'write']
]
]
]
]
]
'as' => 'project_permission'
'in' => [
'user_id' => '$$project_permission.user_id'
'permission' => '$$project_permission.permission'
]
]
]
]
]
]
For this I almost get the correct response:
[
0 => [
'_id' => '56a9e5c5d18cacc72a485839'
'short_id' => 3
'title' => 'Modified title'
'owner_id' => '562f692a05dfe9560fb6e428'
'updated_at' => 1572435428
'owner_name' => 'Borat Sagdiyev'
'shared_permissions' => true
'permissions' => [
0 => [
'user_id' => '582b30dd1e634e6362e1b504'
'permission' => 'write'
]
]
]
1 => []
]
The problem with this is that empty array, where the result was filtered out - and it wouldn't be a problem if the empty array wouldn't be in the result, because if I use the pagination, then it says two results, instead of one. And we know that in the worst case we would get back an array of empty arrays only.
So what I would like to achieve is this last example results without empty arrays in a way where the pagination will be fine with it too.
ps.: unwind is not an option, because of some structural conventions.
Any ideas?
To perform an equality match between a field from the input documents with a field from the documents of the “joined” collection, the $lookup stage has the following syntax:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}

Combine mongoDB $lookup with $project

Right now I'm using the $project aggregation for filtering out unnecessary fields. Also I'm using the $lookup aggregation to link two collections togethe and I know how to use both of them in the main collection.
Now my question is; how can I put this $project aggregation inside of a lookup?
What I have now is looks like this:
[
'$lookup' => [
'from' => Media::collectionName(),
'localField' => '_id',
'foreignField' => 'project_id',
'as' => 'mediaList'
]
],
[
'$project' => [
'title' => 1,
'owner_id' => 1,
'owner_name' => 1,
'created_at' => 1,
'updated_at' => 1,
'status' => 1,
'discount' => 1,
'company' => 1,
'media' => [
'$filter' => [
'input' => '$mediaList',
'as' => 'media',
'cond' => $mediaFilter
]
]
]
],
So I can filtering out the unnecessary fields in the main collection. How can I do this in the sub-collection?
If I understood your question correctly, you want to apply a $filter operation on your $mediaList field.
To avoid a redundant $project stage (where you have to declare every single field you want to keep), use $addFields stage instead, like this :
[
'$lookup' => [
'from' => Media::collectionName(),
'localField' => '_id',
'foreignField' => 'project_id',
'as' => 'media' // Note that I use the same name for the field
]
],
[
'$addFields' => [
'media' => [
'$filter' => [
'input' => '$media',
'as' => 'media',
'cond' => $mediaFilter
]
]
]
],

Mongo aggregate multi match/group

I have a data of stores, report_type is every type of data
- type 3 stored how much money that clients buy and how many transaction.
- type 5 stored how many people and average age of them.
I need an array like this:
"report_type3"
(
[_id] => 2441
[amountclient] => 3749078452,
[trans] => 1000
),
(
[_id] => 2442
[amountclient] => 15533754,
[trans] => 900
)
,
"report_type5"
(
[_id] => 2441
[people] => 120,
[avgage] => 23,
),
(
[_id] => 2441
[people] => 120,
[avgage] => 23,
)
My query looks like this, but it's not working
$cursor = $collection->aggregate([
['$match' => [ 'id_station' => ['$in' => $store ], 'report_type' => 3,
'date' => ['$gte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-07-01")*1000), '$lte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-05")*1000)]
]
],
['$group' => [
'_id' => [ 'id_station' => '$id_station', 'receiptid' => '$receiptid' ],
'amount' => [ '$sum' => '$amount' ]
]], .... *I group by receiptid because there are many items in one clients.*
[ '$group' => [
'_id' => '$_id.id_station',
'amount' => [ '$sum' => '$amount' ],
'trans' => [ '$sum' => '$trans' ]
]],
[
'$project' => [
'report_type3' => ['_id','$amountclient','$trans']
]
],
['$match' => [ 'id_station' => ['$in' => $store ], 'report_type' => 5,
'date' => ['$gte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-07-01")*1000), '$lte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-05")*1000)]
]
],
[ '$group' => [
'_id' => ['$_id.id_station', 'area_type' => '$area_type'],
'people' => [ '$sum' => '$people' ],
'avgage' => [ '$avg' => '$age' ]
]],
[
'$project' => [
'report_type5' => [ '_id', 'people', 'avgage' ]
]
]
]);
Could you please help me on this?

Categories