Course contents
Query DSL · 9 / 28

Query structure and contexts

Separate eligibility from scoring and read Query DSL as a tree.

7 sectionsElasticsearch 9.4.3Not read yet

What you will learn

  • A tree of clauses
  • Two questions for a document
  • Constant scores and caching

Separate eligibility from scoring and read Query DSL as a tree.

course-products examples use the teaching mapping and product from chapter 5, “Mapping and field types”. Exercise walkthroughs below use the trainer’s separate public dataset.

Chapter map
  • A tree of clauses
  • Two questions for a document
  • Constant scores and caching

Separate eligibility from scoring and read Query DSL as a tree.

The course targets Elasticsearch 9.4.3. Exercise examples use the public products dataset; other requests illustrate separate teaching indices and may require setup or privileges unavailable in the trainer. Responses are shortened fragments; scores may vary. Check deployment and license requirements for advanced features.

01 / 07

A tree of clauses

A leaf query checks a condition such as term, range or match. A compound query combines queries or changes their scoring, as bool, dis_max and constant_score do. JSON describes a tree, not an instruction sequence. Filter array order does not guarantee execution order: the engine optimizes internally.

Check your understanding
02 / 07

Two questions for a document

Query context can score how well a document matches. Filter context decides eligibility. Stock, tenant_id and price bounds are normally constraints. Moving match into filter does not change its analyzer, but removes its scoring contribution. This is a semantic decision, not just a mechanical optimization.

Check your understanding
03 / 07

Constant scores and caching

match_all selects every document; match_none selects none. constant_score wraps a filter with a uniform contribution. Filter context can make results eligible for caching, but caching depends on usage, segments and internal policies. A filter is not guaranteed to be cached or faster on the second request.

Exact-brand matches receive a uniform score contribution.

POST /course-products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "brand": "Sony"
        }
      }
    }
  }
}

How to read this result

Exact-brand matches receive a uniform score contribution.

Check your understanding
04 / 07

Catalog reasoning example

For “wireless” with price<100, name matching supports relevance comparison while price determines budget eligibility. A product costing 20 is not automatically more textually relevant than one costing 90. Put match in must and range in filter. A user-selected price sort is a separate ordering contract. Putting every clause in must can add unwanted scoring and make results harder to explain.

Check your understanding
05 / 07

Retrieve all products

The root of a search body is a JSON object. query defines which documents match. match_all with an empty object imposes no restriction, so it is useful for exploring a dataset before adding conditions. size controls the number of returned hits, not how many documents match; sort makes this small demonstration reproducible. JSON requires double quotes and does not allow comments or a trailing comma.

hits.total.value reports the number of matching documents; relation: eq means this count is exact. hits.hits is the returned page. _id identifies a hit, _source contains requested original fields, and _score is the relevance metadata. took is measured in milliseconds and varies between runs. Our fragments omit took, shard information and index metadata; that omission is presentation, not another Elasticsearch response format.

FieldMappingPurpose
idintegerStable product identifier and tie-breaker
name / descriptiontextAnalyzed full-text search
brand / brand.keywordtext / keywordText search / exact brand value
category / statuskeywordCase-sensitive exact values
price / ratingdouble / floatNumeric values; rating is optional
in_stock / created_atboolean / dateAvailability / creation date

Try a different scenario

POST /products/_search
{
  "query": {
    "match_all": {}
  },
  "size": 2,
  "_source": [
    "name",
    "price"
  ],
  "sort": [
    {
      "id": "asc"
    }
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 300,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "1",
        "_score": null,
        "_source": {
          "name": "Wireless",
          "price": 100
        }
      },
      {
        "_id": "2",
        "_score": null,
        "_source": {
          "name": "Reference device",
          "price": 100
        }
      }
    ]
  }
}

How to read this result

Two hits are returned, but total is 300. The id order comes from sort, not from relevance. _source is limited to name and price to keep the example readable. Later lessons explain these output controls in detail.

Apply this in exercise 1
06 / 07

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
07 / 07

Combine relevance and filters

A practical search often combines a scored match in must with stock and price restrictions in filter. All conditions remain mandatory, but only the text query contributes to relevance. The example searches gaming and restricts products to in-stock items below 800.

Putting the text clause under filter can return the same matching set while discarding text ranking. Putting business restrictions under must can introduce scoring contributions. Preserve the distinction even when a small sample makes the outputs appear similar.

Try a different scenario

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

How to read this result

Returned products satisfy both filters, and their relative ranking is driven by the text match. total includes every matching eligible product, not just the small returned page.

Apply this in exercise 20

Check your understanding

Answer before expanding each explanation.

1. What does Query DSL JSON describe?

A tree of clauses and search parameters.

2. Does filter remove text analysis?

No, match still analyzes input but does not contribute a score.

3. Where should tenant_id go?

In a required constraint, normally server-controlled bool.filter.

4. Is filter always cached?

No, cache policy and segment conditions apply.

5. When is constant_score useful?

When all eligible documents should receive the same contribution.

Key takeaways

  • A tree of clauses and search parameters.
  • No, match still analyzes input but does not contribute a score.
  • In a required constraint, normally server-controlled bool.filter.
  • No, cache policy and segment conditions apply.
  • When all eligible documents should receive the same contribution.

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.