Dat was een zware!
Eerst de kale oplossing:
db.test.aggregate([
{ "$match": { "user": "Hans" } },
// duplicate each document: one for "age", the other for "childs"
{ $project: { age: "$age", childs: "$childs",
data: {$literal: ["age", "childs"]}}},
{ $unwind: "$data" },
// pivot data to something like { data: "age", value: "40" }
{ $project: { data: "$data",
value: {$cond: [{$eq: ["$data", "age"]},
"$age",
"$childs"]} }},
// Group by data type, and count
{ $group: { _id: {data: "$data", value: "$value" },
count: { $sum: 1 },
value: {$first: "$value"} }},
// aggregate values in an array for each independant (type,value) pair
{ $group: { _id: "$_id.data", values: { $push: { count: "$count", value: "$value" }} }} ,
// project value to the correctly name field
{ $project: { result: {$cond: [{$eq: ["$_id", "age"]},
{age: "$values" },
{childs: "$values"}]} }},
// group all data in the result array, and remove unneeded `_id` field
{ $group: { _id: null, result: { $push: "$result" }}},
{ $project: { _id: 0, result: 1}}
])
Produceren:
{
"result" : [
{
"age" : [
{
"count" : 3,
"value" : "40"
},
{
"count" : 1,
"value" : "50"
}
]
},
{
"childs" : [
{
"count" : 1,
"value" : "1"
},
{
"count" : 3,
"value" : "2"
}
]
}
]
}
En nu, voor wat uitleg:
Een van de belangrijkste problemen hier is dat elk binnenkomend document deel moet uitmaken van twee verschillende bedragen. Ik heb dat opgelost door een letterlijke array toe te voegen ["age", "childs"]
naar uw documenten, en ze vervolgens af te wikkelen door die array. Op die manier wordt elk document tweemaal in de latere fase.
Als dat eenmaal is gebeurd, verander ik de gegevensweergave in iets dat veel beter beheersbaar is, om de verwerking te vergemakkelijken, zoals { data: "age", value: "40" }
De volgende stappen voeren de gegevensaggregatie op zich uit. Tot het derde $project
stap die de waardevelden toewijst aan de corresponderende age
of childs
veld.
De laatste twee stappen wikkelen de twee documenten gewoon in één, waarbij de onnodige _id
. wordt verwijderd veld.
Pffff!