Skip to content

03 ‐ Explorando os Dados: Query DSL

inhogoes edited this page May 13, 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 numérico sobre um conjunto de documentos.
  • Bucket Aggregations: agrupam documentos em categorias (buckets) com base em um campo ou critério.
  • Pipeline Aggregations: operam sobre o resultado de outras agregações, permitindo cálculos encadeados.

Preparação — criando o índice de laboratório

Todos os exemplos desta seção usam o índice saldo. Execute os comandos abaixo antes de prosseguir.

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 Aggregations — principais funções

Função O que calcula Exemplo de uso
avg Média dos valores de um campo numérico Tempo médio de resposta de uma API
sum Soma dos valores Total de vendas de um período
min Menor valor Menor saldo em conta
max Maior valor Maior transação do dia
value_count Contagem de documentos que têm o campo Quantos registros têm o campo preenchido
cardinality Contagem de valores únicos (aproximado) Quantos usuários distintos fizeram login
percentiles Distribuição dos valores em percentis p50, p95, p99 do tempo de resposta
percentile_ranks Em qual percentil um valor específico se encontra Em qual percentil está o tempo de 200ms
stats Retorna count, min, max, avg e sum em uma única agregação Resumo estatístico de um campo
extended_stats Tudo do stats mais variância, desvio padrão e bounds Análise estatística avançada

Exemplos:

GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "total_saldo":    { "sum":         { "field": "saldo" } },
    "menor_saldo":    { "min":         { "field": "saldo" } },
    "maior_saldo":    { "max":         { "field": "saldo" } },
    "clientes":       { "value_count": { "field": "num_conta" } },
    "estados_unicos": { "cardinality": { "field": "estado" } }
  }
}
GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "resumo_saldo": {
      "stats": {
        "field": "saldo"
      }
    }
  }
}
GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "distribuicao_saldo": {
      "percentiles": {
        "field": "saldo",
        "percents": [25, 50, 75, 95, 99]
      }
    }
  }
}

Nota sobre cardinality: o resultado é aproximado, não exato. O Elasticsearch usa o algoritmo HyperLogLog++ para calcular cardinalidade em escala — a margem de erro padrão é de 5%, configurável com o parâmetro precision_threshold.


Bucket Aggregations — principais funções

Função O que faz Exemplo de uso
terms Agrupa por valor único de um campo Contagem por estado, por status HTTP, por usuário
range Agrupa por intervalos numéricos definidos manualmente Faixas de saldo: 0–1000, 1000–5000, acima de 5000
date_histogram Agrupa por intervalos de tempo Documentos por hora, por dia, por mês
histogram Agrupa por intervalos numéricos fixos Distribuição de valores em faixas de 1000 em 1000
filter Um único bucket com os documentos que atendem a uma query Percentual de erros vs total
filters Múltiplos buckets, cada um com sua própria query Logs de erro por categoria de erro
missing Bucket com documentos que não têm o campo Registros sem cidade preenchida

Exemplos:

GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "faixas_de_saldo": {
      "range": {
        "field": "saldo",
        "ranges": [
          { "to": 1000 },
          { "from": 1000, "to": 3000 },
          { "from": 3000 }
        ]
      }
    }
  }
}
GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "saldo_por_estado": {
      "terms": {
        "field": "estado",
        "size": 10,
        "order": { "_count": "desc" }
      },
      "aggs": {
        "media_saldo": { "avg": { "field": "saldo" } },
        "total_saldo": { "sum": { "field": "saldo" } }
      }
    }
  }
}

Nota sobre terms: por padrão retorna os 10 buckets com mais documentos. O parâmetro size controla quantos buckets retornar. O order permite ordenar por contagem (_count) ou por uma métrica aninhada.


Pipeline Aggregations — principais funções

Função O que calcula
avg_bucket Média dos valores de uma métrica entre todos os buckets
sum_bucket Soma dos valores de uma métrica entre todos os buckets
max_bucket Identifica o bucket com o maior valor de uma métrica
min_bucket Identifica o bucket com o menor valor de uma métrica
derivative Taxa de variação entre buckets consecutivos (equivale a uma derivada)
cumulative_sum Soma acumulada ao longo dos buckets
GET /saldo/_search
{
  "size": 0,
  "aggs": {
    "saldo_por_estado": {
      "terms": { "field": "estado" },
      "aggs": {
        "media_saldo": { "avg": { "field": "saldo" } }
      }
    },
    "estado_maior_media": {
      "max_bucket": {
        "buckets_path": "saldo_por_estado>media_saldo"
      }
    }
  }
}

Laboratório de agregações

Os exemplos desta seção usam o índice saldo criado acima. As queries de metric, bucket e pipeline a seguir devem ser executadas após a inserção dos documentos.

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