Laravel 中如何按集合的关系对集合进行排序

Far*_*had 1 php laravel

我的酒店有一个复杂的过滤器,最后我有一个集合,我想按嵌套关系对父关系进行排序,所以这里我有如下:

  public function resultFilter($from_date, $to_date, $bed_count, $city_id, $stars, $type_id, $hits, $price, $accommodation_name, $is_recommended, $start_price, $end_price, $has_discount, $facility_id)
    {
//        $data = QueryBuilder::for(Accommodation::class)
//            ->allowedFilters(['city_id','grade_stars','accommodation_type_id'])
//            ->allowedIncludes('gallery')
//            ->when($bed_count, function ($q, $bed_count) {
//                $q->with([
//                    'accommodationRoomsLimited' => function ($q) use ($bed_count) {
//                        $q->where('bed_count', $bed_count);
//                    }
//                ]);
//            })
//            ->paginate(10);
//        ->get();
//            ->orderBy('hits','DESC')->paginate(10);
        $data = Accommodation::with(['city','accommodationFacilities', 'gallery', 'accommodationRoomsLimited.discount', 'accommodationRoomsLimited', 'accommodationRoomsLimited.roomPricingHistorySearch' => function ($query) use ($from_date, $to_date) {
            $query->whereDate('from_date', '<=', $from_date);
            $query->whereDate('to_date', '>=', $to_date);
        }])->when($bed_count, function ($q, $bed_count) {
                $q->whereHas('accommodationRoomsLimited', function($query) use ($bed_count) {
                    $query->where('bed_count', $bed_count);
                });
        })->when($accommodation_name, function ($query, $accommodation_name) {
            $query->where('name', 'like', $accommodation_name);
        })->when($is_recommended, function ($query,$is_recommended){
            $query->where('is_recommended', $is_recommended);
        })->when($start_price, function ($query, $start_price) {
                $query->with([
                    'accommodationRoomsLimited.roomPricingHistorySearch' => function ($q) use ($start_price) {
                        $q->where('sales_price', '<', $start_price);
                    }
                ]);
            })->when($has_discount, function ($query, $has_discount) {
                $query->with([
                    'accommodationRoomsLimited' => function ($q) use ($has_discount) {
                        $q->has('discount');
                    }
                ]);
            })
            ->whereIn('city_id', $city_id)
            ->whereIn('grade_stars', $stars)
            ->orWhere('accommodation_type_id', $type_id);
        if ($hits) { // or == 'blabla'
            $data = $data->orderBy('hits','DESC');
        } elseif ($price) { // == A-Z or Z-A for order asc,desc
            $f = $data->get();
            foreach ($f as $datas) {
                foreach ($datas->accommodationRoomsLimited as $g) {
                    dd($data);
                    $data = $data->accommodationRoomsLimited()->orderBy($g->roomPricingHistorySearch->sales_price);
                }
            }
        }
        $data = $data->paginate(10);
        return $data;
    }
Run Code Online (Sandbox Code Playgroud)

因此,如果您阅读代码,我添加了 sales_price,如果请求中存在 $price,我想通过它对 $data 进行排序。因此,在短期问题中,我想在上面的查询中按 sales_price 对 $data 进行排序。

注意 :此过滤器可能会变得更加复杂,因此任何其他最佳实践或更好的方法(例如 spatie 查询构建器或本地范围)将受到赞赏,尽管我尝试了两者,但它们有自己的限制

小智 5

我以前遇到过这个问题。看来我需要先解释一下关于急切加载的事情。

不能通过 来订购eager loading,您可以在获取数据后订购。因为 急切加载会拆分连接查询以获得更好的性能。例如您查询accomodation与城市有关系。该accomodation表有 1000 条记录,该city表有 10.000 条记录。假设急切加载的最大 id 是 250,表city_id中唯一的accomodationid 是 780。将生成 5 个查询。

$data = Accomodation::with('city')->get();
Run Code Online (Sandbox Code Playgroud)
  1. select * from accomodation
  2. select * from city where id in [unique_id_1 - unique_id_250]
  3. select * from city where id in [unique_id_251 - unique_id_500]
  4. select * from city where id in [unique_id_501 - unique_id_750]
  5. select * from city where id in [unique_id_751 - unique_id_780]

然后 Laravel 将完成根据查询结果创建关系的工作city。通过这种方法,您将解决N+1连接查询的问题,因此它应该更快。

然后想象您想要accomodation通过查询生成器中的方法city.name进行排序with。我们以第三个查询为例。

$data = Accomodation::with([
    'city' => function($q) { return $q->orderBy('name'); },
])->get();
Run Code Online (Sandbox Code Playgroud)

查询将是:

select * from city where id in [unique_id_251 - unique_id_500] order by name

城市结果将被排序,但 laravel 将以同样的方式读取它。它将 accomodation首先创建,然后将其与city查询相关联。所以订单来源city不会影响accomodation订单。

那么如何订购呢?我找到了几种方法来实现这一目标。

  1. 加入查询。这是最简单的方法,但会使查询速度变慢。如果您的数据不是很大并且连接查询不会损害您的性能。也许 0.003 秒的更好性能并不值得你花费 8 个小时。

  2. sortBy在采集功能中。您可以使用集合中的方法对其进行排序。例如,如果您想订购accomodation基于country.namefromcity关系,此脚本将为您提供帮助。

$data = Accomodation::with('city.country')->get();
$data->sortBy(function($item) {
    return $item->city->country->name;
});
Run Code Online (Sandbox Code Playgroud)
  1. 压平集合。此方法将尝试展平集合,因此结果将类似于join查询然后对其进行排序。您可以使用map集合中的方法。我确实相信所有过滤器和可搜索字符串都应该包含在数据中。
$data->map(function($item) {
    return [
      'city_name' => $city->name,
      ...
      all_searchable_data,
      all_shareable_data,
      ...
    ];
  })->sortBy('key1);
Run Code Online (Sandbox Code Playgroud)
  1. 如果可能,更改急切加载方向。您可以通过更改基本型号来订购它。例如,您可以使用city以下命令accomodation来订购它:city.name
$data = City::with('accomodation')->orderBy('name')->get();
Run Code Online (Sandbox Code Playgroud)

最后,如果您的数据很少更改(例如每 2 小时更改一次),您可能会考虑使用缓存。您只需每 2 小时使缓存失效并创建新的缓存即可。根据我的经验,如果数据很大,缓存总是比查询数据库更快。您只需要知道间隔或event使缓存失效。

您选择什么都取决于您。但请记住这一点,当您使用 Laravel 的集合处理批量数据时,它可能比从数据库查询慢。也许是因为PHP的性能。

对我来说最好的方法是先用eager loading然后->map()再用cache。为什么我需要map在缓存之前先使用它?原因是,通过选择某些属性会减少缓存大小。然后你将获得更多的性能。我可以说它将产生更具可读性和美观的代码。

奖金 这就是我这样做的方式。

$data = Cache::remember("accomodation", 10, function() {
  $data = Accommodation::with([
    'city',
    ...
    ])
    ->get();

  return $data->map(function($item) {
    return [
      'city_name' => $item->city->name,
      ...
      all_additional_data,
      all_searchable_data,
      all_shareable_data,
      ...
    ];
  });
}

return $data->search(function($item) use ($searchKey, $searchAnnotiation, $searchValue) {
  switch ($searchAnnotiation) {
    case '>':
      return $item[$searchKey] > $searchValue;
      break;
    case '<':
      return $item[$searchKey] < $searchValue;
      break;
  }
})->sortBy($sortKey)->paginate();
Run Code Online (Sandbox Code Playgroud)

缓存会保存处理后的数据。因此,所需的执行时间是从缓存中获取数据、过滤数据并对其进行排序。然后将其转换为分页。您可以在这些流程中设置任何附加缓存以获得更快的结果。

$data->paginate()通过paginateCollection.