【问题标题】:Query to Collectively Match Intersection of Documents against a Field查询以将文档的交叉点与字段集中匹配
【发布时间】:2018-11-24 17:11:54
【问题描述】:

在 MongoDB 中,我如何返回所有文档:

  1. 共享特定字段的值。
  2. 有一个等于一个值的字段。
  3. 在满足 1 和 2 的所有文档之间,它们还共同匹配输入数组的所有字段,该数组包含 1 到 n 个值。即,必须考虑每个值。

例如,给定以下集合,我想匹配所有文档:

  1. 有相同的channelId
  2. dayOfWeek 等于星期一
  3. 在所有满足 1 和 2 的文档之间,它们还应该共同匹配产品 ID [1, 2]。即,如果我有一个与dayOfWeek 匹配的channelId,它还必须有一个具有productId 为1 的文档和另一个具有productId 为2 的文档。

    {
        channelId: "ID-A",
        dayOfWeek: "MONDAY",
        productId: "1"
    }, 
    {
        channelId: "ID-A",
        dayOfWeek: "MONDAY",
        productId: "2"
    },
    {
        channelId: "ID-B",
        dayOfWeek: "MONDAY",
        productId: "1"
    },
    {
        channelId: "ID-B",
        dayOfWeek: "MONDAY",
        productId: "3"
    },
    {
        channelId: "ID-C",
        dayOfWeek: "MONDAY",
        productId: "1"
    },
    {
        channelId: "ID-C",
        dayOfWeek: "TUESDAY",
        productId: "2"
    }
    

在这种情况下,期望的回报是:

    {
        channelId: "ID-A",
        dayOfWeek: "MONDAY",
        productId: "1"
    }, 
    {
        channelId: "ID-A",
        dayOfWeek: "MONDAY",
        productId: "2"
    }
  • ID-B 被移除,因为它没有等于 2 的productId
  • ID-C 已被删除,因为虽然两个 productId 都匹配,但在匹配的项目之间它们没有等于星期一的 dayOfWeek

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    基本上,您需要在过滤dayOfWeek 的通用条件后,通过通用键将所有内容组合在一起,然后查看分组是否确实“配对”并且实际上包含您想要“相交”的两个结果":

    db.collection.aggregate([
      { "$match": { "dayOfWeek": "MONDAY" } },
      { "$group": {
        "_id": "$channelId",
        "docs": { "$push": "$$ROOT" },
        "count": { "$sum": 1 }
      }},
      { "$match": {
        "count": { "$gt": 1 },
        "docs": {
          "$all": [
            { "$elemMatch": { "productId": "1" } },
            { "$elemMatch": { "productId": "2" } }
          ]
        }
      }}
    ])
    

    一旦将$all 与其中的$elemMatch 条件“组合”在一起,就可以确保现在数组内的“组合”文档中满足“两个”条件。另请注意,如果您实际上是指“只有两个”,那么您可以查找"count": 2 而不是简单的"count": { "$gt": 1 },这里的示例意味着“分组”至少与“某物”配对,如果不完全是“两个”。

    这基本上返回一个结果,每个分组的匹配文档为:

    {
            "_id" : "ID-A",
            "docs" : [
                    {
                            "_id" : ObjectId("5b22f455fe0315289f716483"),
                            "channelId" : "ID-A",
                            "dayOfWeek" : "MONDAY",
                            "productId" : "1"
                    },
                    {
                            "_id" : ObjectId("5b22f455fe0315289f716484"),
                            "channelId" : "ID-A",
                            "dayOfWeek" : "MONDAY",
                            "productId" : "2"
                    }
            ],
            "count" : 2
    }
    

    如果您需要“仅文档”作为结果,那么您可以在 $unwind$replaceRoot 之后进一步处理,前提是您的支持 MongoDB 版本超过 3.4:

    db.collection.aggregate([
      { "$match": { "dayOfWeek": "MONDAY" } },
      { "$group": {
        "_id": "$channelId",
        "docs": { "$push": "$$ROOT" },
        "count": { "$sum": 1 }
      }},
      { "$match": {
        "count": { "$gt": 1 },
        "docs": {
          "$all": [
            { "$elemMatch": { "productId": "1" } },
            { "$elemMatch": { "productId": "2" } }
          ]
        }
      }},
      { "$unwind": "$docs" },
      { "$replaceRoot": { "newRoot": "$docs" } }
    ])
    

    或使用$project 并明确命名您不使用的所有字段:

    db.collection.aggregate([
      { "$match": { "dayOfWeek": "MONDAY" } },
      { "$group": {
        "_id": "$channelId",
        "docs": { 
          "$push": {
            "_id": "$_id",
            "channelId": "$channelId",
            "dayOfWeek": "$dayOfWeek",
            "productId": "$productId
          }
        },
        "count": { "$sum": 1 }
      }},
      { "$match": {
        "count": { "$gt": 1 },
        "docs": {
          "$all": [
            { "$elemMatch": { "productId": "1" } },
            { "$elemMatch": { "productId": "2" } }
          ]
        }
      }},
      { "$unwind": "$docs" },
      { "$project": {
        "_id": "$docs._id",
        "channelId": "$docs.channelId",
        "dayOfWeek": "$docs.dayOfWeek",
        "productId": "$docs.productId"
      }}
    ])
    

    实际上,在最后一种形式中,该语句基本上与 MongoDB 的每个版本兼容,因为聚合框架与版本 2.2 一起发布。

    或者,您实际上可以“使用”$setIsSubset 运算符,只要您拥有带有 $expr 的 MongoDB 3.6 或更高版本:

    db.collection.aggregate([
      { "$match": { "dayOfWeek": "MONDAY" } },
      { "$group": {
        "_id": "$channelId",
        "docs": { "$push": "$$ROOT" },
        "count": { "$sum": 1 }
      }},
      { "$match": {
        "count": { "$gt": 0 },
        "$expr": {
          "$setIsSubset": [ [ "1", "2" ], "$docs.productId" ]
        }
      }}
    ])
    

    您甚至可以使用 $redact 或使用 $project 后跟另一个 $match 来改变它,但这并不是重点,好像您可能认为“集合运算符”实际上并不是最适合特定结果你在这里找。

    请注意,任何类型的“交集”或“子集”基本上都依赖于能够相互比较文档。这本质上意味着将“分组”的东西放入一个数组中进行比较。如果实际结果大小导致对exceed the BSON limit 进行这样的“分组”,那么您实际上无法使用这样的方法,并且除了通过游标将匹配的文档加载到初始查询过滤器并检查之外别无选择。

    因此,对于“完整性”,您可以考虑在 $lookup 可用于使用“自引用连接”而不是使用 $push 来累积匹配文档的情况下:

    db.collection.aggregate([
      { "$match": { "dayOfWeek": "MONDAY" } },
      { "$group": {
        "_id": "$channelId",
        "count": { "$sum": 1 }
      }},
      { "$match": { "count": { "$gt": 1 } } },  // keep only "multiple" groups
      { "$lookup": { 
        "from": "collection",
        "localField": "_id",
        "foreignField": "channelId",
        "as": "docs"
      }},
      { "$unwind": "$docs" },
      // ** See note below about the $match **
      //{ "$match": { "docs.productId": { "$in": [ "1", "2" ] } } },
    ])
    

    这里的优点是"docs" 的“数组”实际上从来没有按照$lookup + $unwind Coalescence 的特殊处理来构造,它基本上将unwinding 操作“汇总”到$lookup 本身内。这样,您将获得相同的文档,这些文档将通过$push 操作累积,但已经以不破坏16MB BSON limit 的方式“分离”到自己的文档中。

    但限制仍然存在,即在这种形式下实际上无法比较“集合”,因为您需要“数组”才能查看“分组”项目是否在“集合”内。所以实际上是为了避免违反限制而避免的“分组”动作。尽管如此,这通常比仅将匹配文档的光标迭代到 "MONDAY" 更好,因为您已经通过 "channelId" 指示了“分组”结果。

    唯一可以进行的其他比较是使用$in 使用额外的$match。这将再次被“汇总”到实际的$lookup 操作中,以有效地仅返回那些也符合该条件的文档。然而,结果本质上是“否定”的,因为它实际上与使用 $in 和初始查询获得的结果相同,当然意味着“仅”那些文档包含 "1""2" 而不是任何其他值,因为这些值是“子集的一部分”。

    【讨论】:

    • 很好的答案!谢谢!
    猜你喜欢
    • 2015-12-03
    • 1970-01-01
    • 1970-01-01
    • 2014-08-24
    • 1970-01-01
    • 2021-11-18
    • 2020-06-01
    • 2018-02-12
    • 1970-01-01
    相关资源
    最近更新 更多