{"id":962,"date":"2013-03-11T11:53:05","date_gmt":"2013-03-11T10:53:05","guid":{"rendered":"https:\/\/www.unicoda.com\/?p=962"},"modified":"2013-03-11T11:53:05","modified_gmt":"2013-03-11T10:53:05","slug":"elasticsearch-and-the-mystery-of-auto-completion","status":"publish","type":"post","link":"https:\/\/www.unicoda.com\/?p=962","title":{"rendered":"ElasticSearch and the Mystery of Auto-completion"},"content":{"rendered":"<p>Few months ago, I was looking for information concerning auto-completion with ElasticSearch as a source. I found many solutions, but none of them really fit my needs. Nevertheless, it helped me building my request.<\/p>\n<h1>What&rsquo;s the situation Doc?<\/h1>\n<p>I needed to be able to extract values from an indexed field in ElasticSearch according to a group of letters (user input). In fact, my indexed document contains a field named <em>map<\/em> which is an array of string. The idea is the following: if the user is looking for the value \u00ab\u00a0name\u00a0\u00bb for instance, let&rsquo;s say he will first type \u00ab\u00a0na\u00a0\u00bb. So here we must be able to suggest searches to the user. Furthermore, with this 2 letters, we must proposed existing fields like \u00ab\u00a0name\u00a0\u00bb, \u00ab\u00a0native\u00a0\u00bb or \u00ab\u00a0nature\u00a0\u00bb.<\/p>\n<p>I&rsquo;m using Elastical to interact with ElasticSearch.<br \/>\nFirst, I&rsquo;m building a regex which will be used later in the request.<\/p>\n<pre>var search = req.body.searched.toLowerCase(),\r\nfirstLetter = search.charAt(0),\r\nrest = req.body.searched.slice(1),\r\nreg = \"^[\"+firstLetter+firstLetter.toUpperCase()+\"]\"+rest+\".*\";<\/pre>\n<p>The regex is build to match with both an upper case char or a lower case one in first position.<\/p>\n<h1>What about the request?<\/h1>\n<pre>var request = {\r\n  query: {\r\n    query_string: {\r\n      default_field: \"map\",\r\n      default_operator: \"AND\",\r\n      query: req.body.searched+\"*\"\r\n    }\r\n  },\r\n  facets:{\r\n    map:{\r\n      terms:{\r\n        field: \"map.exact\",\r\n        regex: reg,\r\n        size: 10\r\n      }\r\n    }\r\n  }\r\n}<\/pre>\n<p>First, I&rsquo;m asking ElasticSearch to retrieve all documents which match req.body.searched+\u00a0\u00bb*\u00a0\u00bb where req.body.searched contains the user input. I&rsquo;ve change the default operator to \u00ab\u00a0AND\u00a0\u00bb rather than \u00ab\u00a0OR\u00a0\u00bb in order to be able to deal with fields like \u00ab\u00a0Nom de la gare\u00a0\u00bb or \u00ab\u00a0Name of the dog\u00a0\u00bb. By default, ElasticSearch uses the \u00ab\u00a0OR\u00a0\u00bb operator, so it will ask for \u00ab\u00a0name\u00a0\u00bb OR \u00ab\u00a0of\u00a0\u00bb OR \u00ab\u00a0the\u00a0\u00bb OR \u00ab\u00a0dog\u00a0\u00bb; which is not what I wanted.<\/p>\n<p>Then, I&rsquo;m using facets to retrieve values in the field map of found documents matching the given regex. I&rsquo;m using map.exact for the same reason I must use the \u00ab\u00a0AND\u00a0\u00bb operator.<\/p>\n<p>This request works great with on the tests I&rsquo;ve made. Remains to be seen if it can handle big indexes.<\/p>\n<p>I can now ask ElasticSearch with Elastical and build a clean response:<\/p>\n<pre>elastical.search(request, function (err, results, full) {\r\n  var terms = [];\r\n  async.forEach(full.facets.map.terms, function(data, callback) {\r\n    terms.push(data.term);\r\n    callback();\r\n  }, function(err) {\r\n    res.send(terms);\r\n  });\r\n});<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Few months ago, I was looking for information concerning auto-completion with ElasticSearch as a source. I found many solutions, but none of them really fit my needs. Nevertheless, it helped me building my request. What&rsquo;s the situation Doc? I needed to be able to extract values from an indexed field in ElasticSearch according to a &hellip; <a href=\"https:\/\/www.unicoda.com\/?p=962\" class=\"more-link\">Continuer la lecture<span class=\"screen-reader-text\"> de &laquo;&nbsp;ElasticSearch and the Mystery of Auto-completion&nbsp;&raquo;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[70,37,71],"class_list":["post-962","post","type-post","status-publish","format-standard","hentry","category-code","tag-autocompletion","tag-elasticsearch","tag-facets"],"_links":{"self":[{"href":"https:\/\/www.unicoda.com\/index.php?rest_route=\/wp\/v2\/posts\/962","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.unicoda.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.unicoda.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.unicoda.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.unicoda.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=962"}],"version-history":[{"count":5,"href":"https:\/\/www.unicoda.com\/index.php?rest_route=\/wp\/v2\/posts\/962\/revisions"}],"predecessor-version":[{"id":1183,"href":"https:\/\/www.unicoda.com\/index.php?rest_route=\/wp\/v2\/posts\/962\/revisions\/1183"}],"wp:attachment":[{"href":"https:\/\/www.unicoda.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=962"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.unicoda.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=962"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.unicoda.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=962"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}