How to check available dates in laravel and mongo - php

I am developing a hotel booking availability check using laravel and mongodb. I am confused to write query to find available dates. I tried a method but it is like between and. I am not sure query is correct or not.
I have included laravel raw query and mongo query in this question. Both are same.
Why query is not correct?
Because, Guest checking on 2017-09-02 and checkout on 2017-09-05. Currently I am fetching data like 2017-09-02 >= checkin_from && 2017-09-05 <= checkin_from. If 2017-09-02 is the checkin_from date then, this is correct. But if checkin_from is 2017-08-25 and reserve_to is 2017-09-06. Here dates 2017-09-02 to 2017-09-05 includes. In this case how will we check?
Is this possible with query?
Or
Array 1: Fetch all the bookings and store in an array.
Array 2: Then prepare date (checkin and checkout date from frontend) using DatePeriod and DateInterval and store in an array. Then check matching array(1 & 2).
Which method do I follow?
Query Currently I am using
$bookings = Booking::raw(function ($collection) use ($dateBegin, $dateEnd) {
return $collection->aggregate([
[
'$match' => [
'cabinname' => 'test',
'checkin_from' => ['$gte' => $dateBegin, '$lte' => $dateEnd],
'is_delete' => 0,
],
],
[
'$project' => [
'beds' => 1,
'dormitory' => 1,
'sleeps' => 1,
'status' => 1,
'has status' => [
'$in' => ['status', ['1', '4', '5', '7']]
]
],
]
]);
});
Mongo Query
db.booking.aggregate([
{
$match: {
"cabinname" : 'Test',
"checkin_from" : {$gte :[ new Date ('2017-09-01') ], $lte : [ new Date ('2017-09-03') ] },
"is_delete" : 0,
}
},
{
$project: {
"beds" : 1,
"cabinname":1,
"checkin_from":1,
"reserve_to":1,
"has status" : {
$in: [ "status", ['1', '4', '5', '7'] ]
}
}
}
])
Data from database
{ "_id" : ObjectId("5888fbd5d2ae672245fb5f79"), "cabinname" : "Test", "checkin_from" : ISODate("2017-08-29T22:00:00Z"), "reserve_to" : ISODate("2017-09-03T22:00:00Z"), "beds" : "8" }
{ "_id" : ObjectId("58a4812bd2ae67066eeaea41"), "cabinname" : "Test", "checkin_from" : ISODate("2017-09-01T22:00:00Z"), "reserve_to" : ISODate("2017-09-05T22:00:00Z"), "beds" : "18" }
{ "_id" : ObjectId("58bac8a5d2ae67951845edaf"), "cabinname" : "Test", "checkin_from" : ISODate("2017-09-01T22:00:00Z"), "reserve_to" : ISODate("2017-09-02T22:00:00Z"), "beds" : "0" }
{ "_id" : ObjectId("58d03541d2ae671c668b4568"), "cabinname" : "Test", "checkin_from" : ISODate("2017-09-02T22:00:00Z"), "reserve_to" : ISODate("2017-09-04T22:00:00Z"), "beds" : "14" }

My query is not working because of wrong conditions. I have updated the conditions now I am getting exact results.
{checkin_from:{$lte: new Date ('2017-09-02')}, reserve_to:{$gt: new Date ('2017-09-02')}}
$bookings = Booking::raw(function ($collection) use ($dateBegin, $dateEnd) {
return $collection->aggregate([
[
'$match' => [
'cabinname' => 'test',
'checkin_from' => ['$lte' => $dateBegin],
'reserve_to' => ['$gt' => $dateBegin],
'is_delete' => 0,
],
],
[
'$project' => [
'beds' => 1,
'dormitory' => 1,
'sleeps' => 1,
'status' => 1,
'has status' => [
'$in' => ['status', ['1', '4', '5', '7']]
]
],
]
]);
});

Related

Mongodo - Sorting only works on a subset of data

I'm trying to sort the returned data from an API call based on a mongo aggregation. My issue is that the sorting works if I filter the data to return a few hundred records, but if I return all possible records the sorting doesn't work. My desired goal is to sort by resource name, then trade date.
For example
GU_400020 June 2nd
GU_400020 June 2nd
GU_400020 June 3rd
GU_400020 June 3rd
GU_400021 June 2nd
Currently I'm getting something like this
GU_400020 June 2nd
GU_400020 June 2nd
GU_400021 June 2nd
GU_400021 June 2nd
GU_400020 June 3rd
GU_400020 June 3rd
GU_400021 June 3rd
GU_400021 June 3rd
This is my query
SD_ActualScheduleAvailability.aggregate([{"$match":{"$and":[{"TRADE_DATE":{"$gte":"2010-06-02"}},{"TRADE_DATE":{"$lte":"2010-06-03"}}],"RS_VERSION":"1","RUN_TYPE":{"$in":["EP1"]}}},{"$project":{"TRADE_DATE":1,"RESOURCE_NAME":1,"PARTICIPANT_NAME":1,"RUN_TYPE":1,"RESOURCE_TYPE":1,"DELIVERY_DATE":1,"DELIVERY_HOUR":{"$convert":{"input":"$DELIVERY_HOUR","to":"int"}},"DELIVERY_INTERVAL":{"$convert":{"input":"$DELIVERY_INTERVAL","to":"int"}},"DELIVERY_TIME":1,"DELIVERY_ORDER":1}},{"$facet":{"items":[{"$skip":0},{"$limit":1000}],"totalItems":[{"$count":"totalItems"}]}},{"$project":{"resultsWithTotal":{"$setUnion":["$items","$totalItems"]}}},{"$unwind":"$resultsWithTotal"},{"$replaceRoot":{"newRoot":"$resultsWithTotal"}},{"$sort":{"PARTICIPANT_NAME":1,"RESOURCE_NAME":1,"DELIVERY_DAY":1,"DELIVERY_HOUR":1,"DELIVERY_INTERVAL":1,"DELIVERY_ORDER":1}}],{"allowDiskUse":true})
This is the code
$pipeline = [
[
'$match' => $matchPipeline
],
[
'$project'=> [
"TRADE_DATE" => 1,
"RESOURCE_NAME" => 1,
"PARTICIPANT_NAME" => 1,
"RUN_TYPE" => 1,
"RESOURCE_TYPE" => 1,
"DELIVERY_DATE" => 1,
"DELIVERY_HOUR" => ['$convert'=> [ 'input'=> '$DELIVERY_HOUR', 'to'=> 'int']],
"DELIVERY_INTERVAL" => ['$convert'=> [ 'input'=> '$DELIVERY_INTERVAL', 'to'=> 'int']],
"DELIVERY_TIME" => 1,
"DELIVERY_ORDER" => 1,
]
],
[
'$facet'=> [
'items'=> [[ '$skip'=> $offset ], [ '$limit'=> $limit ]],
'totalItems'=> [['$count'=> "totalItems"]]
]
],
[
'$project' => [
"resultsWithTotal"=> ['$setUnion'=> ['$items', '$totalItems']]
]
],
[
'$unwind'=> '$resultsWithTotal'
],
[
'$replaceRoot'=> ['newRoot'=> '$resultsWithTotal']
],
[
'$sort' => [
'PARTICIPANT_NAME' => 1,
'RESOURCE_NAME' => 1,
'DELIVERY_DAY' => 1,
'DELIVERY_HOUR' => 1,
'DELIVERY_INTERVAL' => 1,
'DELIVERY_ORDER' => 1,
]
],
];
Sample Document
{
"_id" : ObjectId("5eae17810ac25308b9e70f1e"),
"PID" : "1",
"RS_VERSION" : "1",
"TRADE_DATE" : "2010-06-03",
"RESOURCE_NAME" : "GU_400020",
"PARTICIPANT_NAME" : "NA",
"RUN_TYPE" : "EP1",
"RESOURCE_TYPE" : "APTG",
"DELIVERY_DATE" : "2010-06-04",
"DELIVERY_HOUR" : "1",
"DELIVERY_INTERVAL" : "1",
"DELIVERY_TIME" : "00:00",
"DELIVERY_ORDER" : "1",
}
If I query for subset of the data, the sorting works as desired for example
$matchPipeline["RESOURCE_NAME"] = ['$in'=> ['GU_400020', 'GU_400021']];
However if I return all the possible resources, the sorting breaks.

arrayFilters on mongodb php update [duplicate]

This question already has answers here:
How to Update Multiple Array Elements in mongodb
(16 answers)
Closed 3 years ago.
I want to update many documents on my db but are nested arrays so I need to use arrayFilters. In the shell works perfect but in my php code never works.
I have tried this way:
$command = new \MongoDB\Driver\Command(
[
'update' => 'students2',
'updates' => [["grades.grade" => ['$gte' => 85]], ['$set' => ["grades.$[elem].mean" => 98 ]], 'arrayFilters' => $filters, 'multi' => true]
]
);
with error: BSON field 'update.updates.grades.grade' is an unknown field
this way:
$db->students2->updateMany([],['$set'=>["grades.$[elem].mean" => 100 ]],['arrayFilters'=> ["elem.grade"=> ['$gte'=>85 ]]]);
with error: "arrayFilters" option has invalid keys for a BSON array
If I execute this command works right but only in one document:
$command = new \MongoDB\Driver\Command(
[
'findAndModify' => 'students2',
'query' => ["grades.grade" => ['$gte' => 85]],
'update' => ['$set' => ["grades.$[elem].mean" => 98 ]],
'upsert' => true,
'returnDocument' => true,
'new' => true,
'arrayFilters' => [['elem.grade'] => ['$gte' => 85]]
]
);
this is my data in mongodb table:
{
"_id" : 1,
"grades" : [
{ "grade" : 80, "mean" : 75, "std" : 6 },
{ "grade" : 85, "mean" : 100, "std" : 4 },
{ "grade" : 85, "mean" : 100, "std" : 6 }
]
}
{
"_id" : 2,
"grades" : [
{ "grade" : 90, "mean" : 100, "std" : 6 },
{ "grade" : 87, "mean" : 100, "std" : 3 },
{ "grade" : 85, "mean" : 100, "std" : 4 }
]
}
What am I doing wrong?
Thanks so much
Try This way
$updateResult = $collection->updateMany(
["inspector" => "J. Clouseau", "Sector" => 4 ],
['$set' => ['Patrolling' => false]]
);

Aggregate total and distinct count

I have a document to stored products, one receipt can have many products inside.
{"_id" : "59871e65fffe165420e0b324"
"receiptid" : "4BA/ZFY7AQ4HvfjUMTRTLg=="
"area_type" : 1
"report_type" : 3
"id_station" : 2317
"date" : "2017-08-05 00:00:00.000"
"time" : "1501979220"
"amount" : 10113958
"item" : 32
},
{"_id" : "59871e65fffe165420e0b324"
"receiptid" : "4BA/ZFY7AQ4HvfjUMTRTLg=="
"area_type" : 1
"report_type" : 3
"id_station" : 2317
"date" : "2017-08-05 00:00:00.000"
"time" : "1501979320"
"amount" : 4000000
"item" : 12
}
I want to count total amount and total receipt in one query:
$store = array(2317);
$cursor = $collection->aggregate([
['$match' => [ 'id_station' => ['$in' => $store ], 'date' => ['$gte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000), '$lte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000)] ] ],
['$group' => ['_id' => ["id_station" => '$id_station'],
"amountclient"=> ['$sum' => '$amount']
]
],
['$group' => ['_id' => ["id_station" => '$id_station', "receiptid" => '$receiptid'],
"receipt"=> ['$sum' => 1]
]
]
]);
But the query doesn't show anything, how can I correct it?
I want the result:
{"store" => xxxx, "amount" => xxxx, "number of receipt" => xxx}
You want "distinct counts", which means you actually "compound" the grouping the other way around to what you are attempting:
$store = array(2317);
$cursor = $collection->aggregate([
['$match' => [
'id_station' => ['$in' => $store ],
'date' => [
'$gte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000),
'$lte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000)
]
]],
['$group' => [
'_id' => [ 'id_station' => '$id_station', 'receiptid' => '$receiptid' ],
'amount' => [ '$sum' => '$amount' ]
]],
[ '$group' => [
'_id' => '$_id.id_station',
'amount' => [ '$sum' => '$amount' ],
'numReceipt' => [ '$sum' => 1 ]
]]
]);
The first $group "includes" the "receiptid" field in the grouping _id so that the results returns are the "distinct" combination of the two keys. This allows the accumulation of the "amount" over that combination, and means that only the "distinct" values of "receipt_id" per each "id_station" are actually returned.
The "second" $group cuts down the key to just the "distinct" "id_station" value alone. Note the notation as '$_id.id_station' since the value was placed in a "compound key" of the _id in the previous $group stage. This is how "pipelines" work, by only having the available "input" that was the "output" of the previous stage.
For the "amount" you can pass the value into $sum again, and where there were "multiple combinations of "id_station" and "receiptid" then this is now reduced down to the total for just the "id_station" key. So you are "totaling the totals" from the previous stage output by the new reduced grouping key.
As for the "number of receipts", since the first $group made those values "distinct" within each "id_station", then the number is simply the [ '$sum' => 1 ] result.
Basically on the included data in the question where both the keys are shared in the documents, it would return the "total amount" and the count of 1 for the "receipts" since there is only one "distinct" value.

Join more than one field using aggregate $lookup

I need to join more than two fields in two collections using aggregate $lookup. is it possible to join? please let me know if it is possible. Here i have two collections:
For Example:
"people" collections fields "city,state,country" in "country" collection fields "city_id,state_id,country_id", I want to join this three fields in following collections.
"People"
{
"_id" : 1,
"email" : "admin#gmail.com",
"userId" : "AD",
"userName" : "admin",
"city" : 1,
"state" : 1,
"country" : 1
}
"country"
{
"country_id" : 1,
"userId" : "AD",
"phone" : "0000000000",
"stateinfo":[{
"state_id" : 1,
"state_name" : "State1"
},{
"state_id" : 2,
"state_name" : "State2"
}
],
"cityinfo":[{
"city_id" : 1,
"city_name" : "city1"
},{
"city_id" : 2,
"city_name" : "city2"
}
]
}
This is probably a lot more simple than you think, considering that of course all of the "three" fields are contained within the one "country" document. So it's just a matter of doing the $lookup by "country_id" and then using the retrived content to populate the other fields.
var pipeline = [
{ "$lookup": {
"from": "country",
"localField": "country",
"foreignField": "country_id",
"as": "country"
}},
{ "$project": {
"email": 1,
"userId": 1,
"userName": 1,
"country": {
"$arrayElemAt": [
{ "$filter": {
"input": {
"$map": {
"input": "$country",
"as": "country",
"in": {
"country_id": "$$country.country_id",
"userId": "$$country.userId",
"phone": "$$country.phone",
"stateInfo": {
"$arrayElemAt": [
{ "$filter": {
"input": "$$country.stateInfo",
"as": "state",
"cond": { "$eq": [ "$$state.state_id", "$state" ] }
}},
0
]
},
"cityinfo": {
"$arrayElemAt": [
{ "$filter": {
"input": "$$country.cityinfo",
"as": "city",
"cond": { "$eq": [ "$$city.city_id", "$city" ] }
}},
0
]
}
}
}
},
"as": "country",
"cond": { "$eq": [ "$$country.userId", "$userId" ] }
}},
0
]
}
}}
]
db.people.aggregate(pipeline)
That should give you a result like:
{
"_id" : 1,
"email" : "admin#gmail.com",
"userId" : "AD",
"userName" : "admin",
"country" : {
"country_id" : 1,
"userId" : "AD",
"phone" : "0000000000",
"stateinfo": {
"state_id" : 1,
"state_name" : "State1"
},
"cityinfo": {
"city_id" : 1,
"city_name" : "city1"
}
}
So once the array is matched in by $lookup it all comes down to using $filter to do the matcing and $arrayElemAt to get the first match from each filtered array.
Since the outer array has "inner" arrays, you want to use $map for the "outer" source and apply $filter to each of it's "inner" arrays.
You can get more fancy with $let to get that "reduced" array content down to the returned sub-document and then just directly reference the resulting properties for an even "flatter" response, but the general concept of "matching" the array elements remains the same as above.
For a PHP structure translation:
$pipeline = array(
array(
'$lookup' => array(
'from' => 'country',
'localField' => 'country'
'foreignField' => 'country_id',
'as' => 'country'
)
)
array(
'$project' => array(
'email' => 1,
'userId' => 1,
'userName' => 1,
'country' => array(
'$arrayElemAt' => array(
array(
'$filter' => array(
'input' => array(
'$map' => array(
'input' => '$country',
'as' => 'country',
'in' => {
'country_id' => '$$country.country_id',
'userId' => '$$country.userId',
'phone' => '$$country.phone',
'stateInfo' => array(
'$arrayElemAt' => array(
array(
'$filter' => array(
'input' => '$$country.stateInfo',
'as' => 'state',
'cond' => array( '$eq' => array( '$$state.state_id', '$state' ) )
)
),
0
)
),
'cityinfo' => array(
'$arrayElemAt' => array(
array(
'$filter' => array(
'input' => '$$country.cityinfo',
'as' => 'city',
'cond' => array( '$eq' => array( '$$city.city_id', '$city' ) )
)
),
0
)
)
}
)
),
'as' => 'country',
'cond' => array( '$eq' => array( '$$country.userId', '$userId' ) )
)
),
0
)
)
)
)
);
$people->aggregate($pipeline);
You can usually check your PHP matches a JSON structure when you are working from a JSON example by dumping the pipeline structure:
echo json_encode($pipeline, JSON_PRETTY_PRINT)
And that way you cannot go wrong.
As another final note here, the process after the $lookup is done is quite "complex" even if very efficient. So I would advise that unless there is some need to take this aggregation pipeline further and actually "aggregate" something, then you are probably better off doing that "filtering" in client code rather than doing it on the server.
The client code to do the same thing is far less "obtuse" than what you need to tell the aggregation pipeline to do. So unless this "really" saves you a lot of bandwidth usage by reducing down the matched array, or indeed you if can just "lookup" by doing another query instead, then stick with doing it in code and/or do the seperate query.

Null set returned by mongodb aggregate Cursor in PHP

MongoDB aggregate query returning empty set .Below is the query i am using in a php script to retrieve data from mongoDB .Please let me know where i am going wrong.
$result = $collection->aggregateCursor([[ '$match'=> [ 'date'=> [ '$gte'=>ISODate("2015-06-01T00:00:00Z"), '$lte'=>ISODate("2015-06-03T00:00:00Z")] ] ],[ '$group'=> [ '_id'=> '$date', 'count'=> [ '$sum'=>1 ] ] ]]);
If i run same query in mongoDB shell.it is showing the output as expected.
db.mnumber.aggregate([{ $match: { date: { $gte:new ISODate("2015-06-01T00:00:00Z"), $lte:new ISODate("2015-06-03T00:00:00Z") } } },{ $group: { _id: "$date", 'count': { $sum:1 } } }])
{ "_id" : ISODate("2015-06-01T00:00:00Z"), "count" : 10000 }
{ "_id" : ISODate("2015-06-02T00:00:00Z"), "count" : 10000 }
{ "_id" : ISODate("2015-06-03T00:00:00Z"), "count" : 10000 }
Sample data in collection:
{
"_id" : ObjectId("55743941789a9abe7f4af3fd"),
"msisdn" : "1234567890",
"act_date" : ISODate("2014-11-24T00:00:00Z"),
"date" : ISODate("2015-06-07T00:00:00Z"),
"recharge_stats" : {
"recharge_amt" : 0,
"rechargetype" : "WEB"
},
"voice_usage" : {
"local_og_mou" : 20,
"local_other_mobile_og_mou" : 0,
"nld_og_mou" : 0,
"nld_other_mobile_og_mou" : 10
},
"gprs_usage" : {
"total_access_count" : 1,
"total_datavolume_mb" : 42
},
"sms_usage" : {
"freesms" : 3,
"local_sms_count" : 0,
"nat_sms_count" : 0,
"inter_sms_count" : 0
},
"campaign_details" : {
"camp_id" : "M01124",
"message" : "Hello .",
"msg_id" : "9174051951412054925609431100",
"cmp_activation_status" : "YES"
}
}
Try to generate a MongoDate() object as follows
$dateFrom = new MongoDate(strtotime("2015-06-01T00:00:00Z"));
$dateTo = new MongoDate(strtotime("2015-06-03T00:00:00Z"));
which you can then use in your aggregation pipeline instead of the MongoDB ISODate objects in your PHP query.
/* Run the command cursor */
$result = $collection->aggregateCursor(
[
[ '$match' => [ 'date'=> [ '$gte' => $dateFrom, '$lte' => $dateTo ] ] ],
[ '$group' => [ '_id' => '$date', 'count' => [ '$sum' => 1 ] ] ]
]
);

Categories