【问题标题】:Finding two elements in an array of documents that appear in a given order在以给定顺序出现的文档数组中查找两个元素
【发布时间】:2018-10-02 23:09:31
【问题描述】:

假设我在数据库中有以下文档结构:

{ name: String,
  subDocs: [{
    index: Number,
    value: Number
  }]
}

所以一个典型的文档看起来像:

{ name: 'Document 1',
  subDocs: [ 
    { index: 0, value: 30 },
    { index: 1, value: 40 },
    { index: 2, value: 10 },
    { index: 3, value: 20 },
    { index: 4, value: 700 },
    { index: 5, value: 40 }
  ]
}

现在,我想查找包含值 A = 10 和 B = 40 的子文档的所有文档,但数组中项目的出现必须满足以下要求 A.index

{ name: 'Document 2',
  subDocs: [ 
    { index: 0, value: 40 },
    { index: 1, value: 70 },
    { index: 2, value: 10 },
    { index: 3, value: 20 },
    { index: 4, value: 700 }
  ]
}

我们可以在不牺牲这种查询性能的情况下使用 Mongoose 实现它吗?

【问题讨论】:

  • 拿到文档后为什么不对它们进行排序?
  • 嗨@GeorgeBailey。你能详细说明一下这个想法吗?我认为在这种情况下排序没有帮助。我还希望所有工作都由 MongoDB 引擎完成,因此我不需要在我的后端代码中额外过滤它。
  • 您提到的对象包含 10,20 以外的值?你的问题有点不清楚对不起:p
  • 对不起,如果问题不清楚。字段“值”是一个整数,因此它可以保存您可以成像的任何有效整数:)
  • 其中包含值为 A = 10 和 B = 40 的子文档,那么,这怎么能是预期的输出呢? { name: 'Some name', subDocs: [ { index: 0, value: 30 }, { index: 1, value: 40 }, { index: 2, value: 10 }, { index: 3, value: 20 }, { index: 4, value: 700 }, { index: 5, value: 40 } ] }

标签: mongodb mongoose mongodb-query


【解决方案1】:

如果您想在查询中使用这种约束,那么您基本上有两个选项,具体取决于您的 MongoDB 版本支持的内容:

MongoDB 3.6

您最好使用$expr“除了” 来实际选择有效文档:

var A = 10, B = 40;

Model.find({
  "subDocs.value": { "$all": [A, B] },
  "$expr": {
    "$lt": [
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$indexOfArray": [ "$subDocs.value", A ]}
      ]},
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$indexOfArray": [ "$subDocs.value", B ]}  
      ]}
    ]
  }
})

或匹配 "last" 出现:

Model.find({
  "subDocs.value": { "$all": [A, B] },
  "$expr": {
    "$lt": [
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$subtract": [
          { "$subtract": [{ "$size": "$subDocs.value" }, 1 ] },
          { "$indexOfArray": [ { "$reverseArray": "$subDocs.value" }, A ] }
        ]}
      ]},
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$subtract": [
          { "$subtract": [{ "$size": "$subDocs.value" }, 1 ] },
          { "$indexOfArray": [ { "$reverseArray": "$subDocs.value" }, B ] }
        ]}
      ]}
    ]
  }
})

早期版本

同样的事情,但没有原生运算符,您需要使用 $where 的 JavaScript 评估:

var A = 10, B = 40;

Model.find({
  "subDocs.value": { "$all": [A, B] },
  "$where": `this.subDocs.find( e => e.value === ${A}).index
      < this.subDocs.find( e => e.value === ${B}).index`
})

或匹配 "last" 出现:

Model.find({
  "subDocs.value": { "$all": [10,40] },
  "$where": `let arr = this.subDocs.reverse();
      return arr.find( e => e.value === ${A}).index
        > arr.find( e => e.value === ${B}).index`
})

如果您在聚合管道中需要它,那么您可以使用 $redact 以及与第一个示例类似的逻辑:

var A = 10, B = 40;

Model.aggregate([
  { "$match": { "subDocs.value": { "$all": [A, B] } } },
  { "$redact": {
    "$cond": {
      "if": {
        "$lt": [
          { "$arrayElemAt": [
            "$subDocs.index",
            { "$indexOfArray": [ "$subDocs.value", A ]}
          ]},
          { "$arrayElemAt": [
            "$subDocs.index",
            { "$indexOfArray": [ "$subDocs.value", B ]}  
          ]}
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

可以说,“比较逻辑”实际上并不是“查询运算符表达式”本身所固有的,因此 “最佳” 可以应用于索引的唯一部分是使用 @987654324 @ 查询运算符在所有情况下。基本的剩余逻辑实际上在评估主表达式“之后”和“除”之后应用,以便除了满足 $expr$where 的表达式之外不返回任何结果。

每个的基本逻辑本质上是从“第一个”数组成员中提取"index"属性的值,该成员实际上与"value"属性中的相应值匹配。如果这是“小于”,则条件为true,这满足返回的文档。

因此请注意,“计算评估”与查询运算符的效率相匹配,并且不与能够访问“索引”的其他查询运算符条件“组合”使用,那么“完整集合扫描”将是启动。

但总体结果肯定比将所有匹配项返回到第一个查询条件然后在从数据库返回“之后”的游标上拒绝它们更有效。


另请参阅有关 JavaScript 的 $arrayElemAt$indexOfArray$ltArray.find() 的文档

【讨论】:

  • 非常感谢@Neil Lunn。我认为我唯一需要的是返回我的 B 匹配的最后一个索引的东西。否则,如果 A=10 和 B=40 并且我有一个 subDocs 集合,例如: [40, 10, 40] 它将与文档不匹配。这是因为它会发现 10 作为 A,第一个(而不是最后一个)40 作为 B,索引将是 A.index > B.index... 所以不匹配。
  • 不幸的是,我在 Mongoose 中没有看到像 $lastIndexOfArray 这样的东西。也许我们可以在应用 $indexOfArray 运算符(对于 B 值)之前以某种方式反转查询中的集合?
  • @ŁukaszSzkup 您确实注意到我故意在答案正文中突出显示 "first"。这不是你问题的细节,你真的不应该做类似 " 的事情......但是如果我也需要这个" 在提问时怎么办。是的,我们可以"$reverse" 数组。它是 MongoDB 和 JavaScript 中的运算符。
  • @ŁukaszSzkup 不知道我为什么这样做,但为 last 添加了示例。不要要求更多的改动。如果您有新问题,请改用Ask a New Question
  • 嗨@Neil Lunn,“但是”语句不是额外的细节。这自然源于我最初的问题。我稍后在评论中注意到它,因为您的原始查询无法捕获具有 [40,10,40] 集合的项目,其中 A=10 和 B=40,即使它应该根据我原始问题中的标准匹配它。所以我只需要在查询的后半部分反转数组,我的意思是 B 部分,因为我们需要 A 的第一个索引和 B 的最后一个索引。无论如何,非常感谢您的时间并解决了我的问题:)
猜你喜欢
  • 2011-06-19
  • 1970-01-01
  • 2012-01-21
  • 1970-01-01
  • 2016-04-14
  • 1970-01-01
  • 1970-01-01
  • 2016-10-10
  • 1970-01-01
相关资源
最近更新 更多