Agregación padre-hijo de varios niveles de ElasticSearch

79

Tengo una estructura padre / hijo en 3 niveles. Digamos:

Empresa -> Empleado -> Disponibilidad

Dado que la disponibilidad (y también el empleado) se actualiza con frecuencia aquí, elijo usar la estructura principal / secundaria contra anidada. Y la función de búsqueda funciona bien (todos los documentos en fragmentos correctos).

Ahora quiero ordenar esos resultados. Ordenarlos por metadatos de la empresa (primer nivel) es fácil. Pero necesito ordenar también por tercer nivel (disponibilidad).

Quiero una lista de empresas ordenadas por:

  • Distancia desde la ubicación dada ASC
  • Calificación DESC
  • Disponibilidad más pronto ASC

Por ejemplo:

La Compañía A está a 5 millas de distancia, tiene una calificación de 4 y lo más pronto posible, uno de sus empleados está disponible en 20 horas. La Compañía B también está a 5 millas de distancia, también tiene una calificación de 4, pero lo más pronto posible, uno de sus empleados está disponible en 5 horas.

Por lo tanto, el resultado de la clasificación debe ser B, A.

Me gustaría agregar un peso especial a cada uno de estos datos, así que comencé a escribir agregaciones que luego podría usar en mi script custom_score.

Lo esencial para crear índices, importar datos y buscar

Ahora, me las arreglé para escribir una consulta que en realidad devuelve el resultado, pero el depósito de agregación de disponibilidad está vacío. Sin embargo, también obtengo resultados demasiado estructurados, me gustaría aplanarlos.

Actualmente vuelvo:

ID de empresa -> ID de empleado -> primera disponibilidad

Me gustaría tener una agregación como:

IDS de la empresa -> primera disponibilidad

De esta manera puedo hacer mi custom_scoreguión para calcular la puntuación y ordenarlos correctamente.

Pregunta más simplificada:
¿Cómo se puede ordenar / agregar por hijos de varios niveles (grandes) y posiblemente aplanar el resultado?

Pete Minus
fuente
¿Podría agregar su mapeo y algunos documentos de ejemplo (con descendientes) a la esencia? Es difícil ver cómo inventar documentos falsos que permitan probar adecuadamente su sistema.
Sloan Ahrens
Hola, Sloan, he añadido mapas y resultados de muestra. Le he quitado un poco para entenderlo mejor. La pila completa contiene muchos más datos :) ¡Gracias!
Pete Minus
Tenía la misma pregunta aquí . Aunque probablemente sea menos eficiente, solo solicito todos los resultados que tienen un tipo predeterminado de DocCount. Luego hice mi propio aplanamiento, clasificación y limitación recursiva, que no era lo ideal.
Matt Traynham
1
He ejecutado su esencia, pero al buscar aparece el error 500 Query Failed [Failed to execute main query]]; nested: NullPointerException;. ¿Puede ejecutar su esencia en su entorno local y asegurarse de que esté bien? ¡Gracias!
Val
¿Por qué no crea una ecuación para sus resultados? ¡Tus datos no son borrosos! ¿Agrega cada consulta? . El agregado son acciones de entrada, no una consulta o salida. Una pregunta "¿cómo verificas que este resultado sea verdadero (correcto)?"
dsgdfg

Respuestas:

3

No necesita agregaciones para hacer esto:

Estos son los criterios de clasificación:

  1. Distancia ASC (ubicación de la empresa)
  2. Calificación DESC (company.rating_value)
  3. Disponibilidad futura más pronto ASC (company.employee.availability.start)

Si ignora el n. ° 3, puede ejecutar una consulta de empresa relativamente simple como esta:

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

El número 3 es complicado porque necesita buscar la disponibilidad ( empresa> empleado> disponibilidad ) para cada empresa más cercana al momento de la solicitud y utilizar esa duración como un tercer criterio de clasificación.

Usaremos una function_scoreconsulta a nivel de nietos para tomar la diferencia de tiempo entre el tiempo de solicitud y cada disponibilidad en el hit _score. (Luego usaremos _scorecomo tercer criterio de clasificación).

Para llegar a los nietos necesitamos usar una has_childconsulta dentro de una has_childconsulta.

Para cada empresa, queremos el Empleado disponible más pronto (y, por supuesto, su Disponibilidad más cercana). Elasticsearch 2.0 nos dará una "score_mode": "min"para casos como este, pero por ahora, dado que estamos limitados "score_mode": "max", haremos que el nieto _scoresea ​​el recíproco de la diferencia horaria.

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

Así que ahora el _scorepara cada nieto ( Disponibilidad ) será 1 / number-of-hours-until-available(para que podamos usar el tiempo recíproco máximo hasta que esté disponible por Empleado, y el Empleado disponible recíproco máximo (¿ly?) Por Compañía).

Poniendo todo junto, seguimos consulta compañía pero el uso de empresa> empleado> Disponibilidad para generar el _scoreusar como el # 3 criterio de clasificación:

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-Moisés
fuente
Puede obtener un rendimiento ligeramente mejor utilizando una función de decaimiento lineal en lugar de un script para generar _scoredesde el tiempo hasta que esté disponible .
Peter Dixon-Moses
Elasticsearch deshabilitó las secuencias de comandos dinámicas de forma predeterminada. Mejor es utilizar scripts indexados. Ver aquí: elastic.co/blog/...
schellingerht
Pete Minus: ¿Pudiste hacer que esto funcionara? Sé que esta es una pregunta anterior, sin embargo, hay muchas personas interesadas en tu solución.
Peter Dixon-Moses
Peter Dixon-Moses: Finalmente me di por vencido y escribí dos consultas: primero buscar por Compañía / Empleado y luego buscar 100 Compañías principales a través de Disponibilidad y luego fusionar. ¿Por qué? Se necesitaba demasiado tiempo / esfuerzo para construirlo solo en ES. El tiempo dedicado a la búsqueda es aceptable.
Pete Minus