Прежде всего, я должен сказать, что у меня нет большого опыта работы с mongoDB и, вероятно, из-за этого я не могу решить следующую проблему. Проблема в том, что мне нужно агрегировать записи не по полным часам, а по часам, когда приходит первая запись и в часе от предыдущего события.
Пример:
Совершенно ясно, как агрегировать по минутам, часам, дням и т. д., но неясно, могу ли я достичь желаемой агрегации только с mongodb.
ОБНОВЛЯТЬ
Образец данных:
{
"_id":{
"$oid":"5f7eddd73b0e3e7259cf18b9"
},
"agentCodeSystem":"2.16.840.1.113883.2.4.3.118.2.1",
"agentCode":"653259001653360",
"agentName":"Diabetic",
"date":"20201008",
"organisationId":"97490137",
"user":{
"identifiers":[
{
"codeSystemName":"organisation/user",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"653259001653360",
"displayName":""
}
],
"organisation":{
"identifiers":[
{
"codeSystemName":"my-system-name",
"oid":"2.16.840.1.113883.2.4.6.1",
"code":"97490137",
"displayName":""
}
],
"type":"Organisation type",
"name":"My org name"
},
"overseer":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"653259001653360",
"displayName":""
}
],
"name":"Diabetic",
"role":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.4.30.2",
"code":"99",
"displayName":"overig"
}
],
"name":"Overig"
}
},
"role":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.4.30.2",
"code":"99",
"displayName":"overig"
}
],
"name":"Overig"
},
"name":"Diabetic"
},
"event":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.13",
"code":"uid",
"displayName":""
}
],
"eventType":{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"5",
"displayName":""
},
"actionCode":"R",
"dateTime":"20200612165713",
"date":"2019-12-13",
"additionalText":"MyService"
},
"userSubjects":[
{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"653259001653360",
"displayName":""
}
],
"type":"",
"name":"Diabetic",
"gender":"Man",
"birthDate":"2019-10-09",
"organisationId":"97490137",
"organisationName":"my org name"
}
],
"organisationSubjects":[
{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.6.1",
"code":"123214453",
"displayName":""
}
],
"type":"org type",
"name":"org name"
}
]
}
{
"_id":{
"$oid":"5f7eddd73b0e3e7259cf18b9"
},
"agentCodeSystem":"2.16.840.1.113883.2.4.3.118.2.1",
"agentCode":"653259001653360",
"agentName":"Diabetic",
"date":"20201008",
"organisationId":"97490137",
"user":{
"identifiers":[
{
"codeSystemName":"organisation/user",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"653259001653360",
"displayName":""
}
],
"organisation":{
"identifiers":[
{
"codeSystemName":"my-system-name",
"oid":"2.16.840.1.113883.2.4.6.1",
"code":"97490137",
"displayName":""
}
],
"type":"Organisation type",
"name":"My org name"
},
"overseer":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"653259001653360",
"displayName":""
}
],
"name":"Diabetic",
"role":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.4.30.2",
"code":"99",
"displayName":"overig"
}
],
"name":"Overig"
}
},
"role":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.4.30.2",
"code":"99",
"displayName":"overig"
}
],
"name":"Overig"
},
"name":"Diabetic"
},
"event":{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.13",
"code":"uid",
"displayName":""
}
],
"eventType":{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"5",
"displayName":""
},
"actionCode":"R",
"dateTime":"20200612155713",
"date":"2019-12-13",
"additionalText":"MyService"
},
"userSubjects":[
{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.3.118.2.1",
"code":"653259001653360",
"displayName":""
}
],
"type":"",
"name":"Diabetic",
"gender":"Man",
"birthDate":"2019-10-09",
"organisationId":"97490137",
"organisationName":"my org name"
}
],
"organisationSubjects":[
{
"identifiers":[
{
"codeSystemName":"my code system name",
"oid":"2.16.840.1.113883.2.4.6.1",
"code":"123214453",
"displayName":""
}
],
"type":"org type",
"name":"org name"
}
]
}
Это возможно, но потребует огромного количества проекционных вычислений и этапов, и сложность действительно того не стоит, если вы не ограничены в производительности. Вам лучше делать это в памяти, учитывая, что набор не будет превышать ваши возможности. Если это так, вы должны просто пакетно разделить записи. В любом случае кешируйте вычисленные результаты. Кроме того, вы зависите от первой записи в зависимости от ее рекордного времени, верно?
@zhulien да, я завишу от первой записи, и мне нужна необходимая информация только для статистики, поэтому производительность не должна быть проблемой, поэтому все в порядке, если программа будет собирать статистику, например, в течение недели. Спасибо zhulien за ваши усилия.
@exStas На самом деле я начал играть с примером решения для вас, но диапазоны групп (11:55–12:55, 12:56–13:56) должны рассчитываться для каждой отдельной даты, а также смешиваться между датами, поэтому это довольно усложняет код монго, поэтому я бы придерживался стратегии реализации вне БД ради вашего собственного здравомыслия.
@zhulien, если хотите, можете опубликовать ответ со своими рекомендациями, и я приму его.
Как вы можете сгруппировать по любому часу, когда ваше поле date
содержит только дату, но не информацию о времени? Пожалуйста, предоставьте действительные образцы данных, тогда я смогу вам помочь. Я думаю, вам нужно использовать этап $reduce.
@WernfriedDomscheit я обновил вопрос. Сейчас все в порядке или мне предоставить больше информации?
Как можно сгруппировать один документ? date:2020-06-12
недействителен JSON
@WernfriedDomscheit это просто пример какой-то записи. У меня их количество. Я исправил это, так что теперь это действительный JSON
И как вы группируете один документ? Предоставьте образец входных данных, который должен дать результат, который вы показываете в своем ответе. - Это последний раз, когда я прошу об этом.
@WernfriedDomscheit я предоставил реальные данные json, которые я храню. Я немного изменил имена и значения некоторых полей. Необходимо сгруппировать/уменьшить/и т. д. по «event.dateTime» в диапазоне «event.date» для определенного «organisationId». Вы можете заполнить больше документов в БД с заданными интервалами времени в примере таблицы.
Не портите свои посты. Размещая на этом сайте, вы безоговорочно предоставляете сети Stack Exchange право распространять этот контент по лицензии CC BY-SA 4.0 до тех пор, пока она считает это целесообразным. Альтернативы удалению см.: Я лучше подумал над своим вопросом; могу ли я удалить его?
Поскольку это непростая задача в MongoDB, вам лучше сделать это в вашей бизнес-логике. Результат, которого вы хотите достичь, в основном представляет собой форму агрегации по диапазонам. Дело в том, что ваши диапазоны не являются предопределенным набором, а являются динамическими и рассчитываются на основе первой записи + дополнительного значения времени. Итак, в основном у вас есть:
[firstTime -> firstTime + 60m]
, [firstTime + 60m + 1s -> firstTime + 60m + 60m]
, ... до бесконечности. Это выполнимо, но требует либо хакерских динамических выражений для этапа группировки, либо проекционных расчетов. Оба эти подхода кажутся ненужными сложными для вашего варианта использования.
Вы можете проверить некоторые связанные сценарии здесь и здесь, если вы действительно хотите пойти по этому пути.
Если вы решите сделать это в своей бизнес-логике, в зависимости от размера вашего набора данных, вы можете пакетировать данные, чтобы не превысить доступную память, но это должно быть проблемой, только если вы вычисляете агрегат в первый раз с приличным большой набор необработанных данных. Кроме того, в любом случае вы должны кэшировать результаты вычислений, поскольку они не будут меняться (конечно, за исключением последнего открытого диапазона дат). Учитывая, что вы делаете это для статистики, и вы не ограничены в производительности, вы определенно должны сделать себе одолжение и выбрать более удобный способ сделать это в коде.
Ваши образцы данных по-прежнему бесполезны! Удалите все не относящиеся к делу вещи. И дайте нам больше, чем просто 2 документа, чтобы охватить все требования.
В любом случае, я буду стараться изо всех сил.
Это очень плохой дизайн для хранения значений даты/времени в виде строки (или числа). Всегда используйте подходящие Date
объекты.
db.collection.aggregate([
// convert string into proper `Date` object
{ $set: { ts: { $dateFromString: { dateString: "event.dateTime", format: "%Y%m%d%H:%M:%S" } } } }
{ $sort: { ts: 1 } },
// put documents into an array
{ $group: { _id: { date: "$event.date", organisationId: "$organisationId" }, data: { $push: "$$ROOT" } } },
{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
$cond: {
// compare timestamp with boundary of previous element
if: { $gt: ["$$this.ts", { $last: "$$value.boundary" }] },
// new interval: increase bucket number and set new boudnary
then: {
$mergeObjects: [
"$$this", {
bucket: { $add: [{ $ifNull: [{ $last: "$$value.bucket" }, 0] }, 1] },
boundary: { $add: ["$$this.ts", 1000 * 60 * 60] }
}
]
},
// same interval: append element
else: { $mergeObjects: ["$$this", { boundary: { $last: "$$value.boundary" }, bucket: { $last: "$$value.bucket" } }] }
}
}
]
]
}
}
}
}
},
{ $unwind: "$data" }, // transpose array back to documents
// count the documents by bucket number
{
$group: {
_id: { date: "$data.date", organisationId: "$data.organisationId", bucket: "$data.bucket" },
ts_min: { $min: "$data.ts" },
ts_max: { $max: "$data.ts" },
count: { $sum: 1 }
}
},
// some cosmetic
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$_id"] } } },
{ $unset: "_id" }
])
Благодарю вас за ваше усилие! Я только что проверил запрос и подтверждаю, что он работает нормально! Чего я не совсем понимаю, так это зачем нам здесь нужна операция $concatArrays? Что именно он здесь делает? Заранее спасибо!
Мне нужно создать массив, в котором я могу сравнить текущую метку времени с предыдущими. Без $concatArrays
поле data
будет иметь только последнее значение (для 13:04), больше ничего
Хм, а почему поле data
будет пустым, если мы будем помещать документы в это поле в операции $group
?
Пожалуйста, предоставьте некоторые образцы данных. Что вы пробовали до сих пор?