Laravel Eloquent/MySQL 如何从每个父记录的最后一个条目中获取去年的记录?

Posted

技术标签:

【中文标题】Laravel Eloquent/MySQL 如何从每个父记录的最后一个条目中获取去年的记录?【英文标题】:Laravel Eloquent/MySQL how to get one last year records from last entry of each parent record? 【发布时间】:2022-01-06 22:36:25 【问题描述】:

假设我在遗留系统中有以下关系,无法改变它。

    省表(有很多区) 地区表(有许多村庄) 村表(有很多医院) hospital_types 表(hasOne 医院) hospitals 表(属于 hospital_types,hasMany 监控) 监控表(hasMany rateLog)

我想要的是带医院的省份,并在去年对每家医院进行一次监测。

注意:上一年不是从当前日期算起,而是取每家医院的最后一次监测日期,然后从那个日期开始计算,到那个日期为止需要12个月

假设医院 A 已于 2020 年 11 月 1 日进行最后一次监测,则应在 2020 年 11 月 1 日至 2019 年 11 月 1 日期间完成所有监测。其他医院也一样。

已经用 Laravel 资源实现了,但是有点慢,也不知道在资源返回数据后如何实现 orderBy 功能,因为我需要基于监视的分页和排序,现在我通过资源处理它.

目前我正在使用 hasManyThrough 关系,在 get 之后,我将数据传递给资源集合并处理监控的 get。但是这样就不能实现排序和分页了。

$data = Province::with(['districts.hospitalTypes])->get();

然后我将这些数据传递给资源并获得监控,但是我如何应用排序和分页,因为排序是基于监控的。并且使用资源比急切加载要慢一些。 我正在寻找一种方法来使用带有这些条件的急切负载。

class ProvinceResource extends JsonResource

    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    
        return [
            'id' => $this->id,
            'name' => $this->name,
            'districts' => DistrictResource::collection($this->whenLoaded("districts"))
        ];
    

class DistrictResource extends JsonResource

    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    
        return [
            'id' => $this->id,
            'name' => $this->name,
            'hospitals' => HospitalTypeResource::collection($this->whenLoaded("hospital_types"))
        ];
    

class HospitalTypeResource extends JsonResource

    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    
        $district = $this->village->district;
        
        $oneLastYearRateLog = $this->hospital->last12MonthRageLogs();
        $rateLogCount = $oneLastYearRateLog->count();

        $oneLastYearMonitoringCount = $this->hospital->last12MonthMonitors()->count();

        return [
            "id" => $this->hospital->id,
            "code" => $this->code,
            "name" => $this->name,
            "overallRating"=> $rateLogCount == 0 ? 0 : $oneLastYearRateLog->sum('rate') / $rateLogCount,
            "ratingsCount" => $oneLastYearMonitoringCount
        ];
    

下面是我在医院模型中采取监控的关系。

class Hospital extends Model

 /**
 * Get hospital monitors
 */
public function monitors()

    return $this->hasMany(Monitoring::class, 'hospital_id', 'id');


public function last12MonthMonitors()

    $lastMonitoring = $this->lastMonitoring();
    if($lastMonitoring) 
        return $this->monitors()->whereRaw('monitoring_date >= "'. $lastMonitoring->monitoring_date.'" - INTERVAL 12 month');
    
    return $this->monitors();


private function lastMonitoring()

    return $this->monitors()->select('monitoring_date')->latest('monitoring_date')->first();


/**
 * Get rate logs
 */
public function rateLogs()

    return $this->hasManyThrough(RateLog::class, Monitoring::class, 'hospital_id')
        ->where('rate', '!=', 'NA');


/**
 * Get last 12 rate logs
 */
public function last12MonthRageLogs()

    $lastMonitoring = $this->lastMonitoring();
    if($lastMonitoring) 
        return $this->rateLogs()->whereRaw('monitoring.monitoring_date >= "'.$lastMonitoring->monitoring_date.'" - INTERVAL 12 month');
    
    return $this->rateLogs();

【问题讨论】:

请您显示您的Resource 的代码以及您如何过滤监控。 @Rwd 请立即查看 你使用的是什么版本的 Laravel? @Rwd Laravel 7,但可以升级到 8 你能分享模型的表格结构吗?请将表结构分享为 github gist 【参考方案1】:

您似乎并不急于加载hospital 关系。我将首先添加它,这样您就不必为每个 HospitalType 运行单个查询:

$data = Province::with(['districts.hospitalTypes.hospital'])->get();

我相信您查询的速度可能会慢一点的原因之一是您的lastMonitoring 检查。首先,这个查询将针对每个 Hospital 运行,其次,您似乎没有存储/缓存该值,因此它实际上将被查询两次。


如果您更新到 Laravel 8(最低 v8.4.2),您将能够为您的 lastMonitoring 方法使用 Has one of many relationship,例如:

public function latestMonitoring()

    return $this->hasOne(Monitoring::class)->latestOfMany('monitoring_date');

那么您也可以预先加载该关系:

$data = Province::with(['districts.hospitalTypes.hospital.latestMonitoring'])->get();

您还需要更新 HospitalType 模型中的一些其他方法:

public function monitors()

    return $this->hasMany(Monitoring::class, 'hospital_id', 'id');


/**
 * Get rate logs
 */
public function rateLogs()

    return $this->hasManyThrough(RateLog::class, Monitoring::class, 'hospital_id')
        ->where('rate', '!=', 'NA');


public function latestMonitoring()

    return $this->hasOne(Monitoring::class, 'hospital_id', 'id')->latestOfMany('monitoring_date');


public function last12MonthMonitors()

    return $this->monitors()->when($this->latestMonitoring, function ($query) 
        $query->where('monitoring_date', '>=', $this->latestMonitoring->monitoring_date->subYear());
    );


/**
 * Get last 12 rate logs
 */
public function last12MonthRageLogs()

    return $this->rateLogs()->when($this->latestMonitoring, function ($query) 
        $query->where('monitoring.monitoring_date', '>=', $this->latestMonitoring->monitoring_date->subYear());
    );

【讨论】:

感谢您的帮助,升级 Laravel 和 php 后,实现了您的解决方案,但它不起作用。问题是它永远不会进入when($this-> latestMonitoring ,因为它总是返回null。 @jones 啊,当然。对于那个很抱歉!我已经更新了latestMonitoring 关系以使用相同的foreignKeylocalKeyhostpital_idid)作为现在应该可以工作的monitors 关系。 @Jones 在last12MonthMonitorslast12MonthRageLogs 中仅适用于延迟预加载,还是其他关系仅适用于延迟预加载?

以上是关于Laravel Eloquent/MySQL 如何从每个父记录的最后一个条目中获取去年的记录?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 App\Exceptions 从 laravel 7 升级到 laravel 8

如何在 Laravel 8 中使用 srmklive/laravel-paypal v3

如何使用 Laravel Facades

如何将 Laravel 项目转换为 laravel api?

如何搭建一个laravel项目

如何使用laravel保存所有行数据每个行名或相等