检索文档

现在,我们已经在 Elasticsearch 中存储了一些数据,我们可以开始根据这个项目的需求进行工作了。第一个需求就是要能搜索每一个员工的数据。

对于 Elasticsearch 来说,这是非常简单的。我们只需要执行一次 HTTP GET 请求,然后指出文档的地址,也就是索引、类型以及 ID 即可。通过这三个部分,我们就可以得到原始的 JSON 文档:

  1. GET /megacorp/employee/1

返回的内容包含了这个文档的元数据信息,而 John Smith 的原始 JSON 文档也在 _source 字段中出现了:

  1. {
  2. "_index" : "megacorp",
  3. "_type" : "employee",
  4. "_id" : "1",
  5. "_version" : 1,
  6. "found" : true,
  7. "_source" : {
  8. "first_name" : "John",
  9. "last_name" : "Smith",
  10. "age" : 25,
  11. "about" : "I love to go rock climbing",
  12. "interests": [ "sports", "music" ]
  13. }
  14. }

我们通过将HTTP后的请求方式由 PUT 改变为 GET 来获取文档,同理,我们也可以将其更换为 DELETE 来删除这个文档,HEAD 是用来查询这个文档是否存在的。如果你想替换一个已经存在的文档,你只需要使用 PUT 再次发出请求即可。


简易搜索

GET 命令真的相当简单,你只需要告诉它你要什么即可。接下来,我们来试一下稍微复杂一点的搜索。

我们首先要完成一个最简单的搜索命令来搜索全部员工:

  1. GET /megacorp/employee/_search

你可以发现我们正在使用 megacorp 索引,employee 类型,但是我们我们并没有指定文档的ID,我们现在使用的是 _search 端口。你可以再返回的 hits 中发现我们录入的三个文档。搜索会默认返回最前的10个数值。

  1. {
  2. "took": 6,
  3. "timed_out": false,
  4. "_shards": { ... },
  5. "hits": {
  6. "total": 3,
  7. "max_score": 1,
  8. "hits": [
  9. {
  10. "_index": "megacorp",
  11. "_type": "employee",
  12. "_id": "3",
  13. "_score": 1,
  14. "_source": {
  15. "first_name": "Douglas",
  16. "last_name": "Fir",
  17. "age": 35,
  18. "about": "I like to build cabinets",
  19. "interests": [ "forestry" ]
  20. }
  21. },
  22. {
  23. "_index": "megacorp",
  24. "_type": "employee",
  25. "_id": "1",
  26. "_score": 1,
  27. "_source": {
  28. "first_name": "John",
  29. "last_name": "Smith",
  30. "age": 25,
  31. "about": "I love to go rock climbing",
  32. "interests": [ "sports", "music" ]
  33. }
  34. },
  35. {
  36. "_index": "megacorp",
  37. "_type": "employee",
  38. "_id": "2",
  39. "_score": 1,
  40. "_source": {
  41. "first_name": "Jane",
  42. "last_name": "Smith",
  43. "age": 32,
  44. "about": "I like to collect rock albums",
  45. "interests": [ "music" ]
  46. }
  47. }
  48. ]
  49. }
  50. }

注意:反馈值中不仅会告诉你匹配到哪些文档,同时也会把这个文档都会包含到其中:我们需要搜索的用户的所有信息。

接下来,我们将要尝试着实现搜索一下哪些员工的姓氏中包含 Smith。为了实现这个,我们需要使用一种轻量的搜索方法。这种方法经常被称做 查询字符串(query string) 搜索,因为我们通过URL来传递查询的关键字:

  1. GET /megacorp/employee/_search?q=last_name:Smith

我们依旧使用 _search 端口,然后可以将参数传入给 q=。这样我们就可以得到姓Smith的结果:

  1. {
  2. ...
  3. "hits": {
  4. "total": 2,
  5. "max_score": 0.30685282,
  6. "hits": [
  7. {
  8. ...
  9. "_source": {
  10. "first_name": "John",
  11. "last_name": "Smith",
  12. "age": 25,
  13. "about": "I love to go rock climbing",
  14. "interests": [ "sports", "music" ]
  15. }
  16. },
  17. {
  18. ...
  19. "_source": {
  20. "first_name": "Jane",
  21. "last_name": "Smith",
  22. "age": 32,
  23. "about": "I like to collect rock albums",
  24. "interests": [ "music" ]
  25. }
  26. }
  27. ]
  28. }
  29. }

使用Query DSL搜索

查询字符串是通过命令语句完成 点对点(ad hoc) 的搜索,但是这也有它的局限性(可参阅《搜索局限性》章节)。Elasticsearch 提供了更加丰富灵活的查询语言,它被称作 Query DSL,通过它你可以完成更加复杂、强大的搜索任务。

DSL (Domain Specific Language 领域特定语言) 需要使用 JSON 作为主体,我们还可以这样查询姓 Smith 的员工:

  1. GET /megacorp/employee/_search
  2. {
  3. "query" : {
  4. "match" : {
  5. "last_name" : "Smith"
  6. }
  7. }
  8. }

这个请求会返回同样的结果。你会发现我们在这里没有使用 查询字符串,而是使用了一个由 JSON 构成的请求体,其中使用了 match 查询法,随后我们还将会学习到其他的查询类型。

更加复杂的搜索

接下来,我们再提高一点儿搜索的难度。我们依旧要寻找出姓 Smith 的员工,但是我们还将添加一个年龄大于30岁的限定条件。我们的查询语句将会有一些细微的调整来以识别结构化搜索的限定条件 filter(过滤器):

  1. GET /megacorp/employee/_search
  2. {
  3. "query" : {
  4. "filtered" : {
  5. "filter" : {
  6. "range" : {
  7. "age" : { "gt" : 30 } <1>
  8. }
  9. },
  10. "query" : {
  11. "match" : {
  12. "last_name" : "Smith" <2>
  13. }
  14. }
  15. }
  16. }
  17. }
  1. 这一部分的语句是 range filter ,它可以查询所有超过30岁的数据 — gt 代表 greater than (大于)

  2. 这一部分我们前一个操作的 match query 是一样的

先不要被这么多的语句吓到,我们将会在之后带你逐渐了解他们的用法。你现在只需要知道我们添加了一个 filter,可以在 match 的搜索基础上再来实现区间搜索。现在,我们的只会显示32岁的名为Jane Smith的员工了:

  1. {
  2. ...
  3. "hits": {
  4. "total": 1,
  5. "max_score": 0.30685282,
  6. "hits": [
  7. {
  8. ...
  9. "_source": {
  10. "first_name": "Jane",
  11. "last_name": "Smith",
  12. "age": 32,
  13. "about": "I like to collect rock albums",
  14. "interests": [ "music" ]
  15. }
  16. }
  17. ]
  18. }
  19. }

全文搜索

上面的搜索都很简单:名字搜索、通过年龄过滤。接下来我们来学习一下更加复杂的搜索,全文搜索——一项在传统数据库很难实现的功能。
我们将会搜索所有喜欢 rock climbing 的员工:

  1. GET /megacorp/employee/_search
  2. {
  3. "query" : {
  4. "match" : {
  5. "about" : "rock climbing"
  6. }
  7. }
  8. }

你会发现我们同样使用了 match 查询来搜索 about 字段中的 rock climbing。我们会得到两个匹配的文档:

  1. {
  2. ...
  3. "hits": {
  4. "total": 2,
  5. "max_score": 0.16273327,
  6. "hits": [
  7. {
  8. ...
  9. "_score": 0.16273327, <1>
  10. "_source": {
  11. "first_name": "John",
  12. "last_name": "Smith",
  13. "age": 25,
  14. "about": "I love to go rock climbing",
  15. "interests": [ "sports", "music" ]
  16. }
  17. },
  18. {
  19. ...
  20. "_score": 0.016878016, <1>
  21. "_source": {
  22. "first_name": "Jane",
  23. "last_name": "Smith",
  24. "age": 32,
  25. "about": "I like to collect rock albums",
  26. "interests": [ "music" ]
  27. }
  28. }
  29. ]
  30. }
  31. }
  1. 相关评分

通常情况下,Elasticsearch 会通过相关性来排列顺序,第一个结果中,John Smith 的 about 字段中明确地写到 rock climbing。而在 Jane Smith 的 about 字段中,提及到了 rock,但是并没有提及到 climbing,所以后者的 _score 就要比前者的低。

这个例子很好地解释了 Elasticsearch 是如何执行全文搜索的。对于 Elasticsearch 来说,相关性的概念是很重要的,而这也是它与传统数据库在返回匹配数据时最大的不同之处。

段落搜索

能够找出每个字段中的独立单词固然很好,但是有的时候你可能还需要去匹配精确的短语或者 段落。例如,我们只需要查询到 about 字段只包含 rock climbing 的短语的员工。

为了实现这个效果,我们将对 match 查询变为 match_phrase 查询:

  1. GET /megacorp/employee/_search
  2. {
  3. "query" : {
  4. "match_phrase" : {
  5. "about" : "rock climbing"
  6. }
  7. }
  8. }

这样,系统会没有异议地返回 John Smith 的文档:

  1. {
  2. ...
  3. "hits": {
  4. "total": 1,
  5. "max_score": 0.23013961,
  6. "hits": [
  7. {
  8. ...
  9. "_score": 0.23013961,
  10. "_source": {
  11. "first_name": "John",
  12. "last_name": "Smith",
  13. "age": 25,
  14. "about": "I love to go rock climbing",
  15. "interests": [ "sports", "music" ]
  16. }
  17. }
  18. ]
  19. }
  20. }

高亮我们的搜索

很多程序希望能在搜索结果中 高亮 匹配到的关键字来告诉用户这个文档是 如何 匹配他们的搜索的。在 Elasticsearch 中找到高亮片段是非常容易的。

让我们回到之前的查询,但是添加一个 highlight 参数:

  1. GET /megacorp/employee/_search
  2. {
  3. "query" : {
  4. "match_phrase" : {
  5. "about" : "rock climbing"
  6. }
  7. },
  8. "highlight": {
  9. "fields" : {
  10. "about" : {}
  11. }
  12. }
  13. }

当我们运行这个查询后,相同的命中结果会被返回,但是我们会得到一个新的名叫 highlight 的部分。在这里包含了 about 字段中的匹配单词,并且会被 <em></em> HTML字符包裹住:

  1. {
  2. ...
  3. "hits": {
  4. "total": 1,
  5. "max_score": 0.23013961,
  6. "hits": [
  7. {
  8. ...
  9. "_score": 0.23013961,
  10. "_source": {
  11. "first_name": "John",
  12. "last_name": "Smith",
  13. "age": 25,
  14. "about": "I love to go rock climbing",
  15. "interests": [ "sports", "music" ]
  16. },
  17. "highlight": {
  18. "about": [
  19. "I love to go <em>rock</em> <em>climbing</em>" <1>
  20. ]
  21. }
  22. }
  23. ]
  24. }
  25. }
  1. 在原有文本中高亮关键字。