当前位置: 首页 > 知识库问答 >
问题:

仅返回嵌套数组中匹配的子文档元素

沃瑾瑜
2023-03-14

主要集合是零售商,其中包含用于商店的数组。每个商店都包含一系列优惠(您可以在此商店购买)。这提供了具有数组大小的数组。(见下面的例子)

现在我试图找到所有的优惠,这是在大小L

{
    "_id" : ObjectId("56f277b1279871c20b8b4567"),
    "stores" : [
        {
        "_id" : ObjectId("56f277b5279871c20b8b4783"),
        "offers" : [
            {
                "_id" : ObjectId("56f277b1279871c20b8b4567"),
                "size": [
                    "XS",
                    "S",
                    "M"
                ]
            },
            {
                "_id" : ObjectId("56f277b1279871c20b8b4567"),
                "size": [
                    "S",
                    "L",
                    "XL"
                ]
            }
        ]
    }
}

我尝试了这个查询:db.get集合('零售商'). fint({'stores.offers.size':'L'})

我期待这样的输出:

 {
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
    {
        "_id" : ObjectId("56f277b5279871c20b8b4783"),
        "offers" : [
            {
                "_id" : ObjectId("56f277b1279871c20b8b4567"),
                "size": [
                    "S",
                    "L",
                    "XL"
                ]
            }
        ]
    }
}

但是,我的查询的输出还包含与sizeXS、X和M不匹配的offer。

如何强制MongoDB只返回符合我查询的报价?

问候和感谢。

共有3个答案

马朝斑
2023-03-14

它也可以在没有骨料的情况下工作。以下是解决方案链接:https://mongoplayground.net/p/Q5lxPvGK03A

db.collection.find({
  "stores.offers.size": "L"
},
{
  "stores": {
    "$filter": {
      "input": {
        "$map": {
          "input": "$stores",
          "as": "store",
          "in": {
            "_id": "$$store._id",
            "offers": {
              "$filter": {
                "input": "$$store.offers",
                "as": "offer",
                "cond": {
                  "$setIsSubset": [
                    [
                      "L"
                    ],
                    "$$offer.size"
                  ]
                }
              }
            }
          }
        }
      },
      "as": "store",
      "cond": {
        "$ne": [
          "$$store.offers",
          []
        ]
      }
    }
  }
})
焦同
2023-03-14

由于您的阵列已嵌入,我们无法使用$elemMatch,相反,您可以使用聚合框架获得结果:

db.retailers.aggregate([
{$match:{"stores.offers.size": 'L'}}, //just precondition can be skipped
{$unwind:"$stores"},
{$unwind:"$stores.offers"},
{$match:{"stores.offers.size": 'L'}},
{$group:{
    _id:{id:"$_id", "storesId":"$stores._id"},
    "offers":{$push:"$stores.offers"}
}},
{$group:{
    _id:"$_id.id",
    stores:{$push:{_id:"$_id.storesId","offers":"$offers"}}
}}
]).pretty()

此查询的作用是将数组展开(两次),然后匹配大小,然后将文档重塑为以前的形式。您可以删除$group步骤并查看它如何打印。玩得开心点!

那存
2023-03-14

因此,您的查询实际上选择了“文档”,就像它应该选择的那样。但是您要寻找的是“过滤包含的数组”,以便返回的元素只匹配查询的条件。

当然,真正的答案是,除非你真的通过过滤掉这些细节来节省大量带宽,否则你甚至不应该尝试,或者至少在第一次位置匹配之后。

MongoDB有一个位置$操作符,它将从查询条件返回匹配索引处的数组元素。但是,这只返回最外层数组元素的“第一个”匹配索引。

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
)

在这种情况下,它意味着“仅存储”数组位置。因此,如果有多个“存储”条目,则只会返回包含匹配条件的元素中的“一个”。但是,这对“offers”的内部数组没有任何作用,因此匹配的“stores”数组中的每个“offer”仍将返回。

MongoDB没有办法在标准查询中“过滤”这个,所以以下方法不起作用:

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$.offers.$': 1 }
)

MongoDB实际执行这种级别操作的唯一工具是聚合框架。但是分析应该告诉你为什么你“可能”不应该这样做,而只是在代码中过滤数组。

按照每个版本如何实现这一点的顺序排列。

首先使用MongoDB 3.2.x,使用$filter操作:

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$stores",
            "as": "store",
            "in": {
              "_id": "$$store._id",
              "offers": {
                "$filter": {
                  "input": "$$store.offers",
                  "as": "offer",
                  "cond": {
                    "$setIsSubset":  [ ["L"], "$$offer.size" ]
                  }
                }
              }
            }
          }
        },
        "as": "store",
        "cond": { "$ne": [ "$$store.offers", [] ]}
      }
    }
  }}
])

然后使用MongoDB 2.6.x及更高版本以及$map$setDifference

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$setDifference": [
        { "$map": {
          "input": {
            "$map": {
              "input": "$stores",
              "as": "store",
              "in": {
                "_id": "$$store._id",
                "offers": {
                  "$setDifference": [
                    { "$map": {
                      "input": "$$store.offers",
                      "as": "offer",
                      "in": {
                        "$cond": {
                          "if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
                          "then": "$$offer",
                          "else": false
                        }
                      }
                    }},
                    [false]
                  ]
                }
              }
            }
          },
          "as": "store",
          "in": {
            "$cond": {
              "if": { "$ne": [ "$$store.offers", [] ] },
              "then": "$$store",
              "else": false
            }
          }
        }},
        [false]
      ]
    }
  }}
])

最后,在MongoDB 2.2.x之上引入聚合框架的任何版本中。

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$unwind": "$stores" },
  { "$unwind": "$stores.offers" },
  { "$match": { "stores.offers.size": "L" } },
  { "$group": {
    "_id": {
      "_id": "$_id",
      "storeId": "$stores._id",
    },
    "offers": { "$push": "$stores.offers" }
  }},
  { "$group": {
    "_id": "$_id._id",
    "stores": {
      "$push": {
        "_id": "$_id.storeId",
        "offers": "$offers"
      }
    }
  }}
])

让我们把解释分解一下。

所以一般来说,$filter是这里的方法,因为它是根据目的设计的。由于数组有多个级别,您需要在每个级别上应用这个。因此,首先,您将深入到商店中的每个提供来检查和$filter该内容。

这里的简单比较是“大小”数组是否包含我要查找的元素”。在此逻辑上下文中,简单的操作是使用$setIsSubset操作将[“L”]的数组(“集合”)与目标数组进行比较。如果该条件为true(它包含“L”),则“offers”的数组元素将保留并在结果中返回。

在更高级别的$filter中,您将查看前一个$filter的结果是否为“offers”返回空数组[]。如果该元素不是空的,则返回该元素,否则将删除该元素。

这与现代过程非常相似,只是因为这个版本中没有$filter,所以您可以使用$map检查每个元素,然后使用$set差异过滤掉返回的任何元素asfalse.

因此$map将返回整个数组,但是$cond操作只是决定是返回元素还是返回一个false值。在将$setDifference[false]的单个元素“set”进行比较时,将删除返回数组中的所有false元素。

在所有其他方面,逻辑与上述相同。

所以在MongoDB 2.6下面,处理数组的唯一工具是$unWind,仅仅为了这个目的,你不应该为此目的使用聚合框架。

这个过程看起来确实很简单,只需简单地“拆开”每个数组,过滤掉你不需要的东西,然后把它放回原处。主要关心的是“两个”$group阶段,“第一个”重建内部数组,下一个重建外部数组。所有级别都有不同的_id值,因此只需要在每个级别的分组中都包含这些值。

但问题是,$unwind的成本非常高。虽然它仍然有它的用途,但它的主要用途不是对每个文档进行这种过滤。事实上,在现代版本中,它的唯一用途应该是当数组的一个元素需要成为“分组键”本身的一部分时。

因此,在这样一个数组的多个级别上获取匹配不是一个简单的过程,事实上,如果实现不正确,那么代价会非常高昂。

只有这两个现代清单才可以用于此目的,因为除了“查询”$match之外,它们还使用了一个“单一”管道阶段来进行“过滤”。产生的效果比标准形式的.find()稍微多一些开销。

但是,通常情况下,这些列表仍然有一定的复杂性,除非您真的大幅减少此类过滤返回的内容,从而显著提高服务器和客户端之间使用的带宽,这样,您就可以更好地过滤初始查询和基本投影的结果。

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
).forEach(function(doc) {
    // Technically this is only "one" store. So omit the projection
    // if you wanted more than "one" match
    doc.stores = doc.stores.filter(function(store) {
        store.offers = store.offers.filter(function(offer) {
            return offer.size.indexOf("L") != -1;
        });
        return store.offers.length != 0;
    });
    printjson(doc);
})

因此,使用返回的对象“发布”查询处理远没有使用聚合管道来做这件事那么迟钝。如上所述,唯一的“真正”区别是,您丢弃了“服务器”上的其他元素,而不是在收到“每个文档”时删除它们,这可能会节省一点带宽。

但是,除非您在仅使用$match$project的现代版本中执行此操作,否则服务器上处理的“成本”将大大超过通过首先剥离不匹配元素来减少网络开销的“收益”。

在所有情况下,你得到相同的结果:

{
        "_id" : ObjectId("56f277b1279871c20b8b4567"),
        "stores" : [
                {
                        "_id" : ObjectId("56f277b5279871c20b8b4783"),
                        "offers" : [
                                {
                                        "_id" : ObjectId("56f277b1279871c20b8b4567"),
                                        "size" : [
                                                "S",
                                                "L",
                                                "XL"
                                        ]
                                }
                        ]
                }
        ]
}
 类似资料:
  • 我有一个如下的猫鼬模式: 我已经获取了所有具有给定id的特定用户发表评论的帖子,即所有那些评论数组中包含uid等于给定uid的对象的文档。例如,假设用户id为101,则查询结果可能如下所示: 为了更好地理解一切,我想获取所有那些特定用户评论过的帖子,甚至回复过的帖子,以及只有该用户的评论和回复。 我所能做的就是: 此查询仅返回相应帖子的第一个匹配的评论项数组。我想要所有符合条件的物品。 请帮助我为

  • 我是Elasticsearch的新手,我提出了一个问题,Elasticsearch嵌套查询是否只能为嵌套字段返回匹配的嵌套文档。 对于示例,我有一个名为的类型,其中嵌套字段名为 和嵌套查询 我需要的是搜索有提到足球的评论的博客文章,每个博客文章的评论数与足球相匹配(在例子中它数为1,因为另一个评论刚刚提到篮球)。 然而,Elasticsearch似乎总是返回完整的文档,所以我如何才能实现它,或者我

  • 将mongodb与pymongo一起使用,我有以下文档: 我想更新示例子文档(这是一个数组元素,因为可能有多个示例)。我有以下代码,但它不工作... 谁能告诉我这个有什么问题吗?

  • 我想要的是返回整个cidr数组。 我使用的是Mongo3.2和pymongo(Python3.7)

  • 我正在查看MongoDB在和投影上的留档。我试图弄清楚如何只返回投影数组字段的子集,但我似乎无法弄清楚。 相关帖子: > 我没有试图从mongob聚合框架中执行$片-获取嵌套数组的第一个文档字段。 我也没有尝试从mongo projection中的仅返回数组值展平子文档,因为我仍然需要顶部文档中的一些字段。 假设我在集合中有以下文档: 我想要执行的查询是: 我希望它只返回数组中为的子文档下的。例如

  • 问题内容: 我有以下查询: 这将同时返回“匹配”对象(整个文档)和“ inner_hits”对象(嵌套在匹配内部)。 有没有办法让我只返回出现在“ inner_hits”结果中的匹配“查询”元素,而没有获取整个文档? 问题答案: 应该可以通过以下方式 在顶层 禁用source- field 来实现