ElasticSearch многоуровневая агрегация родитель-потомок

79

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

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

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

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

Мне нужен список компаний, отсортированных по:

  • Расстояние от местоположения с учетом ASC
  • Рейтинг DESC
  • Самая быстрая доступность ASC

Например:

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

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

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

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

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

Сейчас возвращаюсь:

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

Я хотел бы иметь такую ​​агрегацию:

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

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

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

Пит Минус
источник
Не могли бы вы добавить свое отображение и несколько примеров документов (с потомками) к сути? Трудно придумать поддельные документы, позволяющие адекватно протестировать вашу систему.
Sloan Ahrens
Привет, Слоун, я добавил отображение и образцы результатов. Я немного разделил его для облегчения понимания. Полный стек содержит намного больше данных :) Спасибо!
Pete Minus
У меня был один и тот же вопрос здесь . Хотя, вероятно, менее производительно, я просто запрашиваю все результаты, которые имеют тип DocCount по умолчанию. Затем я выполнил собственное рекурсивное выравнивание, сортировку и ограничение, что было не идеально.
Мэтт Трейнхэм
1
Я выполнил вашу суть, но при поиске получаю ошибку 500 Query Failed [Failed to execute main query]]; nested: NullPointerException;. Можете ли вы выполнить свою суть в своей локальной среде и убедиться, что это нормально? Благодаря!
Val
Почему бы не составить уравнение для ваших результатов. Ваши данные не расплывчаты! Вы агрегируете каждый запрос? . Агрегат - это действия ввода, а не запрос или вывод. Вопрос "как вы проверяете, что этот результат верен (верно)?"
dsgdfg

Ответы:

3

Для этого вам не нужны агрегаты:

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

  1. Расстояние ASC (company.location)
  2. Рейтинг DESC (company.rating_value)
  3. Скоро в будущем 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(чтобы мы могли использовать максимальное обратное время, пока оно не будет доступно для каждого сотрудника, и максимальное обратное (ли?) Доступное время для сотрудника на компанию).

Собирая все вместе, мы продолжаем запрашивать компанию, но используем команду company> employee> availability, чтобы сгенерировать _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" }
 }
}
Питер Диксон-Мозес
источник
Вы можете получить немного лучшую производительность, используя функцию линейного затухания, а не сценарий, который будет генерироваться _scoreот времени до готовности .
Питер Диксон-Мозес,
По умолчанию Elasticsearch отключил динамические сценарии. Лучше использовать индексированные скрипты. Смотрите здесь: elastic.co/blog/...
schellingerht
Пит Минус: Тебе удалось заставить это работать? Я знаю, что это старый вопрос, но многие люди заинтересованы в вашем решении.
Питер Диксон-Мозес,
Питер Диксон-Мозес: В конце концов я сдался и написал два запроса - сначала поиск по компании / сотруднику, затем поиск 100 лучших компаний по доступности, а затем слияние. Почему? На создание этого только в ES ушло слишком много времени / усилий. Время на поиски приемлемо.
Пит Минус