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 每天按奇数小时对数据进行分组的主要内容,如果未能解决你的问题,请参考以下文章

如何按特定日期范围(例如小时、日、月)对数据进行分组?

Laravel 选择列,然后按日期对列值进行分组

如何使用带有 Pandas 的时间戳按小时对数据帧进行分组

使用 Python,如何按小时对 Dataframe 中的列进行分组?

按日期分组Java

Laravel 8:按嵌套列对分组数组进行排序 - 我应该在 sortBy 闭包中放入啥?