Skip to content

03 ‐ Explorando os Dados: Query DSL

inhogoes edited this page May 8, 2026 · 3 revisions

3.1 - Query DSL: visão geral

A Query DSL (Domain-Specific Language) é a linguagem de consulta tradicional do Elasticsearch. Baseada em JSON, ela permite construir desde buscas simples até queries complexas com múltiplos filtros, combinações booleanas e agregações.

No Elasticsearch 9.x, a Query DSL continua sendo a forma principal de interagir com os dados via API, e é o que será usado nos laboratórios desta seção.

Toda query no Elasticsearch é enviada como corpo de uma requisição HTTP para o endpoint _search de um índice:

GET /nome-do-indice/_search
{
  "query": {
    ...
  }
}

Existem dois contextos de consulta com comportamentos distintos:

  • Query context: o Elasticsearch calcula um score de relevância para cada documento retornado. Usado quando a ordem de relevância importa — por exemplo, em buscas de texto livre.
  • Filter context: o Elasticsearch apenas verifica se o documento corresponde ou não ao critério, sem calcular score. É mais rápido porque os resultados são cacheados. Usado para filtros exatos como datas, status, IDs e valores numéricos.

Na prática, a maioria das queries combina os dois contextos: usa filter para reduzir o conjunto de documentos e query para ranquear os resultados relevantes dentro desse conjunto.


3.2 - Laboratório: inserindo documentos no Elasticsearch

Neste laboratório você vai criar um índice, inserir documentos em lote e verificar a inserção. Todos os comandos devem ser executados no Kibana Dev Tools (Menu → Dev Tools).

Criando o índice

O índice cadastro_clientes será usado ao longo de toda esta seção. Ao criá-lo com um mapping explícito, garantimos que cada campo seja tratado com o tipo correto desde o início.

Boas práticas em nomes de campos: evite acentos e caracteres especiais em nomes de campos — use endereco em vez de endereço. Isso evita problemas de encoding em integrações com outras ferramentas e simplifica scripts e queries.

PUT /cadastro_clientes
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "nome_completo": { "type": "text" },
      "sexo":          { "type": "keyword" },
      "codigo":        { "type": "integer" },
      "endereco":      { "type": "text" }
    }
  }
}

Explicação dos tipos:

  • text: campo de texto analisado. O Elasticsearch tokeniza o valor e cria o índice invertido para full-text search.
  • keyword: valor exato, não analisado. Usado para filtros, ordenação e agregações.
  • integer: número inteiro.

Inserindo documentos em lote com a API Bulk

A API _bulk permite inserir, atualizar ou deletar múltiplos documentos em uma única requisição, o que é muito mais eficiente do que enviar um documento por vez.

Cada documento na API bulk é representado por duas linhas: a primeira indica a operação e o destino, a segunda contém o documento em si.

POST /_bulk
{ "index" : { "_index" : "cadastro_clientes", "_id" : "1" } }
{ "nome_completo" : "João da Silva", "sexo" : "masculino", "codigo" : 1, "endereco" : "Rua Exemplo, 123, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "2" } }
{ "nome_completo" : "Maria Oliveira", "sexo" : "feminino", "codigo" : 2, "endereco" : "Avenida Exemplo, 456, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "3" } }
{ "nome_completo" : "Roberto Carlos", "sexo" : "masculino", "codigo" : 3, "endereco" : "Travessa Exemplo, 789, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "4" } }
{ "nome_completo" : "Ana Maria", "sexo" : "feminino", "codigo" : 4, "endereco" : "Rua Segunda, 101, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "5" } }
{ "nome_completo" : "Paulo Souza", "sexo" : "masculino", "codigo" : 5, "endereco" : "Avenida Principal, 202, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "6" } }
{ "nome_completo" : "Clara Costa", "sexo" : "feminino", "codigo" : 6, "endereco" : "Rua Terceira, 303, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "7" } }
{ "nome_completo" : "Pedro Marques", "sexo" : "masculino", "codigo" : 7, "endereco" : "Sexta Avenida, 606, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "8" } }
{ "nome_completo" : "Sandra Gomes", "sexo" : "feminino", "codigo" : 8, "endereco" : "Rua Quinta, 505, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "9" } }
{ "nome_completo" : "Ricardo Almeida", "sexo" : "masculino", "codigo" : 9, "endereco" : "Avenida Sexta, 606, Cidade, Estado" }
{ "index" : { "_index" : "cadastro_clientes", "_id" : "10" } }
{ "nome_completo" : "Tereza Costa", "sexo" : "feminino", "codigo" : 10, "endereco" : "Rua Sétima, 707, Cidade, Estado" }

Verificando a inserção

GET /cadastro_clientes/_search
{
  "query": {
    "match_all": {}
  }
}

O campo hits.total.value na resposta deve indicar 10 documentos.


3.3 - Operações CRUD

As operações CRUD (Create, Read, Update, Delete) são a base da interação com dados no Elasticsearch.

Create — criando documentos

Com ID definido (usa PUT):

PUT /cadastro_clientes/_doc/11
{
  "nome_completo": "Carlos Teixeira",
  "sexo": "masculino",
  "codigo": 11,
  "endereco": "Rua Oito, 808, Cidade, Estado"
}

Sem ID — o Elasticsearch gera um ID automaticamente (usa POST):

POST /cadastro_clientes/_doc
{
  "nome_completo": "Lucia Andrade",
  "sexo": "feminino",
  "codigo": 12,
  "endereco": "Avenida Nove, 909, Cidade, Estado"
}

Read — lendo documentos

Por ID:

GET /cadastro_clientes/_doc/1

Por query:

GET /cadastro_clientes/_search
{
  "query": {
    "term": {
      "sexo": "feminino"
    }
  }
}

Atenção: o campo sexo é do tipo keyword, por isso usamos term (busca exata) e não match (full-text). Usar match em campos keyword funciona em muitos casos, mas não é a forma correta — match analisa o texto antes de buscar, enquanto term busca o valor exato como está armazenado.

Update — atualizando documentos

Atualização parcial de um documento (só altera os campos informados):

POST /cadastro_clientes/_update/1
{
  "doc": {
    "endereco": "Nova Rua, 123, Nova Cidade, Estado"
  }
}

Atualização em massa com _update_by_query — atualiza todos os documentos que atendem a uma query:

POST /cadastro_clientes/_update_by_query
{
  "script": {
    "source": "ctx._source['endereco'] = 'Endereço Atualizado'",
    "lang": "painless"
  },
  "query": {
    "term": {
      "sexo": "feminino"
    }
  }
}

O script usa Painless, a linguagem de scripting nativa do Elasticsearch. ctx._source é o documento atual sendo processado.

Delete — deletando documentos

Por ID:

DELETE /cadastro_clientes/_doc/1

Em massa com _delete_by_query — deleta todos os documentos que atendem a uma query:

POST /cadastro_clientes/_delete_by_query
{
  "query": {
    "range": {
      "codigo": {
        "lt": 5
      }
    }
  }
}

3.4 - Paginação e ordenação

Por padrão, o Elasticsearch retorna os 10 primeiros resultados de uma busca. Para navegar por conjuntos maiores de dados, use os parâmetros from e size.

Paginação com from e size

GET /cadastro_clientes/_search
{
  "from": 0,
  "size": 5,
  "query": {
    "match_all": {}
  }
}
  • from: posição inicial (começa em 0)
  • size: número de documentos a retornar

Para a segunda página com 5 resultados por página, use from: 5. Para a terceira, from: 10, e assim por diante.

Limitação: a combinação de from + size não pode ultrapassar 10.000 por padrão (parâmetro index.max_result_window). Para conjuntos maiores que isso, é necessário ajustar esse parâmetro no índice ou adotar outras estratégias de paginação.

Ordenação com sort

GET /cadastro_clientes/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    { "codigo": { "order": "desc" } }
  ]
}

Para ordenar por múltiplos campos:

GET /cadastro_clientes/_search
{
  "sort": [
    { "sexo":   { "order": "asc" } },
    { "codigo": { "order": "asc" } }
  ]
}

Campos do tipo text não podem ser usados diretamente para ordenação. Use campos keyword ou campos numéricos. Se precisar ordenar por um campo de texto, defina um sub-campo keyword no mapping (fields: { keyword: { type: keyword } }).


3.5 - Tipos de busca com a Query DSL

Boolean Query

A bool query é a mais usada na prática porque permite combinar múltiplos critérios. Seus quatro operadores têm comportamentos distintos:

Operador Comportamento
must O documento precisa atender ao critério. Contribui para o score.
filter O documento precisa atender ao critério. Não contribui para o score. Resultado cacheado.
should O documento pode atender ao critério. Se atender, o score aumenta.
must_not O documento não pode atender ao critério. Executado no filter context — não afeta o score.
GET /cadastro_clientes/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "nome_completo": "Costa" } }
      ],
      "filter": [
        { "term": { "sexo": "feminino" } }
      ],
      "must_not": [
        { "range": { "codigo": { "lt": 5 } } }
      ]
    }
  }
}

Match Query

Busca por texto analisado em um campo. O Elasticsearch tokeniza o valor antes de buscar — por isso match é usado em campos do tipo text.

GET /cadastro_clientes/_search
{
  "query": {
    "match": {
      "nome_completo": "Sandra Gomes"
    }
  }
}

Por padrão, match usa o operador OR entre os termos: retorna documentos que contenham "Sandra" ou "Gomes". Para exigir todos os termos, use "operator": "and".

Match Phrase Query

Busca por uma sequência exata de palavras, respeitando a ordem e a proximidade:

GET /cadastro_clientes/_search
{
  "query": {
    "match_phrase": {
      "endereco": "Avenida Sexta"
    }
  }
}

Term e Terms Query

Busca por valor exato em campos keyword, numéricos ou datas. Não analisa o texto.

Um valor:

GET /cadastro_clientes/_search
{
  "query": {
    "term": {
      "sexo": "masculino"
    }
  }
}

Múltiplos valores (equivalente ao IN do SQL):

GET /cadastro_clientes/_search
{
  "query": {
    "terms": {
      "codigo": [7, 8, 9]
    }
  }
}

Range Query

Filtra documentos cujos valores estão dentro de um intervalo. Funciona com números, datas e strings.

GET /cadastro_clientes/_search
{
  "query": {
    "range": {
      "codigo": {
        "gte": 5,
        "lte": 10
      }
    }
  }
}

Operadores disponíveis: gte (maior ou igual), gt (maior), lte (menor ou igual), lt (menor).

Exists Query

Retorna documentos em que o campo especificado existe e não é nulo:

GET /cadastro_clientes/_search
{
  "query": {
    "exists": {
      "field": "endereco"
    }
  }
}

Fuzzy Query

Encontra termos parecidos com o termo buscado, tolerando erros de digitação. O grau de tolerância é baseado na distância de edição entre os termos (quantas letras precisam ser alteradas para transformar um no outro).

GET /cadastro_clientes/_search
{
  "query": {
    "fuzzy": {
      "nome_completo": {
        "value": "Tiexeira"
      }
    }
  }
}

Query String

Permite escrever queries complexas usando uma sintaxe de string com operadores lógicos e curingas:

GET /cadastro_clientes/_search
{
  "query": {
    "query_string": {
      "query": "(Avenida AND Sexta) OR (Carlos AND Rua)"
    }
  }
}

Boosting Query

Reduz o score de documentos que correspondem a uma condição indesejada sem excluí-los dos resultados:

GET /cadastro_clientes/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": { "nome_completo": "Costa" }
      },
      "negative": {
        "range": { "codigo": { "lte": 7 } }
      },
      "negative_boost": 0.2
    }
  }
}

Esta query retorna Clara Costa (codigo 6) e Tereza Costa (codigo 10). Clara Costa tem o score reduzido pelo fator 0.2 por satisfazer a condição negative — ela ainda aparece nos resultados, mas ranqueada abaixo de Tereza Costa.


3.6 - Filters na Query DSL

Filtros são executados no filter context — sem cálculo de score e com resultado cacheado. São sempre a escolha certa quando o critério é binário (o documento atende ou não atende), sem necessidade de ordenar por relevância.

Os filtros são aplicados dentro do operador filter da bool query:

Term filter:

GET /cadastro_clientes/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": { "sexo": "masculino" }
      }
    }
  }
}

Range filter:

GET /cadastro_clientes/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "codigo": { "gte": 5, "lte": 10 }
        }
      }
    }
  }
}

Exists filter:

GET /cadastro_clientes/_search
{
  "query": {
    "bool": {
      "filter": {
        "exists": { "field": "endereco" }
      }
    }
  }
}

Múltiplos filtros combinados:

GET /cadastro_clientes/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term":  { "sexo": "feminino" } },
        { "range": { "codigo": { "gte": 5 } } }
      ]
    }
  }
}

3.7 - Agregações

As agregações permitem resumir e analisar conjuntos de dados diretamente no Elasticsearch, sem exportar os dados para outra ferramenta. Existem três categorias principais:

  • Metric Aggregations: calculam um valor sobre um conjunto de documentos — média, soma, mínimo, máximo, contagem, percentis.
  • Bucket Aggregations: agrupam documentos em categorias (buckets) com base em um campo ou critério — equivalente ao GROUP BY do SQL.
  • Pipeline Aggregations: operam sobre o resultado de outras agregações, permitindo cálculos encadeados.

Laboratório de agregações

Vamos criar um índice saldo para explorar as agregações com dados financeiros fictícios.

Criando o índice:

PUT /saldo
{
  "mappings": {
    "properties": {
      "num_conta": { "type": "keyword" },
      "nome":      { "type": "text" },
      "sexo":      { "type": "keyword" },
      "cidade":    { "type": "keyword" },
      "estado":    { "type": "keyword" },
      "saldo":     { "type": "double" }
    }
  }
}

Inserindo documentos:

POST /_bulk
{ "index" : { "_index" : "saldo", "_id" : "1" } }
{ "num_conta": "1001", "nome": "Ana Beatriz", "sexo": "feminino", "cidade": "Recife", "estado": "PE", "saldo": 1000.00 }
{ "index" : { "_index" : "saldo", "_id" : "2" } }
{ "num_conta": "1002", "nome": "Carlos Eduardo", "sexo": "masculino", "cidade": "Rio de Janeiro", "estado": "RJ", "saldo": 3000.00 }
{ "index" : { "_index" : "saldo", "_id" : "3" } }
{ "num_conta": "1003", "nome": "Juliana Martins", "sexo": "feminino", "cidade": "Belo Horizonte", "estado": "MG", "saldo": 1500.00 }
{ "index" : { "_index" : "saldo", "_id" : "4" } }
{ "num_conta": "1004", "nome": "Roberto Silva", "sexo": "masculino", "cidade": "Porto Alegre", "estado": "RS", "saldo": 1800.00 }
{ "index" : { "_index" : "saldo", "_id" : "5" } }
{ "num_conta": "1005", "nome": "Fernanda Oliveira", "sexo": "feminino", "cidade": "Goiânia", "estado": "GO", "saldo": 2200.00 }
{ "index" : { "_index" : "saldo", "_id" : "6" } }
{ "num_conta": "1006", "nome": "Lucas Peixoto", "sexo": "masculino", "cidade": "Manaus", "estado": "AM", "saldo": 1900.00 }
{ "index" : { "_index" : "saldo", "_id" : "7" } }
{ "num_conta": "1007", "nome": "Patrícia Rocha", "sexo": "feminino", "cidade": "Recife", "estado": "PE", "saldo": 2100.00 }
{ "index" : { "_index" : "saldo", "_id" : "8" } }
{ "num_conta": "1008", "nome": "Eduardo Lima", "sexo": "masculino", "cidade": "Manaus", "estado": "AM", "saldo": 2300.00 }
{ "index" : { "_index" : "saldo", "_id" : "9" } }
{ "num_conta": "1009", "nome": "Sandra Gomes", "sexo": "feminino", "cidade": "Goiânia", "estado": "GO", "saldo": 2000.00 }
{ "index" : { "_index" : "saldo", "_id" : "10" } }
{ "num_conta": "1010", "nome": "André Fonseca", "sexo": "masculino", "cidade": "Goiânia", "estado": "GO", "saldo": 9000.00 }

Metric Aggregation — média do saldo:

GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "media_saldo": {
      "avg": {
        "field": "saldo"
      }
    }
  }
}

"size": 0 instrui o Elasticsearch a não retornar os documentos em si — apenas o resultado da agregação.

Bucket Aggregation — agrupando por estado com média de saldo:

GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "saldo_por_estado": {
      "terms": {
        "field": "estado"
      },
      "aggs": {
        "media_saldo": {
          "avg": {
            "field": "saldo"
          }
        }
      }
    }
  }
}

Esta query agrupa os documentos por estado e, dentro de cada grupo, calcula a média do saldo. As agregações podem ser aninhadas dessa forma sem limite de profundidade.

Pipeline Aggregation — média das médias por sexo:

GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "saldo_por_sexo": {
      "terms": {
        "field": "sexo"
      },
      "aggs": {
        "media_saldo": {
          "avg": {
            "field": "saldo"
          }
        }
      }
    },
    "media_geral_dos_sexos": {
      "avg_bucket": {
        "buckets_path": "saldo_por_sexo>media_saldo"
      }
    }
  }
}

A pipeline aggregation avg_bucket opera sobre os resultados da agregação saldo_por_sexo, calculando a média das médias de cada bucket. O buckets_path usa a notação agregacao_pai>agregacao_filha para referenciar o resultado desejado.

Clone this wiki locally