Laravel Eloquent / MySQL как получить одну прошлогоднюю запись из последней записи каждой родительской записи?

#php #mysql #laravel #groupwise-maximum

#php #mysql #laravel #groupwise-максимум

Вопрос:

Предположим, у меня есть следующие отношения в устаревшей системе, которые не могут ее изменить.

  1. таблица провинций (имеет множество районов)
  2. таблица районов (много деревень)
  3. таблица деревень (много больниц)
  4. таблица hospital_types (имеет одну больницу)
  5. таблица больниц (принадлежит hospital_types, мониторинг hasMany)
  6. таблица мониторинга (hasMany rateLog)

Я хочу взять провинции с больницами и провести мониторинг каждой больницы за последний год.

Примечание: один прошлый год означает не текущую дату, это означает, что мы должны взять дату последнего мониторинга каждой больницы, а затем рассчитать с этой даты, чтобы занять 12 месяцев до этой даты

Предположим, что больница А в последний раз отслеживалась в 2020-11-01 годах, весь мониторинг должен проходить в период с 2020-11-01 по 2019-11-01. И то же самое для других больниц.

Уже реализовано с помощью Laravel resource, но это немного медленно, а также не знаю, как мы можем реализовать функцию OrderBy после того, как ресурс вернул данные, потому что мне нужна разбивка на страницы и сортировка на основе мониторинга, который теперь я обрабатываю с помощью resource.

В настоящее время я использую отношение hasManyThrough, и после получения я передаю данные в сбор ресурсов и обрабатываю получение мониторинга. Но при этом невозможно реализовать сортировку и разбивку на страницы.

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

Затем я передаю эти данные ресурсу и получаю мониторинг, но как я могу применить сортировку и разбивку на страницы, потому что сортировка основана на мониторинге. И использование ресурса немного медленнее, чем быстрая загрузка.
Я ищу способ использовать нетерпеливую загрузку с учетом этих критериев.

 class ProvinceResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  IlluminateHttpRequest  $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  IlluminateHttpRequest  $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  IlluminateHttpRequest  $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();
}
 

Комментарии:

1. Пожалуйста, не могли бы вы показать код для вашего Resource и как вы фильтруете мониторинг.

2. @Rwd Пожалуйста, проверьте сейчас

3. Какую версию Laravel вы используете?

4. @Rwd Laravel 7, но открыт для обновления до 8

5. Можете ли вы поделиться структурой таблиц моделей? Пожалуйста, поделитесь структурой таблицы в качестве github gist

Ответ №1:

Похоже, вы не хотите загружать hospital отношения. Я бы начал с добавления этого, чтобы вам не приходилось запускать один запрос для каждого HospitalType :

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

Я считаю, что одна из причин, по которой ваш запрос может выполняться немного медленнее, связана с вашей lastMonitoring проверкой. Во-первых, этот запрос будет выполняться для каждого Hospital , а во-вторых, вы, похоже, не сохраняете / кэшируете это значение, поэтому на самом деле это будут запросы дважды.


Если вы обновитесь до Laravel 8 (минимальная версия 8.4.2), вы сможете использовать отношение Has one of many для вашего lastMonitoring метода, например:

 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());
    });
}
 

Комментарии:

1. Спасибо за вашу помощь, после обновления Laravel и PHP реализовал ваше решение, но оно не работает. Проблема в том, что она никогда не попадает внутрь when($this-> latestMonitoring , потому что она всегда возвращает null.

2. @jones Ах, конечно. Извините за это! Я обновил latestMonitoring отношение, чтобы использовать то же foreignKey самое и localKey ( hostpital_id и id ), monitors что и отношение, которое теперь должно работать.

3. @Jones Как в last12MonthMonitors и last12MonthRageLogs работает только с отложенной загрузкой, или другое отношение работает только с отложенной загрузкой?