查询统一匹配文档与场的交集

时间:2018-06-14 20:47:10

标签: mongodb mongodb-query aggregation-framework

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

  1. 分享特定字段的值。
  2. 有一个等于值的字段。
  3. 在满足1& 1的所有文件之间如图2所示,它们还共同匹配输入的1到n值的数组的所有字段。即,必须考虑每个值。
  4. 例如,鉴于以下集合,我想匹配以下所有文件:

    1. 拥有相同的channelId
    2. dayOfWeek等于星期一
    3. 在满足1& 1的所有文件之间2,它们也应该集体匹配产品ID [1,2]。也就是说,如果我的channelIddayOfWeek匹配,则它还必须包含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"
      }
      
    4. 在这种情况下,期望的回报是:

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

1 个答案:

答案 0 :(得分:1)

基本上,在对dayOfWeek的常见条件进行过滤后,需要通过公共密钥$group将所有内容放在一起,然后查看分组是否确实“配对”并实际包含两个结果想要“相交”:

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
}

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

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"
  }}
])

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

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

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,那么它们实际上不能使用这样的方法,除了通过游标将匹配的文档加载到初始查询过滤器并检查之外别无选择。 / p>

因此,对于“完整性”,您可以考虑在这种情况下,$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动作“卷起”在"MONDAY"内{3}}本身。通过这种方式,您可以通过$lookup操作获得相同的文档,但已经以不会破坏$push的方式“分离”到自己的文档中。

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

可以在那里进行的唯一其他比较是使用16MB BSON limit使用$match。这将再次“卷起”到实际的$in操作中,以有效地仅返回那些也匹配该条件的文档。然而,结果基本上是“否定”,因为使用$lookup初始查询可以实现相同的结果,当然也就是说 “only” 那些包含"2"SP的文档,而不包含任何其他关于这些值的文档,而不是“子集的一部分”。