Learn Elasticsearch - Advanced Search - Compound & Boolean Queries
Episode 8 of 31

Learn Elasticsearch - Advanced Search - Compound & Boolean Queries

Composing complex searches: the bool query with must, should, must_not, and filter; boosting query, constant_score, dis_max; filter caching and strategies for using filter vs query for optimal performance.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

The basic queries from episode 6 are fine for simple cases, but the real world is rarely that simple: users want "plain shirts, black, under 100 thousand, a specific brand, but not kids' sizes". A single query DSL clause isn't enough — you need compound queries: combining several queries into one with boolean logic and relevance control.

Episode 8 covers the main compound queries: bool with must, should, must_not, and filter; boosting, constant_score, and dis_max; plus the filter caching strategy that determines search performance in production.

Bool Query

bool is the most important compound query in Elasticsearch — like WHERE + OR + NOT in SQL, but far more expressive. It has four clauses:

ClauseBehaviorRelevance score
mustDocuments must match (AND logic)Computed
shouldDocuments ideally match (OR logic)Computed
must_notDocuments must not matchNot computed
filterDocuments must match, results are cachedNot computed
Bool query dengan empat klausa
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "kaos polos" } }
      ],
      "should": [
        { "match": { "description": "bahan katun" } }
      ],
      "must_not": [
        { "term": { "size.keyword": "kids" } }
      ],
      "filter": [
        { "term": { "category.keyword": "fashion" } },
        { "range": { "price": { "lte": 100000 } } }
      ]
    }
  }
}

Let's read it slowly: documents must contain the words "kaos polos" in their name, are more relevant if the description mentions "bahan katun", must not be of kids' size, and must be in the fashion category with a price below 100 thousand. Note the should: without must/filter, at least one should must match; with must/filter, should only becomes a relevance bonus.

Scoring with should

The should clause boosts the score of documents that match it. This is a common trick for setting priority: find the required matches with must, then use should to push the most relevant documents to the top. You can control this further with minimum_should_match if you need to require a minimum number of matching should clauses.

Boosting Query

Sometimes we don't want to exclude documents, only lower their priority. boosting consists of positive (what you're searching for, with a boost) and negative (what gets penalized, with a negative_boost between 0 and 1):

Menurunkan prioritas produk dengan skor rendah
{
  "query": {
    "boosting": {
      "positive": {
        "match": { "name": "kaos" }
      },
      "negative": {
        "term": { "rating.keyword": "low" }
      },
      "negative_boost": 0.2
    }
  }
}

Documents with a "low" rating still appear, but their score is multiplied by 0.2 — effectively sinking below other results.

Constant Score Query

constant_score wraps a query and gives it a constant score — usually combined with filter. Since filters don't compute scores, the result uses a default score that can be set with boost:

Skor konstan untuk hasil filter
{
  "query": {
    "constant_score": {
      "filter": {
        "term": { "category.keyword": "fashion" }
      },
      "boost": 1.5
    }
  }
}

When to use it? When you only need filtering without caring how relevant something is — for example a list of products in a single category sorted by price rather than score. The results are uniform and fast.

Dis Max Query

dis_max (disjunction max) combines several queries over several fields, but takes the highest score among them instead of summing them. This is ideal when the same word can appear in different fields and you don't want a "doubled" score to distort ranking:

dis_max: ambil skor terbaik dari field mana pun
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "name": "kaos" } },
        { "match": { "brand": "kaos" } }
      ],
      "tie_breaker": 0.3
    }
  }
}

If a document matches both name and brand, dis_max doesn't sum them — it takes the highest, then adds tie_breaker (0–1) times the second score as a small bonus. The result is fairer ranking than bool must, which sums scores.

Tip

Your choices: bool for AND/OR logic with full control, dis_max for picking the best field without penalizing multiple matches, constant_score for pure filtering. Most production searches use bool as their outer framework.

Filter Caching and Optimization

Why Filters Are Faster

This is the main reason filters are cached: results from the same query run repeatedly — for example the same category filter on every page — can be cached in memory so subsequent queries don't recompute them. Scored queries are not cached this way. Under high load, moving rarely-changing criteria into filter can drastically reduce search latency.

Using Filter vs Query Correctly

CriteriaRight Place
Category, status, price, datefilter — yes/no, cached
Free-text searchmust or should — needs scoring
Exclusions without scoringmust_not
Mandatory constraints without affecting scorefilter

Rule of thumb: if a criterion doesn't change relevance ranking, put it in filter. Filters that rarely change (category, price range) deliver the biggest cache benefit; filters that constantly change (for example a dynamic user-location filter) won't benefit from the cache as much.

Warning

The filter cache uses JVM memory. Too many distinct filters actually pressure the cache and trigger pressure — a sign that the filters are too "unique" to be useful. Monitor it via the filter_cache node stats; we'll discuss these metrics in episode 21.

Common Mistakes

  1. must for criteria that don't need scoring. Category and price in must force wasteful score computation — move them to filter.

  2. must_not using match instead of term. For keyword fields, term is more appropriate; match analyzes text and can produce surprises.

  3. Putting the entire filter set in query_string. Query strings are prone to parsing errors; break them into a structured bool.

  4. Too many should clauses without minimum_should_match. Results can become too loose or unpredictable.

  5. Disproportionate boosting. Extreme boost values make results nonsensical — increase gradually and evaluate.

Conclusion

In episode 8 you mastered compound queries: bool with must, should, must_not, and filter; boosting to lower priority; constant_score for constant-score filtering; dis_max for picking the best field; plus the filter caching strategy and when to use filter vs query.

Key takeaways:

  • bool is the main framework for production queries — master all four clauses.
  • should gives a relevance bonus when must/filter already exist.
  • filter is cached and doesn't compute scores — put yes/no criteria there.
  • dis_max takes the best score, great for words that can appear in several fields.
  • Excessive boosting ruins ranking — apply it gradually.

Relevant search alone isn't enough — you also want the data picture: total sales, average price per category, monthly trends. That's the job of aggregations. In episode 9 we'll cover aggregations: metrics (sum, avg, stats, cardinality, percentiles), buckets (terms, histogram, date_histogram, nested), and pipeline (moving average, derivative, cumulative sum, bucket sorting). See you there!

Learn Elasticsearch - Advanced Search - Compound & Boolean Queries | Learn Elasticsearch