Query to get time count from mongodb - php

I have my data in mongodb database as
{
"_id": ObjectId("5319acf0b06f6e98371ca505"),
"field1": "",
"date": "07-03-2014",
"os": "android",
"time": "11:26:40",
"userid": "xxxx"
}
I wanted to get count of all records having time from 00 to 23.
I have written my query as
$time_start = date('00');
$time_end = date('23');
$keys = array("time" => 1);
$initial = array("counttime" => array());
$reduce = "function(obj, prev) {
if (obj.time != null)
if (obj.time instanceof Array) prev.counttime += obj.time.length;
else prev.counttime++; }";
$condition = array(
'condition' => array(
"time" => array(
'$gt' => $time_start,
'$lte' => $time_end
)
)
);
$g = $collection->group($keys, $initial, $reduce , $condition);`
I have tried to get hour but thats not working for me.
I have used as
$condition = array(
'condition' => array(
"HOUR(time)" => array(
'$gt' => $time_start,'$lte' => $time_end)
)
);
Anyone who can help me out?

You can do this using .aggregate(), which you really should favor over .group() in any case, as it uses native code rather than JavaScript for it's results:
db.collection.aggregate([
// Match one date (tell you later)
{ "$match": { "date": "07-03-2014" } },
// Project the hour
{ "$project": {
"hour": { "$substr": [ "$time", 0, 2 ] }
}},
// Group on the hour
{ "$group": {
"_id": "$hour",
"count": { "$sum": 1 }
}}
])
Now this relies on that you have your "time" stored as a string. So you $project the "hour" part of that "time" and then $group on it, keeping a count of records found.
I restricted this to one "date" because as with "time" this is also a string. Depending on what you want to do you can possibly get unexpected results from this, so generally speaking, using "strings" for date representation is not a good idea.
If you just had a proper BSON date in a field called "timestamp" then you can do things like this, where you can even set ranges:
var start_date = new Date("2014-02-01");
var end_date = new Date("2014-03-01");
db.collection.aggregate([
// Match on the date range
{ "$match": { "timestamp": { "$gte": start_date, "$lt": end_date } },
// Project day and hour
{ "$project": {
"day": {
"year": { "$year": "$timestamp" },
"month": { "$month": "$timestamp" },
"day": { "$dayOfMonth": "$timestamp" },
"hour": { "$hour": "$timestamp" }
}
}},
// Group on the day
{ "$group": {
"_id": "$day",
"count": { "$sum": 1 }
}}
])
But with your present date format, ranges are going to be a problem. So best to convert where you can.

Related

Better way to iterate multidimensional array of unknown complexity from API than a dozen foreach loops in PHP

I am getting an array from an API that varies in number of levels but follows the same basic structure - here is a truncated sample as this particular repsonse is 25K lines:
{
"Rows": {
"Row": [
{
"Header": {
"ColData": [
{
"value": "Ordinary Income/Expenses"
},
{
"value": ""
}
]
},
"Rows": {
"Row": [
{
"Rows": {},
"Summary": {
"ColData": [
{
"value": "Gross Profit"
},
{
"value": ""
}
]
},
"type": "Section"
},
{
"Header": {
"ColData": [
{
"value": "Income"
},
{
"value": ""
}
]
},
"Rows": {
"Row": [
{
"Header": {
"ColData": [
{
"value": "40000 Sales Income",
"id": "31"
},
{
"value": ""
}
]
},
"Rows": {
"Row": [
{
"Rows": {
"Row": [
{
"ColData": [
{
"value": "2022-01-24"
},
{
"value": "Invoice",
"id": "148774"
},
{
"value": "208232"
},
{
"value": "Hyatt:#211102",
"id": "7568"
},
{
"value": "JN",
"id": "4100000000000368107"
},
{
"value": "CAPTIVE AIRE"
},
{
"value": "11000 Accounts Receivable",
"id": "80"
},
{
"value": "38748.00"
},
{
"value": "38748.00"
}
],
"type": "Data"
},
I need to traverse the json, and where there is data in both [Header][ColData][value] AND [Header][ColData][id] extract the value, id (in this snippet "value": "40000 Sales Income", "id": "31") and the data that immediately follows the "value"/"id" in [Rows][Row][Rows][Row][ColData] (in this snippet starting with "ColData": [{"value": "2022-01-24"...)
[Rows][Row][Rows][Row][ColData] will have one to a few hundred subarrays. I can extract the data from the subarrays once they are found - it's just managing the varying depths of the array that is warping my brain.
[Rows][Row][Rows][Summary] can be discarded as well.
I have tried multiple foreach loops - but by time I get 5 or 6 levels deep it gets very confusing. The number of Header sections varies depending on the report type. The [Rows][Row] nesting is multiple layers deep... I'm sure there has to be a better way than nesting foreach loops...
This is what I've come up with. Kindly modify it to meet your need.
$array = json_decode($json, true);
function traverse_some_array($array){
$result = [];
foreach($array['Rows']['Row'] ?? [] as $row){
if( !empty($row['Header']['ColData']) ){
foreach($row['Header']['ColData'] as $coldata){
if( isset($coldata['value'], $coldata['id']) ){
// there is data in both [Header][ColData][value] AND [Header][ColData][id]
// extract the value, id (in this snippet "value": "40000 Sales Income", "id": "31")
$extract_data = [
'value' => $coldata['value'],
'id' => $coldata['id']
];
// the data that immediately follows the "value"/"id" in [Rows][Row][Rows][Row][ColData]
$immediate_coldata = $row['Rows']['Row'] ?? [];
// you can do what ever you want with the results, eg return it or push it to a result array
$result[] = [
'extract_data' => $extract_data,
'immediate_coldata' => $immediate_coldata
];
}else{
// continue traversing the array
$result = array_merge($result, traverse_some_array($row));
}
}
}
}
return $result;
}
print_r(traverse_some_array($array));

How to combine array with same values

Okay so not 100% sure how to even ask this correctly but I have an array that I'm trying to consolidate and group together based on each hour, and then data for each year underneath that time.
My current array looks like this:
[
{
"name": "7:00 AM",
"data": [
{
"x": "2019",
"y": 1
}
]
},
{
"name": "7:00 AM",
"data": [
{
"x": "2020",
"y": 221
}
]
}
]
So I'm looking to combine these into a single array based on the name value and then all of the data will be combined:
[
{
"name": "7:00 AM",
"data": [
{
"x": "2019",
"y": 1
},
{
"x": "2020",
"y": 221
}
]
}
]
EDIT: And just to add how I'm originally getting and formatting these values, I'm using Larvel and running the following query:
$shipments = Orders::whereNotNull('shipped_at')->groupBy(DB::raw('YEAR(created_at)'))->selectRaw('count(*) as count, HOUR(shipped_at) as hour')->selectRaw("DATE_FORMAT(created_at, '%Y') label")->orderBy('label')->orderBy('hour')->groupBy('hour')->get();
And that gives me the following data:
And then I'm building the original array like this:
$shipping_chart_year = [];
foreach ($shipments as $item) {
$hour = Carbon::parse($item['hour'] . ':00:00')->timezone('America/Chicago')->format('g:i A');
$shipping_chart_year[] = ['name' => $hour, 'data' => [['x' => $item['label'], 'y' => $item['count']]]];
}
This functions formats array as you expected to have.
function formatArray($array){
$response = [];
$keysIndexes = [];
foreach($array as $value) {
if(!array_key_exists($value['name'], $keysIndexes)) {
array_push($response, $value);
$keysIndexes[$value['name']] = sizeof($keysIndexes);
} else {
array_push($response[$keysIndexes[$value['name']]]['data'], $value['data'][0]);
}
}
return $response;
}
if you want to achieve this with the Laravel Collections (you can do so much with it!), here you go (based on your current array structure).
It is much more readable and way easier to achieve.
$output = collect(json_decode($data, true))
->groupBy('name')
->transform(function ($items, $name) {
// Merge nested arrays into one
$data = $items->map(function ($item) {
return $item['data'][0];
})->toArray();
return [
'name' => $name,
'data' => $data,
];
})
->values() // removes the key
->toJson(); // returns the collection to json

How to loop through an array to set price based on given dates in `Php`

There is an array of objects which store the list of cars (data comes from API calls).
How can the prices be dynamically set based on the actual date? (ie: How to compare $today and then set the price if it matches the criteria )
For Luxury Price for Honda:-
From Jan 1 - Aug 26: 6000
From Aug 27 - Aug 31: 4000
From Sep 1 - Sep 30: 8000
From Oct 1 onwards: 4000
$today = strtotime(Carbon::now()->toDateString()) * 1000;
cars= [{
"id": "1",
"name": "Honda",
"prices": [
{
"id": "100",
"carId":1000,
"type": "Luxury",
"price": "4000",
"startdate": 1630058167000, // (Aug 27)
},
{
"id": "101",
"carId":1000,
"type": "Luxury",
"price": "6000",
},
{
"id": "102",
"carId":1200,
"type": "Standard",
"price": "2000",
},
{
"id": "103",
"carId":1000,
"type": "Luxury",
"price": "8000",
"startdate": 1630490167000, // (Sep 1)
"enddate": 1632995767000, // (Sep 30)
},
],
}
....
];
How can prices be looped to check if $today date falls in 1 of the categories and then set the price to the given price
Edit- Code attempt
$conditionPrice = "";
$today = strtotime(Carbon::now()->toDateString()) * 1000;
foreach ($cars as $car) {
foreach ($car->prices as $price) {
//case of only startDate present
if (property_exists($price, 'startDate') && $todayDate >= $price->startDate) {
//if the property is not there - it throws errors
//want $conditionPrice = $price->price; after it checks all the cases to see if $today date falls in that range
}
}
}
You can use laravel's groupBy function and then map the data you want to group it to. non tested :
return Model::all()
->groupBy('key', function($group) {
return $group->reduce(fn($acc, $value) => $acc+ $value))
});
});
I needed to do same, but with a more complex array_filter on the keys.
Here's how I did it, using a similar method.
// Filter out array elements with keys shorter than 4 characters
$a = array(
0 => "val 0",
"one" => "val one",
"two" => "val two",
"three"=> "val three",
"four" => "val four",
"five" => "val five",
"6" => "val 6"
);
$f = array_filter(array_keys($a), function ($k){ return strlen($k)>=4; });
$b = array_intersect_key($a, array_flip($f));
print_r($b);
This outputs the result:
Array
(
[three] => val three
[four] => val four
[five] => val five
)
i hope this will help you .

Mongo find timestamps in a array, between two dates

I have documents with a array property timestamps as seen in the document below. I wish to find all documents with timestamps between two dates. My find query looks as follow:
$subset = $db->col->find(
array(
'timestamps'=> array(
'$elemMatch'=> array(
'$gte'=> new MongoDate(strtotime('2016-05-25 13:00:00')),
'$lte'=> new MongoDate(strtotime('2016-05-25 14:00:00'))
)
)
)
);
But it does not return the expected documents. I tried to Google and search StackOverflow for answer but I cannot find anything explaining how to search for timestamps in a array, between two dates.
EDIT:
It seems the problem lies with PHP because the same Python query works:
import datetime
f = '%Y-%m-%d %H:%M:%S'
c = db.col.find({
'timestamps': {
'$elemMatch': {
'$gte': datetime.datetime.strptime('2016-05-25 13:00:00', f),
'$lte': datetime.datetime.strptime('2016-05-25 14:00:00', f)
}
}
})
Any help would be appreciated.
{
"month": {
"$date": 1464134400000
},
"timestamps": [
{
"$date": 1464181803000
},
{
"$date": 1464182103000
},
{
"$date": 1464182403000
},
{
"$date": 1464183003000
},
{
"$date": 1464183603000
},
{
"$date": 1464184203000
},
{
"$date": 1464184803000
},
{
"$date": 1464185403000
},
{
"$date": 1464186003000
}
],
"status_history": [
1,
1,
1,
0,
1,
1,
1,
0,
1
],
"_id": 12345
}
I had a look at find in array between dates in mongo and Get data between two dates mongo.
It was a timezone issue. I created a DateTime and set the timezone to Etc/GMT and the expected data returned correctly.
$gte = new MongoDate (new DateTime( '2016-05-25 13:00:00', new DateTimeZone( 'Etc/GMT' )));
$lte = new MongoDate (new DateTime( '2016-05-25 14:00:00', new DateTimeZone( 'Etc/GMT' )));

PHP calculate date range from array

I use service API which provide a schedule in json in format describes date ranges:
.... // Other items
{
"registration": "SP-TEST",
"records": [
{
"from": "2014-12-06T13:40Z",
"available": true
},
{
"from": "2014-12-07T14:30Z",
"available": false
},
{
"from": "2014-12-13T14:30Z",
"available": true
},
{
"from": "2014-12-13T16:30Z",
"available": false
},
{
"from": "2014-12-15T14:30Z",
"available": true
}
]
},
....
But it uncomfortable for use and search. I need import it to MySQL DB and perform search in date range where is available, so I need combine arrays something like:
[{
"registration": "SP-TEST",
"from": "2014-12-06T13:40Z",
"to": "2014-12-07T14:30Z"
},
{
"registration": "SP-TEST",
"from": "2014-12-13T14:30Z",
"to": "2014-12-13T16:30Z"
},
{
"registration": "SP-TEST",
"from": "2014-06-06T13:40Z",
"to": "2014-06-07T14:30Z"
},
{
"registration": "SP-TEST",
"from": "2014-12-15T14:30Z",
"to": "2014-02-07T14:30Z"
}]
I use usort function to sort by time source array (json_decode($schedule)):
usort($schedule->records, function($a, $b) {
return strtotime($a->from) - strtotime($b->from);
});
So, if this code is correct I can use foreach to populate new array, but it does not work, because a little problem: "records" can contain just one record. It can have "available": true or "available": false, which means that it available or not from current date up 2 month.
Maybe somebody prompts me a right way?
Resolved. Sort by date, then foreach.
function schedule($records) {
date_default_timezone_set('UTC'); // 0 timezone
$result = array();
foreach ($records as $k => $v) {
$record = array_merge($record, array(
'available' => $v->available,
'location' => $v->location,
'from' => $v->from,
'depart' => '',
'arrive' => '',
'to'=> date("Y-m-d\TH:i\Z", strtotime("+2 month", strtotime($v->from)))
));
if (count($records) > 1) {
$record['to'] = date("Y-m-d\TH:i\Z", strtotime("+2 month", strtotime($record['from'])));
}
if (isset($records[$k+1])) {
$record['to'] = $records[$k+1]->from;
$record['depart'] = $v->location;
$record['arrive'] = $records[$k+1]->location;
}
$result[] = $record;
}
return $result;
}

Categories