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

Laravel三级关联下经纬度距离过滤功能失效问题求助

解决Laravel三级关联(hasManyThrough)下经纬度距离过滤失效问题

我看到你遇到的问题了:用hasManyThrough关联到第三层模型(PartnerOutlet),想要根据第三层的经纬度做距离过滤和排序,但当前的whereHas写法无法生效,主要原因有两个:

  1. whereHas只负责筛选主模型,不传递关联字段到主查询:你在whereHas闭包里计算的distance和做的orderBy,都是作用于关联模型的子查询里,不会影响主模型的结果集排序,也不会把distance字段带回主模型。
  2. hasManyThrough的whereHas闭包是针对目标模型(PartnerOutlet),但你需要把距离条件和排序整合到主查询中。

下面给你两种可行的解决方案:


方案一:手动Join关联表(最直接高效)

既然hasManyThrough本质是两次表关联,我们可以手动Join中间表(PartnerOutletPromotion)和目标表(PartnerOutlet),直接在主查询里计算距离、筛选和排序:

if ($request->filled(['latitude', 'longitude'])) {
    $latitude = $request->latitude;
    $longitude = $request->longitude;

    // 替换成你的主模型表名和对应的外键
    $mainModelTable = 'your_main_model_table'; // 比如如果主模型是Product,这里是products
    $mainModelForeignKey = 'product_id'; // 主模型在partner_outlet_promotions表中的外键

    $list = $list
        // Join中间表
        ->join('partner_outlet_promotions', "{$mainModelTable}.id", '=', "partner_outlet_promotions.{$mainModelForeignKey}")
        // Join目标表(存经纬度的表)
        ->join('partner_outlets', 'partner_outlet_promotions.partner_outlet_id', '=', 'partner_outlets.id')
        // 计算距离,用参数绑定避免SQL注入
        ->selectRaw("{$mainModelTable}.*, 6371 * acos(cos(radians(?)) * cos(radians(partner_outlets.latitude)) * cos(radians(partner_outlets.longitude) - radians(?)) + sin(radians(?)) * sin(radians(partner_outlets.latitude))) AS distance", [$latitude, $longitude, $latitude])
        // 筛选距离小于100000的记录
        ->having('distance', '<', 100000)
        // 按距离排序
        ->orderBy('distance', 'asc')
        // 去重,避免主模型因多个关联outlet而重复
        ->distinct();
}

return response()->json($list->get());

注意事项:

  • 一定要替换$mainModelTable和$mainModelForeignKey为你实际的表名和外键。
  • 使用selectRaw的参数绑定可以避免SQL注入风险,比直接拼接字符串更安全。
  • distinct()用来防止主模型记录因为关联多个符合条件的PartnerOutlet而重复出现。

方案二:使用子查询计算距离(适合不想用Join的场景)

如果你更倾向于使用Laravel的关联语法而不是手动Join,可以用selectSub把距离计算作为子查询加入主查询,同时嵌套whereHas做筛选:

首先,确保你的PartnerOutletPromotion模型有一个到PartnerOutlet的关联:

// 在PartnerOutletPromotion模型中
public function partnerOutlet()
{
    return $this->belongsTo(PartnerOutlet::class);
}

然后修改查询代码:

if ($request->filled(['latitude', 'longitude'])) {
    $latitude = $request->latitude;
    $longitude = $request->longitude;

    // 子查询:获取当前主模型关联的最近outlet的距离
    $distanceSubquery = PartnerOutlet::selectRaw('6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))', [$latitude, $longitude, $latitude])
        ->whereColumn('partner_outlets.id', 'partner_outlet_promotions.partner_outlet_id')
        ->whereRaw('6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude))) < 100000', [$latitude, $longitude, $latitude])
        ->limit(1);

    $list = $list
        // 筛选存在符合距离条件的关联记录
        ->whereHas('partnerOutletPromotion', function ($query) use ($latitude, $longitude) {
            $query->whereHas('partnerOutlet', function ($q) use ($latitude, $longitude) {
                $q->selectRaw('*, 6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude))) AS distance', [$latitude, $longitude, $latitude])
                  ->having('distance', '<', 100000);
            });
        })
        // 将距离作为子查询字段加入主查询
        ->selectSub($distanceSubquery, 'distance')
        // 按距离排序
        ->orderBy('distance', 'asc');
}

return response()->json($list->get());

为什么原来的代码失效?

再回头看你原来的写法:

  • whereHas('partnerOutletPromotion', ...) 只是筛选出存在符合条件的PartnerOutlet的主模型记录,但不会把distance字段带到主查询中。
  • 你在闭包里的orderBy('distance', 'asc')是对关联模型的子查询排序,不会影响主模型结果的顺序。
  • 最终返回的$list->get()是主模型的集合,根本没有distance字段,所以排序完全不生效。

用上面的两种方案,就能把距离计算整合到主查询中,实现正确的过滤和排序。

内容的提问来源于stack exchange,提问作者Muhammad Mehdi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 02:07:48