Course contents
Theory course · 4 / 6

Results & relevance

Control sorting, pagination and returned fields. Boost changes relevance; scores depend on the corpus and are not probabilities.

5 sectionsElasticsearch 9.4.3Not read yet

What you will learn

  • Build deterministic sorting and pagination.
  • Return only the fields the client needs.
  • Understand boost and preserve scored text matching.

Matching and presentation are separate stages. A query determines the full candidate set; sort, from, size and _source determine what the client receives. A correct filter can still yield an incorrect exercise answer if the page, order or returned fields differ from the contract.

Relevance scores depend on query structure and corpus statistics, including term frequency and document length. They are neither probabilities nor permanent constants. Compare relative document order when reasoning about relevance. Explicit field sorting can replace relevance ordering, so use it only when the requirement asks for it.

A page is the last stage
  • Query candidates
  • Eligibility filters
  • Score or field sort
  • from + size
  • _source projection

Filtering happens before pagination, and source projection changes the returned payload rather than document eligibility.

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

Sort by price

sort is an ordered list of keys. Elasticsearch compares the first key, then the second only when the first is equal. The example asks for the most expensive products, with id ascending to resolve equal prices. Ascending and descending orders are expressed as asc and desc.

Sort on fields with sortable indexed values, such as price, rating, id and brand.keyword. The analyzed name field is not a suitable keyword sort key in this mapping. When explicit non-score sorting is used, _score may be null because scoring is not needed; null is not evidence of a failed query.

Try a different scenario

POST /products/_search
{
  "query": {
    "match_all": {}
  },
  "size": 2,
  "_source": [
    "name",
    "price"
  ],
  "sort": [
    {
      "price": "desc"
    },
    {
      "id": "asc"
    }
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 300,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "10",
        "_score": null,
        "_source": {
          "name": "Logitech Wireless Charger 10",
          "price": 2499
        }
      },
      {
        "_id": "20",
        "_score": null,
        "_source": {
          "name": "Samsung Wireless Charger 20",
          "price": 2499
        }
      }
    ]
  }
}

How to read this result

Prices descend. If several products have the highest price, the lower id comes first. Compare sort priority rather than assuming the second key reorders the whole result.

Apply this in exercise 16
02 / 05

Fetch the second page

from is a zero-based offset, not a page number. For page p with length n, use from = (p − 1) × n and size = n. The example requests page four of three items, so from is 9. Filtering and sorting occur before the offset is applied.

Use the same deterministic sort for every page. Otherwise equal values can produce overlaps or omissions between pages. This trainer caps returned hits at 100 and from + size at 500; it is designed for small educational pages rather than deep production pagination.

Try a different scenario

POST /products/_search
{
  "query": {
    "match_all": {}
  },
  "size": 3,
  "_source": [
    "name",
    "price"
  ],
  "sort": [
    {
      "id": "asc"
    }
  ],
  "from": 9
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 300,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "10",
        "_score": null,
        "_source": {
          "name": "Logitech Wireless Charger 10",
          "price": 2499
        }
      },
      {
        "_id": "11",
        "_score": null,
        "_source": {
          "name": "Dell Wireless Headphones 11",
          "price": 49
        }
      },
      {
        "_id": "12",
        "_score": null,
        "_source": {
          "name": "apple Premium Wireless Headphones 12",
          "price": 99
        }
      }
    ]
  }
}

How to read this result

The IDs are 10, 11 and 12, while total still describes all 300 matches. Page length and population size answer different questions.

Apply this in exercise 17
03 / 05

Choose returned fields

_source filtering selects original JSON properties returned for each hit. It does not change the query, its score or the fields available for sorting. The example returns only name and category while sorting by id, demonstrating that a sort field need not be returned.

_id and _score are hit metadata, outside _source. Requesting fewer source properties therefore does not remove the hit identifier. Use original field paths rather than assuming that mapping multi-fields such as brand.keyword were separately written into _source.

Try a different scenario

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

How to read this result

Each source object has exactly name and category. The hit still has _id and _score, and the order still follows id.

Apply this in exercise 18
04 / 05

Prioritize title matches

In multi_match, the caret notation name^3 gives the name field extra weight relative to an unboosted description. The example searches travel in both fields. Boost changes the contribution of a field; it does not guarantee that every title match universally outranks every description match regardless of content and corpus statistics.

Do not multiply a displayed _score yourself or compare it to a hardcoded target. The engine computes the score from the query and index. In the trainer, a relevance exercise compares document-order relationships and the requested construction. Its task may require a particular boost value even when another value looks plausible.

Try a different scenario

POST /products/_search
{
  "query": {
    "multi_match": {
      "query": "travel",
      "fields": [
        "name^3",
        "description"
      ]
    }
  },
  "size": 2,
  "_source": [
    "name",
    "description"
  ]
}
Elasticsearch response
Shortened response · JSON
{
  "hits": {
    "total": {
      "value": 178,
      "relation": "eq"
    },
    "hits": [
      {
        "_id": "3",
        "_score": 4.8997154,
        "_source": {
          "name": "Sony Travel Speaker 3",
          "description": "Precision equipment for professionals"
        }
      },
      {
        "_id": "13",
        "_score": 4.8997154,
        "_source": {
          "name": "Apple Travel Speaker 13",
          "description": "Premium wireless sound for travel"
        }
      }
    ]
  }
}

How to read this result

The numeric scores show one execution, not permanent expected values. Inspect which field contains travel, then compare the result with an unboosted query to observe the ranking effect.

Apply this in exercise 19
05 / 05

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

Key takeaways

  • Sort keys are applied in the order written; id resolves ties.
  • from = (page − 1) × size; total is not a page length.
  • _source changes the payload, not matching or sorting.
  • Boost changes a field contribution; scores vary with the corpus.
  • Keep scored text in must and eligibility in filter.

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.