1. Introducción

En este post veremos como hacer búsquedas sobre objetos anidados o arrays en Elasticsearch. Partiremos de los índices y documentos que se crearon en el post Primeros pasos en Elasticsearch

2. Creación de índices, tipos e inserción de datos

Creación del índice myindex

curl -X PUT http://localhost:9200/myindex?pretty

Documentos insertados

{"name":"admin","surname":"admin","gender":"male","money":0,"roles":["ROLE_ADMIN"]}
{"name":"Jorge","surname":"Hernández Ramírez","gender":"male","money":1000,"roles":["ROLE_ADMIN"],"teams":[{"name":"UD.Las Palmas","sport":"Football"},{"name":"Real Madrid","sport":"Football"},{"name":"McLaren","sport":"F1"}]}
{"name":"Jose","gender":"male","surname":"Hernández Ramírez","money":2000,"roles":["ROLE_USER"],"teams":[{"name":"UD. Las Palmas","sport":"Football"},{"name":"Magnus Carlsen","sport":"Chess"}]}
{"name":"Raul","surname":"González Blanco","gender":"male","money":200000,"roles":["ROLE_USER"],"teams":[{"name":"Real Madrid","sport":"Football"},{"name":"Real Madrid","sport":"Basketball"}]}
{"name":"Constanza","surname":"Ramírez Rodríguez","gender":"female","money":500,"roles":["ROLE_USER"],"teams":[{"name":"UD. Las Palmas","sport":"Football"}]}

3. Búsquedas sobre objetos anidados

Buscar aquellos documentos cuyo nombre de equipo sea Magnus Carlen

curl -XGET 'localhost:9200/myindex/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool" : {
      "must" : [
      	{"match" : { "teams.name" : "Magnus Carlsen" }}
      ]
    }
  }
}
'
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.18985549,
    "hits" : [ {
      "_index" : "myindex",
      "_type" : "user",
      "_id" : "3",
      "_score" : 0.18985549,
      "_source" : {
        "name" : "Jose",
        "gender" : "male",
        "surname" : "Hernández Ramírez",
        "money" : 2000,
        "roles" : [ "ROLE_USER" ],
        "teams" : [ {
          "name" : "UD. Las Palmas",
          "sport" : "Football"
        }, {
          "name" : "Magnus Carlsen",
          "sport" : "Chess"
        } ]
      }
    } ]
  }
}

Buscar aquellos documentos cuyo nombre de equipo sea Magnus Carlen y cuyo deporte sea Football

curl -XGET 'localhost:9200/myindex/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool" : {
      "must" : [
      	{"match" : { "teams.name" : "Magnus Carlsen" }},
      	{"match" : { "teams.sport" : "Football" }}
      ]
    }
  }
}
'

El resultado esperado debería ser 0, ya que el deporte de Magnus Carlsen es el ajedrez, sin embargo aparece el documento.

{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.26574233,
    "hits" : [ {
      "_index" : "myindex",
      "_type" : "user",
      "_id" : "3",
      "_score" : 0.26574233,
      "_source" : {
        "name" : "Jose",
        "gender" : "male",
        "surname" : "Hernández Ramírez",
        "money" : 2000,
        "roles" : [ "ROLE_USER" ],
        "teams" : [ {
          "name" : "UD. Las Palmas",
          "sport" : "Football"
        }, {
          "name" : "Magnus Carlsen",
          "sport" : "Chess"
        } ]
      }
    } ]
  }
}

La explicación de esto es que el documento se persiste como:

{
  "name": ["Jose"],
  "gender": ["male"],
  "surname": ["Hernández", "Ramírez"],
  "roles": ["ROLE_USER"],
  "teams.name": ["UD. Las Palmas", "Magnus Carlen"],
  "teams.sport": ["Football", "Chess"]
}'

De forma que si buscamos por el nombre Magnus Carlen y por el deporte Football el documento aparecerá. La forma de resolver esto es utilizando Mappings cuando creamos nuestros índices.

4. Creación de Mapping

Borramos el índice index

curl -X DELETE http://localhost:9200/myindex?pretty

Lo creamos utilizando un Mapping
Esta es la forma de indicarle a Elasticsearch los tipos de datos que vamos a utilizar así como la forma de indicarle si dentro de nuestros documentos en el tipo user vamos a tener objetos anidados.

curl -X PUT 'localhost:9200/myindex' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "user": {
      "properties": {
        "money": {"type": "integer"},
        "teams": {
          "type": "nested", 
          "properties": {
            "name":    { "type": "string"  },
            "sport": { "type": "string"  }
          }
        }
      }
    }
  }
}
'

Deberemos insertar de nuevo los datos que se especificaron en el paso 2. Creación de índices, tipos e inserción de datos

5. Nested Querys

Para realizar consultas sobre los objetos anidados, una vez creados los índices, deberemos utilizar la etiqueta nested.

Obtener aquellos documentos que tengan al menos un equipo cuyo nombre sea Magnus Carlen y su deporte sea Ajedrez

curl -XGET 'localhost:9200/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "query": {
        "nested" : {
            "path" : "teams",
            "query" : {
                "bool" : {
                    "must" : [
                    { "match" : {"teams.name" : "Magnus Carlen"} },
                    { "match" : {"teams.sport" : "Chess"} }
                    ]
                }
            }
        }
    }
}
'
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 10,
    "successful" : 5,
    "failed" : 5,
    "failures" : [ {
      "shard" : 0,
      "index" : "indexproduct",
      "node" : "-brG604_RqGSP-UZ2pfg1g",
      "reason" : {
        "type" : "query_parsing_exception",
        "reason" : "[nested] failed to find nested object under path [teams]",
        "index" : "indexproduct",
        "line" : 5,
        "col" : 22
      }
    } ]
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.8969545,
    "hits" : [ {
      "_index" : "myindex",
      "_type" : "user",
      "_id" : "3",
      "_score" : 0.8969545,
      "_source" : {
        "name" : "Jose",
        "gender" : "male",
        "surname" : "Hernández Ramírez",
        "money" : 2000,
        "roles" : [ "ROLE_USER" ],
        "teams" : [ {
          "name" : "UD. Las Palmas",
          "sport" : "Football"
        }, {
          "name" : "Magnus Carlsen",
          "sport" : "Chess"
        } ]
      }
    } ]
  }
}

Obtener aquellos documentos que tengan al menos un equipo cuyo nombre sea Magnus Carlen y su deporte sea Football

curl -XGET 'localhost:9200/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "query": {
        "nested" : {
            "path" : "teams",
            "query" : {
                "bool" : {
                    "must" : [
                    { "match" : {"teams.name" : "Magnus Carlen"} },
                    { "match" : {"teams.sport" : "Football"} }
                    ]
                }
            }
        }
    }
}
'
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 10,
    "successful" : 5,
    "failed" : 5,
    "failures" : [ {
      "shard" : 0,
      "index" : "indexproduct",
      "node" : "-brG604_RqGSP-UZ2pfg1g",
      "reason" : {
        "type" : "query_parsing_exception",
        "reason" : "[nested] failed to find nested object under path [teams]",
        "index" : "indexproduct",
        "line" : 5,
        "col" : 22
      }
    } ]
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

Como es evidente el resultado ahora es 0.