stemming.asciidoc 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. [[mixing-exact-search-with-stemming]]
  2. === Mixing exact search with stemming
  3. When building a search application, stemming is often a must as it is desirable
  4. for a query on `skiing` to match documents that contain `ski` or `skis`. But
  5. what if a user wants to search for `skiing` specifically? The typical way to do
  6. this would be to use a <<multi-fields,multi-field>> in order to have the same
  7. content indexed in two different ways:
  8. [source,js]
  9. --------------------------------------------------
  10. PUT index
  11. {
  12. "settings": {
  13. "analysis": {
  14. "analyzer": {
  15. "english_exact": {
  16. "tokenizer": "standard",
  17. "filter": [
  18. "lowercase"
  19. ]
  20. }
  21. }
  22. }
  23. },
  24. "mappings": {
  25. "properties": {
  26. "body": {
  27. "type": "text",
  28. "analyzer": "english",
  29. "fields": {
  30. "exact": {
  31. "type": "text",
  32. "analyzer": "english_exact"
  33. }
  34. }
  35. }
  36. }
  37. }
  38. }
  39. PUT index/_doc/1
  40. {
  41. "body": "Ski resort"
  42. }
  43. PUT index/_doc/2
  44. {
  45. "body": "A pair of skis"
  46. }
  47. POST index/_refresh
  48. --------------------------------------------------
  49. // CONSOLE
  50. With such a setup, searching for `ski` on `body` would return both documents:
  51. [source,js]
  52. --------------------------------------------------
  53. GET index/_search
  54. {
  55. "query": {
  56. "simple_query_string": {
  57. "fields": [ "body" ],
  58. "query": "ski"
  59. }
  60. }
  61. }
  62. --------------------------------------------------
  63. // CONSOLE
  64. // TEST[continued]
  65. [source,js]
  66. --------------------------------------------------
  67. {
  68. "took": 2,
  69. "timed_out": false,
  70. "_shards": {
  71. "total": 1,
  72. "successful": 1,
  73. "skipped" : 0,
  74. "failed": 0
  75. },
  76. "hits": {
  77. "total" : {
  78. "value": 2,
  79. "relation": "eq"
  80. },
  81. "max_score": 0.18232156,
  82. "hits": [
  83. {
  84. "_index": "index",
  85. "_type": "_doc",
  86. "_id": "1",
  87. "_score": 0.18232156,
  88. "_source": {
  89. "body": "Ski resort"
  90. }
  91. },
  92. {
  93. "_index": "index",
  94. "_type": "_doc",
  95. "_id": "2",
  96. "_score": 0.18232156,
  97. "_source": {
  98. "body": "A pair of skis"
  99. }
  100. }
  101. ]
  102. }
  103. }
  104. --------------------------------------------------
  105. // TESTRESPONSE[s/"took": 2,/"took": "$body.took",/]
  106. On the other hand, searching for `ski` on `body.exact` would only return
  107. document `1` since the analysis chain of `body.exact` does not perform
  108. stemming.
  109. [source,js]
  110. --------------------------------------------------
  111. GET index/_search
  112. {
  113. "query": {
  114. "simple_query_string": {
  115. "fields": [ "body.exact" ],
  116. "query": "ski"
  117. }
  118. }
  119. }
  120. --------------------------------------------------
  121. // CONSOLE
  122. // TEST[continued]
  123. [source,js]
  124. --------------------------------------------------
  125. {
  126. "took": 1,
  127. "timed_out": false,
  128. "_shards": {
  129. "total": 1,
  130. "successful": 1,
  131. "skipped" : 0,
  132. "failed": 0
  133. },
  134. "hits": {
  135. "total" : {
  136. "value": 1,
  137. "relation": "eq"
  138. },
  139. "max_score": 0.8025915,
  140. "hits": [
  141. {
  142. "_index": "index",
  143. "_type": "_doc",
  144. "_id": "1",
  145. "_score": 0.8025915,
  146. "_source": {
  147. "body": "Ski resort"
  148. }
  149. }
  150. ]
  151. }
  152. }
  153. --------------------------------------------------
  154. // TESTRESPONSE[s/"took": 1,/"took": "$body.took",/]
  155. This is not something that is easy to expose to end users, as we would need to
  156. have a way to figure out whether they are looking for an exact match or not and
  157. redirect to the appropriate field accordingly. Also what to do if only parts of
  158. the query need to be matched exactly while other parts should still take
  159. stemming into account?
  160. Fortunately, the `query_string` and `simple_query_string` queries have a feature
  161. that solves this exact problem: `quote_field_suffix`. This tells Elasticsearch
  162. that the words that appear in between quotes are to be redirected to a different
  163. field, see below:
  164. [source,js]
  165. --------------------------------------------------
  166. GET index/_search
  167. {
  168. "query": {
  169. "simple_query_string": {
  170. "fields": [ "body" ],
  171. "quote_field_suffix": ".exact",
  172. "query": "\"ski\""
  173. }
  174. }
  175. }
  176. --------------------------------------------------
  177. // CONSOLE
  178. // TEST[continued]
  179. [source,js]
  180. --------------------------------------------------
  181. {
  182. "took": 2,
  183. "timed_out": false,
  184. "_shards": {
  185. "total": 1,
  186. "successful": 1,
  187. "skipped" : 0,
  188. "failed": 0
  189. },
  190. "hits": {
  191. "total" : {
  192. "value": 1,
  193. "relation": "eq"
  194. },
  195. "max_score": 0.8025915,
  196. "hits": [
  197. {
  198. "_index": "index",
  199. "_type": "_doc",
  200. "_id": "1",
  201. "_score": 0.8025915,
  202. "_source": {
  203. "body": "Ski resort"
  204. }
  205. }
  206. ]
  207. }
  208. }
  209. --------------------------------------------------
  210. // TESTRESPONSE[s/"took": 2,/"took": "$body.took",/]
  211. In the above case, since `ski` was in-between quotes, it was searched on the
  212. `body.exact` field due to the `quote_field_suffix` parameter, so only document
  213. `1` matched. This allows users to mix exact search with stemmed search as they
  214. like.
  215. NOTE: If the choice of field passed in `quote_field_suffix` does not exist
  216. the search will fall back to using the default field for the query string.