Laravel 每天按奇数小时对数据进行分组
Posted
技术标签:
【中文标题】Laravel 每天按奇数小时对数据进行分组【英文标题】:Laravel group data by odd amount of hours throughout days 【发布时间】:2021-07-25 02:26:27 【问题描述】:我正在尝试将 Laravel 项目中的一些数据按与标准有点不同的日期格式进行分组。我有一个数据库和一个查询,它根据用户想要查看的时间段获取用户网站的“正常运行时间检查”,然后我需要将其作为某种时间线显示给用户。
为了减少数据中的“噪音”(在给定时间段内可能没有足够的正常运行时间检查)我想在 3 小时内对我的所有结果进行分组一整天,所以我将拥有以下所有数据:
2021-05-02 03:00:00 2021-05-02 06:00:00 2021-05-02 09:00:00等等,现在我按小时返回数据,但不知道如何修改它以达到预期的结果
// get the uptime checks for past X hours
$uptimeData = UptimeChecks::where('user_id', 1)
->where('monitor_id', 1)
->where('checked_at', '>=', '2021-05-02 13:00:00')
->where('checked_at', '<=', '2021-05-03 13:00:00')
->orderBy('checked_at', 'asc')
->select('event', 'checked_at')
->get();
$uptimeDataTimeline = $uptimeData->groupBy(function ($item, $key)
$date = Carbon::parse($item->checked_at);
// group by hour, how can I get say every 3 hours worth of data?
return $date->format('Y-m-d H:00:00');
);
$uptimeDataTimeline = $uptimeDataTimeline->map(function ($checksInPeriod, $key)
$down = 0;
$up = 0;
$total = 0;
$uptime = 0;
$fill = '#1fc777'; // green
// $checksInPeriod is all of the data for a given hour at the moment
// I need to group by a bigger period, say, every 3 hours
// add our events
foreach ($checksInPeriod as $key => $value)
$total++;
if (strtolower($value['event']) == 'down') $down++;
if (strtolower($value['event']) == 'up') $up++;
// calculate uptime
$uptime = floatval(number_format(round($up / $total, 5) * 100, 2, '.', ','));
// fill colours
if ($uptime < 100) $fill = '#9deab8'; // lighter green
if ($uptime < 99) $fill = '#fbaa49'; // amber
if ($uptime < 98) $fill = '#e0465e'; // red
return [
'total_events' => $total,
'down_events' => $down,
'up_events' => $up,
'uptime' => $uptime,
'fill' => $fill
];
);
不确定如何修改返回格式的groupBy
函数,因为我的理解是不可能这样做?顺便说一句,我正在使用 Carbon。
更新
我一直在挖掘,并且遇到了CarbonInterval
功能,它允许我生成一些间隔,我已经尝试实现这个,我似乎得到了一个等间隔的时间段,但我的数据已经用完了并且不包含两个区间之间的所有数据(见附图)
$intervals = CarbonInterval::hours(2)->toPeriod($from, $to);
$uptimeDataTimeline = $uptimeData->groupBy(function ($item, $key) use ($intervals)
$date = Carbon::parse($item->checked_at);
foreach ($intervals as $key => $interval)
if ($date->hour == Carbon::parse($interval)->addHours(1)->hour)
$actualHour1 = Carbon::parse($interval)->hour;
if (strlen($actualHour1) == 1) $actualHour1 = "0$actualHour1";
return $date->format("Y-m-d $actualHour1:00:00");
else if ($date->hour == Carbon::parse($interval)->addHours(2)->hour)
$actualHour2 = Carbon::parse($interval)->subHours(2)->hour;
if (strlen($actualHour2) == 1) $actualHour2 = "0$actualHour2";
return $date->format("Y-m-d $actualHour2:00:00");
return $date->format('Y-m-d H:00:00');
);
例如,我应该在 07
键中看到第 7 小时和第 8 小时的所有检查,但我看到的只是一小时(小时 11
)的数据?
【问题讨论】:
所以你想有 8 个分区,每个分区 3 小时? 没错啊 【参考方案1】:当您需要时间片时,最好使用 DateInterval 或更好的 CarbonInterval。它们为您提供的是遍历这些切片并对样本数据进行相等/不相等操作的能力,这样您就可以轻松地将这些时间切片组织到它们各自的“槽”中的数据
这里是一个关于如何做的总体思路
$intervals = \Carbon\CarbonInterval::hours(3)->toPeriod('2021-05-02 13:00:00', '2021-05-03 13:00:00');
//we get time slots of 3 hours between provided datetimes
foreach ($intervals as $date)
$dtArr[] = strtotime($date->format('Y-m-d H:i:s')); //we collect those "time markers"
$result = [
'first'=> 0,
'second'=>0.
'third'=>0,
'forth'=>0,
'fifth'=>0,
'sixth'=>0,
'seventh'=>0,
'eighth'=>0
]; //array to accumulate your aggregations to correct time slot
foreach ($uptimeData as $sample)
//loop over sample set
$ordinality = getSlotNo($sample->checked_at); //eg. third
//read the accumulated total in $result and add this too
$result[$ordinality] += 1;
function getSlotNo($dt)
$ts = strtotime($dt);
//eg. say greater than or equal to "13:00" but smaller than "16:00" -> You go in first slot
if($ts>=$dtArr[0] && $ts<$dtArr[1])
//first slot
return 'first';
elseif($ts>=$dtArr[1] && $ts<$dtArr[2])
//eg. say greater than or equal to "16:00" but smaller than "19:00" -> You go in second slot
//second slot
return 'second';
elseif($ts>=$dtArr[2] && $ts<$dtArr[3])
//third slot
return 'third';
// and so on
更新
尝试类似这样的方法,修改 slot getter 以“向前看”并决定结果
$i=0;
foreach ($intervals as $date)
$dtArr[] = strtotime($date->format('Y-m-d H:i:s')); //we collect those "time markers"
$result['int_'.$i] = 0;
$i++;
//fake data
$uptimeData=collect([
(object)['checked_at'=>'2021-05-03 10:10:00'],
(object)['checked_at'=>'2021-05-03 11:20:00'],
(object)['checked_at'=>'2021-05-03 12:20:00'],
(object)['checked_at'=>'2021-05-03 13:20:00'],
(object)['checked_at'=>'2021-05-03 14:20:00'],
]);
foreach ($uptimeData as $sample)
//loop over sample set
$ordinalInfo = getSlotNo($sample->checked_at, $dtArr); //eg. third
//read the accumulated total in $result and add this too
if($ordinalInfo['match'])
$result['int_'.$ordinalInfo['index']] += 1;
/**
* @param $dt
* @return int index in $dtArr this value belongs to
*/
function getSlotNo($dt, $dtArr)
$ts = strtotime($dt);
$info = [];
for($i =0; $i<count($dtArr); $i++)
if(!empty($dtArr[$i+1])) // if not reached the last item ie. still there's a next
if($ts>=$dtArr[$i] && $ts<$dtArr[$i+1])
//i'th slot
$info=['match'=>true,'index'=>$i];
break;
else
// at last item ie. ( $i == count($dtArr)-1 )
if($ts<=$dtArr[$i])
$info=['match'=>true,'index'=>$i];
else
$info=['match'=>false,'index'=>NULL];
return $info;
【讨论】:
这太好了,有没有一种更“自动化”的方式来构建getSlotNo
函数而不定义内部的单个函数?如果没有超过一段时间的数据,那么例如会失败
如果您的意思是避免手动编码 if-else 结构,那么您可以查看更新后的代码
我已经用一些新代码和屏幕截图更新了我的帖子描述,因为使用了你的部分建议我已经取得了更多的成就,也许我在描述中的更新会提供更多的洞察力,因为我数据已关闭 :(
抱歉回复晚了,这样你可以把所有的样本点收集到一个平面主数组中,然后按checked_at
排序以上是关于Laravel 每天按奇数小时对数据进行分组的主要内容,如果未能解决你的问题,请参考以下文章