简体   繁体   中英

Find a document that contains a specific value in an array but not if it's the last element

My current approach is:

var v = 'Value';
Collection.find({arrayToLookIn: v}).forEach(function(obj) {
  if (obj.arrayToLookIn.indexOf(v) !== obj.arrayToLookIn.length - 1) {
    // do stuff
  }
}

I was wondering if there's a way to specify such a rule in the find() call and do this without the inner check?

I've looked through https://docs.mongodb.org/manual/tutorial/query-documents/#match-an-array-element but didn't spot what I seek.

First question, please be gentle :)

What you can do now

You want$where , which can use JavaScript evaluation to match the document. So here you ask the evaluating code to test each array element, but not the last one:

Collection.find({
    "arrayToLookIn": v,
    "$where": function() {
        var array = this.arrayToLookIn;
        array.pop();                    // remove last element
        return array.some(function(el) { return el == 'Value' });
    }
})

Note that as it is JavaScript sent to the server the "Value" needs to be specified in that code rather than using a variable. You can optionally contruct the JavaScript code as a "string" to join in that variable as a literal and submit that as the argument to $where .

Note that I'm leaving in the basic equality match, as $where cannot match using an index like that can, and therefore it's job is to "filter" out the results where the match is on the last element, and not test every single document to find whether it is even there at all.


Better Future Way

For the curious, as of the present MongoDB 3.0 release series there is not a really efficient way to do this with the aggregation framework, so the JavaScript evalution is the better option.

You would presently need to do something silly like find the last element in a $group after $unwind and then $match out the value after another $unwind . It's not efficient and prone to error where the value exists more than once.

Future releases will have a $slice operator which could be used like this with $redact :

Collection.aggregate([
    // Still wise to do this as mentioned earlier
    { "$match": { "arrayToLookIn": v } },

    // Then only return if the match was not in the last element
    { "$redact": {
        "$cond": {
            "if": {
                "$setIsSubset": [
                    [v],
                    { "$slice": [
                        "$arrayToLookIn",
                        0,
                        { "$subtract": [ { "$size": "$arrayToLookIn" }, 1 ] }
                    ]}
                ]
            },
            "then": "$$KEEP",
            "else": "$$PRUNE"
        }
    }}

Where $setIsSubset does the comparison of the array which has had it's last entry removed via $slice by only returning elements from 0 to the $size minus 1 .

And that should be more efficient than $where as it uses native coded operations for the comparison, when the next release that has that $slice for aggregation becomes available.

Not to mention $unwind also has an option to include the index position in future releases as well. But it's still not a great option even with that addition.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM