【问题标题】:MongoDB projections and fields subsetMongoDB 投影和字段子集
【发布时间】:2017-10-12 23:44:21
【问题描述】:

我想使用 mongo 投影来向我的应用程序返回更少的数据。我想知道这是否可能。

例子:

user: {
  id: 123,
  some_list: [{x:1, y:2}, {x:3, y:4}],
  other_list: [{x:5, y:2}, {x:3, y:4}]
}

如果查询user_id = 123 和一些“投影过滤器”,例如user.some_list.x = 1user.other_list.x = 1,是否有可能达到给定的结果?

user: {
  id: 123,
  some_list: [{x:1, y:2}],
  other_list: []
}

我们的想法是让 mongo 工作得更多一些,并为应用程序检索更少的数据。在某些情况下,我们会在应用程序端丢弃 80% 的集合元素。所以,最好不要再回来了。

问题:

  1. 有可能吗?
  2. 我怎样才能做到这一点。 $elemMatch 似乎对我没有帮助。我正在尝试放松,但没有达到目标
  3. 如果可能的话,这种投影过滤是否可以从user.some_list.x 上的索引中受益?或者一旦用户已经通过它的 id 找到了,就根本没有?

谢谢。

【问题讨论】:

  • "$elemMatch 似乎对我没有帮助" 那么您实际尝试了什么?似乎db.user.find({},{ "some_list": { "$elemMatch": { "x": 1 } }, "other_list": { "$elemMatch": { "x": 1 } } }) 完全符合您的要求。请注意,有 两个 不同的 $elemMatch 运算符,其中链接的一个是“投影”运算符。你可以用.aggregate() 做更多花哨的事情,但是一个简单的“奇异”匹配是由那个非常简单的案例来处理的。
  • 我在文档中找到了这一点:“$elemMatch 运算符将查询结果中的 字段的内容限制为仅包含与 $elemMatch 条件匹配的第一个元素。”。在我的示例中,有一个元素与过滤器 user.some_list.x = 1 匹配,但在我的实际情况中,数组中可以存在多个匹配过滤条件的元素。

标签: mongodb indexing mongodb-query filtering projection


【解决方案1】:

您可以在 MongoDB v3.0 中执行以下操作:

db.collection.aggregate({
    $match: {
        "user.id": 123
    }
}, {
    $redact: {
        $cond: {
             if: {
                 $or: [ // those are the conditions for when to include a (sub-)document
                     "$user", // if it contains a "user" field (as is the case when we're on the top level
                     "$some_list", // if it contains a "some_list" field (would be the case for the "user" sub-document)
                     "$other_list", // the same here for the "other_list" field
                     { $eq: [ "$x", 1 ] } // and lastly, when we're looking at the innermost sub-documents, we only want to include items where "x" is equal to 1
                 ] 
             },
             then: "$$DESCEND", // descend into sub-document
             else: "$$PRUNE" // drop sub-document
        }
    }
})

根据您的数据设置,您还可以做些什么来简化此查询:包括没有“x”字段的所有内容,或者如果存在则它需要等于 1,如下所示:

$redact: {
    $cond: {
         if: {
             $eq: [ { "$ifNull": [ "$x", 1 ] }, 1 ] // we only want to include items where "x" is equal to 1 or where "x" does not exist
         },
         then: "$$DESCEND", // descend into sub-document
         else: "$$PRUNE" // drop sub-document
    }
}

您建议的索引对$redact 阶段没有任何作用。但是,如果您在开始时更改 $match 阶段以摆脱所有不匹配的文档,您可以从中受益,如下所示:

$match: {
    "user.id": 123,
    "user.some_list.x": 1 // this will use your index
}

【讨论】:

  • 太棒了!这看起来正是我正在寻找的。我正在阅读文档以尝试了解正在发生的事情,大声笑。一种“奇怪”的行为是我有其他收藏(another_collection),看起来它也被过滤了,甚至没有在编辑中列出。你能想象为什么吗?但这看起来不是问题,因为我将过滤文档中的所有 4 个集合,并且在这个玩具示例中只使用了两个。
  • 我认为您的真实数据与您在此处发布的数据略有不同。而且我建议的解决方案有些不稳定,因为在不同级别的子文档上存在具有相同名称的字段可能会导致一些意外行为。所以我想,你需要对你的真实数据模型进行一些试验才能达到你的目标(或者升级 MongoDB 并使用 $filter 这无论如何都是个好主意)。
  • 我所有的内部对象都有一个将在比较中使用的日期字段。听说如果它不存在可能会导致错误,但事实并非如此。我使用所有 4 个真实的集合进行了一些测试,并且工作起来非常有魅力。再次感谢你。但我现在面临的挑战是使用 groovy 驱动程序语言编写它。我正在与BasicDBObject 之类的东西斗争。
  • 看起来我对redact 产生了一些“副作用”。 some_listother_list 都有内部集合。在我的真实示例中,有 4 个集合,其中 1 个,内部集合返回大小为 0(以某种方式过滤)。为什么会这样?我想过滤user.some_list.x == 1,如果为真则返回,但user.some_list.inner_list 应该保持不变。任何线索为什么会发生?这些内部列表与外部对象(属性等)无关。他们没有x 属性,也不应该拥有。它们不是过滤器的主题。
【解决方案2】:

很有可能。

对于findOne,查询是第一个参数,投影是第二个参数。在 Node/Javascript 中(类似于 bash):

db.collections('users').findOne( { 
    id = 123 
}, {
    other_list: 0
} )

将返回不带other_list 字段的对象。或者您可以指定{ some_list: 1 } 作为投影,返回的只有_id 和some_list

【讨论】:

  • 感谢您的回答。但是我事先并不知道我想摆脱 other_list 的所有元素。我想摆脱所有不满足给定条件的元素。在示例中,other_list 将剩下 0,some_list 将剩下 1。我需要检索整个user 文档,但过滤嵌套集合some_listother_list
【解决方案3】:

$filter 是你的朋友。下面产生您寻求的输出。尝试更改 $eq 字段和目标值,以查看数组中或多或少的项目被拾取。请注意我们如何 $project 将新字段(some_list 和 other_list)“置于”旧字段之上,实质上是用过滤后的版本替换它们。

db.foo.aggregate([
{$match: {"user.id": 123}}
,{$project: { "user.some_list": { $filter: {
            input: "$user.some_list",
            as: "z",
            cond: {$eq: [ "$$z.x", 1 ]}
        }},
          "user.other_list": { $filter: {
    input: "$user.other_list",
            as: "z",
            cond: {$eq: [ "$$z.x", 1 ]}
              }}
}}
                ]);

【讨论】:

  • 在出现invalid operator '$filter' 错误后,我在文档中找到了这个:“3.2 版中的新功能。”我正在使用 mongo 3.0。我不好不告诉它。我会尝试看看我可以使用什么,但如果可以的话,请提供另一个查询。谢谢。
猜你喜欢
  • 2015-12-19
  • 2014-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多