Как сгруппировать / объединить данные в Mongo dB для создания событий

#node.js #mongodb #mongoose #mongodb-query #aggregation-framework

#node.js #mongodb #мангуст #mongodb-запрос #агрегация-фреймворк

Вопрос:

У меня есть данные mongo в таком формате

 [
  { 
     _id:ObjectId("5f71890730a4421699b1fbff"),
    timestamp: ISODate("2020-01-12T03:07:52Z"),
    running_fig: "circle",
  },
  {
    _id:ObjectId("5f718ac330a4421699b1fc15"),
    timestamp: ISODate("2020-01-12T03:08:48Z"),
    running_fig: "circle",
  },
  {
    _id:ObjectId("5f718ac330a4421699b1fc16"),
    timestamp: ISODate("2020-01-12T03:09:32Z"),
    running_fig: "rombous",
  },
  {
    _id:ObjectId("5f718ac330a4421699b1fc14"),
    timestamp: ISODate("2020-01-12T03:10:11Z"),
    running_fig: "triangle",
  },
  {
    _id:ObjectId("5f718ac330a4421699b1fc13"),
    timestamp: ISODate("2020-01-12T03:11:52Z"),
    running_fig: "triange",
  },
  {
    _id:ObjectId("5f718ac330a4421699b1fc12"),  
    timestamp: ISODate("2020-01-12T03:15:22Z"),
    running_fig: "circle",
  },
  {
     _id:ObjectId("5f718ac330a4421699b1fc1e"),
    timestamp: ISODate("2020-01-12T03:20:52Z"),
    running_fig: "circle",
  },
  
]
 

** Теперь я хочу составить диаграмму событий в соответствии с текущим временем выполнения, я ожидал получить результат запроса в приведенной ниже форме, которую я дал**

 [
  {
running_fig:“circle”,
from: 2020-12-21T03: 07: 52Z,
to: 2020-12-21T03: 09: 48Z,
duration: 2 min.
  },
  {
running_fig:“rombous”,
from: 2020-12-21T03: 09: 48Z,
to: 2020-12-21T03: 10: 32Z,
duration: 1 min.
  },
  {
running_fig:“triangle”,
from: 2020-12-21T03: 10: 32Z,
to: 2020-12-21T03: 15: 22Z,
duration: 5 min.
  },
  {
running_fig:“circle”,
from: 2020-12-21T03: 15: 22Z,
to: 2020-12-21T03: 25: 52Z (current time),
duration: 10 min.
  }
]
 

итак, я хочу, чтобы результирующие данные были в этом формате, чтобы я мог соответствующим образом создать диаграмму, в этих данных моя диаграмма
running_fig circle start_time — это собственные временные метки, а ее end_time — следующие временные метки
running_fig в моем случае следующий ромб, поэтому продолжительность running_fig circle равна (3:09 -3:07) = 2 минуты , и они объединение в единые данные показывает мой ожидаемый результат.
кто-нибудь, пожалуйста, помогите мне выполнить этот запрос, заранее спасибо

Комментарии:

1. Почему первый круг заканчивается на 2020-12-21T03:09:48Z ? Я не понимаю, откуда берется время. То же самое относится и к другим временам.

2. @WernfriedDomscheit спасибо за ваше внимание, в этом случае данные продолжают поступать. когда значение running_fig изменяется в моем случае circle на Rombous, это означает, что при времени начала круга (3:07) и времени начала Rombous (3:09) между этой продолжительностью circle переходит в running_fig, поэтому его продолжительность составляет 3 минуты. то же самое относится ко всем.

3. Не ясно, что вы имеете в виду. Пожалуйста, предоставьте точные входные данные в соответствии с желаемым результатом.

4. @WernfriedDomscheit спасибо за ответ. на самом деле данные моего датчика регистрируются в MongoDB при изменении данных, у меня есть около 10 параметров для регистрации, когда любой параметр изменяет его, чтобы регистрировать все данные в моей базе данных, поэтому я получаю тот же 2020-12-21T03:09:48Z

5. Я все еще не понимаю твоей логики. Скорее всего, вам придется работать с $reduce . Конечно, он будет содержать выражение {$last: "$$value."$timestamp"}

Ответ №1:

Как уже упоминалось, ваши выборочные данные не соответствуют ожидаемому результату, поэтому трудно понять логику. Но эта агрегация должна показать направление, в котором она может развиваться.

Версия с использованием $reduce :

 db.collection.aggregate([
   { $sort: { timestamp: -1 } },
   // Transform documents to array
   { $group: { _id: null, data: { $push: "$ROOT" } } },
   // combine timestamp with previous timestamp
   {
      $set: {
         data: {
            $reduce: {
               input: "$data",
               initialValue: [],
               in: {
                  $concatArrays: ["$value",
                     [{
                        running_fig: "$this.running_fig",
                        from: "$this.timestamp",
                        to: { $ifNull: [{ $last: "$value.from" }, "$NOW"] }
                     }]
                  ]
               }
            }
         }
      }
   },
   { $unwind: "$data" },
   { $sort: { "data.from": 1 } },
   { $group: { _id: null, data: { $push: "$ROOT.data" } } },
   // find consequtive running_fig
   {
      $set: {
         data: {
            $reduce: {
               input: "$data",
               initialValue: [],
               in: {
                  $concatArrays: ["$value",
                     [{
                        $cond: {
                           if: { $ne: [{ $last: "$value.running_fig" }, "$this.running_fig"] },
                           then: "$this",
                           else: null
                        }
                     }]
                  ]
               }
            }
         }
      }
   },
   // remove null values from array
   { $set: { data: { $filter: { input: "$data", cond: { $ne: ["$this", null] } } } } },
   { $unwind: "$data" }
   { $replaceRoot: { newRoot: "$data" } }
])
 

Версия с использованием $map и $range :

 db.collection.aggregate([
   { $sort: { timestamp: 1 } },
   { $group: { _id: null, data: { $push: "$ROOT" } } },
   {
      $set: {
         data: {
            $map: {
               input: { $range: [0, { $size: "$data" }] },
               as: "idx",
               in:
                  {
                     $cond: {
                        if: {
                           $ne: [
                              { $arrayElemAt: ["$data.running_fig", "$idx"] },
                              { $arrayElemAt: ["$data.running_fig", { $add: ["$idx", 1] }] }
                           ]
                        },
                        then: {
                           running_fig: { $arrayElemAt: ["$data.running_fig", "$idx"] },
                           from: { $arrayElemAt: ["$data.timestamp", "$idx"] },
                           to: { $ifNull: [{ $arrayElemAt: ["$data.timestamp", { $add: ["$idx", 1] }] }, "$NOW"] }
                        },
                        else: null
                     }
                  }
            }
         }
      }
   },
   { $set: { data: { $filter: { input: "$data", cond: { $ne: ["$this", null] } } } } },
   { $unwind: "$data" },
   { $replaceRoot: { newRoot: "$data" } }
]);
 

Комментарии:

1. очень, очень спасибо, я ожидаю того же. еще один вопрос, который я должен задать, если у меня есть миллиарды данных, так какой метод более эффективен. @WernfriedDomscheit

2. Использование $map явно быстрее, см. jira.mongodb.org/browse/SERVER-53503

Ответ №2:

Это немного утомительное решение.

Объяснение

  1. Поместите данные в массив (при условии, что вы получаете данные от датчика 1)
  2. Сортировка по метке времени
  3. проверьте предварительный индекс в массиве, если предварительный индекс running_fig = текущий индекс running_fig, удалите эти данные из массива.
  4. Добавьте следующие индексные данные.
  5. если следующая временная метка индекса равна нулю, добавьте текущую временную метку

Код

 db.collection.aggregate([
  /** group by machine name*/
  {
    "$group": {
      "_id": "$sensor",
      docs: {
        $push: "$ROOT"
      }
    },
    
  },
  /** sort by date to make event list*/
  {
    $sort: {
      "docs.timestamp": -1
    }
  },
  /** get pre data*/
  {
    $project: {
      docs: {
        /** transform the "docs" field*/
        $map: {
          /** into something*/
          input: {
            $range: [
              0,
              {
                $size: "$docs"
              }
            ]
          },
          /** an array from 0 to n - 1 where n is the number of documents*/
          as: "this",
          /** which shall be accessible using "$this"*/
          in: {
            $mergeObjects: [
              /** we join two documents*/
              {
                $arrayElemAt: [
                  "$docs",
                  "$this"
                ]
              },
              /** one is the nth document in our "docs" array*/
              {
                "pre_index": {
                  $cond: [
                    {
                      "$gte": [
                        {
                          "$subtract": [
                            "$this",
                            1
                          ]
                        },
                        0
                      ]
                    },
                    {
                      "$arrayElemAt": [
                        "$docs",
                        {
                          "$subtract": [
                            "$this",
                            1
                          ]
                        },
                        
                      ]
                    },
                    null
                  ]
                },
                index: "$this"
              }/** and the second document is the one with our "index" field*/
              
            ]
          }
        }
      }
    }
  },
  /**remove same state data*/
  {
    $project: {
      _id: "$_id",
      noDuplicateArray: {
        $filter: {
          input: "$docs",
          as: "a",
          cond: {
            $ne: [
              "$a.running_fig",
              "$a.pre_index.running_fig"
            ]
          }
        }
      }
    }
  },
  /**add next data*/
  {
    $project: {
      docs: {
        /** transform the "docs" field*/
        $map: {
          /** into something*/
          input: {
            $range: [
              0,
              {
                $size: "$noDuplicateArray"
              }
            ]
          },
          /** an array from 0 to n - 1 where n is the number of documents*/
          as: "this",
          /** which shall be accessible using "$this"*/
          in: {
            $mergeObjects: [
              /** we join two documents*/
              {
                $arrayElemAt: [
                  "$noDuplicateArray",
                  "$this"
                ]
              },
              /** one is the nth document in our "docs" array*/
              {
                "to": {
                  "$arrayElemAt": [
                    "$noDuplicateArray",
                    {
                      $add: [
                        "$this",
                        1
                      ]
                    },
                    
                  ]
                },
                index: "$this"
              }/** and the second document is the one with our "index" field*/
              
            ]
          }
        }
      }
    }
  },
  {
    "$unwind": "$docs"
  },
  {
    "$project": {
      _id: "$docs._id",
      sensor: "$_id",
      from: "$docs.timestamp",
      to: {
        "$ifNull": [
          "$docs.to.timestamp",
          "$NOW"
        ]
      },
      running_fig: "$docs.running_fig",
      duration: {
        $concat: [
          {
            $toString: {
              $round: [
                {
                  $divide: [
                    {
                      $subtract: [
                        {
                          "$ifNull": [
                            "$docs.to.timestamp",
                            "$NOW"
                          ]
                        },
                        "$docs.timestamp"
                      ]
                    },
                    60000
                  ]
                },
                1
              ]
            }
          },
          " min"
        ]
      }
    }
  }
])
 

Игровая площадка Монго : https://mongoplayground.net/p/CxHNdO6vLop

Комментарии:

1. Я думаю, что это $sort должно прийти раньше $group — и date в любом случае поля нет.

2. @WernfriedDomscheit каков параметр производительности, если миллион данных