Course contents
Theory course · 2 / 6

Boolean queries

Combine queries with bool. must contributes to relevance, filter does not; must_not excludes documents. should is optional when must or filter is present unless minimum_should_match is set.

5 sectionsElasticsearch 9.4.3Not read yet

What you will learn

  • Translate AND, OR and NOT into bool clauses.
  • Keep eligibility separate from relevance.
  • Make alternatives mandatory when the task requires them.

Business requirements rarely fit one clause. A product may need to match a phrase, be in stock, lie inside a price interval and avoid an excluded status. A bool query joins these clauses. Read each level as a separate logical expression: sibling filter clauses are joined by AND, while should provides alternatives whose required count must be understood.

The distinction between query context and filter context is about scoring. A scored clause asks how well a document matches; a filter asks only whether it qualifies. Putting a clause under an outer filter also suppresses scoring of its descendants, even when a nested bool contains must.

How bool admits a document
  • must + filter: required
  • must_not: reject matches
  • should: required count
  • Admitted document + score

A document must satisfy required clauses and avoid exclusions. Required should matches are checked before the result is ranked.

Examples use the public products dataset on Elasticsearch 9.4.3. Responses are shortened JSON fragments: timing, shard and index metadata are omitted. Numeric scores are illustrative and can vary. Examples teach the method; exercise values may differ.

01 / 05

Combine required conditions

Every clause in bool.must must match. In query context these clauses also contribute to scoring. A list of match and term clauses therefore requires both text relevance and an exact condition. The example searches for gaming in name and requires availability; it is an AND, not an alternative between a word and a boolean.

A single clause can be written as an object, while several clauses are normally written as an array. Choose a clear consistent style. In ordinary product search it is often better to move availability into filter, but this section isolates how must works; an exercise can deliberately require a particular context.

Try a different scenario

POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "gaming"
          }
        },
        {
          "term": {
            "in_stock": true
          }
        }
      ]
    }
  },
  "size": 2,
  "_source": [
    "name",
    "in_stock"
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 20,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "4",
        "_score": 2.7367332,
        "_source": {
          "name": "Logitech Gaming Keyboard 4",
          "in_stock": true
        }
      },
      {
        "_id": "14",
        "_score": 2.7367332,
        "_source": {
          "name": "Samsung Gaming Keyboard 14",
          "in_stock": true
        }
      }
    ]
  }
}

How to read this result

Every hit matches the text and is in stock. The score comes from scored clauses rather than from the number of JSON fields in the request.

Apply this in exercise 6
02 / 05

Filter without scoring

bool.filter requires every listed restriction without adding a relevance contribution. Exact brand, stock and price restrictions are good examples: an available product should not gain relevance just because it is available. With no scored clause, a filter-only bool normally returns score zero.

Context applies to the whole clause below it. A nested bool with two filters represents a grouped AND. Keep query structure readable rather than using extra levels without purpose. Filter clauses can be considered for caching by Elasticsearch, but correctness must never depend on a cache hit or a particular execution order.

ClauseRequired?Adds score in query context?
mustYesYes
filterYesNo
must_notMust not matchNo
shouldDepends on minimum_should_matchYes

Try a different scenario

POST /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "brand.keyword": "Sony"
          }
        },
        {
          "term": {
            "in_stock": false
          }
        },
        {
          "range": {
            "price": {
              "lt": 1200
            }
          }
        }
      ]
    }
  },
  "size": 2,
  "_source": [
    "name",
    "brand",
    "price",
    "in_stock"
  ],
  "sort": [
    {
      "id": "asc"
    }
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 40,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "3",
        "_score": null,
        "_source": {
          "name": "Sony Travel Speaker 3",
          "brand": "Sony",
          "price": 100,
          "in_stock": false
        }
      },
      {
        "_id": "15",
        "_score": null,
        "_source": {
          "name": "Sony USB Cable 15",
          "brand": "Sony",
          "price": 499,
          "in_stock": false
        }
      }
    ]
  }
}

How to read this result

The products satisfy all three restrictions. Sorting by id makes inspection stable; the zero or null score is not an error in a filter-only search.

Apply this in exercise 7
03 / 05

Exclude discontinued products

A must_not clause rejects a document when the inner query matches. Multiple must_not clauses mean that none may match: excluding status A and excluding status B removes both populations. This is not an instruction to find documents that differ from just one of the values.

A bool containing only must_not begins with all documents and subtracts the matches. Missing values can therefore remain in the result: a document without status does not match term status: preorder and is not excluded by that clause. If presence is required, add exists as a separate required condition.

Try a different scenario

POST /products/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "term": {
            "status": "preorder"
          }
        }
      ]
    }
  },
  "size": 2,
  "_source": [
    "name",
    "status"
  ],
  "sort": [
    {
      "id": "asc"
    }
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 200,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "2",
        "_score": null,
        "_source": {
          "name": "Reference device",
          "status": "discontinued"
        }
      },
      {
        "_id": "3",
        "_score": null,
        "_source": {
          "name": "Sony Travel Speaker 3",
          "status": "active"
        }
      }
    ]
  }
}

How to read this result

No returned product has status preorder. This query intentionally keeps active and discontinued products; it teaches exclusion, not a complete availability policy.

Apply this in exercise 8
04 / 05

Match alternative brands

should expresses alternatives. Here the alternatives are categories rather than brands: either Audio or Accessories is enough. Setting minimum_should_match to 1 documents the intended OR even though a bool with only should already requires one match by default. It becomes especially important when adding other required clauses.

Each alternative can itself be a whole query tree. To express (A AND B) OR (C AND D), put two nested bool groups in should, not four independent leaves. The final lesson uses precisely that grouping for separate category-price policies.

ABA AND BA OR B
falsefalsefalsefalse
falsetruefalsetrue
truefalsefalsetrue
truetruetruetrue

Try a different scenario

POST /products/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "category": "Audio"
          }
        },
        {
          "term": {
            "category": "Accessories"
          }
        }
      ],
      "minimum_should_match": 1
    }
  },
  "size": 2,
  "_source": [
    "name",
    "category"
  ],
  "sort": [
    {
      "id": "asc"
    }
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 150,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "2",
        "_score": null,
        "_source": {
          "name": "Reference device",
          "category": "Audio"
        }
      },
      {
        "_id": "4",
        "_score": null,
        "_source": {
          "name": "Logitech Gaming Keyboard 4",
          "category": "Accessories"
        }
      }
    ]
  }
}

How to read this result

Each hit belongs to at least one allowed category. A product is not required to belong to both, which would be impossible for our single-valued category field.

Apply this in exercise 9
05 / 05

Require optional matches

minimum_should_match belongs to the bool whose should clauses it governs. With must or filter present, the default required count is zero; without either, it is one. Set the count explicitly when the requirement is "at least two properties". The example requires two of three words in name while also filtering stock.

This counts matching should clauses, not the number of terms produced by one match query. A single match containing several words is still one should clause. Build separate clauses for separately counted features; inside each match, the text analyzer still handles case and tokenization.

Try a different scenario

POST /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "in_stock": true
          }
        }
      ],
      "should": [
        {
          "match": {
            "name": "wireless"
          }
        },
        {
          "match": {
            "name": "premium"
          }
        },
        {
          "match": {
            "name": "mouse"
          }
        }
      ],
      "minimum_should_match": 2
    }
  },
  "size": 2,
  "_source": [
    "name",
    "in_stock"
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 39,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "7",
        "_score": 3.2707968,
        "_source": {
          "name": "Apple Wireless Mouse 7",
          "in_stock": true
        }
      },
      {
        "_id": "17",
        "_score": 3.2707968,
        "_source": {
          "name": "Dell Wireless Mouse 17",
          "in_stock": true
        }
      }
    ]
  }
}

How to read this result

A qualifying title contains at least two of the requested word features. A document that only passes the stock filter is not enough.

Apply this in exercise 10

Key takeaways

  • must is required scored matching; filter is required eligibility.
  • must_not excludes every document matching any listed exclusion.
  • should defaults change when must or filter is present.
  • Count clauses with minimum_should_match, not words within one match.
  • Group each AND branch before combining branches with OR.

Official reference

Ready to put this into practice?

Confirm reading to save your place. Reading and exercise progress are tracked separately in this browser using your guest cookie.