Эластичная многоуровневая агрегирование родительских дочерних элементов

У меня есть родительская/дочерняя структура на 3 уровнях. Скажем:

Компания → Сотрудник → Доступность

Поскольку доступность (а также Employee) часто обновляется здесь, я выбираю использование родительской/дочерней структуры для вложенных. И функция поиска работает нормально (все документы в правильных осколках).

Теперь я хочу отсортировать эти результаты. Сортировка их по метаданным из компании (1-й уровень) проста. Но мне нужно также сортировать 3-й уровень (доступность).

Я хочу список компаний, которые сортируются по:

  • Расстояние от местоположения, предоставленного ASC
  • Рейтинг DESC
  • Скорейшая доступность ASC

Например:

Компания A находится в 5 км от отеля, имеет рейтинг 4, и вскоре один из их сотрудников будет доступен через 20 часов Компания B также находится в 5 милях от отеля, также имеет рейтинг 4, но скорее всего один из их сотрудников доступен через 5 часов.

Следовательно, результат сортировки должен быть B, A.

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

Полностью для создания индекса, импорта данных и поиска

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

В настоящее время я возвращаюсь:

IDS компании → IDS сотрудников → первая доступность

Я хотел бы иметь агрегацию вроде:

IDS компании → первая доступность

Таким образом, я могу сделать свой custom_score script для вычисления оценки и сортировки их должным образом.

Более упрощенный вопрос:
Как можно сортировать/группировать многоуровневые (большие) дети и, возможно, сглаживать результат.

Ответ 1

Вам не нужны скопления для этого:

Это критерии сортировки:

  • Расстояние ASC (company.location)
  • Рейтинг DESC (company.rating_value)
  • Скорое будущее Доступность ASC (company.employee.availability.start)

Если вы проигнорируете № 3, вы можете запустить относительно простой корпоративный запрос:

GET /companies/company/_search
{
 "query": { "match_all" : {} },
 "sort": {
    "_script": {
        "params": {
            "lat": 51.5186,
            "lon": -0.1347
        },
        "lang": "groovy",
        "type": "number",
        "order": "asc",
        "script": "doc['location'].distanceInMiles(lat,lon)"
    },
    "rating_value": { "order": "desc" }
  }
}

# 3 сложно, потому что вам нужно спуститься и найти доступность (компания > сотрудник > доступность) для каждой компании, ближайшей к моменту запроса, и использовать эта продолжительность как третий критерий сортировки.

Мы собираемся использовать запрос function_score на уровне внука, чтобы получить разницу во времени между временем запроса и каждой доступностью в хит _score. (Тогда мы будем использовать _score как третий критерий сортировки).

Чтобы достичь внуков, нам нужно использовать запрос has_child внутри запроса has_child.

Для каждой компании мы хотим, чтобы скорейший доступный Сотрудник (и, конечно же, их ближайшая доступность). Elasticsearch 2.0 предоставит нам "score_mode": "min" для таких случаев, но пока, поскольку мы ограничены "score_mode": "max", мы сделаем внука _score обратным разнице во времени.

          "function_score": {
            "filter": { 
              "range": { 
                "start": {
                  "gt": "2014-12-22T10:34:18+01:00"
                } 
              }
            },
            "functions": [
              {
                "script_score": {
                  "lang": "groovy",
                  "params": {
                      "requested": "2014-12-22T10:34:18+01:00",
                      "millisPerHour": 3600000
                   },
                  "script": "1 / ((doc['availability.start'].value - new DateTime(requested).getMillis()) / millisPerHour)"
                }
              }
            ]
          }

Итак, теперь _score для каждого внука (Доступность) будет 1 / number-of-hours-until-available (чтобы мы могли использовать максимальное обратное время до тех пор, пока оно не будет доступно для каждого сотрудника, а максимальное взаимное (ly?) доступный сотрудник на компанию).

Объединяя все вместе, мы продолжаем запрашивать компанию, но используем company > employee > availabilty для создания _score для использования в качестве # 3 критерий сортировки:

GET /companies/company/_search
{
 "query": { 
    "has_child" : {
        "type" : "employee",
        "score_mode" : "max",
        "query": {
          "has_child" : {
            "type" : "availability",
            "score_mode" : "max",
            "query": {
              "function_score": {
                "filter": { 
                  "range": { 
                    "start": {
                      "gt": "2014-12-22T10:34:18+01:00"
                    } 
                  }
                },
                "functions": [
                  {
                    "script_score": {
                      "lang": "groovy",
                      "params": {
                          "requested": "2014-12-22T10:34:18+01:00",
                          "millisPerHour": 3600000
                       },
                      "script": "1/((doc['availability.start'].value - new DateTime(requested).getMillis()) / millisPerHour)"
                    }
                  }
                ]
              }
            }
          }
        }
    }
 },
 "sort": {
  "_script": {
    "params": {
        "lat": 51.5186,
        "lon": -0.1347
    },
    "lang": "groovy",
    "type": "number",
    "order": "asc",
    "script": "doc['location'].distanceInMiles(lat,lon)"
  },
  "rating_value": { "order": "desc" },
  "_score": { "order": "asc" }
 }
}