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

Laravel Eloquent/MySQL how to get one last year records from last entry of each parent record?

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

  1. 省table(有许多区)
  2. 地区table(有许多村庄)
  3. 村庄table(有许多医院)
  4. hospital_types table(有一家医院)
  5. 医院table(属于hospital_types,有很多监控)
  6. 监控table(hasMany rateLog)

我要的是带医院的省份,每家医院做一个去年的监测。

注:最后一年不是从现在算起,意思是取每家医院最后一次监测的日期,然后从那个日期算起,到那个日期需要12个月

假设A医院最后一次监测是在2020-11-01,那么应该把2020-11-01到2019-11-01之间的所有监测都拿去。其他医院也一样。

已经用Laravelresource实现了,但是有点慢,也不知道resource返回数据后怎么实现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();
}

您似乎并不急于加载 hospital 关系。我会首先添加这个,这样你就不必 运行 每个 HospitalType:

的单个查询
$data = Province::with(['districts.hospitalTypes.hospital'])->get();

我相信您查询的原因之一可能是运行宁慢一点是因为您的lastMonitoring检查。首先,此查询将为每个 Hospital 获取 运行,其次,您似乎不是 storing/caching 该值,因此实际上将查询两次。


如果您更新到 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());
    });
}