Рассчитать общее количество часов в день сверх даты начала и окончания - PullRequest
0 голосов
/ 18 марта 2019

У меня много таких документов:

{_id: ObjectId("5adc864eaaf408a2b6e325f7"), employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-04-22 12:06:46.623" }, start: { day: "2018-04-22 11:06:46.623" }, date: "2018-04-22 11:06:46.623"}
{_id: ObjectId("5adc864eaaf408a2b6e325c8"),employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-04-22 10:06:46.623" }, start: { day: "2018-04-22 8:06:46.623" }, date: "2018-04-22 11:06:46.623"}
{_id: ObjectId("5adc864eaaf408a2b6e325f6"),employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-05-22 12:06:46.623" }, start: { day: "2018-04-22 11:06:46.623" }, date: "2018-05-22 11:06:46.623"}
{_id: ObjectId("5adc864eaaf408a2b6e325c4"),employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-05-22 10:06:46.623" }, start: { day: "2018-05-22 8:06:46.623" }, date: "2018-05-22 11:06:46.623"}

это представляет деятельность каждого сотрудника в течение дня.

Мне нужно рассчитать количество часов, отработанных в день, взяв количество часов каждого действия между начальной датой «start.day» и конечной датой «end.day» каждого действия и суммируя все действия одного день.

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

1 Ответ

1 голос
/ 18 марта 2019

Итак, первое, что действительно нужно охватить, это то, что ваши текущие «даты» - это все «строки», и это действительно не помогает.Вам лучше будет конвертировать все в дату BSON, поскольку это в основном то, что требуется для операций агрегации в любом случае.

Второй момент заключается в том, что получение итоговых сумм за "каждый день" в течение определенного интервала не так просто.На самом деле вам действительно нужно добавить несколько выражений в MongoDB, чтобы сделать такую ​​вещь:

db.collection.aggregate([
  { "$addFields": {
    "start": { "$toDate": "$start.day" },
    "end": { "$toDate": "$end.day" },
    "date": { "$toDate": "$date" },
    "dayworking": {
      "$map": {
        "input": {
          "$range": [
            0,
            { "$ceil": {
              "$divide": [
                { "$subtract": [
                  { "$toDate": "$end.day" },
                  { "$toDate": "$start.day" }
                ]},
                1000 * 60 * 60 * 24
              ]
            }}
          ]
        },
        "in": {
          "$toDate": {
            "$add": [
              { "$multiply": ["$$this", 1000 * 60 * 60 * 24 ] },
              { "$subtract": [
                { "$toLong": { "$toDate": "$start.day" } },
                { "$mod": [ { "$toLong": { "$toDate": "$start.day" } }, 1000 * 60 * 60 * 24 ] }
              ]}
            ]
          }
        }
      }
    }
  }},
  { "$unwind": "$dayworking" },
  { "$group": {
    "_id": {
      "employee": "$employee",
      "day": "$dayworking"
    },
    "hours": {
      "$sum": {
        "$floor": {
          "$divide": [
            { "$switch": {
              "branches": [
                { 
                  "case": {
                    "$and": [
                      { "$lt": [ "$dayworking", "$start" ] },
                      { "$gt": [
                        { "$add": [ "$dayworking", 1000 * 60 * 60 * 24 ] },
                        "$end"
                      ]}
                    ]
                  },
                  "then": { "$subtract": [ "$end", "$start" ] }
                },
                {
                  "case": {
                    "$lt": [
                      "$end",
                      { "$add": [ "$dayworking", 1000 * 60 * 60 * 24 ] }
                    ]
                  },
                  "then": {
                    "$subtract": [ "$end", "$dayworking" ]
                  }
                },
                {
                  "case": { "$lt": [ "$dayworking", "$start" ] },
                  "then": {
                    "$subtract": [
                      { "$add": [ "$dayworking", 1000 * 60 * 60 * 24 ] },
                      "$start"
                    ]
                  }
                }
              ],
              "default": 1000 * 60 * 60 * 24
            }},
            1000 * 60 * 60
          ]
        }
      }
    }
  }},
  { "$sort": { "_id": 1 } }
])

В основном возвращает каждый день в пределах начального и конечного интервалов как (усечение для краткости):

{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-04-22T00:00:00Z")
        },
        "hours" : 15
}
{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-04-23T00:00:00Z")
        },
        "hours" : 24
}

.... each day in between ...

{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-05-21T00:00:00Z")
        },
        "hours" : 24
}
{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-05-22T00:00:00Z")
        },
        "hours" : 14
}

Которые выделяются 24 часа в «целые дни» и неполные часы в другие.Как видно из вашего примера, первый день содержит данные, которые генерируют как:

{
        "_id" : ObjectId("5adc864eaaf408a2b6e325f7"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-04-22T12:06:46.623Z"),
        "start" : ISODate("2018-04-22T11:06:46.623Z"),
        "date" : ISODate("2018-04-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-04-22T00:00:00Z"),
        "hours" : 1
}
{
        "_id" : ObjectId("5adc864eaaf408a2b6e325c8"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-04-22T10:06:46.623Z"),
        "start" : ISODate("2018-04-22T08:06:46.623Z"),
        "date" : ISODate("2018-04-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-04-22T00:00:00Z"),
        "hours" : 2
}
{
        "_id" : ObjectId("5adc864eaaf408a2b6e325f6"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-05-22T12:06:46.623Z"),
        "start" : ISODate("2018-04-22T11:06:46.623Z"),
        "date" : ISODate("2018-05-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-04-22T00:00:00Z"),
        "hours" : 12
}

Две единственные записи и одна с остатком 12 часов, что составляет 15 часов и последний день:

{
        "_id" : ObjectId("5adc864eaaf408a2b6e325f6"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-05-22T12:06:46.623Z"),
        "start" : ISODate("2018-04-22T11:06:46.623Z"),
        "date" : ISODate("2018-05-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-05-22T00:00:00Z"),
        "hours" : 12
}
{
        "_id" : ObjectId("5adc864eaaf408a2b6e325c4"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-05-22T10:06:46.623Z"),
        "start" : ISODate("2018-05-22T08:06:46.623Z"),
        "date" : ISODate("2018-05-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-05-22T00:00:00Z"),
        "hours" : 2
}

Имеет 2-часовой ввод и еще 12-часовой остаток, что в сумме составляет 14.

Пояснение

Преобразование даты и математика

Для объяснениячто по сути есть две основные вещи, которые необходимо сделать за пределами очевидного «преобразования даты».Кстати, это можно сделать с помощью $toDate из MongoDB 4.0 или через $dateFromString, если у вас есть MongoDB 3.6.Отмечая, что в последнем случае вам также необходимо применить различные методы для "математика даты"

Существуют подробные примеры обработки "математика даты" в более ранней версии MongoDBверсии на Группируйте результат по 15-минутному интервалу времени в MongoDb в тех случаях, когда у вас есть более ранняя версия и сначала требуется $dateFromString или прямое преобразование ваших данных.

Проецирование дат в диапазоне

Следующей основной частью этой работы является то, что вам в основном нужно создать массив дат, к которым документ применяется в исходном документе.Это то, что делает выражение $range, принимая начальное значение (в данном случае 0) и конечное значение, которое мы здесь применяем, чтобы быть "числом дней между" значения даты start и end.

Эта разница возвращается от $subtract в миллисекундах, поэтому используется $divideчерез постоянные миллисекунды в день, чтобы получить целое число.Используйте $ceil здесь для округления, но это также может быть просто $mod с $subtract, где этот оператор недоступен в более ранних версиях.

В этот момент $range действительно только что создал массив целочисленных значений, поэтому $map применяется к этому массиву для преобразования этихфактическим объектам BSON Date, которые должны представлять «день», к которому относятся данные.Опять же, это всего лишь "математика даты" , применяющая сложение значения индекса массива (+1, конечно) к исходной округленной начальной дате.

Расчет часов

Теперь с массивом дат из более ранней стадии и некоторым другим переформатированием значений документа в пригодные для использования даты BSON, вам нужно фактически сравнить этот «массив» содержимогос каждым значением start и end для определения количества часов, примененных в тот день.

Первый базовый случай и почему мы на самом деле создали массив , для этого используется $unwind, что фактически копирует итоговый документ за каждый день, произошедший в течение интервала.Это маленький, но важный шаг, который должен произойти до того, как вы $group и действительно посчитаете вещи.Суть в том, что $group будет фактически использовать эти значения как часть своего «первичного ключа» для вывода, а также сравнения с другой информацией о дате.

Конечно, реальная работа здесь все делается в операторе $switch, который также может быть "вложенным" использованием$cond в более ранних версиях.Здесь вы в основном хотите проанализировать три возможных случаев и, конечно, запасной вариант по умолчанию для "полного дня".

В основном это следующие случаи:

  • Там, где текущий «день группировки» меньше start И «следующий день» будет больше даты end,просто вычтите разницу.

  • Если не указано иное, то, когда дата end меньше, чем «следующий день» для группировки, вычтите это «день группировки» от текущей end даты, чтобы получить часы этого начала дня до end времени.

  • Если не указано иное, то когда «день группирования» меньше start (без другого условия end из более раннего), тогда отработанные часы будут из «следующего дня» , вычитающего start для разницы от start до конца текущего дня.

  • Если это не так, то dпо умолчанию отображается «целый день», который в этом примере обозначается как 24 часа.

Если у вас были другие рабочие часы, то нужно просто настроить его вто есть "начало дня" +8 часов для начала 8 утра.То же самое в основном относится к «концу дня», добавляя что-то вроде +17 для финиша в 5 вечера.Но основные принципы реализуемой логики остаются такими же, как показано выше.

ПРИМЕЧАНИЕ : Основное ограничение здесь $range, котороеЯ полагаю, что это был MongoDB 3.0 или, возможно, 3.2.В любом случае вам, вероятно, действительно не следует использовать какую-либо версию MongoDB до версии 3.4 в настоящее время.

Если у вас была более ранняя версия, то есть еще некоторые подробности о Группировать и считатьначальный и конечный диапазоны с другим моим ранним ответом, показывающим аналогичный процесс, использующий несколько запросов и даже mapReduce() в дополнение к очень похожему $range примеру.

...