如何在 MongoDB 聚合管道中执行嵌套 "joins"(加入 3 个或更多集合)?

How can I perform nested "joins" (joining 3 or more collections) in a MongoDB aggregation pipeline?

假设我们在 MongoDB 中有 3 个假设的集合:customersordersorderItems

每个客户有多个订单,每个订单有多个订单商品。

以下是这 3 个集合的一些样本数据:

客户

[
    {
        customer_id: 1,
        name: "Jim Smith",
        email: "jim.smith@example.com"
    },
    {
        customer_id: 2,
        name: "Bob Jones",
        email: "bob.jones@example.com"
    }
]

订单

[
    {
        order_id: 1,
        customer_id: 1
    },
    {
        order_id: 2,
        customer_id: 1
    }
]

订单商品

[
    {
        order_item_id: 1,
        name: "Foo",
        price: 4.99,
        order_id: 1
    },
    {
        order_item_id: 2,
        name: "Bar",
        price: 17.99,
        order_id: 1
    },
    {
        order_item_id: 3,
        name: "baz",
        price: 24.99,
        order_id: 2
    }
]

想要的结果

如何编写我的聚合管道以使返回的结果看起来像这样?

[
    {
        customer_id: 1,
        name: "Jim Smith",
        email: "jim.smith@example.com"
        orders: [
            {
                order_id: 1,
                items: [
                    {
                        name: "Foo",
                        price: 4.99
                    },
                    {
                        name: "Bar",
                        price: 17.99
                    }
                ]
            },
            {
                order_id: 2,
                items: [
                    {
                        name: "baz",
                        price: 24.99
                    }
                ]
            }
        ]
    },
    {
        customer_id: 2,
        name: "Bob Jones",
        email: "bob.jones@example.com"
        orders: []
    }
]

使用 lookup with pipeline

进行嵌套查找
  • $lookuporders collection,
    • let,定义来自主 collection 的变量 customer_id,使用 $$$$customer_id
    • pipeline 可以像我们在根级管道中一样添加管道阶段
    • $expr 每当我们匹配内部字段时它需要表达式匹配条件,所以 $$customer_id 是 parent collection 在 let 和 [= 中声明的字段21=]是childcollection's/currentcollection的字段
  • $lookuporderitems collection
db.customers.aggregate([
  {
    $lookup: {
      from: "orders",
      let: { customer_id: "$customer_id" },
      pipeline: [
        { $match: { $expr: { $eq: ["$$customer_id", "$customer_id"] } } },
        {
          $lookup: {
            from: "orderitems",
            localField: "order_id",
            foreignField: "order_id",
            as: "items"
          }
        }
      ],
      as: "orders"
    }
  }
])

Playground


Tip:

Several joins considered as bad practice in NoSQL, I would suggest if you could add your order items in orders collection as array, you can save one join process for orderitems, see improved version in playground