导读:本期聚焦于小伙伴创作的《Laravel中如何用MongoDB根据子模型字段总和筛选父模型记录》,敬请观看详情。在订单与商品这类一对多结构中,有时需要找出总金额超过某一阈值的订单。MongoDB本身提供聚合管道,可在$lookup关联子表后,用$project配合$sum计算嵌套数组的字段合计,再通过$match过滤。Laravel使用jenssegers/laravel-mongodb扩展时,能借助原生聚合方法把这一步写成链式调用。不少人在Eloquent里直接对关联模型做sum再过滤,结果只能先取全量父模型再遍历,既耗内存又慢。正确做法是把计算下推到数据库,利用MongoDB的聚合阶段完成关联、求和与筛选,最后用map还原成模型实例。下文将演示具体写法与性能差异。

在 Laravel 里操作 MongoDB 时,我们常遇到这样的需求:父模型(例如订单 Order)拥有多个子模型(例如商品 Item),需要根据子模型某个字段的总和来筛选父模型。传统关系型数据库的 SQL 可以通过子查询或 join 加 group by 解决,而 MongoDB 作为文档数据库,更推荐使用聚合管道(aggregate pipeline)在数据库层完成关联与计算。

Laravel中如何用MongoDB根据子模型字段总和筛选父模型记录

一、数据模型与关联关系

假设我们有两个集合:orders 和 items。orders 文档保存订单基础信息,items 文档通过 order_id 关联订单,并带有 amount 字段表示金额。在 Laravel 中,使用 jenssegers/laravel-mongodb 扩展定义模型如下。

<?php

namespace AppModels;

use JenssegersMongodbEloquentModel;

class Order extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'orders';

    public function items()
    {
        return $this->hasMany(Item::class, 'order_id', '_id');
    }
}

class Item extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'items';
}

上面的代码建立了 Order 与 Item 的一对多关联。注意 MongoDB 的主键默认是 _id,关联字段我们选用 order_id 存储订单的主键值。这种结构下,子文档可以放在独立集合中,便于水平扩展。

如果直接在 Laravel 里用 Eloquent 取数据,很多人会写出类似 Order::with('items')->get() 再遍历求和的代码。这种方式会把所有订单及关联商品拉到内存,数据量大时极易超时或内存溢出,并不是合理的查询方案。

二、MongoDB 聚合管道思路

MongoDB 的聚合管道可以依次执行多个阶段。针对我们的场景,核心步骤如下:先用 $lookup 把 items 关联到 orders;再用 $addFields 或 $project 结合 $sum 计算子表 amount 总和;最后用 $match 筛选总和满足条件的父文档。

在 mongo shell 中,对应的聚合语句大致如下:

db.orders.aggregate([
  {
    $lookup: {
      from: 'items',
      localField: '_id',
      foreignField: 'order_id',
      as: 'item_list'
    }
  },
  {
    $addFields: {
      total_amount: { $sum: '$item_list.amount' }
    }
  },
  {
    $match: {
      total_amount: { $gt: 100 }
    }
  }
]);

这段管道首先在 orders 每条记录上挂载一个 item_list 数组,然后通过 $sum 对这个数组里的 amount 字段求和,生成 total_amount 字段,最后只保留 total_amount 大于 100 的订单。整个过程由 MongoDB 引擎在数据库内完成,不需要把原始子文档全部传到应用层。

需要提醒的是,$lookup 之后 item_list 是一个数组,如果子表数据极多,关联后的文档体积会变大。生产环境应确认索引情况,并在 items 表的 order_id 上建立索引以加速关联。

三、在 Laravel 中编写聚合查询

jenssegers/laravel-mongodb 提供了 raw 方法来执行原生聚合,也支持通过 MongoDB 驱动直接构造管道。下面示例演示如何在 Order 模型中封装一个作用域,用于筛选总金额大于指定值的订单。

<?php

use JenssegersMongodbEloquentModel;
use MongoDBBSONObjectId;

class Order extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'orders';

    public function scopeTotalGt($query, $threshold)
    {
        $pipeline = [
            [
                '$lookup' => [
                    'from' => 'items',
                    'localField' => '_id',
                    'foreignField' => 'order_id',
                    'as' => 'item_list'
                ]
            ],
            [
                '$addFields' => [
                    'total_amount' => ['$sum' => '$item_list.amount']
                ]
            ],
            [
                '$match' => [
                    'total_amount' => ['$gt' => $threshold]
                ]
            ]
        ];

        return $query->raw(function ($collection) use ($pipeline) {
            return $collection->aggregate($pipeline);
        });
    }
}

在控制器中调用时,可以这样使用:

$orders = Order::totalGt(100)->get();

foreach ($orders as $order) {
    // 此时 $order 是 Order 模型实例
    // 如需访问计算字段可读取 $order->total_amount
    echo $order->_id . ' 总金额:' . $order->total_amount . PHP_EOL;
}

这里用到了 Eloquent 的本地作用域(scope 开头的方法),让查询语义更清晰。raw 方法接收一个闭包,闭包参数是 MongoDB 的 Collection 对象,我们可以直接调用其 aggregate 方法并传入管道数组。

如果希望结果仍是标准 Order 模型且能继续使用模型关系,可以只对查出后的 _id 列表再做一次 whereIn 查询,但那样会增加一次数据库往返。大多数情况下,直接在聚合结果上映射为模型已能满足业务需要。

四、性能对比与注意事项

为了直观理解两种写法的差异,我们用一张简表对比常见的两种方案:

方案数据拉取方式内存占用适用规模
先取父模型再遍历 sum应用层加载全部记录小数据量
聚合管道下推计算数据库内完成关联与过滤大数据量

从表中可以看出,聚合管道把过滤逻辑交给 MongoDB,避免了无谓的数据传输。在百万级订单和千万级商品场景下,后者通常能将响应时间从几十秒降到毫秒级。

实际落地时,还应关注索引与字段类型。items.order_id 必须是能直接匹配 orders._id 的类型,若 orders._id 是 ObjectId,则写入 items 时也应存 ObjectId 而非字符串。另外,如果仅需父模型字段,可在管道末尾加 $project 剔除 item_list,减少返回文档大小。

五、小结

在 Laravel 配合 MongoDB 的开发中,根据子模型字段总和查询父模型并不复杂,关键是放弃在应用层循环求和的习惯,转而使用 $lookup 加 $sum 加 $match 的聚合管道。通过 jenssegers/laravel-mongodb 的 raw 方法,我们可以把原生管道无缝嵌入 Eloquent,既保留框架便利性,又获得数据库层的计算能力。

当业务规则变为按平均值、最大值或其他表达式筛选时,只需调整 $addFields 中的表达式与 $match 条件,整体结构保持不变。掌握这种写法,可以有效解决一对多关联下的统计型查询需求。

LaravelMongoDBaggregate修改时间:2026-08-07 17:00:45

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。