Среднее значение разностей, рассчитанных между двумя полями даты

Я работаю над проектом, который использует Elasticsearch для хранения данных и отображения сложной статистики.

У меня есть индекс, который выглядит так:

Reservation {
  id: number
  check_in: Date
  check_out: Date
  created_at: Date
  // other fields...
}

Мне нужно вычислить среднюю разницу в днях между check_in и created_at моими Reservation в определенном диапазоне дат и показать результат в виде числа.

Я пробовал это query:

{
  "script_fields": {
    "avgDates": {
      "script": {
        "lang": "expression",
        "source": "doc['created_at'].value - doc['check_in'].value"
      }
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "created_at": {
              "gte": "{{lastMountTimestamp}}",
              "lte": "{{currentTimestamp}}"
            }
          }
        }
      ]
    }
  },
  "size": 0,
  "aggs": {
    "avgBetweenDates": {
      "avg": {
        "field": "avgDates"
      }
    }
  }
}

Поля с датами сохраняются в формате ISO 8601 (например, 2020-03-11T14:25:15+00:00), я не знаю, может ли это вызвать проблемы .

Он ловит несколько попаданий, так что запрос точно работает! но он всегда возвращает null в качестве значения агрегации avgBetweenDates.

Мне нужен такой результат:

"aggregations": {
    "avgBetweenDates": {
        "value": 3.14159 // Π is just an example!
    }
}

Любые идеи помогут!

Спасибо.


person Malek Boubakri    schedule 11.03.2020    source источник


Ответы (2)


Сценарные поля не являются сохраненными полями в ES. Вы можете выполнять агрегацию только для сохраненных полей, так как scripted fields создаются на лету.

Вы можете просто переместить логику скрипта в Среднее агрегирование, как показано ниже. Обратите внимание, что для понимания я создал образец сопоставления, документы, запрос и его ответ.

Отображение:

PUT my_date_index
{
  "mappings": {
    "properties": {
      "check_in":{
        "type":"date",
        "format": "date_time"
      },
      "check_out":{
        "type": "date",
        "format": "date_time"
      },
      "created_at":{
        "type": "date",
        "format": "date_time"
      }
    }
  }
}

Образцы документов:

POST my_date_index/_doc/1
{
  "check_in": "2019-01-15T00:00:00.000Z",
  "check_out": "2019-01-20T00:00:00.000Z",
  "created_at": "2019-01-17T00:00:00.000Z"
}

POST my_date_index/_doc/2
{
  "check_in": "2019-01-15T00:00:00.000Z",
  "check_out": "2019-01-22T00:00:00.000Z",
  "created_at": "2019-01-20T00:00:00.000Z"
}

Совокупный запрос:

POST my_date_index/_search
{
  "size": 0,
  "aggs": {
    "my_dates_diff": {
      "avg": {
        "script": """
          ZonedDateTime d1 = doc['created_at'].value;
          ZonedDateTime d2 = doc['check_in'].value;
          long differenceInMillis = ChronoUnit.MILLIS.between(d1, d2);
          return Math.abs(differenceInMillis/86400000);
        """
      }
    }
  }
}

Обратите внимание, что вам нужна разница в количестве дней. Приведенная выше логика делает это.

Ответ:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "my_dates_diff" : {
      "value" : 3.5              <---- Average in Number of Days
    }
  }
}

Надеюсь это поможет!

person Opster ES Ninja - Kamal    schedule 11.03.2020
comment
Это решение работает для меня! ???????? Спасибо, Камаль. Но я вынужден переназначить свой индекс только для того, чтобы добавить "format": "date_time". Если да, есть ли способ сделать это, не удаляя мой старый. - person Malek Boubakri; 12.03.2020
comment
@MalekBoubakri Тебе не нужно. Не могли бы вы запустить agg для существующего индекса, который у вас есть, и сообщить мне, работает ли он для вас. Я думаю, что это должно работать, но если это не так, дайте мне знать. - person Opster ES Ninja - Kamal; 12.03.2020
comment
@Kamal Я думаю, что это работает без него! На самом деле, я думаю, что не должен использовать его на всех датах в моем случае! потому что это похоже на формат date_time === strict_date_time, и это заставляет меня все время передавать значение времени, возможно, strict_date_optional_time в некоторых случаях лучше. Сейчас я читаю docs для получения дополнительной информации. благодарю вас. - person Malek Boubakri; 12.03.2020

Поля со сценариями, созданные в контексте _search, могут использовать только в пределах этой области. Они не видны внутри aggregations! Это означает, что вам придется пойти либо с

  • переместите ваш скрипт в раздел aggs и выполните там avg
  • скриптовая метрика агрегация (довольно медленная и сложная)
  • или создать поле dateDifference во время индекса (предпочтительно int — разница временных меток), которое позволит вам выполнять мощные числовые аггрегации, такие как расширенная статистика, которая предоставляет статистически полезные данные, такие как:
{
    ...

    "aggregations": {
        "grades_stats": {
           "count": 2,
           "min": 50.0,
           "max": 100.0,
           "avg": 75.0,
           "sum": 150.0,
           "sum_of_squares": 12500.0,
           "variance": 625.0,
           "std_deviation": 25.0,
           "std_deviation_bounds": {
            "upper": 125.0,
            "lower": 25.0
           }
        }
    }
}

и всегда быстрее, чем вычисление разницы временных меток с помощью сценария.

person Joe Sorocin    schedule 11.03.2020
comment
Спасибо jzzfs. Я выбрал этот первый вариант ????????, я переместил свой скрипт в раздел aggs, как показано в вашей ссылке и продемонстрировал в принятом ответе. По-прежнему считаю, что третий вариант всегда является наиболее эффективным и лучшим выбором, но в моем случае мне нужно запросить его. - person Malek Boubakri; 12.03.2020
comment
Вы можете запросить его! Это обычное новое поле. Вы можете запросить и агрегировать его. - person Joe Sorocin; 12.03.2020