You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 08:56:05