【问题标题】:Why does this RegExp query return all results?为什么这个 RegExp 查询返回所有结果?
【发布时间】:2018-01-24 00:13:29
【问题描述】:

我有一个 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/,它不会正确匹配任何内容。

【问题讨论】:

  • 您能否更新您的问题以提供查询返回但您不想包含在内的示例文档?
  • 您的查询似乎有效。那么您是否要在该文档中过滤explicitMods
  • 如果您不希望在“12 到18" - 换句话说,一个表达式,如/^\d+ to \d+$/。这可能有帮助吗?
  • 构建 PoE 索引器? :)

标签: javascript regex mongodb mongodb-query


【解决方案1】:

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

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

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

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

从这些有不同的方法。首先,实际上 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 解释表达式更快地应用。然而,实际上并没有适用于 $redact 等聚合操作的 $regex 等价的“逻辑运算符”(参见 SERVER-11947 )。

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

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" }
  }}
])

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

/* 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 等操作的一般成本使其实用性存在问题,因此可能需要更多的时间和资源产生比原始查询的结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 2019-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多