Laravel 查询将分组小时的所有 X 和 Y 列相加

Posted

技术标签:

【中文标题】Laravel 查询将分组小时的所有 X 和 Y 列相加【英文标题】:Laravel query add up all of X and Y columns for grouped hour 【发布时间】:2021-05-01 16:56:40 【问题描述】:

在我的 Laravel 项目中,我在检索和分组一些数据时遇到了一些麻烦。我正在尝试从相关列中提取一些数据,计算每个值并提供一个分组时间段的总数,但是为了进行分组,我需要先获取数据,而我的选择并不能很好地工作对了,我错过了什么?

查询

UptimeChecks::where('user_id', 1)
            ->where('monitor_id', 1)
            ->where('checked_at', '>=', '2021-05-01 15:19:00')
            ->where('checked_at', '<=', '2021-05-01 17:19:00')
            ->orderBy('checked_at', 'asc')
            ->select('event', 'checked_at')
            ->get();

我用上面得到的数据看起来像:

 Illuminate\Database\Eloquent\Collection #3447
     all: [
       App\Models\UptimeChecks #3446
         event: "down",
         checked_at: "2021-05-01 15:37:23",
       ,
       App\Models\UptimeChecks #3445
         event: "down",
         checked_at: "2021-05-01 15:38:10",
       ,
       App\Models\UptimeChecks #3444
         event: "down",
         checked_at: "2021-05-01 15:44:07",
       ,
       App\Models\UptimeChecks #3443
         event: "up",
         checked_at: "2021-05-01 15:45:23",
       ,
       App\Models\UptimeChecks #3442
         event: "down",
         checked_at: "2021-05-01 16:05:07",
       ,
       App\Models\UptimeChecks #3441
         event: "up",
         checked_at: "2021-05-01 16:07:07",
       ,
     ],
   

我希望获取分组小时内所有“向下”事件的计数,以及同一小时内“向上”事件的计数,因此预计会返回类似以下内容:


  "2021-05-01 15:00:00": 
    down_events: 3,
    up_events: 1
  ,
  "2021-05-01 16:00:00": 
    down_events: 1,
    up_events: 1
  

不太确定我的查询如何做到这一点?

【问题讨论】:

您可以按日期分组,但日期必须采用一种格式,例如 Y-m-d 或 Y-m-d h:00:00 【参考方案1】:

您可以在查询后使用收集方法groupBy()map() 来实现。

UptimeChecks::...->get()
                 ->groupBy(fn($item) => $item->checked_at->format('Y-m-d H:00:00'))
                 ->map(fn($item) => [
                     'down_events' => $item->where('event', 'down')->count(),
                     'up_events' => $item->where('event', 'up')->count(),
                 ]);

如果您无法使用速记闭包(php 7.4 及更高版本)

UptimeChecks::...->get()
                 ->groupBy(function ($item) 
                     return $item->checked_at->format('Y-m-d H:00:00');
                 )
                 ->map(function ($item) 
                     return [
                         'down_events' => $item->where('event', 'down')->count(),
                         'up_events' => $item->where('event', 'up')->count(),
                     ];
                 );

如果 checked_at 不是时间戳,请将其添加到模型中的 $cast 属性中。

class UptimeChecks extends Model

    protected $casts = [
        'checked_at' => 'datetime',
    ];

或者将$item-&gt;checked_at 封装在Carbon::parse() 调用中。

use Carbon\Carbon;
...
groupBy(fn($item) => Carbon::parse($item->checked_at)->format('Y-m-d H:00:00'))

【讨论】:

给我一个错误:Call to a member function format() on string 用 Carbon::parse 包裹你的 checked_at 或者 check_at 应该在你的 Model 下的 $casts 属性中 我认为checked_at 是一个时间戳。正如@PsyLogic 所说,要么将其添加到您的 $cast 属性中,要么将它们包装在 Carbon::parse 中。我将编辑答案以添加此内容。【参考方案2】:

我认为对于大数据来说,使用 RawSQL 很容易

return UptimeChecks::
        selectRaw("SUM(CASE WHEN event= 'down' THEN 1 ELSE 0 END) AS down_events, ".
                    "SUM(CASE WHEN event= 'up' THEN 1 ELSE 0 END) AS up_events, DATE_FORMAT('checked_at', '%Y-%m-%d %H:00:00') as checkedDate ")
        ->groupByRaw('checkedDate')
        ->get();

您可以多次使用 selectRaw 来清理您的代码

return UptimeChecks::
  selectRaw("SUM(CASE WHEN event= 'down' THEN 1 ELSE 0 END) AS down_events")
 ->selectRaw("SUM(CASE WHEN event= 'up' THEN 1 ELSE 0 END) AS up_events")
 ->selectRaw("DATE_FORMAT('checked_at', '%Y-%m-%d %H:00:00') as checkedDate")
 ->groupByRaw('checkedDate')
 ->get();

【讨论】:

以上是关于Laravel 查询将分组小时的所有 X 和 Y 列相加的主要内容,如果未能解决你的问题,请参考以下文章

sql 时间 分组 查询

MySQL按年/月/周/日/小时分组查询排序limit判空用法

Laravel Eloquent按天分组结果

如何使用 django 查询集按小时对对象进行分组?

Laravel 查询 - 加入和分组

mysql group by 对多个字段进行分组