La query per contare le occorrenze "univoche" all'interno di un "EndpointId"
di ciascuno dei "Uid"
in "Tags"
e il "Type"
in "Sensors"
sarebbe:
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" }
}}
])
O per 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)
}
);
Quali uscite:
{
"EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",
"tagCount" : 4,
"sensorCount" : 16
}
Sebbene in realtà sia il modo "più efficiente" per farlo, considerando che i documenti presentati hanno valori univoci per "Uid"
comunque sarebbe $reduce
gli importi all'interno dei documenti stessi:
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" ] }
}
}
}
}
}
}
}
}}
])
Tuttavia, l'istruzione non si adatta bene a LINQ, quindi ti verrà richiesto di utilizzare BsonDocument
interfaccia per creare il BSON per l'istruzione. E ovviamente dove lo stesso "Uid"
i valori "did" si verificano infatti all'interno di più documenti nella raccolta, quindi il $unwind
le istruzioni sono necessarie per "raggrupparle" tra i documenti dall'interno delle voci dell'array.
Originale
Puoi risolverlo ottenendo il $size
degli array. Per l'array esterno ciò si applica semplicemente al percorso del campo dell'array nel documento e per gli elementi dell'array interno è necessario elaborare con $map
per elaborare ogni "Tags"
elemento e quindi ottenere il $size
di "Sensors"
e $sum
l'array risultante da ridurre al conteggio complessivo.
Per documento sarebbe:
db.collection.aggregate([
{ "$project": {
"tags": { "$size": "$Tags" },
"sensors": {
"$sum": {
"$map": {
"input": "$Tags",
"in": { "$size": "$$this.Sensors" }
}
}
}
}}
])
La posizione in cui hai assegnato le classi nel tuo codice C# sarebbe come:
collection.AsQueryable()
.Select(p => new
{
tags = p.Tags.Count(),
sensors = p.Tags.Select(x => x.Sensors.Count()).Sum()
}
);
Dove ritornano:
{ "tags" : 3, "sensors" : 13 }
{ "tags" : 2, "sensors" : 8 }
Dove vuoi $group
i risultati, ad esempio sull'intera collezione, faresti:
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" }
}
}
}
}
}}
])
Quale per il tuo codice C# come prima sarebbe:
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())
}
);
Dove ritornano:
{ "tags" : 5, "sensors" : 21 }
E per "EndpointId
, quindi utilizzi semplicemente quel campo come chiave di raggruppamento, anziché null
o 0
come viene applicato dalla mappatura del driver 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())
}
);
Che è ovviamente la stessa somma dei due campioni di documenti che ci hai fornito:
{ "tags" : 5, "sensors" : 21 }
Quindi questi sono risultati molto semplici, con una semplice esecuzione della pipeline una volta che ti sei abituato alla sintassi.
Suggerisco di familiarizzare con gli Operatori di aggregazione dalla documentazione di base e, naturalmente, da "Foglio informativo LINQ" di espressioni e la loro mappatura di utilizzo dall'interno del repository di codice del driver C#.
Consulta anche il LINQ Reference nel riferimento al driver C# per altri esempi di come questo si associa al framework di aggregazione di MongoDB in generale.