Eloquent:按关联模型计算属性筛选排序及产品按距离筛选排序并附加距离
关于Eloquent关联模型筛选排序及地理距离查询的解决方案
嘿,我来帮你搞定这两个Eloquent的问题!咱们一个一个来:
1. 如何通过关联模型的计算属性对模型进行筛选和排序?
首先得明确一点:模型上的计算属性(比如PHP方法定义的属性)是在PHP层面运行的,数据库根本不知道这些逻辑,所以没法直接用它们来做数据库层面的筛选或排序。要实现这个需求,你得把计算属性的逻辑转化为数据库能理解的SQL表达式,然后结合关联查询来实现。
举个例子,假设你有Post模型和Comment模型,Comment有个计算属性is_recent(判断评论是否是最近7天发布的),现在要筛选出有最近评论的帖子,并按评论的最新时间排序:
// 不要直接用计算属性,而是把逻辑转成SQL $posts = Post::select('posts.*') ->join('comments', 'posts.id', '=', 'comments.post_id') // 把计算属性的逻辑转成SQL条件 ->where('comments.created_at', '>=', now()->subDays(7)) // 按评论的最新时间排序 ->orderBy('comments.created_at', 'desc') ->distinct() // 避免重复的帖子 ->get();
如果你的计算属性逻辑更复杂(比如涉及多个字段的计算),可以用selectRaw或者whereRaw来写自定义SQL表达式,把关联模型的字段拉进来参与计算。核心思路就是:把PHP层面的计算逻辑搬到数据库查询里,这样才能利用数据库的筛选和排序能力。
2. 基于店铺经纬度筛选产品并按距离排序
针对你给出的Product和Shop模型,我们可以利用数据库的地理空间函数来计算距离,筛选符合条件的产品,同时把距离值附加到结果中。下面分两种常见情况给出实现:
情况1:使用MySQL 5.7+的空间函数(推荐)
MySQL 5.7及以上支持ST_Distance_Sphere函数,可以直接计算两个经纬度点之间的球面距离(单位是米),效率很高:
// 先获取请求参数 $lat = $request->query('lat'); $lon = $request->query('lon'); $rad = $request->query('rad'); // 假设单位是公里 // 构建查询 $products = Product::select('products.*') // 计算距离并别名distance(转成公里) ->selectRaw('ST_Distance_Sphere(POINT(shops.longitude, shops.latitude), POINT(?, ?)) / 1000 AS distance', [$lon, $lat]) // 关联店铺表 ->join('shops', 'products.shop_id', '=', 'shops.id') // 筛选距离小于指定半径的产品 ->whereRaw('ST_Distance_Sphere(POINT(shops.longitude, shops.latitude), POINT(?, ?)) / 1000 < ?', [$lon, $lat, $rad]) // 按距离从小到大排序 ->orderBy('distance') ->get();
情况2:使用Haversine公式(兼容旧版本数据库)
如果你的数据库不支持空间函数,可以用经典的Haversine公式计算球面距离:
$lat = $request->query('lat'); $lon = $request->query('lon'); $rad = $request->query('rad'); // 定义Haversine公式的SQL表达式 $haversine = <<<SQL (6371 * acos(cos(radians(?)) * cos(radians(shops.latitude)) * cos(radians(shops.longitude) - radians(?)) + sin(radians(?)) * sin(radians(shops.latitude)))) SQL; $products = Product::select('products.*') // 计算距离(6371是地球半径,单位公里) ->selectRaw("$haversine AS distance", [$lat, $lon, $lat]) ->join('shops', 'products.shop_id', '=', 'shops.id') // 筛选距离条件 ->whereRaw("$haversine < ?", [$lat, $lon, $lat, $rad]) ->orderBy('distance') ->get();
优化建议
为了让查询更快,建议给shops表的经纬度字段添加空间索引(如果用空间函数的话):
// 在Shop模型的迁移文件中 $table->point('location'); // 存储经纬度点 $table->spatialIndex('location');
或者如果用普通字段存储lat和lon,可以给这两个字段添加联合索引,也能提升查询效率。
内容的提问来源于stack exchange,提问作者eng
相关产品推荐
相关产品推荐

