How to do a group within a group in Mongo? - php

I have a query where by:
$offenses = \App\LawCase::raw(function ($collection) {
return $collection->aggregate([
[
'$match' => ['active' => true, 'type' => 'criminal', 'current_offense_literal'=> ['$exists' => true]]
],
[
'$group' => ['_id' => '$current_offense_category',
'offense_literals' => ['$addToSet' => '$current_offense_literal'], 'code'=> ['$addToSet' => '$current_offense'], 'count' => ['$sum' => 1]]
]
]);
});
It works fine but I want to get how many occurrences of a particular 'current_offense_literal' I got above. I guess a group within a group?

Related

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>
}
}

Mongo $addToSet an Array

I have a working query:
$offenses = \App\LawCase::raw(function ($collection) {
return $collection->aggregate([
[
'$match' => ['active' => true, 'type' => 'criminal', 'current_offense_literal'=> ['$exists' => true]]
],
[
'$group' => ['_id' => '$current_offense_category', 'offense_literals' => ['$addToSet' => '$current_offense_literal']]
]
]);
});
I just want to add an additional value to the addToSet above. For example Each current_offense_literal also has a current_offense_literal_value and I would like to add that along with current_offense_literal.
What i tried:
$offenses = \App\LawCase::raw(function ($collection) {
return $collection->aggregate([
[
'$match' => ['active' => true, 'type' => 'criminal', 'current_offense_literal'=> ['$exists' => true]]
],
[
'$group' => ['_id' => '$current_offense_category', 'offense_literals' => ['$addToSet' => ['$current_offense_literal'=>['$push'=> '$current_offense_literal_value']]]]
]
]);
});
But I am getting:
Unrecognized expression '$current_offense_literal'.
try doing this,
$offenses = \App\LawCase::raw(function ($collection) {
return $collection->aggregate([
[
'$match' => ['active' => true, 'type' => 'criminal', 'current_offense_literal'=> ['$exists' => true]]
],
[
'$group' => ['_id' => '$current_offense_category', 'offense_literals' => ['$addToSet' => ['current_offense_literal'=>'$current_offense_literal', 'current_offense_literal_value' =>'$current_offense_literal_value']]]
]
]);
});

Multiple group by in (aggregation) Mongo

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..}}
])

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
]
]
]
],

Is $sort always required after a $project and $match in Mongodb aggregation pipeline?

I have a working aggregation call that performs a $project, $match and $sort (by _id) which works perfectly fine.
In order to optimize my app, I tried dropping the $sort part at the end because I'm actually sorting by a datetime string in my php app after the cursor function is returned. Hence why I no longer wish to sort by _id.
However, when I remove this part of the pipeline, it returns no data. Is this a required part of my call?
$pipeline = [
['$project' => [
'date' => '$date',
'time' => '$time',
'datetime' => '$datetime',
'dev_id' => '$dev_id'
],
],
['$match' => [
'dev_id' => ['$in' => $deviceIdArray],
'datetime' => ['$gte' => $this->dateFrom, '$lte' => $this->dateTo]],
],
['$sort' => ['_id' => -1],
],
];
First you are doing project then doing match, so you are not getting any result as output.
Try the code below:-
$pipeline = [
['$match' => [
'dev_id' => ['$in' => $deviceIdArray],
'datetime' => ['$gte' => $this->dateFrom, '$lte' => $this->dateTo]],
]
['$project' => [
'date' => '$date',
'time' => '$time',
'datetime' => '$datetime',
'dev_id' => '$dev_id'
],
]
];

Categories