查找以给定顺序出现的文档数组中的两个元素

时间:2018-04-23 00:42:07

标签: mongodb mongoose mongodb-query

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

{ 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的subDocs的所有文档。但是数组中项目的出现必须满足以下要求A.index< B.index。所以基本上A值的项目必须出现在集合中比B更早的位置。所以上面的对象符合要求而不是这个,因为值不会出现在顺序中:

{ 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实现它而不牺牲这种查询的性能吗?

1 个答案:

答案 0 :(得分:1)

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

MongoDB 3.6

您最好将$expr "以及#34; 用于任何正常的查询条件,以实际选择有效的文档:

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`
})

或匹配&#34; last&#34; 事件:

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

可以说&#34;比较逻辑&#34;实际上并不是&#34;查询运算符表达式&#34;在所有情况下,&#34;最佳&#34; 可以应用于索引的唯一部分是使用$all查询运算符。剩下的基本逻辑实际上适用于&#34;&#34;评估主要表达式&#34;除了&#34;为了使那些符合$expr$where的表达式的人不会返回任何结果。

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

请注意,&#34;计算评估&#34;匹配查询运算符的效率,并且没有被使用&#34;组合&#34;能够访问&#34;索引&#34;的其他查询运算符条件,然后是&#34;完整的集合扫描&#34;将启动。

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

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