为什么此RegExp查询返回所有结果?

时间:2017-08-16 01:04:37

标签: javascript regex mongodb mongodb-query

我有一台MongoDB 3.2服务器。我的收藏包含以下文件:

{
    "name": "string",
    "explicitMods": [
        "+48 to Blah",
        "-13% to Blah",
        "12 to 18 to Blah"
    ]
}

如果我写这个:

  

myCollection.find({" explicitMods":/ bad string /})

我得到零结果,正如所料。

但是,如果我这样写:

  

myCollection.find({" explicitMods":/ \ d + to \ d + /}}

我收集了该集合中的所有文件。这是意料之外的,因为我实际上想要包含像12 to 18这样的子串的文档。如果我将正则表达式更改为/\d+ to \d+z/,则它正确匹配任何内容。

1 个答案:

答案 0 :(得分:1)

您发出的查询“正确”会返回实际符合您要求的条件的文档。这就是您正在测试的属性中的“至少一个”数组元素实际上与查询中的条件匹配。

由此我们可以推测出两种可能的结果:

  1. 您的意图是仅返回所有数组条目满足条件的文档。

  2. 您的意图是“过滤”文档中“数组”中的条目,只返回满足条件的结果。

  3. 从中可以看出各种方法。首先,实际上没有MongoDB的这样的查询运算符要求“全部”数组元素必须通过“常规查询”由给定条件满足。因此,您需要以不同的形式应用逻辑。

    一个这样的选择是以检查数组内容的方式使用$where的JavaScript评估。在这里,除了常规查询过滤器之外,您还可以应用Array.every()来测试您的情况,因为这实际上是在做一些有用的工作。

    给出源文件,如:

    /* 1 */
    {
        "_id" : ObjectId("5993a35be38f41729f1d6501"),
        "name" : "string",
        "explicitMods" : [ 
            "+48 to Blah", 
            "-13% to Blah", 
            "12 to 18 to Blah"
        ]
    }
    
    /* 2 */
    {
        "_id" : ObjectId("5993a35be38f41729f1d6502"),
        "name" : "string",
        "explicitMods" : [ 
            "12 to 18 to Blah"
        ]
    }
    

    如果您的目的只是返回与“所有”数组元素匹配的“文档”,则发出声明:

    db.myCollection.find({ 
      "explicitMods": /\d+ to \d+/,
      "$where": function() { return this.explicitMods.every(e => /\d+ to \d+/.test(e)) }
      }
    })
    

    仅返回匹配的文档:

    {
        "_id" : ObjectId("5993a35be38f41729f1d6502"),
        "name" : "string",
        "explicitMods" : [ 
            "12 to 18 to Blah"
        ]
    }
    

    在使用$where的替代情况下,MongoDB的聚合框架允许使用“本机编码运算符”的表达式,这些运算符通常比JavaScript解释表达式应用更快。但是,实际上没有SERVER-11947$regex等效的“逻辑运算符”适用于$redact等聚合操作。

    因此,此处可用的唯一方法是使用$match使用常规查询条件“之后”使用$unwind对数组元素进行非规范化:

    db.myCollection.aggregate([
      // Match "possible" documents
      { "$match": { "explicitMods": /\d+ to \d+/ } },
    
      // unwind to denormalize
      { "$unwind": "$explicitMods" },
    
      // Match on the "array" items now as documents
      { "$match": { "explicitMods": /\d+ to \d+/ } },
    
      // Optionally "re-group" back to documents with only matching array items
      { "$group": {
        "_id": "$_id",
        "name": { "$first": "$name" },
        "explicitMods": { "$push": "$explicitMods" }
      }}
    ])
    

    那个将返回“both”文档,但只返回那些匹配数组项的文档:

    /* 1 */
    {
        "_id" : ObjectId("5993a35be38f41729f1d6501"),
        "name" : "string",
        "explicitMods" : [ 
            "12 to 18 to Blah"
        ]
    }
    
    /* 2 */
    {
        "_id" : ObjectId("5993a35be38f41729f1d6502"),
        "name" : "string",
        "explicitMods" : [ 
            "12 to 18 to Blah"
        ]
    }
    

    当然,您可以对该主题应用“变体”,并根据过滤条件“测试数组的长度”,以决定返回哪个文档:

    db.myCollection.aggregate([
      { "$match": { "explicitMods": /\d+ to \d+/ } },
      { "$addFields": { "origSize": { "$size": "$explicitMods" } } },
      { "$unwind": "$explicitMods" },
      { "$match": { "explicitMods": /\d+ to \d+/ } },
      { "$group": {
        "_id": "$_id",
        "name": { "$first": "$name" },
        "origSize": { "$first": "$origSize" },
        "explicitMods": { "$push": "$explicitMods" },
      }},
      { "$redact": {
        "$cond": {
          "if": { 
            "$eq": [
              { "$size": "$explicitMods" },
              "$origSize"
            ]
          },
          "then": "$$KEEP",
          "else": "$$PRUNE"
        }
      }}
    ])
    

    虽然它与$where使用“本地运营商”的原始选项做同样的事情,但$unwind这样的操作的一般成本使其效用值得怀疑,因此可能需要更多产生结果的时间和资源比原始查询。