问题
The following query in mongo, behaves strange :
db.items.findOne({},{ "List": { "$slice": [ skip, 3 ] }})
First: Instead of returning one object with ["_id","List"] keys only, it returns a full object.
Second:
if skip
is negative and |skip|
is higher than list.length then it returns the first three elements as though skip==0
I would expect for:
{
"_id" : ObjectId("542babf265f5de9a0d5c2928"),
"List" : [
1,
2,
3,
4,
5
]
"other" : "not_important"
}
query:
db.items.findOne({},{ "List": { "$slice": [-10, 3 ] }})
to get:
{
"_id" : ObjectId("542babf265f5de9a0d5c2928"),
"List" : []
}
instead, I get:
{
"_id" : ObjectId("542babf265f5de9a0d5c2928"),
"List" : [
1,
2,
3
]
"other" : "not_important"
}
Why?
I use mongoDB 2.4.10
回答1:
Second: if skip is negative and |skip| is higher than list.length then it returns the first three elements as though skip==0
Yes. That is how the javascript Array.prototype.slice()
method works, which is internally used by mongodb.
According to the ECMAScript® Language Specification,
If relativeStart is negative, let k be max((len + relativeStart),0); else let k be min(relativeStart, len).
In your case relativeStart is -10
,
k = max((-10+5),0), k = 0;
(where, 5
is the length of your array).
Hence k
or skip
will always be 0
, in these cases.
First: Instead of returning one object with ["_id","List"] keys only, it returns a full object.
Yes, the projection operator works that way. Unless a inclusion
or exclusion
is explicitly specified in the projection parameter, the whole document is retrieved with the projection operators such as $slice
,$elemmatch
being applied.
db.items.findOne({},{"_id":1,"List": { "$slice": [-10, 3 ] }})
would return:
{ "_id" : ObjectId("542babf265f5de9a0d5c2928"), "List" : [ 1, 2, 3 ] }
The second parameter to the findOne()
method is not only for simple projection
purpose, fields are not projected, only if any one of the field
names have a value of 0
or 1
against them. If not the whole document is returned. If any field has a projection operator
to be applied, it would be applied
and projected
.
The projection mechanism seems to happen in the below manner, whenever the $slice
operator is involved.
- By default all the fields would be included for projection.
- By Default all the fields whose values are derived based on the projection operator,
$slice
, if truthy, are always displayed, irrespective of the below.
Steps taking place for exclusion or inclusion.
- The list of fields specified in the projection parameter are accumulated in their specified order.
- For only the first field encountered with value '0' or '1': If the field has a value '0' - then it is excluded, and all the remaining fields are marked to be included. If a field has '1' - then it is included, and all the remaining fields are marked to be excluded.
- For all the subsequent fields, they are excluded or included based on their values.
回答2:
Whilst this behavior is by design for the $slice operator, it is possible since MongoDB 3.2 to evaluate this and alter the result with the aggregation operator for $slice:
Given the example documents:
{ "_id" : ObjectId("5922846dbcf60428d0f69f6e"), "a" : [ 1, 2, 3, 4 ] }
{ "_id" : ObjectId("5922847cbcf60428d0f69f6f"), "a" : [ 5, 6 ] }
If given a conditional expression to test against the length of the array with $size and only perform the $slice when the reverse index was greater than or equal to that length, or otherwise return an empty array:
db.collection.aggregate([
{ "$project": {
"a": {
"$cond": {
"if": { "$gte": [ { "$size": "$a" }, 4 ] },
"then": { "$slice": [ "$a", -4, 2 ] },
"else": { "$literal": [] },
}
}
}}
])
Then of course you get:
{ "_id" : ObjectId("5922846dbcf60428d0f69f6e"), "a" : [ 1, 2 ] }
{ "_id" : ObjectId("5922847cbcf60428d0f69f6f"), "a" : [ ] }
So that is how you could get MongoDB to return a "slice" that acts in this way.
来源:https://stackoverflow.com/questions/26203217/mongo-slice-query-reverse-index-out-of-range