This tutorial shows how common Elasticsearch search requests can be expressed using Azure Cosmos DB query patterns. Some Elasticsearch capabilities map directly to Cosmos DB full-text search functions, while others require separate queries or application-layer handling. Each step shows the closest Cosmos DB pattern and explains what changes in the process.
In Elasticsearch, one search request can return hits, highlights, and aggregation buckets together. In Azure Cosmos DB, it is clearer to separate those concerns: one query retrieves ranked results, separate aggregate queries calculate facets, and the application handles presentation features such as highlighting.
Step 1: Translate the ranked result query
Start with an Elasticsearch multi_match query for “shoes” across title, description, brand, and category. The request returns the first 20 hits.
| Elasticsearch | Azure Cosmos DB |
|
|
This translation focuses on the core search behavior rather than an exact one-to-one mapping of every Elasticsearch construct. In Elasticsearch, title3 and brand2 are field boosts that make matches in the title and brand fields more influential in relevance scoring. The Azure Cosmos DB query shown here does not replicate those boost factors because field boosting is not currently supported in Azure Cosmos DB for NoSQL full-text search. Instead, results are ranked using the built-in BM25-based full-text scoring model. As a result, the returned result set and ranking order will differ from Elasticsearch when field boosts contribute to relevance. However, the BM25 scoring model is still designed to surface relevant matches first, so users can expect relevant results even though the ranking behavior is not identical.
Step 2: Split categorical facets into aggregate queries
The Elasticsearch query returns categories, subcategories, and brands as aggregation buckets in a single request.
In Azure Cosmos DB, run one aggregate query for each field. Each query repeats the result query’s text predicate and returns a facet key with a count.
| Elasticsearch | Azure Cosmos DB |
|
Category
Subcategory
Brand
|
What changes: Instead of receiving all buckets in the result response, the application issues focused aggregate queries and combines the counts with the result list.
Step 3: Translate derived rating buckets
The Elasticsearch query creates rating facets by converting each rating to its floor value before grouping.
| Elasticsearch | Azure Cosmos DB |
|
|
What changes: In Elasticsearch, the bucket value is produced by a script inside the aggregation. In Azure Cosmos DB, the derived bucket value is expressed directly in SQL using FLOOR(c.rating) and then used in both SELECT and GROUP BY. The result is the same type of facet output: one row per rating bucket, with a count of matching documents in each bucket.
Step 4: Translate price range facets
The Elasticsearch query creates fixed price ranges and returns a count for each bucket. In Azure Cosmos DB, the same result can be produced with conditional aggregate expressions: each SUM counts documents that fall into one price range.
| Elasticsearch | Azure Cosmos DB |
|
|
What changes: Elasticsearch represents price ranges as a dedicated range aggregation. In Azure Cosmos DB, each range is written as a conditional count in the SELECT clause. The query still uses the same full-text predicate as the result query, so the price buckets count only documents that match the current search. This keeps the facet counts aligned with the result list.
Price boundaries can vary by what a user searches for, so precomputing them is rarely the default solution. You can also group by a derived key such as FLOOR(c.rating). More information about faceting can be found here.
Step 5: Handle highlighting in the application
The Elasticsearch request configures highlighted fragments and mark tags for title and description.
| Elasticsearch | Azure Cosmos DB |
|
Azure Cosmos DB does not return highlighted fragments as part of the full-text query response. To recreate this behavior, run the search query normally, then generate snippets in the application layer by locating the matched query terms in fields such as title and description and wrapping them with display markup like <mark>…</mark>. |
Native hit highlighting is currently under development.
Run the translated search
- Execute the result query. Use the ranked query to retrieve the current page of matching documents.
- Execute the required facet queries. Run category, subcategory, brand, rating, and price aggregates for the same search state.
- Apply active filters everywhere. When a user narrows the search, add that filter to the result query and to every aggregate query before refreshing the UI.
- Render the experience. Display results, counts, and client-side highlights together so navigation reflects a consistent result set.
Validate behavior, not just syntax
Start with a representative set of search terms and documents, then repeat the validation with production-like data. Compare ranked results, selected filters, facet counts, page behavior, request units, and end-to-end latency. This turns the tutorial from a syntax exercise into application-level validation.
Series roadmap
This post is the third and final post in the series. In Blog 1, we introduced the overall migration path for Elasticsearch users moving to Azure Cosmos DB. In Blog 2, we looked at how Elasticsearch mappings can be translated into Azure Cosmos DB indexing and full text search configuration. In this post, we brought those concepts together by walking through how to translate an Elasticsearch Search Request into an Azure Cosmos DB query.
With these three posts, you now have a starting framework for evaluating and migrating Elasticsearch-backed search workloads to Azure Cosmos DB: understand the feature model, map your indexing configuration, and translate your query patterns. For more guidance, review the Azure Cosmos DB full text search documentation or reach out to the Azure Cosmos DB team for help with your migration scenario.
0 comments
Be the first to start the discussion.