Laravel如何通过Query Builder按商家距离排序优惠信息?
按经纬度距离排序Offer的Laravel实现方案
问题背景
需要基于商家与用户的经纬度距离对优惠信息(Offer)排序展示,现有代码通过关联查询筛选商家状态、优惠有效期,目前用Raw SQL实现了距离计算但希望减少Raw语法依赖,寻求更优实现或可用扩展包。
现有基础查询代码:
$data['offers']= Offer:: whereHas('merchants', function (Builder $query) { $query->where('dd_merchant', '=', '1')->where('status', '=', 'Open'); }) ->where('status', '=', 'Loaded') ->where("start", "<=", date("Y-m-d")) ->where("end", ">=", date("Y-m-d")) ->orderBy('title','ASC') ->paginate(20);
方案一:优化Query Builder写法(减少Raw依赖且更安全)
不用嵌套子查询,直接通过selectRaw绑定参数计算距离,避免SQL注入风险,同时保留Query Builder的链式调用和分页功能:
use Illuminate\Database\Eloquent\Builder; $circleRadius = 3959; // 单位:英里,若用千米则改为6371 $maxDistance = 100; $userLat = $customer->latitude; $userLng = $customer->longitude; $now = now()->toDateString(); // 用Laravel的Carbon更规范 $data['offers'] = Offer::select('offers.*', 'merchants.latitude', 'merchants.longitude') // 绑定参数的selectRaw,避免直接拼接变量 ->selectRaw( "{$circleRadius} * acos(cos(radians(?)) * cos(radians(merchants.latitude)) * cos(radians(merchants.longitude) - radians(?)) + sin(radians(?)) * sin(radians(merchants.latitude))) AS distance", [$userLat, $userLng, $userLat] ) ->join('merchants', 'offers.mid', '=', 'merchants.mid') ->where('merchants.dd_merchant', 1) ->where('merchants.status', 'Open') ->where('offers.status', 'Loaded') ->where('offers.start', '<=', $now) ->where('offers.end', '>=', $now) ->having('distance', '<', $maxDistance) // 计算字段用having而非where ->orderBy('distance', 'ASC') ->paginate(20);
说明:
- 用
join替代whereHas,因为需要获取商家的经纬度字段用于计算 selectRaw通过参数绑定传递用户经纬度,避免SQL注入- 计算生成的
distance字段需用having过滤,不能用where
方案二:使用Composer扩展包(完全规避Raw语法)
如果想彻底不用Raw SQL,推荐使用专注于Laravel距离计算的扩展包:
1. mattkingshott/laravel-distance
这个包提供了链式调用的方法来处理距离计算和排序,无需手动编写地理SQL。
安装:
composer require mattkingshott/laravel-distance
使用步骤:
首先确保Offer模型与Merchant模型建立关联:
// Offer.php public function merchant() { return $this->belongsTo(Merchant::class, 'mid'); }
然后编写查询:
$userLocation = [$customer->latitude, $customer->longitude]; $maxDistance = 100; // 单位默认英里,可通过参数指定千米 $data['offers'] = Offer::whereHas('merchant', function (Builder $query) { $query->where('dd_merchant', 1)->where('status', 'Open'); }) ->where('status', 'Loaded') ->where('start', '<=', now()->toDateString()) ->where('end', '>=', now()->toDateString()) // 按距离排序,参数:用户坐标、商家纬度字段、商家经度字段 ->orderByDistance($userLocation, 'merchant.latitude', 'merchant.longitude') // 过滤距离范围内的结果 ->havingDistance($userLocation, '<', $maxDistance, 'merchant.latitude', 'merchant.longitude') ->paginate(20);
这个包会自动生成安全的SQL语句,完全不用自己处理地理计算的Raw代码,同时支持单位切换、多种距离算法。
内容的提问来源于stack exchange,提问作者Patrick Simard
相关产品推荐
相关产品推荐

