代码之家  ›  专栏  ›  技术社区  ›  Always Learning

查询以将文档交叉点与字段一起匹配

  •  2
  • Always Learning  · 技术社区  · 8 年前

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

    1. 共享特定字段的值。
    2. 有一个等于值的字段。
    3. 在所有满足1&2的文档之间,它们还共同匹配1到n值的输入数组的所有字段。也就是说,必须对每个价值进行核算。

    例如,在下面的集合中,我希望匹配以下所有文档:

    1. 有同样的 channelId
    2. dayOfWeek 等于星期一
    3. 在所有满足1&2要求的文档之间,它们还应共同匹配产品ID[1,2]。也就是说,如果我有 通道ID 那是匹配的 星期几 ,它还必须有一个 productId 和另一个文档 产品ID 第页,共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,因为它没有 产品ID 等于2。
    • 删除ID-C,因为尽管两个productID都匹配,但在匹配的项之间,它们没有 星期几 等于星期一。
    1 回复  |  直到 8 年前
        1
  •  1
  •   Neil Lunn    8 年前

    基本上你需要 $group 所有的东西都由共同的关键过滤后的共同条件 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 如果您有超过3.4的支持MongoDB版本:

    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 只要您有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 或者使用 $项目 接着是另一个 $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 行动要在 $查找 本身。这样,您就可以通过 推送美元 操作,但已“分离”成自己的文档,且不会破坏 16MB BSON limit .

    但是,在这种形式下,“集合”实际上无法进行比较,因为您需要“数组”来查看“分组”项是否在“集合”中。所以实际上是为了避免极限破坏而避免的“分组”行为。尽管如此,这通常比简单地将匹配文档的光标迭代到 "MONDAY" 单独使用,因为您已经有“分组”结果的指示 "channelId" .

    唯一可以做的其他比较是使用附加的 $匹配 使用 $in .这将再次“卷起”到实际 $查找 操作只有效地返回那些也符合该条件的文档。然而,结果本质上是“否定的”,因为它实际上是可以用 美元 对于初始查询,当然意味着 “仅限” 那些文件包含 "1" "2" 而不是其他任何关于这些值的“子集的一部分”。

    推荐文章