【问题标题】:is this the right way to join two collections and combine results这是加入两个集合并合并结果的正确方法吗
【发布时间】:2020-09-06 18:38:00
【问题描述】:

我正在尝试加入两个集合并从两个集合中获取字段。集合具有非常基本的结构,它们的外观如下:

"products": [
    {
      "_id": 0,
      "name": "product 0",
      "desc": "some product",
      "sales_reps": [
        {
          "sales_rep_id": 0,
          "is_doing_good": true
        },
        {
          "sales_rep_id": 1,
          "is_doing_good": true
        }
      ]
    }
  ]

  "sales_rep_master": [
    {
      "_id": 0,
      "name": "sales rep 0"
    },
    {
      "_id": 1,
      "name": "sales rep 1"
    },
    {
      "_id": 2,
      "name": "sales rep 2"
    }
  ]

我正在尝试通过products.sales_reps.sales_rep_id = sales_rep_master._id 加入他们。这是我的查询的样子:

[
  {
    "$match": { <--Filter on product._id
      "_id": 0 
    }
  },
  {
    "$unwind": { <-- Expand sales reps array
      "path": "$sales_reps" 
    }
  },
  {
    "$lookup": { <-- join with sales rep master and filter our where is_doing_well is false
      "from": "sales_rep_master",
      "let": {
        "sr_id": "$sales_reps.sales_rep_id",
        "is_doing_well": "$sales_reps.is_doing_good"
      },
      "pipeline": [
        {
          "$match": {
            "$and": [
              {
                "$expr": {
                  "$eq": [
                    "$_id",
                    "$$sr_id"
                  ]
                }
              },
              {
                "$expr": {
                  "$eq": [
                    "$$is_doing_well",
                    true
                  ]
                }
              }
            ]
          }
        },
        {
          "$addFields": {
            "doing_good": "$$is_doing_well"
          }
        }
      ],
      "as": "sales_reps"
    }
  },
  {
    "$unwind": { <<- expand newly created sales reps array
      "path": "$sales_reps"
    }
  },
  {
    "$group": { <-- Group product and sales reps
      "_id": "$_id",
      "product": {
        "$first": "$$ROOT"
      },
      "sales_reps": {
        "$push": "$sales_reps"
      }
    },
    
  },
  {
    "$set": { <-- add sales_reps inside product
      "product.sales_reps": "$sales_reps"
    }
  },
  {
    "$replaceRoot": { <-- replace root
      "newRoot": "$product"
    }
  }
]

这个查询工作正常,我得到了预期的输出,但我觉得我在这里做错了,因为这似乎做太多只是为了从两个集合中获取字段。在我排除之后还有更多阶段。

这里是演示:https://mongoplayground.net/p/_Tz3fm4a-J8

我在这里做错了什么还是应该是这样?

【问题讨论】:

  • 我只需要知道,{"$expr":{"$eq":["$$is_doing_well",true]}} 你为什么要在管道内部检查这个?管道是使用连接表阶段。 is_doing_well 在您的 product 收藏中。所以如果你需要检查is_doing_well:true,你可以在查找之前进行。 mongoplayground.net/p/Jq7ohYeqwub 是否可以帮助您,如果可以,我可以简要回答一下
  • 您好@varman 知识,$in 内部查找管道将不会使用索引(如果提供),请查看此 question 并点击链接和票证。
  • @turivishal 我刚刚通过了jira.mongodb.org/browse/SERVER-32549 票。 $unwind 当我们知道有数千条记录时,性能会更加糟糕。
  • @turivishal 并且他表示需要匹配两个变量。(可能是他的场景错误,我对此提出质疑)。我可以通过定期查找完成并需要更多阶段
  • @varman 是的,你是正确的 $unwind,看看这个问题,在第一个管道中,他正在使用 _id 搜索它只会返回一个文档,其次我认为它不是存储的好方法数千个数组元素,如果数组字段包含有限的数据,那么只为一个文档的数组展开是很好的选择,你的方法非常好,但它取决于 OP 的原始数据。我会检查explain stat 的两种方法。

标签: mongodb mongodb-query


【解决方案1】:

您可以优化您的查询,

  • $match你俩条件,减少不匹配的文件
  • $unwind解构sales_reps数组
  • $matchsales_reps.is_doing_good 减少不匹配文档的条件
  • $lookup加入收藏
  • $unwind解构sales_reps
  • $addFields 添加字段 doing_good
  • $group 按 id 并重构 sales_reps 数组
db.products.aggregate([
  {
    $match: {
      _id: 0,
      "sales_reps.is_doing_good": true
    }
  },
  { $unwind: "$sales_reps" },
  { $match: { "sales_reps.is_doing_good": true } },
  {
    $lookup: {
      from: "sales_rep_master",
      localField: "sales_reps.sales_rep_id",
      foreignField: "_id",
      as: "sales_reps"
    }
  },
  { $unwind: "$sales_reps" },
  { $addFields: { "sales_reps.doing_good": true } },
  {
    $group: {
      _id: "$_id",
      desc: { $first: "$desc" },
      name: { $first: "$name" },
      sales_reps: { $push: "$sales_reps" }
    }
  }
])

Playground


另一种可能的方式,从上面的例子中减少$addFields管道,

  • $mergeObjects 合并字段$push
  {
    $group: {
      _id: "$_id",
      desc: { $first: "$desc" },
      name: { $first: "$name" },
      sales_reps: {
        $push: {
          $mergeObjects: [{ doing_good: true }, "$sales_reps"]
        }
      }
    }
  }

Playground


参考aggregation-pipeline-optimization,使用explain()比较查询的响应统计

【讨论】:

    【解决方案2】:

    可以简化的部分是$lookup,使用localFieldforeignField

    然后我用 $project 而不是 $set$replaceRoot 绑定了一个不同的方法


    mongoplayground

    db.products.aggregate([
      {
        "$match": {
          "_id": 0
        }
      },
      {
        "$unwind": {
          "path": "$sales_reps"
        }
      },
      {
        "$lookup": {
          "from": "sales_rep_master",
          "localField": "sales_reps.sales_rep_id",
          "foreignField": "_id",
          "as": "sales_rep_master"
        }
      },
      {
        "$unwind": {
          "path": "$sales_rep_master"
        }
      },
      {
        "$project": {
          "_id": 1,
          "desc": 1,
          "name": 1,
          "sales_rep_master": 1,
          "sales_reps": {
            "_id": "$sales_rep_master._id",
            "name": "$sales_rep_master.name",
            "doing_good": "$sales_reps.is_doing_good",
            
          }
        }
      },
      {
        "$group": {
          "_id": "$_id",
          "desc": {
            "$first": "$desc"
          },
          "name": {
            "$first": "$name"
          },
          "sales_reps": {
            "$addToSet": "$sales_reps"
          }
        }
      }
    ])
    

    【讨论】:

      【解决方案3】:

      我考虑的东西很少。您不需要为每个条件使用$expr。如果你打算用$in比较,那么你不需要$unwind$unwind 总是在我们增加数据时花费。参考JIRA ticket。但考虑到数据大小,可以使用$in$unwind

      db.products.aggregate([
        {
          "$match": {
            "_id": 0
          }
        },
        {
          $addFields: {
            sales_reps: {
              $filter: {
                input: "$sales_reps",
                cond: {
                  $eq: [ "$$this.is_doing_good", true]
                }
              }
            }
          }
        },
        {
          "$lookup": {
            "from": "sales_rep_master",
            "let": { "sr_id": "$sales_reps.sales_rep_id" },
            "pipeline": [
              {
                "$match": {
                  "$expr": {
                    "$and": [
                      { "$in": [ "$_id","$$sr_id"] },
                      // { other condition }
                    ]
                  }
                }
              },
              {
                "$addFields": {
                  "doing_good": true
                }
              }
            ],
            "as": "sales_reps"
          }
        }
      ])
      

      工作Mongo playground

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-08
      • 1970-01-01
      相关资源
      最近更新 更多