Aggregation $lookup with $let is not working - mongodb

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

Related

How can I filter records with multiple $in condition (some are optional $in) with arrays of string? Mongoose/MongoDB

You can see my Mongodb Records at last... I am now trying to implement search functionality,
I mad checkbox filtration for my project and below I listed those arrays after I clicked multiple checkboxes (see 1, 2 and 3).
I tried in aggregate with multiple match queries with $in, but it doesn't worked. Below arrays are used to check the records.
for example:
["Restaurant", "Mall"] need to check with "commercialType" in records, at the same time ["AC Rooms", "3 Phase Electricity"] need to check with "propertyFeatures.name" in records.. so all matching records must display if records exist with those filtrations.
I tried with multiple $in queries like this, but it gives empty records.
"$match": {
"commercialType": {
"$in": ["Restaurant", "Hotel"]
},
{
"propertyFeatures.name": {
"$in": ['AC Rooms']
}
},
... other match filters
}
1. Below Array is used to find commercialType (field in doc)
[
'Restaurant',
'Office space',
'Hotel'
]
2. Below Array is used to find landType (field in doc)
[
'Bare land',
'Beachfront land',
'Coconut land'
]
3. Below Array is used to find "propertyFeatures.name" (field in doc)
[
'AC Rooms',
'3 Phase Electricity',
'Hot Water'
]
[
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "House",
"propertyFeatures": [
{
"id": 1,
"name": "AC Rooms",
"value": true
}
]
},
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "Land",
"landType": "Bare land",
"propertyFeatures": [
{
"id": 1,
"name": "Wider Road",
"value": true
}
]
},
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "Commercial",
"commercialType": "Restaurant",
"propertyFeatures": [
{
"id": 1,
"name": "3 Phase Electricity",
"value": true
}
]
}
]
You are probably missing $or operator, so your example pipeline becomes
[
{"$match": {
"$or": [
{
"commercialType": {
"$in": ["Restaurant", "Hotel"]
},
{
"propertyFeatures.name": {
"$in": ['AC Rooms']
}
}
]
}
]
MongoDB docs: https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/#error-handling

Why is mongo telling me I need two arguments for $in in the query below?

I have the following query:
const productTypes = await ProductTypes.find(
{type},
{
style: {
$in: styles
}
}
);
This is my styles array:
styles [ 'mom' ]
And I get this error:
MongoServerError: Expression $in takes exactly 2 arguments. 1 were passed in.
At first glance, it looks like your query is wrong. You need so move the style query into the first param {type}.
You can check out a live query here
Something like this:
const productTypes = await ProductTypes.find({
type,
style: {
$in: styles // ["mom"]
}
});
This is the same stuff from the live demo... putting it here for archival purposes.
Test Database
[
{
"type": "a",
"styles": [
"mom",
"dad",
"sister"
]
},
{
"type": "b",
"styles": [
"mom",
"brother",
"dad"
]
}
]
Query
db.collection.find({
type: "a",
styles: {
$in: [
"mom"
]
}
})
Result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"styles": [
"mom",
"dad",
"sister"
],
"type": "a"
}
]

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.

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.

fetching embedded documents on search criteria of another embedded document

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.