Группировка

Последнее обновление: 28.10.2022

Группировка позволяет сгруппировать документы в выборке по определенному критерию. Для группировки у объекта IMongoCollection применяется метод Aggregate, а у его результата - объекта IAggregateFluent вызывается метод Group().

Метод Group в качестве параметра принимает BsonDocument, который описывает, как должна проводиться группировка. Например, пусть у нас будет следующая коллекция employees со следующими документами:

{ "_id" : ObjectId("635ba2034b36410fce764516"), "Name" : "Tom", "Age" : 38, "Company" : { "Title" : "Microsoft" } }
{ "_id" : ObjectId("635ba2034b36410fce764517"), "Name" : "Bob", "Age" : 42, "Company" : { "Title" : "Google" } }
{ "_id" : ObjectId("635ba2034b36410fce764518"), "Name" : "Sam", "Age" : 25, "Company" : { "Title" : "Microsoft" } }
{ "_id" : ObjectId("635ba2034b36410fce764519"), "Name" : "Alice", "Age" : 33, "Company" : { "Title" : "Google" } }
{ "_id" : ObjectId("635ba2034b36410fce76451a"), "Name" : "Dan", "Age" : 33, "Company" : { "Title" : "Microsoft" } }

После "Company" представляет вложенный документ (условную компаниию, где работает человек), в котором свойство "Title" хранит название компании. Сгруппируем данные по этому полю (то есть сгруппируем данные по компании):

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<BsonDocument>("employees");

var employees = await collection.Aggregate()
    .Group( new BsonDocument { 
        { "_id", "$Company.Title" },               // группируем по имени компании
        { "count", new BsonDocument("$sum", 1) }, // получаем количество документов в группе
    })
    .ToListAsync();

foreach (var employeeGroup in employees) 
    Console.WriteLine(employeeGroup);

Итак, в метод Group() передается документ BsonDocument, который устанавливает параметры группировки. При этом нам надо установить параметр "_id" - ключ группировки. В данном случае для ключа выбрано поле Company.Title, при этом к названию поля добавляется знак доллара: "$Company.Title". То есть каждая группа объектов будет иметь одно и то же значение поля "Company.Title".

{ "_id", "$Company.Title" }

И также можно установить дополнительные поля для группы. Так, здесь к группе добавляется поле count, которое будет содержать количество объектов в группе. Для подсчета количества применяется функция $sum

{ "count", new BsonDocument("$sum", 1) }

То есть в данном записи формально мы говорим, что значением поля "count" в выходном документе будет результат, который задается объектом new BsonDocument("$sum", 1). А данный объект выполняет встроенную функцию mongodb - функцию "count", которая вычисляет количество документов в группе. Значение "1" указывает на множитель.

В результате мы получим следующий консольный вывод:

{ "_id" : "Google", "count" : 2 }
{ "_id" : "Microsoft", "count" : 3 }

count - не единственный оператор, который мы можем использовать при группировке. Доступные операторы:

  • $sum вычисляет сумму

  • $avg вычисляет среднее значение

  • $first получает значение поля из первого документа группы

  • $last получает значение поля из последнего документа группы

  • $max вычисляет максимальное значение

  • $min вычисляет минимальное значение

  • $push добавляет значение в массив

  • $addToSet добавляет значение в набор

Например, по группам найдем среднее, максимальное и минимальное значение возраста (то есть значение поля "Age"):

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<BsonDocument>("employees");

var employees = await collection.Aggregate()
    .Group( new BsonDocument {
        { "_id", "$Company.Title" },
        { "minAge", new BsonDocument("$min", "$Age") },
        { "maxAge", new BsonDocument("$max", "$Age") },
        { "avgAge", new BsonDocument("$avg", "$Age") }
    })
    .ToListAsync();

foreach (var employeeGroup in employees) 
    Console.WriteLine(employeeGroup);

Консольный вывод:

{ "_id" : "Microsoft", "minAge" : 25, "maxAge" : 38, "avgAge" : 32.0 }
{ "_id" : "Google", "minAge" : 33, "maxAge" : 42, "avgAge" : 37.5 }

Другая задача - выведем для каждой группы значение "Name" первого и последнего документа группы:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<BsonDocument>("employees");

var employees = await collection.Aggregate()
    .Group( new BsonDocument {
        { "_id", "$Company.Title" },
        { "first", new BsonDocument("$first", "$Name") },
        { "last", new BsonDocument("$last", "$Name") }
    })
    .ToListAsync();

foreach (var employeeGroup in employees) 
    Console.WriteLine(employeeGroup);

Консольный вывод:

{ "_id" : "Microsoft", "first" : "Tom", "last" : "Dan" }
{ "_id" : "Google", "first" : "Bob", "last" : "Alice" }

Третья задача - добавм в каждую группу имена сотрудников соответствующей компании:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection("employees");

var employees = await collection.Aggregate()
    .Group(new BsonDocument {
        { "_id", "$Company.Title" },
        { "employees", new BsonDocument("$push", "$Name") } })
    .ToListAsync();

foreach (var employeeGroup in employees) 
    Console.WriteLine(employeeGroup);

Консольный вывод:

{ "_id" : "Google", "employees" : ["Bob", "Alice"] }
{ "_id" : "Microsoft", "employees" : ["Tom", "Sam", "Dan"] }

Использование классов C#

Вместо стандартного BsonDocument мы можем использовать свойства стандартных классов C#, которые описывают используемые данные:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<Employee>("employees");

var companies = await collection.Aggregate()
    .Group(emp => emp.Company!.Title,   // группировка по свойству Company.Title
    g => new CompanyModel       // из группы создаем объект CompanyModel
    { 
        CompanyName = g.Key, 
        Employees = g.Select(e=>e.Name).ToList() // выбираем в список имена сотрудников
    })
    .ToListAsync();

foreach (var company in companies)
{
    Console.WriteLine(company.CompanyName);
    foreach (var employeeName in company.Employees)
        Console.WriteLine(employeeName);
    Console.WriteLine();
}

class CompanyModel
{
    public string CompanyName { get; set; } = "";
    public List<string> Employees { get; set; } = new();
}
record Employee(ObjectId Id, string Name, int Age, Company? Company);
record Company(string Title);

В данном случае коллекция типизируется типом Employee. В перегруженную версию метода Group передаем два делегата. Первый делегат выбирает из класса Employee свойство-признак, по которому будет идти группировка:

.Group(emp => emp.Company!.Title,   // группировка по свойству Company.Title

Второй параметр-делегат в качестве параметра принимает созданную группу и возвращает сгенерированный на базе группы объект. В данном случае для представления объекта определен специальный класс CompanyModel (хотя можно было бы обойтись и анонимным объектом).

g => new CompanyModel       // из группы создаем объект CompanyModel
    { 
        CompanyName = g.Key, 
        Employees = g.Select(e=>e.Name).ToList() // выбираем в список имена сотрудников
    })

Метод g.Select() здесь фактически выполняет функцию операции "$push" - выбирает все имена сотрудников и формирует из них список.

Подобным образом можно задействовать и другие операторы группировки:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<Employee>("employees");


var companies = await collection.Aggregate()
    .Group(emp => emp.Company!.Title,   // группировка по свойству Company.Title
    g =>  new     // из группы создаем анонимный объект
    { 
        CompanyName = g.Key, 
        Employees = g.Select(e=>e.Name).ToList(), // выбираем в список имена сотрудников
        SumAge = g.Sum(e=>e.Age),
        MinAge = g.Min(e=>e.Age),
        MaxAge = g.Max(e=>e.Age),
        AvgAge = g.Average(e=>e.Age),
        First = g.First().Name,
        Lass = g.Last().Name
    })
    .ToListAsync();

foreach (var company in companies)
{
    Console.WriteLine(company.ToJson());
}

record Employee(ObjectId Id, string Name, int Age, Company? Company);
record Company(string Title);
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850