|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Distance feature |
| 4 | +parent: Specialized queries |
| 5 | +nav_order: 5 |
| 6 | +has_math: true |
| 7 | +--- |
| 8 | + |
| 9 | +# Distance feature query |
| 10 | + |
| 11 | +Use the `distance_feature` query to boost the relevance of documents that are closer to a specific date or geographic point. This can help you prioritize more recent or nearby content in your search results. For example, you can assign more weight to products manufactured more recently or boost items closest to a user-specified location. |
| 12 | + |
| 13 | +You can apply this query to fields containing date or location data. It's commonly used within a `bool` query `should` clause to improve relevance scoring without filtering out results. |
| 14 | + |
| 15 | +## Configuring the index |
| 16 | + |
| 17 | +Before using the `distance_feature` query, ensure that your index contains at least one of the following field types: |
| 18 | + |
| 19 | +- [`date`]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/date/) |
| 20 | +- [`date_nanos`]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/date-nanos/) |
| 21 | +- [`geo_point`]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/geo-point/) |
| 22 | + |
| 23 | +In this example, you'll configure the `opening_date` and `coordinates` fields that you can use to run distance feature queries: |
| 24 | + |
| 25 | +```json |
| 26 | +PUT /stores |
| 27 | +{ |
| 28 | + "mappings": { |
| 29 | + "properties": { |
| 30 | + "opening_date": { |
| 31 | + "type": "date" |
| 32 | + }, |
| 33 | + "coordinates": { |
| 34 | + "type": "geo_point" |
| 35 | + } |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | +``` |
| 40 | +{% include copy-curl.html %} |
| 41 | + |
| 42 | +Add sample documents to the index: |
| 43 | + |
| 44 | +```json |
| 45 | +PUT /stores/_doc/1 |
| 46 | +{ |
| 47 | + "store_name": "Green Market", |
| 48 | + "opening_date": "2025-03-10", |
| 49 | + "coordinates": [74.00, 40.70] |
| 50 | +} |
| 51 | +``` |
| 52 | +{% include copy-curl.html %} |
| 53 | + |
| 54 | +```json |
| 55 | +PUT /stores/_doc/2 |
| 56 | +{ |
| 57 | + "store_name": "Fresh Foods", |
| 58 | + "opening_date": "2025-04-01", |
| 59 | + "coordinates": [73.98, 40.75] |
| 60 | +} |
| 61 | +``` |
| 62 | +{% include copy-curl.html %} |
| 63 | + |
| 64 | +```json |
| 65 | +PUT /stores/_doc/3 |
| 66 | +{ |
| 67 | + "store_name": "City Organics", |
| 68 | + "opening_date": "2021-04-20", |
| 69 | + "coordinates": [74.02, 40.68] |
| 70 | +} |
| 71 | +``` |
| 72 | +{% include copy-curl.html %} |
| 73 | + |
| 74 | +## Example: Boost scores based on recency |
| 75 | + |
| 76 | +The following query searches for documents with a `store_name` matching `market` and boosts recently opened stores: |
| 77 | + |
| 78 | +```json |
| 79 | +GET /stores/_search |
| 80 | +{ |
| 81 | + "query": { |
| 82 | + "bool": { |
| 83 | + "must": { |
| 84 | + "match": { |
| 85 | + "store_name": "market" |
| 86 | + } |
| 87 | + }, |
| 88 | + "should": { |
| 89 | + "distance_feature": { |
| 90 | + "field": "opening_date", |
| 91 | + "origin": "2025-04-07", |
| 92 | + "pivot": "10d" |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | +} |
| 98 | +``` |
| 99 | +{% include copy-curl.html %} |
| 100 | + |
| 101 | +The response contains the matching document: |
| 102 | + |
| 103 | +```json |
| 104 | +{ |
| 105 | + "took": 4, |
| 106 | + "timed_out": false, |
| 107 | + "_shards": { |
| 108 | + "total": 1, |
| 109 | + "successful": 1, |
| 110 | + "skipped": 0, |
| 111 | + "failed": 0 |
| 112 | + }, |
| 113 | + "hits": { |
| 114 | + "total": { |
| 115 | + "value": 1, |
| 116 | + "relation": "eq" |
| 117 | + }, |
| 118 | + "max_score": 1.2372394, |
| 119 | + "hits": [ |
| 120 | + { |
| 121 | + "_index": "stores", |
| 122 | + "_id": "1", |
| 123 | + "_score": 1.2372394, |
| 124 | + "_source": { |
| 125 | + "store_name": "Green Market", |
| 126 | + "opening_date": "2025-03-10", |
| 127 | + "coordinates": [ |
| 128 | + 74, |
| 129 | + 40.7 |
| 130 | + ] |
| 131 | + } |
| 132 | + } |
| 133 | + ] |
| 134 | + } |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +### Example: Boost scores based on geographic proximity |
| 139 | + |
| 140 | +The following query searches for documents with a `store_name` matching `market` and boosts results closer to the given origin point: |
| 141 | + |
| 142 | +```json |
| 143 | +GET /stores/_search |
| 144 | +{ |
| 145 | + "query": { |
| 146 | + "bool": { |
| 147 | + "must": { |
| 148 | + "match": { |
| 149 | + "store_name": "market" |
| 150 | + } |
| 151 | + }, |
| 152 | + "should": { |
| 153 | + "distance_feature": { |
| 154 | + "field": "coordinates", |
| 155 | + "origin": [74.00, 40.71], |
| 156 | + "pivot": "500m" |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | +} |
| 162 | +``` |
| 163 | +{% include copy-curl.html %} |
| 164 | + |
| 165 | +The response contains the matching document: |
| 166 | + |
| 167 | +```json |
| 168 | +{ |
| 169 | + "took": 3, |
| 170 | + "timed_out": false, |
| 171 | + "_shards": { |
| 172 | + "total": 1, |
| 173 | + "successful": 1, |
| 174 | + "skipped": 0, |
| 175 | + "failed": 0 |
| 176 | + }, |
| 177 | + "hits": { |
| 178 | + "total": { |
| 179 | + "value": 1, |
| 180 | + "relation": "eq" |
| 181 | + }, |
| 182 | + "max_score": 1.2910118, |
| 183 | + "hits": [ |
| 184 | + { |
| 185 | + "_index": "stores", |
| 186 | + "_id": "1", |
| 187 | + "_score": 1.2910118, |
| 188 | + "_source": { |
| 189 | + "store_name": "Green Market", |
| 190 | + "opening_date": "2025-03-10", |
| 191 | + "coordinates": [ |
| 192 | + 74, |
| 193 | + 40.7 |
| 194 | + ] |
| 195 | + } |
| 196 | + } |
| 197 | + ] |
| 198 | + } |
| 199 | +} |
| 200 | +``` |
| 201 | + |
| 202 | +## Parameters |
| 203 | + |
| 204 | +The following table lists all top-level parameters supported by `distance_feature` queries. |
| 205 | + |
| 206 | +| Parameter | Required/Optional | Description | |
| 207 | +|-----------|-------------------|-------------| |
| 208 | +| `field` | Required | The name of the field used to calculate distances. Must be a `date`, `date_nanos`, or `geo_point` field with `index: true` (default) and `doc_values: true` (default). | |
| 209 | +| `origin` | Required | The point of origin used to calculate distances. Use a [date]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/date/) or [date math expression]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/date/#date-math) (for example, `now-1h`) for `date` fields or a [geopoint]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/geo-point/) for `geo_point` fields. | |
| 210 | +| `pivot` | Required | The distance from the `origin` at which scores receive half of the `boost` value. Use a time unit (for example, `10d`) for date fields or a distance unit (for example, `1km`) for geographic fields. For more information, see [Units]({{site.url}}{{site.baseurl}}/api-reference/common-parameters/#units).| |
| 211 | +| `boost` | Optional | A multiplier for the relevance score of matching documents. Must be a non-negative float. Default is `1.0`. | |
| 212 | + |
| 213 | +## How scores are calculated |
| 214 | + |
| 215 | +The `distance_feature` query calculates a document's relevance score using the following formula: |
| 216 | + |
| 217 | +$$ \text{score} = \text{boost} \cdot \frac {\text{pivot}} {\text{pivot} + \text{distance}} $$, |
| 218 | + |
| 219 | +where $$\text{distance}$$ is the absolute difference between the `origin` and the field's value. |
| 220 | + |
| 221 | +## Skipping non-competitive hits |
| 222 | + |
| 223 | +Unlike other score-modifying queries like the `function_score` query, the `distance_feature` query is optimized to efficiently skip non-competitive hits when total hit tracking (`track_total_hits`) is disabled. |
0 commit comments