ElasticSearch wielopoziomowa agregacja rodzic-dziecko

79

Mam strukturę rodzica / dziecka na 3 poziomach. Powiedzmy:

Firma -> Pracownik -> Dostępność

Ponieważ dostępność (a także pracownik) jest tutaj często aktualizowana, wybieram użycie struktury nadrzędnej / podrzędnej zamiast zagnieżdżonej. Funkcja wyszukiwania działa dobrze (wszystkie dokumenty we właściwych fragmentach).

Teraz chcę posortować te wyniki. Sortowanie ich według metadanych z firmy (poziom 1) jest łatwe. Ale muszę też sortować według poziomu trzeciego (dostępność).

Chcę listę firm posortowanych według:

  • Odległość od lokalizacji podanej ASC
  • Ocena DESC
  • Najszybsza dostępność ASC

Na przykład:

Firma A jest oddalona o 5 mil, ma ocenę 4 i najwcześniej jeden z jej pracowników jest dostępny za 20 godzin. Firma B jest również oddalona o 5 mil, również ma ocenę 4, ale najwcześniej jeden z jej pracowników jest dostępny za 5 godzin.

Dlatego wynik sortowania musi być B, A.

Chciałbym dodać specjalną wagę do każdej z tych danych, więc zacząłem pisać agregacje, które będę mógł później wykorzystać w moim skrypcie custom_score.

Pełny sens tworzenia indeksu, importowania danych i wyszukiwania

Teraz udało mi się napisać zapytanie, które faktycznie zwraca wynik, ale zasobnik agregacji dostępności jest pusty. Jednak wyniki są zbyt ustrukturyzowane, chciałbym je spłaszczyć.

Obecnie wracam:

IDS firmy -> IDS pracowników -> pierwsza dostępność

Chciałbym mieć agregację taką jak:

IDS firmy -> pierwsza dostępność

W ten sposób mogę zrobić swoje custom_score skrypt, aby obliczyć wynik i odpowiednio je posortować.

Bardziej uproszczone pytanie: w
jaki sposób można sortować / agregować według wielopoziomowych (wielkich) elementów potomnych i ewentualnie spłaszczyć wynik.

Pete Minus
źródło
Czy możesz dodać swoje mapowanie i kilka przykładowych dokumentów (z potomkami) do sedna? Trudno jest zrozumieć, jak wymyślić fałszywe dokumenty, które pozwalają na odpowiednie testowanie systemu.
Sloan Ahrens
Hej Sloan - dodałem mapowanie i przykładowe wyniki. Zdjąłem go trochę, aby ułatwić zrozumienie. Pełny stos zawiera dużo więcej danych :) Dzięki!
Pete Minus
Miałem to samo pytanie tutaj . Choć prawdopodobnie mniej wydajne, po prostu proszę o wszystkie wyniki, które mają domyślny rodzaj DocCount. Następnie wykonałem własne rekurencyjne spłaszczanie, sortowanie i ograniczanie, co nie było idealne.
Matt Traynham,
1
Wykonałem twoją treść, ale podczas wyszukiwania otrzymuję błąd 500 Query Failed [Failed to execute main query]]; nested: NullPointerException;. Czy potrafisz przeprowadzić sedno w swoim lokalnym środowisku i upewnić się, że wszystko jest w porządku? Dzięki!
Val
Utwórz równanie dla swoich wyników. Twoje dane nie są rozmyte! Agregujesz każde zapytanie? . Agregacja to akcje wejściowe, a nie zapytanie lub dane wyjściowe. Pytanie „Jak sprawdzić, czy ten wynik jest prawdziwy (prawda)?”
dsgdfg

Odpowiedzi:

3

Nie potrzebujesz do tego agregacji:

Oto kryteria sortowania:

  1. Odległość ASC (company.location)
  2. Ocena DESC (company.rating_value)
  3. Soonest Future Availability ASC (company.employee.availability.start)

Jeśli zignorujesz # 3, możesz uruchomić stosunkowo proste zapytanie firmowe, takie jak:

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" }
  }
}

Punkt 3 jest trudny, ponieważ musisz sięgnąć w dół i znaleźć dostępność ( firma> pracownik> dostępność ) dla każdej firmy najbliższej godzinie złożenia wniosku i użyć tego czasu jako trzeciego kryterium sortowania.

Użyjemy function_scorezapytania na poziomie wnuka, aby zmierzyć różnicę czasu między czasem żądania a każdą dostępnością w działaniu _score. (Wtedy użyjemy _scoretrzeciego kryterium sortowania).

Aby dotrzeć do wnuków, musimy użyć has_childzapytania wewnątrz has_childzapytania.

Dla każdej firmy zależy nam na jak najszybszym dostępnym Pracowniku (i oczywiście najbliższej Dostępności). Elasticsearch 2.0 da nam takie "score_mode": "min"przypadki, ale na razie, ponieważ jesteśmy ograniczeni, "score_mode": "max"sprawimy, że wnuk _scorebędzie odwrotnością różnicy czasu.

          "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)"
                }
              }
            ]
          }

Tak więc teraz _scoredla każdego wnuka ( Dostępność ) będzie 1 / number-of-hours-until-available(tak, abyśmy mogli wykorzystać maksymalny wzajemny czas, aż będzie dostępny dla pracownika, oraz maksymalny wzajemny (?) Dostępny pracownik na firmę).

Kładzenie to wszystko razem, nadal zapytań firmy ale korzystają firmy> Pracownik> availabilty do generowania _scoreużyć jako # 3 kryterium sortowania:

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" }
 }
}
Peter Dixon-Moses
źródło
Możesz uzyskać nieco lepszą wydajność, używając funkcji zaniku liniowego zamiast skryptu do generowania _scoreod czasu do momentu, gdy będzie dostępny .
Peter Dixon-Moses
Elasticsearch domyślnie wyłączyło dynamiczne skrypty. Lepiej jest używać indeksowanych skryptów. Zobacz tutaj: elastic.co/blog/...
schellingerht
Pete Minus: Czy udało ci się to naprawić? Wiem, że to starsze pytanie, ale jest wiele osób zainteresowanych Twoim rozwiązaniem.
Peter Dixon-Moses
Peter Dixon-Moses: W końcu zrezygnowałem i napisałem dwa zapytania - najpierw w celu wyszukania według firmy / pracownika, a następnie wyszukania 100 najlepszych firm poprzez dostępność, a następnie połączenie. Czemu? Zbudowanie go tylko w ES wymagało zbyt wiele czasu / wysiłku. Czas spędzony na wyszukiwaniu jest akceptowalny.
Pete Minus