问题
I am combining two collection using $lookup and I am able to apply a filter on the 'joined' collection and use a projection on the starting collection , but I did not manage to combine both a filter and a projection on the joined collection trying several approach using $redact and $project. I have looked intensively on stackoverflow, but I could not find this combination. Here come an example: collection meta:
{ "Exp": "A","test": "OK","date": "3"}
{ "Exp": "B","test": "OK","date": "5"}
{ "Exp": "C","test": "Failed","date": "9"}
collection merge (to be joined'):
{ "Exp": "A","M1": "2","M2": "test","T": "1"}
{ "Exp": "A","M1": "2","M2": "val", "T": "2"}
{ "Exp": "A","M1": "2", "M2": "val","T": "3"}
{ "Exp": "B","M1": "1", "M2": "test","M4": "1","T": "1"}
{ "Exp": "B","M1": "1","M2": "val","M4": "1","T": "2"}
{ "Exp": "B","M1": "1","M2": "val","M4": "1","T": "3"}
{ "Exp": "C","M1": "2","M2": "test","M3": "2","T": "1"}
{ "Exp": "C","M1": "2","M2": "val","M3": "2","T": "2"}
{ "Exp": "C","M1": "2","M2": "val","M3": "2","T": "3"}
And the query is: Join 'meta' and 'merge' using 'Exp', and select only those where meta.test="OK" and merge.M2="val", but show only meta.Exp, meta.test and merge.M1, merge.M2, and merge.T.
This is how far I got:
db.meta.aggregate([
{ $match: { test: "OK" }},
{ $lookup:
{ from: "merge",
localField: "Exp",
foreignField: "Exp",
as: "kin"
}
},
{ $project:
{ "Exp": true,
"test": true,
kin :
{ $filter:
{ input: "$kin",
as: "kin",
cond: { $eq: [ "$$kin.M2", "val" ]}
}
}
}
}
])
but trying to include an additional projection on merge.M1, merge.M2, and merge.T together with the filter keeps failing. The result should be:
{ "Exp" : "B",
"test" : "OK",
"kin" : [
{ "M1" : "1",
"M2" : "val",
"T" : "2"},
{ "M1" : "1",
"M2" : "val",
"T" : "3"}]
}
{ "Exp" : "A",
"test" : "OK",
"kin" : [
{ "M1" : "2",
"M2" : "val",
"T" : "2"},
{ "M1" : "2",
"M2" : "val",
"T" : "3"}
]
}
Thanks for hints! Jordi
回答1:
You use $map to specify which fields of an array to return:
db.meta.aggregate([
{ "$match": { test: "OK" }},
{ "$lookup":{
"from": "merge",
"localField": "Exp",
"foreignField": "Exp",
"as": "kin"
}},
{ "$project": {
"Exp": 1,
"test": 1,
"kin": {
"$map": {
"input": {
"$filter": {
"input": "$kin",
"as": "k",
"cond": { "$eq": [ "$$k.M2", "val" ] }
}
},
"as": "k",
"in": {
"M1": "$$k.M1",
"M2": "$$k.M2",
"T": "$$k.T"
}
}
}
}}
])
Which returns for you:
/* 1 */
{
"_id" : ObjectId("5979a8857dcd6a5f6a9b4b9a"),
"Exp" : "A",
"test" : "OK",
"kin" : [
{
"M1" : "2",
"M2" : "val",
"T" : "2"
},
{
"M1" : "2",
"M2" : "val",
"T" : "3"
}
]
}
/* 2 */
{
"_id" : ObjectId("5979a8857dcd6a5f6a9b4b9b"),
"Exp" : "B",
"test" : "OK",
"kin" : [
{
"M1" : "1",
"M2" : "val",
"T" : "2"
},
{
"M1" : "1",
"M2" : "val",
"T" : "3"
}
]
}
回答2:
An alternative way, proposed by a colleaque is using a second $projection. I think this is more efficient than $map, because it does not go over each element of the array. The trick is to repeat the projections from the first $projection.
db.meta.aggregate([
{ $match: { test: "OK" }},
{ $lookup: { from: "merge", localField: "Exp", foreignField: "Exp", as:
"kin" }},
{ $project:
{ "Exp": true, "test": true, "date": true,
kin : { $filter: { input: "$kin", as: "kin", cond: { $eq: [
"$$kin.M2",
"val" ]}}
}
}
},
{ $project: {
"Exp": true, "test": true, "date": true,
"kin.M1": true, "kin.M2": true, "kin.T": true }
}
])
来源:https://stackoverflow.com/questions/45326664/how-to-project-specific-fields-in-array-on-filtered-lookup