Find first element in nested array that match conditions

本秂侑毒 提交于 2019-12-05 16:18:33

Best probably to search using $where in addition to the normal query, and still keep things on the server:

db.getCollection('collection').find({
  "array": {
    "$elemMatch": { "field": "BCD", "enabled": "true" },
  },
  "$where": function() {
    return this.array.map((e,i) => Object.assign(e,{ i }))
      .filter( e => e.field === "BCD" && e.enabled === "true" )
      .map( e => e.i )[0] <=
    this.array.map(e => e.enabled).indexOf("true")
  }  
})

And if you have MongoDB 3.4 with support for $indexOfArray and $range, then it may look longer but it actually most efficient with $redact:

db.getCollection('collection').aggregate([
  { "$match": {
    "array": {
      "$elemMatch": { "field": "BCD", "enabled": "true" },
    }
  }},
  { "$redact": {
    "$cond": {  
      "if": {
        "$lte": [
          { "$arrayElemAt": [
            { "$map": {
              "input": {
                "$filter": {
                  "input": {
                    "$map": {
                      "input": {
                        "$zip": {
                          "inputs": [
                            "$array",
                            { "$range": [0, { "$size": "$array" }] }
                          ]
                        }    
                      },
                      "as": "a",
                      "in": {
                        "field": { "$arrayElemAt": [ "$$a.field", 0 ] },
                        "enabled": { "$arrayElemAt": [ "$$a.enabled", 0 ] },
                        "index": { "$arrayElemAt": [ "$$a", 1 ] }    
                      }
                    }
                  },
                  "as": "a",
                  "cond": {
                    "$and": [
                      { "$eq": [ "$$a.field", "BCD" ] },
                      { "$eq": [ "$$a.enabled", "true" ] }
                    ]
                  }    
                }
              },
              "as": "a",
              "in": "$$a.index"  
            }},
            0
          ]},
          { "$indexOfArray": [ "$array.enabled", "true" ] } 
        ] 
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

So there really is no actual query operation that enforces that, but both of these cases keep the selection "on the server" as opposed to sending data over the wire to the client and then filtering.

Because if you do that it kind of negates the purpose of using a database in the first place. So you really want this to happen on the server.

Why not search by enabled field and then check if field is appropriate or not?

db.collection("col").findOne({
    "array.enabled": true
}, {
    array: {
        $elemMatch {
            enabled: true
        }
    }
})
.then(function(docs){
    docs.forEach(function(doc){
        if(doc.array[0].field == "ABC"){
            // Ok, we get it
        }
    })
})

Second argument to find is projection, so application doesn't download complete array from document, but only first matched element of it.

There is $where keyword that allow to generate complex conditions for similar purpose.

find($where: 'this.array.filter(function(e){return e.enabled=="true"})[0].field=="ABC"')

As this does not use any indexes, I'll add more conditions to benefit from optimisation.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!