La requête pour compter les occurrences "uniques" dans un "EndpointId"
de chacun des "Uid"
dans "Tags"
et le "Type"
dans "Sensors"
serait :
db.collection.aggregate([
{ "$unwind": "$Tags" },
{ "$unwind": "$Tags.Sensors" },
{ "$group": {
"_id": {
"EndpointId": "$EndpointId",
"Uid": "$Tags.Uid",
"Type": "$Tags.Sensors.Type"
},
}},
{ "$group": {
"_id": {
"EndpointId": "$_id.EndpointId",
"Uid": "$_id.Uid",
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.EndpointId",
"tagCount": { "$sum": 1 },
"sensorCount": { "$sum": "$count" }
}}
])
Ou pour C#
var results = collection.AsQueryable()
.SelectMany(p => p.Tags, (p, tag) => new
{
EndpointId = p.EndpointId,
Uid = tag.Uid,
Sensors = tag.Sensors
}
)
.SelectMany(p => p.Sensors, (p, sensor) => new
{
EndpointId = p.EndpointId,
Uid = p.Uid,
Type = sensor.Type
}
)
.GroupBy(p => new { EndpointId = p.EndpointId, Uid = p.Uid, Type = p.Type })
.GroupBy(p => new { EndpointId = p.Key.EndpointId, Uid = p.Key.Uid },
(k, s) => new { Key = k, count = s.Count() }
)
.GroupBy(p => p.Key.EndpointId,
(k, s) => new
{
EndpointId = k,
tagCount = s.Count(),
sensorCount = s.Sum(x => x.count)
}
);
Qui sort :
{
"EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",
"tagCount" : 4,
"sensorCount" : 16
}
Bien qu'il s'agisse en fait de la manière "la plus efficace" de le faire, étant donné que les documents présentés ont des valeurs uniques pour "Uid"
de toute façon serait de $reduce
les montants dans les documents eux-mêmes :
db.collection.aggregate([
{ "$group": {
"_id": "$EndpointId",
"tags": {
"$sum": {
"$size": { "$setUnion": ["$Tags.Uid",[]] }
}
},
"sensors": {
"$sum": {
"$sum": {
"$map": {
"input": { "$setUnion": ["$Tags.Uid",[]] },
"as": "tag",
"in": {
"$size": {
"$reduce": {
"input": {
"$filter": {
"input": {
"$map": {
"input": "$Tags",
"in": {
"Uid": "$$this.Uid",
"Type": "$$this.Sensors.Type"
}
}
},
"cond": { "$eq": [ "$$this.Uid", "$$tag" ] }
}
},
"initialValue": [],
"in": { "$setUnion": [ "$$value", "$$this.Type" ] }
}
}
}
}
}
}
}
}}
])
Cependant, l'instruction ne correspond pas vraiment bien à LINQ, vous devrez donc utiliser le BsonDocument
interface pour construire le BSON pour l'instruction. Et bien sûr où le même "Uid"
les valeurs "did" se produisent en fait dans plusieurs documents de la collection, puis le $unwind
des instructions sont nécessaires pour les « regrouper » dans les documents à partir des entrées du tableau.
Original
Vous résolvez ce problème en obtenant le $size
des tableaux. Pour le tableau externe, cela s'applique simplement au chemin du champ du tableau dans le document, et pour les éléments du tableau interne, vous devez traiter avec $map
afin de traiter chaque "Tags"
élément puis obtenir l'élément $size
de "Sensors"
et $sum
le tableau résultant à réduire au nombre total.
Par document ce serait :
db.collection.aggregate([
{ "$project": {
"tags": { "$size": "$Tags" },
"sensors": {
"$sum": {
"$map": {
"input": "$Tags",
"in": { "$size": "$$this.Sensors" }
}
}
}
}}
])
Ce à quoi vous avez attribué des classes dans votre code C# ressemblerait à :
collection.AsQueryable()
.Select(p => new
{
tags = p.Tags.Count(),
sensors = p.Tags.Select(x => x.Sensors.Count()).Sum()
}
);
Où ceux-ci reviennent :
{ "tags" : 3, "sensors" : 13 }
{ "tags" : 2, "sensors" : 8 }
Où vous voulez $group
les résultats, comme par exemple sur toute la collection, alors vous feriez :
db.collection.aggregate([
/* The shell would use $match for "query" conditions */
//{ "$match": { "EndpointId": "89799bcc-e86f-4c8a-b340-8b5ed53caf83" } },
{ "$group": {
"_id": null,
"tags": { "$sum": { "$size": "$Tags" } },
"sensors": {
"$sum": {
"$sum": {
"$map": {
"input": "$Tags",
"in": { "$size": "$$this.Sensors" }
}
}
}
}
}}
])
Ce qui pour votre code C# comme avant serait :
collection.AsQueryable()
.GroupBy(p => "", (k,s) => new
{
tags = s.Sum(p => p.Tags.Count()),
sensors = s.Sum(p => p.Tags.Select(x => x.Sensors.Count()).Sum())
}
);
Où ceux-ci reviennent :
{ "tags" : 5, "sensors" : 21 }
Et pour "EndpointId
, alors vous utilisez simplement ce champ comme clé de regroupement, plutôt que le null
ou 0
tel qu'il est appliqué par le mappage du pilote C# :
collection.AsQueryable()
/* Use the Where if you want a query to match only those documents */
//.Where(p => p.EndpointId == "89799bcc-e86f-4c8a-b340-8b5ed53caf83")
.GroupBy(p => p.EndpointId, (k,s) => new
{
tags = s.Sum(p => p.Tags.Count()),
sensors = s.Sum(p => p.Tags.Select(x => x.Sensors.Count()).Sum())
}
);
Qui est bien sûr la même somme des deux exemples de documents que vous nous avez donné :
{ "tags" : 5, "sensors" : 21 }
Ce sont donc des résultats très simples, avec une exécution simple du pipeline une fois que vous vous êtes habitué à la syntaxe.
Je suggère de vous familiariser avec les Opérateurs d'agrégation de la documentation de base, et bien sûr le "Aide-mémoire LINQ" d'expressions et leur mappage d'utilisation à partir du référentiel de code du pilote C#.
Voir également la référence LINQ dans la référence du pilote C # pour d'autres exemples de la façon dont cela correspond au cadre d'agrégation de MongoDB en général.