fetching embedded documents on search criteria of another embedded document - mongodb

I have a collection HostelTbl which has many documents one of the document is like
{
"_id": ObjectId("5ae69fb4d2ccda0e70005551"),
"Name": "Hostel-MGL-02",
...
"RoomsDetails": [
{
"RoomId": "9a21e427-16fd-7a9e-bc16-537dc55093bb",
"FloorNumber": "1",
"RoomNumber": "101",
...
},
{
"RoomId": "050c65ab-355e-9bec-00a0-2999e75bcdc4",
"FloorNumber": "1",
"RoomNumber": "104",
...
},
{
"RoomId": "f197c635-a6fb-b4d6-9cf5-ed53177edd47",
"FloorNumber": "2",
"RoomNumber": "109",
...
},
...
{
...
}
],
"AllotmentsDetails": [
{
"AllotmentId": "5eb05e1d-2690-afad-747a-ef29450ae3f3",
"StudentId": ObjectId("5ab8d5d4ff24ae120400085e"),
"RoomId": "9a21e427-16fd-7a9e-bc16-537dc55093bb",
"FromDate": ISODate("2018-06-12T22:00:00.0Z"),
"ToDate": ISODate("2018-07-04T22:00:00.0Z"),
...
},
{
"AllotmentId": "40d73d84-0f94-48d8-823c-2255fd2ae0ed",
"StudentId": ObjectId("5ab8d5bcff24ae120400085d"),
"RoomId": "050c65ab-355e-9bec-00a0-2999e75bcdc4",
"FromDate": ISODate("2018-01-21T22:00:00.0Z"),
"ToDate": ISODate("2018-02-06T22:00:00.0Z"),
...
},
{
"AllotmentId": "40d73564-0f94-48d8-823c-2255fd2440428",
"StudentId": ObjectId("5ab8d5bc4624ae1204000855),
"RoomId": "f197c635-a6fb-b4d6-9cf5-ed53177edd47",
"FromDate": ISODate("2018-03-21T22:00:00.0Z"),
"ToDate": ISODate("2018-05-04T22:00:00.0Z"),
...
}
]
}
I have two embedded documents inside one documents. One is storing rooms information and another storing rooms allotments to a student information.
Now I want to fetch those available rooms from RoomsDetails if their allotments detail's "ToDate" field contains the dates in past from today inside AllotmentsDetails Embedded document. Like in the above case the rooms with ids
"050c65ab-355e-9bec-00a0-2999e75bcdc4", f197c635-a6fb-b4d6-9cf5-ed53177edd47 contains ToDate dates in the past inside AllotmentsDetails embedded documents. It should fetch those two rooms only.
I am not able to find such queries. Please help!!!
I am trying to tweak the below lines of code
public function fetchAllAvailableRooms()
{
$cursor = $this->collection->aggregate(array(
array(
'$match' => array(
"_id" => new MongoDB\BSON\ObjectID($this->id)
)
),
array(
'$project' => array(
'RoomsDetails' => array(
'$filter' => array(
'input' => '$AllotmentsDetails',
'as' => 'allot',
'cond' => array(
'$lt' => array('$$allot.FromDate', $this->Today)
)
)
),
'Name' => 1
)
)
))->toArray();
return $cursor;
}

You can use the $map and $filter aggregation operators for that.
$map - for AllotmentsDetails transformation into final list
of the room ids
$filter - for RoomsDetails filtering by the
final list of the room ids
Example:
public function fetchAllAvailableRooms()
{
$cursor = $this->collection->aggregate([
['$match' => ["_id" => new MongoDB\BSON\ObjectID($this->id)]],
[
'$addFields' => [
'filteredIds' => [
'$map' => [
'input' => '$AllotmentsDetails',
'as' => 'item',
'in' => [
'$cond' => [
['$lt' => ['$$item.ToDate', $this->Today]],
'$$item.RoomId',
false
]
]
]
]
]
],
[
'$project' => [
'RoomsDetails' => [
'$filter' => [
'input' => '$RoomsDetails',
'as' => 'item',
'cond' => ['$in' => ['$$item.RoomId', '$filteredIds']]
]
]
]
]
])->toArray();
return $cursor;
}
/* Result example
{
"_id" : ObjectId("5ae69fb4d2ccda0e70005551"),
"RoomsDetails" : [
{
"RoomId" : "050c65ab-355e-9bec-00a0-2999e75bcdc4",
"FloorNumber" : "1",
"RoomNumber" : "104"
},
{
"RoomId" : "f197c635-a6fb-b4d6-9cf5-ed53177edd47",
"FloorNumber" : "2",
"RoomNumber" : "109"
}
]
}
*/
P.S. PHP code is not tested, so i'm not 100% sure if it is working.
P.P.S. The final solution is strongly depends on the details of your project.

Related

Query on the basis of empty array and non existing field with other conditions in expr

I have documents like
{
'_id': ...
'registration_temp_perm_no' => 'SIEI/216/2020',
'first_name' => 'Azra',
'last_name' => 'Ali'
},
{
'_id': ...
'registration_temp_perm_no': ....
'first_name': ...,
'last_name' : ...,
'transport_details' : [
{
...
status : "Inactive"
}
]
},
{
'_id': ...
'registration_temp_perm_no':...
'first_name' : ....
'last_name': .....,
'transport_details': []
}
Note that the documents are created/updated via front end functionalities in the following three ways
where embedded document 'transport_details' does not exist at all.
where embedded document 'transport_details' contains empty array
where embedded document 'transport_details' may contain multiple sub arrays
Now I want to fetch those documents where the status of last sub array is "Inactive" or "Requested" and where the sub array "transport_details" doesn't exist and "transport_details" is empty.
I have written query like
$query = array(
'$or' => array(
array("first_name" => new MongoDB\BSON\Regex($arg, 'i')),
array("middle_name" => new MongoDB\BSON\Regex($arg, 'i')),
array("last_name" => new MongoDB\BSON\Regex($arg, 'i')),
array("registration_temp_perm_no" => $arg)
),
"schoolId"=> new MongoDB\BSON\ObjectID($this->schoolId),
'$expr' => [
'$or' => [
['transport_details' => ['$exists' => false]],
['transport_details' => ['$size' => 0]],
['$eq'=> [['$arrayElemAt' => ['$transport_details.status', -1]], "Requested"]],
['$eq'=> [['$arrayElemAt' => ['$transport_details.status', -1]], "Inactive"]],
]
]
);
If we remove the lines ['transport_details' => ['$exists' => false]], ['transport_details' => ['$size' => 0]], the query is able to fetch those documents where the status of last sub array is "Inactive" or "Requested" otherwise it does not work.
When you use $expr, all next operators should have Aggregation-expressions syntax. Check full list here
You need to change
['transport_details' => ['$exists' => false]],
['transport_details' => ['$size' => 0]]
to
['$eq'=> ['$transport_details', undefined]], //equivalent to {$exists:false}
['$eq'=> [['$size' => '$transport_details'], 0]] // $size returns number, we compare numbers
If we apply $IfNull operator, we combine "no exist" + "empty array" into single condition.
Try this one:
db.collection.find({
... your extra conditions here
$expr: {
$or: [
{
$eq: [
{
$size: {
$ifNull: [
"$transport_details",
[]
]
}
},
0
]
},
{
$eq: [
{
$arrayElemAt: [
"$transport_details.status",
-1
]
},
"Requested"
]
},
{
$eq: [
{
$arrayElemAt: [
"$transport_details.status",
-1
]
},
"Inactive"
]
}
]
}
})
MongoPlayground

Filter on projection not working in mongodb

I have done aggregation query on student table with activity table which gives the desired fields except one field i.e "ActivityForMediumSize". This field comes empty.
The code is
$query = ["is_temp_deleted"=>0, "funRun" => "Yes"];
$pipeline = array(
array(
'$match' => $query
),
array(
'$lookup' => array(
'from' => 'studentTbl',
'localField' => '_id',
'foreignField' => 'activity_details.activityId',
'as' => 'studentsOfActivities'
)
),
['$lookup'=> [
'from'=> 'studentTbl',
'localField' => 'academic_year_Id',
'foreignField' => 'studentsOfActivities.academic_year_Id',
'as'=> 'Students'
]],
['$project' => [
'_id' => 1.0,
'activityName' => 1.0,
'activityTime' => 1.0,
'activityDate' => 1.0,
'funRun' => 1.0,
'Students' => 1.0,
'studentsOfActivities' => 1.0,
'studentsOfWeekgroup' => [
'$filter' => [
'input' => '$Students',
'as' => 'item',
'cond' => [
'$eq' => ['$$item.academic_year_Id', new MongoDB\BSON\ObjectID($this->id)],
]
]
],
'ActivityForMediumSize' => [
'$filter' => [
'input' => '$studentsOfActivities',
'as' => 'item',
'cond' => [
'$eq' =>['$$item.activity_details.-1.shirtSize', 'M'],
]
]
],
'StudentCountPerActivity'=>['$size'=>'$studentsOfActivities'],
// 'MediumCount'=>['$size'=>'$countForMedium']
]],
);
$cursor = $this->db->activitiesTbl->aggregate($pipeline)->toArray();
I have checked in console that data in "studentsOfActivities" appears like
0 =>
"_id": ObjectId("5bf518378d03ec0b2400536f"),
"ssn_Last_four": "0001",
"first_name": "Thomas",
"activity_details": [
{
"studentId": ObjectId("5bf518378d03ec0b2400536f"),
"funrun": "Yes",
"weekgroupId": ObjectId("5bf50bd48d03ec0b2400536d"),
"activityId": ObjectId("5c10a0e08d03ec1834001c6d"),
"attending": "Yes",
"id": ObjectId("5c10e5ae3250041188005dde")
},
{
"studentId": ObjectId("5bf518378d03ec0b2400536f"),
"funrun": "Yes",
"weekgroupId": ObjectId("5bf50bd48d03ec0b2400536d"),
"activityId": ObjectId("5c10a0e08d03ec1834001c6d"),
"attending": "Yes",
"shirtSize": "L",
"id": ObjectId("5c13338032500409f4007cf3")
}
]
1=>
"_id": ObjectId("5bf518378d03ec0b2400536e"),
....
....
The embedded document "activity_details" contains student's all the activities and the last element of it is considered active one i.e why I have used -1 in the above code.
Now in projection "ActivityForMediumSize" I am trying find those students who have selected shirtSize: "M". The filter on projection "ActivityForMediumSize" is not working. Please help !!!
Note that there are some students where the last activity inside "activity_details" which does not contain "shirtSize" field.
I guess "is not working" means you have an empty array. It's because you cannot refer to the last element of an array just by "-1". Instead you need to use array operators to get the last element. For example it can be a combination of arrayElemAt and slice so the filter condition can look like :
$eq: [
{ $let: {
vars: {
last: { $arrayElemAt: [
{ $slice: [ '$$item. activity_details', -1 ] },
0
] }
},
in: '$$last.shirtSize'
} },
'M'
]
It's in js syntax but it should be trivial to translate to php.

Aggregation $lookup with $let is not working

I have a collection TblStudent in mongodb like
{
"_id": ObjectId("5baa85041d7859f40d000029"),
"Name": "John Doe",
"RollNo": 12,
"Class": "Ist"
....
}
I have another collection TblRoute like
{
"_id": ObjectId("5baa818d1d78594010000029"),
"Name": "New york City",
"StopDetails": [
{
"StopId": "abc777",
"Name": "Block no 3"
},
{
"StopId": "abc888",
"Name": "Block no 4"
}
],
"NumberOfSeats": "10",
"StudentDetails": [
{
"StudentId": ObjectId("5baa85041d7859f40d000029"),
"VehicleId": "7756"
},
{
"StudentId": ObjectId("5baa85f61d7859401000002a"),
"VehicleId": "7676"
}
]
}
I am using mongodb 3.6 platform. I am using below lines of code
$query = ['_id' => new MongoDB\BSON\ObjectID($this->id)];
$cursor = $this->db->TblRoute->aggregate([
['$match' => $query],
[
'$lookup' =>
[
'from' => "TblStudent",
'let' => ['StudentId' => '$StudentDetails.StudentId'],
'pipeline' => [
[ '$match' =>
['$expr' =>
['$eq' => ['$StudentId', '$$StudentId' ] ]
]
],
[ '$project' => ['Name' => 1, 'RollNo' => 1 ] ]
],
'as' => "StudentDetails.StudentData"
]
]
]);
I have been trying to fetch data from another collection but with certain fields only. I am trying to fetch student Name and RollNo fields from TblStudent inside
TblRoute in order to make document array light weight. Normally the $lookup stage fetches all the fields from another collection.
I am trying the above code. It throws error message
"StudentDetails.StudentData" is coming empty 'StudentDetails' => MongoDB\Model\BSONDocument::__set_state(array( 'StudentData' => MongoDB\Model\BSONArray::__set_state(array( )), )),
but I think that the code is not written correctly. The actual method might be different. Please help me in sorting out the problem.
I want the output to be something like
{
"_id": ObjectId("5baa818d1d78594010000029"),
"Name": "New york City",
"StopDetails": [
.....
],
"StudentDetails": [
{
"StudentId": ObjectId("5baa85041d7859f40d000029"),
"VehicleId": "7756",
"StudentData": [
"Name": ..
"RollNo":...
]
},
{
"StudentId": ObjectId("5baa85f61d7859401000002a"),
"VehicleId": "7676",
"StudentData": [
"Name": ..
"RollNo":...
]
}
]
}
Use the below aggregation.
Note from the docs
User variable names must begin with a lowercase ascii letter [a-z] or
a non-ascii character.
So change the $let variable to studentid and also fixed other issues in code.
db.TblRoute.aggregate([
{"$match":ObjectId("5baa818d1d78594010000029")},
{"$lookup":{
"from":"TblStudent",
"let":{"studentid":"$StudentDetails.StudentId"},
"pipeline":[
{"$match":{"$expr":{"$in":["$_id","$$studentid"]}}},
{"$project":{"Name":1,"RollNo":1}}
],
"as":"StudentDetails.StudentData"
}}
])

Error message lookup pipeline must be a string, is type array

I am using mongodb 3.6 version.
I have vehicle table with documents like
{
"_id": ObjectId("5b976220d2ccda12fc0050fb"),
"VehicleNumber": "JK 678",
"NumberOfSeats": "47",
}
{
"_id": ObjectId("67976220d2ccda12fc005068"),
"VehicleNumber": "JK 779",
"NumberOfSeats": "47",
}
I have route table with data like
{
"_id": ObjectId("5b7fb426d2ccda11fc005185"),
"Name": "New Jersey City",
"VehicleDetails": [
{
"VehicleEntryId": "b8d0d2b5-8f32-6850-4d79-34ed79138d6d",
"VehicleId": ObjectId("5b976220d2ccda12fc0050fb"),
...
"Status": "Active"
},
{
"VehicleEntryId": "b8d0d2b5-8f32-6850-4d79-34ed79138568",
"VehicleId": ObjectId("67976220d2ccda12fc005068"),
...
"Status": "Active"
}
],
...
}
I have written mongodb aggregation query like
$cursor = $this->collection->aggregate([
['$match' => ["_id" => new MongoDB\BSON\ObjectID('5b7fb426d2ccda11fc005185')]],
[
'$addFields' => [
'filteredIds' => [
'$map' => [
'input' => '$VehicleDetails',
'as' => 'item',
'in' => [
'$cond' => [
['$eq' => ['$$item.Status', 'Active']],
'$$item.VehicleId',
false
]
]
]
]
]
],
array(
'$lookup' => array(
'from' => 'VehicleTbl',
'$pipeline' => [
[ '$match'=> ["_id" => ['$in' => ['$$filteredIds'] ]]],
],
'as' => 'AllotedVehicleDetails'
)
),
])->toArray();
Basically I am trying to fetch all assigned vehicle to a particular route. So I first fetch all the actively alloted vehicle ids by using $addFeilds operator and putting them in "filteredIds" then in lookup I am trying to fetch vehicle informations from vehicle table using pipeline. The above code throws error message 'Uncaught exception 'MongoDB\Driver\Exception\RuntimeException' with message '$lookup argument '$pipeline: [ { $match: { _id: { $in: [ "$$filteredIds" ] } } } ]' must be a string, is type array' in' line no 32.
Please help!!!
use pipeline instead of $pipeline.
Example:
{
$lookup : {
'from' : 'vehicles',
let : { localFilterIds : '$filteredIds'},
pipeline : [
{'$match' : { $expr: { '$in' : [ '$_id', '$$localFilterIds' ] }} }
],
'as' : 'AllotedVehicleDetails'
}
}
Note: This is tested in mongoDb GUI Robo 3T.

aggregate function not working php codeigniter

I wrote a aggregate query to get data in mongodb. same query I need to run via php codigniter,
This is my mongodb query
db.states.aggregate([
{
$project: {
states: {
$filter: {
input: "$states",
as: "state",
cond: { $eq: [ "$$state.country_id", "101" ] }
}
}
}
}
])
same query i need to convert for php codignater. I have used this library to perform the db operarion.
I tried:
$pipeline = [
[
'$project' => [
'states' =>[
'$filter' => [
'input' => '$states',
'as' => 'state',
'cond' => [
'$eq' => [
'$$state.country_id' => $country_id
]
]
]
]
]
]
];
$result = $this->mongo_db2->aggregate('tb10_states', $pipeline);
But, its throw Message: Unrecognized expression '$$state.country_id'
Suggest me How to fix this.