Я новичок в MongoDB и хотел знать, как подсчитывать общее количество документов, вставленных в коллекцию, для каждого 15-минутного интервала, начиная с 12 утра по всемирному координированному времени до текущего времени по всемирному координированному времени.
Ниже приведен образец документа
{
"_id" : ObjectId("5ade8bfc6b941c7726a54f01"),
"Country" : "US"
"Timestamp" : ISODate("2018-04-24T01:44:28.040Z"),
}
Вот ожидаемый результат:
{
"Count": 245,
"ReceiveDateString": "5/2/2018 12:00:00 AM"
},
{
"Count": 239,
"ReceiveDateString": "5/2/2018 12:15:00 AM"
},
{
"Count": 252,
"ReceiveDateString": "5/2/2018 12:30:00 AM"
},
{
"Count": 255,
"ReceiveDateString": "5/2/2018 12:45:00 AM"
},
{
"Count": 242,
"ReceiveDateString": "5/2/2018 1:00:00 AM"
}
.
.
.
and so on until current UTC time.
Я могу сгруппировать по минутам следующим образом:
var filter = Builders<Model>.Filter.Where(r => r.Timestamp > startDate && r.Timestamp < endDate);
var result = Collection.Aggregate()
.Match(filter)
.Group(
r => r.Timestamp.Minute,
g => new
{
ReceiveDate = g.Select(x => x.Timestamp).First(),
Count = g.Count(),
}
).ToEnumerable();
Однако я не могу понять, как преобразовать решение, предоставленное в Группируем результат по 15-минутному интервалу в MongoDb, в запрос драйвера MongoDB C#.
Спасибо.





Если вы ищете «точную вещь», такую как ссылка на сообщение, связанную с .NET, то, вероятно, на самом деле это не будет реализовано таким образом. Вы можете это сделать, но вы, вероятно, не собираетесь беспокоиться и на самом деле выберете одну из других альтернатив, если только вам не нужны «гибкие интервалы» в той степени, в которой я нуждаюсь ...
Если у вас есть современный сервер MongoDB 3.6 или выше, вы можете использовать $dateFromParts для восстановления даты из «округленных» частей, извлеченных из даты:
DateTime startDate = new DateTime(2018, 5, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime endDate = new DateTime(2018, 6, 1, 0, 0, 0, DateTimeKind.Utc);
var result = Collection.Aggregate()
.Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.Group(k =>
new DateTime(k.Timestamp.Year, k.Timestamp.Month, k.Timestamp.Day,
k.Timestamp.Hour, k.Timestamp.Minute - (k.Timestamp.Minute % 15), 0),
g => new { _id = g.Key, count = g.Count() }
)
.SortBy(d => d._id)
.ToList();
Заявление отправлено на сервер:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$dateFromParts" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] },
"second" : 0
}
},
"count" : { "$sum" : 1 }
} },
{ "$sort": { "_id": 1 } }
]
Если у вас нет этой функции, вы можете просто оставить ее выключенной и оставить дату «в разобранном виде», но затем собрать ее снова, когда вы обрабатываете курсор. Просто чтобы смоделировать список:
var result = Collection.Aggregate()
.Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.Group(k => new
{
year = k.Timestamp.Year,
month = k.Timestamp.Month,
day = k.Timestamp.Day,
hour = k.Timestamp.Hour,
minute = k.Timestamp.Minute - (k.Timestamp.Minute % 15)
},
g => new { _id = g.Key, count = g.Count() }
)
.SortBy(d => d._id)
.ToList();
foreach (var doc in result)
{
//System.Console.WriteLine(doc.ToBsonDocument());
System.Console.WriteLine(
new BsonDocument {
{ "_id", new DateTime(doc._id.year, doc._id.month, doc._id.day,
doc._id.hour, doc._id.minute, 0) },
{ "count", doc.count }
}
);
}
Заявление отправлено на сервер:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] }
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
С точки зрения кода между ними очень мало различий. Просто в одном случае "обратное приведение" к DateTime фактически происходит на сервере с $dateFromParts, а в другом мы просто делаем то же самое приведение, используя конструктор DateTime в коде, когда вы повторяете каждый результат курсора.
Таким образом, они действительно почти такие же, с той лишь разницей, что реальная разница в том, где «сервер» выполняет преобразование, возвращаемая дата использует намного меньше байтов на документ. Фактически «в 5 раз» меньше, поскольку все числовые форматы здесь (включая дату BSON) основаны на 64-битных целых числах. Даже в этом случае все эти числа на самом деле «легче», чем отправка любого «строкового» представления даты.
Это основные формы, которые действительно остаются неизменными при отображении на эти разные формы:
var query = from p in Collection.AsQueryable()
where p.Timestamp >= startDate && p.Timestamp < endDate
group p by new DateTime(p.Timestamp.Year, p.Timestamp.Month, p.Timestamp.Day,
p.Timestamp.Hour, p.Timestamp.Minute - (p.Timestamp.Minute % 15), 0) into g
orderby g.Key
select new { _id = g.Key, count = g.Count() };
Заявление отправлено на сервер:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$dateFromParts" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] },
"second" : 0
}
},
"__agg0" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } },
{ "$project" : { "_id" : "$_id", "count" : "$__agg0" } }
]
Или используя GroupBy()
var query = Collection.AsQueryable()
.Where(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.GroupBy(k =>
new DateTime(k.Timestamp.Year, k.Timestamp.Month, k.Timestamp.Day,
k.Timestamp.Hour, k.Timestamp.Minute - (k.Timestamp.Minute % 15), 0),
(k, s) => new { _id = k, count = s.Count() }
)
.OrderBy(k => k._id);
Заявление отправлено на сервер:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$dateFromParts" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] },
"second" : 0
}
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
Как видите, это в основном одна и та же форма
Если вы хотите реплицировать исходная форма "математики даты", как опубликовано, то в настоящее время это выходит за рамки того, что вы действительно можете делать с LINQ или со сборщиками Fluent. Единственный способ получить ту же последовательность - использовать конструкцию BsonDocument:
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var group = new BsonDocument { {
"$group",
new BsonDocument {
{ "_id",
new BsonDocument { {
"$add", new BsonArray
{
new BsonDocument { {
"$subtract",
new BsonArray {
new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", epoch } } },
new BsonDocument { {
"$mod", new BsonArray
{
new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", epoch } } },
1000 * 60 * 15
}
} }
}
} },
epoch
}
} }
},
{
"count", new BsonDocument("$sum", 1)
}
}
} };
var query = sales.Aggregate()
.Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.AppendStage<BsonDocument>(group)
.Sort(new BsonDocument("_id", 1))
.ToList();
Запрос отправлен на сервер:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$add" : [
{ "$subtract" : [
{ "$subtract" : [ "$Timestamp", ISODate("1970-01-01T00:00:00Z") ] },
{ "$mod" : [
{ "$subtract" : [ "$Timestamp", ISODate("1970-01-01T00:00:00Z") ] },
900000
] }
] },
ISODate("1970-01-01T00:00:00Z")
]
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
Основная причина, по которой мы не можем сделать это прямо сейчас, заключается в том, что текущая сериализация операторов в основном не согласуется с тем, что .NET Framework говорит, что вычитание двух значений DateTime возвращает TimeSpan, а конструкция MongoDB вычитания двух дат BSON возвращает миллисекунды с эпохи ", по сути, так работает математика.
«Дословный» перевод выражения lamdba по существу:
p => epoch.AddMilliseconds(
(p.Timestamp - epoch).TotalMilliseconds
- ((p.Timestamp - epoch).TotalMilliseconds % 1000 * 60 * 15))
Но отображение все еще требует некоторой доработки, чтобы либо распознать утверждения, либо формализовать, какие утверждения действительно предназначены для этой цели.
В частности, MongoDB 4.0 вводит оператор $convert и общие псевдонимы $toLong и $toDate, которые все могут использоваться в конвейере вместо текущей обработки «сложения» и «вычитания» с датами BSON. Они начинают формировать более «формальную» спецификацию для таких преобразований, а не показанный метод, который полагался исключительно на эти «сложение» и «вычитание», которые по-прежнему действительны, но такие именованные операторы гораздо яснее понимают намерение в коде:
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$Timestamp" },
{ "$mod": [{ "$toLong": "$Timestamp" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
Совершенно очевидно, что с «формализованными» операторами для построения операторов с помощью LINQ для таких функций «DateToLong» и «LongToDate» оператор становится намного чище без типов «приведений», показанных в «нерабочем» лямбда-выражении. Выполнено.