【问题标题】:mongo $slice query reverse index out of rangemongo $slice 查询反向索引超出范围
【发布时间】:2017-07-01 13:17:59
【问题描述】:

以下查询在 mongo 中的行为很奇怪:

db.items.findOne({},{ "List": { "$slice": [ skip, 3 ] }})

首先: 它不是只返回一个带有 ["_id","List"] 键的对象,而是返回一个完整的对象。

第二: 如果skip 为负且|skip| 高于list.length,则返回前三个元素,就像skip==0 一样

我希望:

{
       "_id" : ObjectId("542babf265f5de9a0d5c2928"),
       "List" : [
                1,
                2,
                3,
                4,
                5
        ]
        "other" : "not_important"
}

查询:

db.items.findOne({},{ "List": { "$slice": [-10, 3 ] }})

得到:

{
       "_id" : ObjectId("542babf265f5de9a0d5c2928"),
       "List" : []
}

相反,我得到:

{
       "_id" : ObjectId("542babf265f5de9a0d5c2928"),
       "List" : [
                1,
                2,
                3
        ]
        "other" : "not_important"
}

为什么?

我使用 mongoDB 2.4.10

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    第二个:如果skip 是负数并且|skip|高于 list.length 则返回前三个元素,就好像 skip==0

    是的。这就是javascriptArray.prototype.slice()方法的工作原理,mongodb内部使用的。

    根据ECMAScript® Language Specification

    如果 relativeStart 为负数,则设 k 为 max((len + relativeStart),0); 否则设 k 为 min(relativeStart, len)。

    在你的情况下relativeStart is -10k = max((-10+5),0), k = 0;(其中,5 是数组的长度)。

    因此,在这些情况下,kskip 将始终为 0

    第一:它返回一个完整的对象,而不是只返回一个带有 ["_id","List"] 键的对象。

    是的,投影运算符就是这样工作的。除非在投影参数中明确指定 inclusionexclusion,否则将使用投影运算符(例如 $slice$elemmatch)检索整个文档。

    db.items.findOne({},{"_id":1,"List": { "$slice": [-10, 3 ] }})
    

    会返回:

    { "_id" : ObjectId("542babf265f5de9a0d5c2928"), "List" : [ 1, 2, 3 ] }
    

    findOne() 方法的第二个参数是not only for simple projection 用途,字段投影,仅当field 名称中的任何一个具有0 或@987654339 的值@反对他们。如果不是,则返回整个文档。如果任何字段有 projection operator 要应用,则应为 appliedprojected

    每当涉及$slice 运算符时,投影机制似乎会以下列方式发生。

    • 默认情况下,所有字段都将包含在投影中。
    • 默认情况下,始终显示其值基于投影运算符 $sliceif truthy 派生的所有字段,与以下内容无关。

    排除或包含的步骤。

    • projection 参数中指定的字段列表按指定顺序累积。
    • 仅适用于遇到值为“0”或“1”的第一个字段: 如果 字段的值为 '0' - 然后它被排除在外,所有剩余的 字段被标记为包含。 如果一个字段有 '1' - 那么它被包括在内,并且所有剩余的字段 被标记为被排除在外。
    • 对于所有后续字段,它们被排除或包含在基于 他们的价值观。

    【讨论】:

    • 嗯,我应该猜到是js原型实现了……至于投影,我在文档的任何地方都看不到。至少对我来说,除非您将其拆分为 2 个不同的声明,否则这听起来并不合理……
    • @skme,是的,文档中的任何地方都没有指定投影参数的性质,只是在一些测试用例之后我才能够弄清楚。包含投影运算符使其有点复杂。
    • @BatScream,你的回答很有帮助,基本上我同意关于投影的推理过程。但是,实际上$elemMatch 的行为与$slice 不同。你可以试试db.items.findOne({},{"List": { "$elemMatch": {$gte:1} }})db.items.findOne({},{"_id":1,"List": { "$elemMatch": {$gte:1} }})db.items.findOne({},{"_id":0,"List": { "$elemMatch": {$gte:1} }})
    • @Wizard,我不确定你的意思。 $elemMatch 运算符的行为和用法与 $slice 完全不同。它不用于分页,而是用于查询数组元素。您编写的查询将适用于排序数字数组,但这不是问题。当您想通过反向索引检索数组时,问题是一般分页。
    • @skme,我给 BatScream 写了评论。可以发现答案中有这样一句话:the whole document is retrieved with the projection operators such as $slice,$elemmatch being applied. 但是这两个算子在投影上的行为不同。
    【解决方案2】:

    虽然 $slice 运算符的这种行为是设计使然,但从 MongoDB 3.2 开始,可以评估此​​行为并使用 $slice 的聚合运算符更改结果:

    给出示例文档:

    { "_id" : ObjectId("5922846dbcf60428d0f69f6e"), "a" : [ 1, 2, 3, 4 ] }
    { "_id" : ObjectId("5922847cbcf60428d0f69f6f"), "a" : [ 5, 6 ] }
    

    如果给定条件表达式以使用$size 测试数组的长度,并且仅在反向索引大于或等于该长度时执行$slice,否则返回空数组:

    db.collection.aggregate([
      { "$project": {
        "a": {
          "$cond": {
            "if": { "$gte": [ { "$size": "$a" }, 4 ] },
            "then": { "$slice": [ "$a", -4, 2 ] },
            "else": { "$literal": [] },
          }
        }
      }}
    ])
    

    那么你当然会得到:

    { "_id" : ObjectId("5922846dbcf60428d0f69f6e"), "a" : [ 1, 2 ] }
    { "_id" : ObjectId("5922847cbcf60428d0f69f6f"), "a" : [ ] }
    

    这就是如何让 MongoDB 返回以这种方式运行的“切片”。

    【讨论】:

    • 很好,我很高兴在运行切片之前不需要进行"a.4":{$exists:true} 检查。两个问题,1. 性能方面,当我有数百万个 ID 数组时,$size 运算符会变慢吗? 2.你知道为什么javascript/mongo是这样实现的吗?
    • @Will 在性能问题上,聚合框架应该或多或少与标准投影中相同的$slice 操作相当。至于“为什么”,这是另一个完全广泛的问题。请放心,这是各种语言实现中的惯例,并且每个语言都可以借鉴其他语言。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-25
    • 2018-03-01
    • 1970-01-01
    • 2015-04-12
    • 1970-01-01
    相关资源
    最近更新 更多