【问题标题】:Aggregate duplicate documents with values in array in Mongo在Mongo中使用数组中的值聚合重复文档
【发布时间】:2021-09-02 14:03:07
【问题描述】:

我有大量文档,如下所示:

{ "_id": "5a760191813a54000b8475f1", "orders": [{ "row": "3", "seat": "11" }, { "row": "3", "seat": "12" }], "product_id": "5a7628bedbcc42000aa7f614" },
{ "_id": "5a75f6f17abe45000a3ba05e", "orders": [{ "row": "3", "seat": "12" }, { "row": "3", "seat": "13" }], "product_id": "5a7628bedbcc42000aa7f614" },
{ "_id": "5a75ebdf813a54000b8475e7", "orders": [{ "row": "5", "seat": "16" }, { "row": "5", "seat": "15" }], "product_id": "5a75f711dbcc42000c459efc" }

我需要能够找到product_idorders 数组中的项目重复的任何文档。我似乎无法全神贯注地完成这项工作。有什么指点吗?

【问题讨论】:

  • 重复您的意思是相同的 product_id 并且至少有一个共同订单?例如上面有重复因为{ "row": "3", "seat": "12" }?查询后你想得到什么输出数据?例如只有重复的“_id”就足够了?或者你想保留所有的文档信息?
  • 订单的所有元素都必须重复?

标签: node.js mongodb aggregation-framework


【解决方案1】:

我不知道你想要什么输出,但这有关于重复的信息,也许你也想在重复上添加展开。

结果文件

  • product_id
  • 订单(发现重复)
  • 重复(具有该订单重复的文档)

为您的数据打印

[{
  "duplicates": [
    "5a760191813a54000b8475f1",
    "5a75f6f17abe45000a3ba05e"
  ],
  "order": {
    "row": "3",
    "seat": "12"
  },
  "product_id": "5a7628bedbcc42000aa7f614"
}]

查询
(在你的驱动程序上运行它,MongoPlayground 不保持字段的顺序并且会显示错误的结果)

aggregate(
[{"$unwind" : {"path" : "$orders"}},
 {
  "$group" : {
    "_id" : {
      "orders" : "$orders",
      "product_id" : "$product_id"
    },
    "duplicates" : {
      "$push" : "$_id"
    }
  }
 },
 {"$match" : {"$expr" : {"$gt" : [ {"$size" : "$duplicates"}, 1 ]}}},
 {
  "$project" : {
    "_id" : 0,
    "order" : "$_id.orders",
    "product_id" : "$_id.product_id",
    "duplicates" : 1
  }
 } 
])

数据(我添加了更多数据)

[
  {
    "_id": "5a760191813a54000b8475f1",
    "orders": [
      {
        "row": "3",
        "seat": "11"
      },
      {
        "row": "3",
        "seat": "12"
      }
    ],
    "product_id": "5a7628bedbcc42000aa7f614"
  },
  {
    "_id": "5a75f6f17abe45000a3ba05g",
    "orders": [
      {
        "row": "3",
        "seat": "12"
      },
      {
        "row": "3",
        "seat": "13"
      }
    ],
    "product_id": "5a7628bedbcc42000aa7f614"
  },
  {
    "_id": "5a75f6f17abe45000a3ba05e",
    "orders": [
      {
        "row": "3",
        "seat": "12"
      },
      {
        "row": "3",
        "seat": "13"
      }
    ],
    "product_id": "5a7628bedbcc42000aa7f614"
  },
  {
    "_id": "5a75ebdf813a54000b8475e7",
    "orders": [
      {
        "row": "5",
        "seat": "16"
      },
      {
        "row": "5",
        "seat": "15"
      }
    ],
    "product_id": "5a75f711dbcc42000c459efc"
  }
]

结果

[{
  "duplicates": [
    "5a75f6f17abe45000a3ba05g",
    "5a75f6f17abe45000a3ba05e"
  ],
  "order": {
    "row": "3",
    "seat": "13"
  },
  "product_id": "5a7628bedbcc42000aa7f614"
},
{
  "duplicates": [
    "5a760191813a54000b8475f1",
    "5a75f6f17abe45000a3ba05g",
    "5a75f6f17abe45000a3ba05e"
  ],
  "order": {
    "row": "3",
    "seat": "12"
  },
  "product_id": "5a7628bedbcc42000aa7f614"
}]

【讨论】:

    【解决方案2】:

    您可以使用以下查询。 $unwind 订单数组,按订单行和产品 $group 并收集匹配的 id 和计数。保留 count 大于 1 的文档。$lookup 按 id 拉入匹配的文档,$replaceRoot 以展平文档。

    db.collection.aggregate([
      {
        "$unwind": "$orders"
      },
      {
        "$group": {
          "_id": {
            "order": "$orders",
            "product_id": "$product_id"
          },
          "count": {
            "$sum": 1
          },
          "doc_ids": {
            "$push": "$_id"
          }
        }
      },
      {
        "$match": {
          "count": {
            "$gt": 1
          }
        }
      },
      {
        "$lookup": {
          "from": "collection",
          "localField": "doc_ids",
          "foreignField": "_id",
          "as": "documents"
        }
      },
      {
        "$unwind": "$documents"
      },
      {
        "$replaceRoot": {
          "newRoot": "$documents"
        }
      }
    ])
    

    https://mongoplayground.net/p/YbztEGttUMx

    【讨论】:

      【解决方案3】:

      虽然这可以完全在 Mongo 中完成,但我不推荐它,因为它的内存效率非常非常低。在对它进行某些操作时,您基本上必须始终将整个集合保存在内存中。

      不过,我将展示此管道,因为我们将使用第二种更具可扩展性的方法。

      我们想基于ordersproduct_id$group,但是有两个问题阻碍了我们。

      1. orders 字段可能不会在所有文档中排序相同,因为 Mongo 不支持“嵌套”排序我们必须将$unwind 数组、$sort 并恢复原始结构。 (请注意,您正在对内存中的整个集合进行排序)。如果您可以确保在orders 数组中保持排序顺序,则可以跳过此管道的痛点之一。
      2. $grouping 对象数组时,Mongo 不一致。完全披露我不完全确定那里发生了什么,但我猜有一些“捷径”是为了提高效率而以某种方式影响稳定性。因此,我们的方法是将这些对象转换为字符串(将“行”和“座位”连接在一起)。
      db.collection.aggregate([
        {
          "$unwind": "$orders"
        },
        {
          $sort: {
            "orders.row": 1,
            "orders.seat": 1
          }
        },
        {
          $group: {
            _id: "$_id",
            tmpOrders: {
              $push: {
                $concat: [
                  "$orders.row",
                  "$orders.seat"
                ]
              }
            },
            product_id: {
              $first: "$product_id"
            }
          }
        },
        {
          $group: {
            _id: {
              orders: "$tmpOrders",
              product: "$product_id"
            },
            dupIds: {
              $push: "$_id"
            }
          }
        },
        {
          $match: {
            "dupIds.0": {
              $exists: true
            }
          }
        },
        {
          $project: {
            _id: 0,
            dups: "$dupIds",
            
          }
        }
      ])
      

      Mongo Playground

      现在正如我所说,这种方法不可扩展,并且在大型集合上运行需要很长时间。所以我建议利用索引并遍历product_id's 并分别执行每个管道。

      // wraps the native Promise, not required.
      import Bluebird = require('bluebird');
      
      // very fast with index.
      const productIds = await collection.distinct('product_id')
      
      await Bluebird.map(productIds, async (productId) => {
          const dups = await collection.aggregate([
              {
                  $match: {
                      product_id: productId
                  }
              }
               ... same pipeline ...
          ])
          
          if (dups.length) {
              // logic required.
          }
          // can control concurrency based on db workload.
      }, { concurrency: 5})
      

      确保使用这种方法,您有一个基于product_id 构建的索引,以便它能够有效地工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-02-27
        • 2015-01-10
        • 2019-11-30
        • 2016-05-12
        • 2018-12-24
        • 2018-03-28
        • 2021-09-23
        相关资源
        最近更新 更多