-
Notifications
You must be signed in to change notification settings - Fork 3
03 ‐ Explorando os Dados: Query DSL
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.
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).
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
enderecoem vez deendereç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.
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" }GET /cadastro_clientes/_search
{
"query": {
"match_all": {}
}
}O campo hits.total.value na resposta deve indicar 10 documentos.
As operações CRUD (Create, Read, Update, Delete) são a base da interação com dados no Elasticsearch.
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"
}Por ID:
GET /cadastro_clientes/_doc/1Por query:
GET /cadastro_clientes/_search
{
"query": {
"term": {
"sexo": "feminino"
}
}
}Atenção: o campo
sexoé do tipokeyword, por isso usamosterm(busca exata) e nãomatch(full-text). Usarmatchem camposkeywordfunciona em muitos casos, mas não é a forma correta —matchanalisa o texto antes de buscar, enquantotermbusca o valor exato como está armazenado.
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.
Por ID:
DELETE /cadastro_clientes/_doc/1Em 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
}
}
}
}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.
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 + sizenão pode ultrapassar 10.000 por padrão (parâmetroindex.max_result_window). Para conjuntos maiores que isso, é necessário ajustar esse parâmetro no índice ou adotar outras estratégias de paginação.
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
textnão podem ser usados diretamente para ordenação. Use camposkeywordou campos numéricos. Se precisar ordenar por um campo de texto, defina um sub-campokeywordno mapping (fields: { keyword: { type: keyword } }).
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 } } }
]
}
}
}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,
matchusa o operadorORentre os termos: retorna documentos que contenham "Sandra" ou "Gomes". Para exigir todos os termos, use"operator": "and".
Busca por uma sequência exata de palavras, respeitando a ordem e a proximidade:
GET /cadastro_clientes/_search
{
"query": {
"match_phrase": {
"endereco": "Avenida Sexta"
}
}
}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]
}
}
}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).
Retorna documentos em que o campo especificado existe e não é nulo:
GET /cadastro_clientes/_search
{
"query": {
"exists": {
"field": "endereco"
}
}
}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"
}
}
}
}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)"
}
}
}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.2por satisfazer a condiçãonegative— ela ainda aparece nos resultados, mas ranqueada abaixo de Tereza Costa.
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 } } }
]
}
}
}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 BYdo SQL. - Pipeline Aggregations: operam sobre o resultado de outras agregações, permitindo cálculos encadeados.
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": 0instrui 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.