uniCloud聚合流水线中通过链式拼接多个$lookup阶段实现多表联查,需用let+pipeline透传字段、避免as重复、区分$与$$符号,并确保权限、索引及云函数部署正确。
直接用 db.collection().aggregate() + 多个 $lookup 阶段,不是“嵌套调用”,而是“链式拼接”。官方不支持在 JQL 里写多层 $lookup,但聚合流水线(aggregate pipeline)天然支持连续 lookup,且可带 pipeline 子查询。
常见错误是把 lookup 当成函数嵌套调用,比如写成 .lookup(...).lookup(...).end() 却漏掉中间的 .match() 或 .project(),导致第二层 lookup 没法访问上一层输出的字段。
from 必须是真实存在的集合名(字符串),不能是变量或表达式lookup 的 as 字段名不能重复,否则后写入的会覆盖前一个lookup 的结果再查第三张表,必须用 let + pipeline,不能只靠 localField/foreignField
$ 符号(如 $_id)和 $$(如 $$book_id)必须严格区分:单美元是当前文档字段,双美元是 let 定义的变量比如订单(order)→ 商品(book)→ 作者(author),其中 order.book_id 关联 book._id,book.author_id 关联 author._id。这时不能指望一次 lookup 跨两跳,必须分步。
关键点在于第二层 lookup 必须放在第一层的 pipeline 内部,且要用 let 把上层字段透传进去:
const res = await db.collection('order').aggregate() .match({ _id: 'xxx' }) .lookup({ from: 'book', let: { book_id: '$book_id' }, pipeline: $.pipeline() .match(dbCmd.expr($.eq(['$_id', '$$book_id']))) .lookup({ from: 'author', let: { author_id: '$author_id' }, pipeline: $.pipeline() .match(dbCmd.expr($.eq(['$_id', '$$author_id']))) .done(), as: 'author' }) .done(), as: 'book' }) .end()
注意:$.pipeline() 是 uniCloud 提供的聚合子管道构造器,不是原生 MongoDB 语法;dbCmd.expr() 用于包裹表达式,避免字符串拼接出错。
主表有多个外键字段(如 user_id、category_id、tag_ids 数组),对应不同副表,这时不能只靠 localField/foreignField 简单匹配 —— 尤其当一个是字符串、一个是数组时。
数组字段联查必须用 let + pipeline + $in 表达式:
user_id),用标准 lookup 即可tag_ids),必须用 let: { tag_ids: '$tag_ids' },再在 pipeline 中用 $.in(['$_id', '$$tag_ids'])
lookup 的 as 名要唯一,比如 as: 'user'、as: 'category'、as: 'tags'
顺序无关,但建议先查单值关联,再查数组关联,避免因数据量大拖慢整个流水线。
最常踩的坑不是语法错,而是权限和索引没配好:
order 和 book 集合都得允许“所有人可读”或按角色配置foreignField 字段没建索引 —— book._id、author._id 这类被查字段必须有索引,否则聚合可能超时或返回空数组uniCloud.databaseForJQL() 却调了聚合方法 —— JQL 实例不支持 aggregate(),必须用 uniCloud.database()
调试时优先在云函数日志里看 console.log(res) 输出,别只盯着前端 uni.showToast —— 很多时候后端已返回数据,但前端没解构对 res.data[0].book 这类嵌套结构。