问题
Consider the following document:
{
"item1" : [
{
"a" : 1,
"b" : 2
}
],
"item2" : [ "a", "b" ]
}
The following query:
db.test.aggregate([
{ "$project": { "items": { "$setIntersection": [ "$item1", "$item2" ] } }}
])
returns the expected result:
{ "_id" : ObjectId("5710785387756a4a75cbe0d1"), "a" : [ ] }
If the document looks like this:
{ "item2" : [ "a", "b" ] }
Then:
db.test.aggregate([ { "$project": {
"a": { "$setIntersection": [ "$item2", [ "a" ] ] } } }
])
Yields:
{ "_id" : ObjectId("5710785387756a4a75cbe0d1"), "a" : [ "a" ] }
But
db.test.aggregate([
{ "$project": { "items": { "$setIntersection": [ "$item2", [ { "a" : 1, "b" : 2 } ] ] } } }
])
failed with :
"errmsg" : "field inclusion is not allowed inside of $expressions"
And:
db.test.aggregate([ { "$project": {
"items": { "$setIntersection": [ "$item2", [ { "a": "b" } ] ] } } }
])
failed with:
"errmsg" : "FieldPath 'b' doesn't start with $"
The only way to make this work is to use the $literal
operator.
Why should we use the $literal operator if $setIntersection arguments are array of sub-documents and not a field in the document?
回答1:
This would appear to be an artifact of MongoDB 3.2 which incorporated a change that allows arrays to be notated while directly interpolating properties of the document.
For example, with a document like:
{ "a": 1, "b": 2, "c": 3, "d": 4 }
Then you are "now" allowed to notate those elements inside an array, like:
db.collection.aggregate([
{ "$project": {
"array": [
{ "field1": "$a", "field2": "$b" },
{ "field1": "$c", "field2": "$d" }
]
}}
])
In previous versions ( in this case MongoDB 2.6 ) you would instead need to use this $map expression:
db.collection.aggregate([
{ "$project": {
"array": {
"$map": {
"input": ["A","B"],
"as": "el",
"in": {
"$cond": {
"if": { "$eq": [ "$$el", "A" ] },
"then": { "field1": "$a", "field2": "$b" },
"else": { "field1": "$c", "field2": "$c" }
}
}
}
}
}}
])
Or in prior versions to that then something a bit more long winded using $unwind
and $group
, but the same basic principle of transposing a "source" array with other data. The main point though is the change in notation allowed in MongoDB 3.2, which would otherwise "error" in a prior version.
Therefore in a prior version, say MongoDB 2.6.x where $setIntersection is supported then the following works just fine, since all values are considered "literal" unless actually referencing an array present in the document:
db.collection.aggregate([
{ "$project": {
"a": {
"$setIntersection": [
[{"a": 1}],
[{"a": 1}]
]
}
}}
])
Provided of course that "collection"
as a collection actually has something in it.
But since MongoDB 3.2 allows a different syntax for "interpolated arrays", it now expects the "right side" to evaluate to a property from the document or other valid expression. So now the $literal syntax is required:
db.collection.aggregate([
{ "$project": {
"a": {
"$setIntersection": [
{ "$literal": [{"a": 1}] },
{ "$literal": [{"a": 1}] }
]
}
}}
])
This generally comes down to the saying that "you can't have your cake and eat it too". The "new" syntax allows you to express array content with "interpolations" in a nice way without resorting to other expressions to "coerce" the content into an array form.
The consequence of this is that every such expression is now expecting "values" to resolve to a property or expression rather than being directly considered a "literal", and where you mean that to be so, you are now required to express that using the $literal operator.
So it is in fact a "breaking" change in allowed syntax between versions. But one that most people should easily live with.
回答2:
This appears to be a compatibility changes in MongoDB 3.2 thus is the expected behavior as mentioned in the Aggregation Compatibility Changes in MongoDB 3.2:
Array elements are no longer treated as literals in the aggregation pipeline. Instead, each element of an array is now parsed as an expression. To treat the element as a literal instead of an expression, use the $literal operator to create a literal value.
来源:https://stackoverflow.com/questions/36639188/setintersection-failed-with-array-of-subdocuments-that-are-not-in-the-collectio