如何在含子查询的距离计算查询中替换DB以返回Pub模型实例
解决Laravel中Haversine公式查询返回模型集合的问题
嘿,我来帮你搞定这个问题!你现在的函数用DB::select返回的是数组,想要拿到Pub模型实例的话,完全不需要在MySQL里声明变量,用Eloquent的查询构造器就能轻松解决,还能顺便修复SQL注入的风险(你原来直接拼接$distance的写法可是有安全隐患的哦)。
下面给你两种可行的方案:
方案一:直接用selectRaw添加计算字段
这种方法最简洁,直接在Eloquent查询里把距离计算作为字段返回,然后用having来过滤(因为WHERE不能直接用查询别名,HAVING可以):
public static function getByDistance($distance) { return Pub::selectRaw('*, (6371 * acos(cos(radians(40.4169473)) * cos(radians(latitude)) * cos(radians(longitude) - radians(-3.7035285)) + sin(radians(40.4169473)) * sin(radians(latitude)))) AS distance') ->having('distance', '<', $distance) ->orderBy('distance') ->get(); }
这样返回的就是Illuminate\Database\Eloquent\Collection集合,每个元素都是Pub模型实例,你可以直接调用模型的属性和方法。
方案二:用子查询(fromSub)
如果你更习惯原来的子查询结构,Laravel提供了fromSub方法来把子查询作为临时表查询,外层依然用Pub模型来获取实例:
public static function getByDistance($distance) { // 先定义子查询,计算每个pub的距离 $subquery = Pub::selectRaw('*, (6371 * acos(cos(radians(40.4169473)) * cos(radians(latitude)) * cos(radians(longitude) - radians(-3.7035285)) + sin(radians(40.4169473)) * sin(radians(latitude)))) AS distance'); // 从子查询结果中过滤并排序 return Pub::fromSub($subquery, 'distances') ->where('distance', '<', $distance) ->orderBy('distance') ->get(); }
额外优化:让经纬度可配置(可选)
如果以后需要更换查询的中心点,你可以把经纬度做成参数传入,这样函数更灵活:
public static function getByDistance($distance, $centerLat = 40.4169473, $centerLng = -3.7035285) { return Pub::selectRaw( '*, (6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) AS distance', [$centerLat, $centerLng, $centerLat] ) ->having('distance', '<', $distance) ->orderBy('distance') ->get(); }
为什么原来用Pub会报错?
你之前直接把DB换成Pub报错,是因为原来的子查询写法没有用Eloquent的正确语法,而上面两种方案都是遵循Eloquent的查询规则,所以能正常返回模型实例。另外,注意我们全程用了参数绑定(Laravel会自动处理),避免了SQL注入风险,这比直接拼接字符串安全多了。
内容的提问来源于stack exchange,提问作者Ber Tsacianegu del Tepuy
相关产品推荐
相关产品推荐

