You're viewing version 3.7 of the OpenSearch documentation. This version is no longer maintained. For the latest version, see the current documentation. For information about OpenSearch version maintenance, see Release Schedule and Maintenance Policy.
Hybrid search with post-filtering
Introduced 2.13
You can perform post-filtering on hybrid search results by providing the post_filter parameter in your query.
The post_filter clause is applied after the search results have been retrieved. Post-filtering is useful for applying additional filters to the search results without impacting the scoring or the order of the results.
Post-filtering does not impact aggregation results.
To filter all subqueries during query execution instead of filtering the final results, use a common filter. For more information, see Hybrid search with pre-filtering.
Example: Faceted search with post-filtering
Post-filtering is commonly used in faceted search, in which the UI displays aggregation counts (such as brand, color, and size filters) alongside search results. Using a post_filter keeps the aggregation counts based on the full unfiltered query while filtering only the displayed hits.
Consider an index containing product documents:
{
"name": "Nike Air Max",
"brand": "Nike",
"color": "Red",
"size": 10,
"price": 120,
"category": "Running Shoes"
}
A user searches for “running shoes”, and the application constructs a query containing aggregations for brand, color, and size:
POST /products/_search
{
"query": {
"match": {
"category": "running shoes"
}
},
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" }
},
"colors": {
"terms": { "field": "color.keyword" }
},
"sizes": {
"terms": { "field": "size" }
}
}
}
The response returns hits from all brands:
Nike Air Max
Nike Pegasus
Adidas Adizero
Puma Velocity
...
The response also returns aggregations that include counts for every brand, color, and size:
Brands: Nike (120), Adidas (80), Puma (45)
Colors: Black (90), White (70), Red (55)
Sizes: 8 (40), 9 (60), 10 (85)
The aggregations are typically displayed as facet filters in the UI. When a user selects a specific brand (for example, Nike) to filter results, using a pre-filter would exclude non-Nike documents before aggregations are computed, causing other brands to disappear from the facet counts.
With post_filter, the query and aggregations run on the full result set. The filter is applied only to the displayed hits:
POST /products/_search
{
"query": {
"match": {
"category": "running shoes"
}
},
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" }
},
"colors": {
"terms": { "field": "color.keyword" }
}
},
"post_filter": {
"term": { "brand.keyword": "Nike" }
}
}
The hits contain only Nike products, but the aggregations still reflect the full unfiltered query:
Brands: Nike (120), Adidas (80), Puma (45)
Colors: Black (90), White (70), Red (55)
All brand options remain visible in the facet, allowing the user to switch brands or compare counts without removing the filter.
How post-filtering affects search results and scoring
Post-filtering can significantly change the final search results and document scores. Consider the following scenarios.
Single-query scenario
Consider a query that returns the following results:
- Query results before normalization:
[d2: 5.0, d4: 3.0, d1: 2.0] - Normalized scores:
[d2: 1.0, d4: 0.33, d1: 0.0]
After applying a post-filter to the initial query results, the results are as follows:
- Post-filter matches
[d2, d4] - Resulting scores:
[d2: 1.0, d4: 0.0]
Note how document d4’s score changes from 0.33 to 0.0 after applying the post-filter.
Multiple-query scenario
Consider a query with two subqueries:
- Query 1 results:
[d2: 5.0, d4: 3.0, d1: 2.0] - Query 2 results:
[d1: 1.0, d5: 0.5, d4: 0.25] - Normalized scores:
- Query 1:
[d2: 1.0, d4: 0.33, d1: 0.0] - Query 2:
[d1: 1.0, d5: 0.33, d4: 0.0]
- Query 1:
- Combined initial scores:
[d2: 1.0, d1: 0.5, d5: 0.33, d4: 0.165]
After applying a post-filter to the initial query results, the results are as follows:
- Post-filter matches
[d2, d4] - Resulting scores:
- Query 1:
[d2: 5.0, d4: 3.0] - Query 2:
[d4: 0.25]
- Query 1:
- Normalized scores:
- Query 1:
[d2: 1.0, d4: 0.0] - Query 2:
[d4: 1.0]
- Query 1:
- Combined final scores:
[d2: 1.0, d4: 0.5]
Observe that:
- Document
d2’s score remains unchanged. - Document
d4’s score has changed.