ElasticSearch багаторівнева агрегація батьків та дітей


79

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

Компанія -> Співробітник -> Наявність

Оскільки Доступність (а також Співробітник) тут часто оновлюється, я вибираю використання батьківської / дочірньої структури проти вкладених. І функція пошуку чудово працює (усі документи мають правильні фрагменти).

Тепер я хочу відсортувати ці результати. Сортувати їх за метаданими від компанії (1-й рівень) легко. Але мені також потрібно сортувати за 3-м рівнем (доступність).

Я хочу список компаній, які сортуються за:

  • Відстань від місця, вказаного ASC
  • Рейтинг DESC
  • Найближча доступність ASC

Наприклад:

Компанія А знаходиться на відстані 5 миль, має рейтинг 4 і найближчим часом один із їх співробітників стає доступним через 20 годин. Компанія В також знаходиться на відстані 5 миль, також має рейтинг 4, але найближчим часом один із їх працівників доступний через 5 годин.

Тому результат сортування повинен бути B, A.

Я хотів би додати особливої ​​ваги кожному з цих даних, тому я почав писати агрегації, які згодом міг би використовувати у своєму скрипті custom_score.

Повна суть створення індексу, імпортування даних та пошуку.

Зараз мені вдалося написати запит, який фактично повертає результат, але сегмент агрегації доступності порожній. Однак я також отримую результати назад занадто структурованими, я хотів би їх згладити.

В даний час я повертаюся:

Ідентифікатори компанії -> Ідентифікатори співробітників -> перша доступність

Я хотів би мати агрегацію, як:

Ідентифікатори компанії -> перша доступність

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

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


Не могли б ви додати своє відображення та кілька прикладів документів (із нащадками) до суті? Важко зрозуміти, як винаходити підроблені документи, що дозволяють адекватно перевірити вашу систему.
Sloan Ahrens

Привіт Слоун - я додав відображення та зразки результатів. Я розібрав його для легшого розуміння. Повний стек містить набагато більше даних :) Дякую!
Піт Мінус

У мене був один і той же питання тут . Хоча, ймовірно, менш ефективні, я просто запитую всі результати, які мають тип DocCount за замовчуванням. Потім я зробив своє власне рекурсивне згладжування, сортування та обмеження, що не було ідеально.
Matt Traynham

1
Я виконав вашу суть, але під час пошуку я отримую помилку 500 Query Failed [Failed to execute main query]]; nested: NullPointerException;. Чи можете ви виконати свою суть на своєму місцевому середовищі та переконатися, що це нормально? Дякую!
Валь

Чому б не створити рівняння для ваших результатів. Ваші дані нечіткі! Ви агрегуєте кожен запит? . Агрегат - це дії введення, а не запит чи вихід. Питання "як ви перевіряєте, чи є цей результат істинним (правильно)?"
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(щоб ми могли використовувати максимум взаємного часу, поки не стане доступним для одного працівника, і максимальний взаємний (ly?) Доступний працівник для кожної компанії).

Поєднуючи все це, ми продовжуємо запитувати компанію, але використовуємо company> worker> 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" }
 }
}

Ви можете отримати трохи кращу продуктивність, використовуючи функцію лінійного затухання, а не скрипт, який генерується _scoreвід часу до моменту його появи .
Пітер Діксон-Мойсей

Elasticsearch за замовчуванням вимкнув динамічні сценарії. Краще використовувати індексовані сценарії. Дивіться тут: elastic.co/blog/…
schellingerht

Піт Мінус: Чи змогли ви це зробити? Я знаю, що це давнє запитання, проте є багато людей, зацікавлених у вашому вирішенні.
Пітер Діксон-Мойсей

Пітер Діксон-Мойсей: Врешті-решт я відмовився і написав два запити - спочатку для пошуку за компанією / співробітником, а потім для пошуку 100 найкращих компаній через наявність, а потім об’єднання. Чому? Потрібно було занадто багато часу / зусиль, щоб побудувати його лише в ES. Час, витрачений на пошук, є прийнятним.
Піт Мінус,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.