Elasticsearch:Terms lookup query - 关联两个不同索引的搜索

我们知道 Elasticsearch 的搜索和传统的 RDMS 搜索是不同的。它不可以使用 joins 来把两个不同索引关联起来,并进行搜索。我们针对多个索引的搜索只限于:

GET index1,index2,other_index*/_search

这样的操作。上面的操作不能使得我们的搜索结果进行任何的关联,因为搜索的结果都是分开的。在实际的使用中,比如我们想从一个索引中搜索到一个关键字,而这个关键字可以作为另外一个搜索中的一个参数来使用。也就是说第二个搜索中关键字是动态的,而不是固定的。比如在如下的搜索中,我希望 blue 这个关键字是从另外一个索引中被搜索出来的而不是硬编码写进去的。

GET my-index-000001/_search
{
  "query": {
    "term": {
      "color": {
        "value": "blue"
      }
    }
  }
}

我们想通过一个搜索的命令来实现,那么我们该如何完成这样的操作呢?

在今天的文章中,我将使用 Terms lookup query 来展示如何实现这样的功能。

 

什么是 Terms lookup?

Terms lookup 将获取现有文档的字段值。 然后,Elasticsearch 将这些值用作搜索词。 搜索大量术语时,这将很有帮助。 由于术语查找从文档中获取值,因此必须启用 _source映射字段以使用术语查找。 _source 字段默认情况下处于启用状态。

注意:默认情况下,Elasticsearch 将字词查询限制为最多 65,536 个字词。 这包括使用术语查找获取的术语。 你可以使用 index.max_terms_count 设置更改此限制。

 

要执行术语查找,请使用以下参数

index

(必需,字符串)从中获取字段值的索引的名称。

id

(必需,字符串)要从中获取字段值的文档的ID。

path

(必需,字符串)要从中获取字段值的字段名称。 Elasticsearch 使用这些值作为查询的搜索词。 如果字段值包含嵌套的内部对象的数组,则可以使用点表示法语法访问这些对象。

routing

(可选,字符串)从中获取术语值的文档的自定义路由值。 如果在为文档建立索引时提供了自定义路由值,则此参数是必需的。

 

Terms lookup 例子

若要查看术语查找的工作原理,请尝试以下示例。

我们按照如下的方法来创建两个不同的索引:

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "color": { "type": "keyword" }
    }
  }
}

PUT my-index-000002
{
  "mappings": {
    "properties": {
      "favorite_color": { "type": "keyword" }
    }
  }
}

我们使用如下的方法来创建上面两个索引的内容:

POST _bulk
{ "index" : { "_index" : "my-index-000001", "_id" : "1" } }
{ "color" : ["blue", "green"] }
{ "index" : { "_index" : "my-index-000001", "_id" : "2" } }
{ "color" : ["blue"] }
{ "index" : { "_index" : "my-index-000002", "_id" : "1" } }
{ "favorite_color" : "blue" }

在上面,我们为 my-index-000001 索引创建了两个文档,为 my-index-000002 索引创建了一个文档。

按照正常的搜索,我们想搜索 my-index-000001 中 color 为 blue 的所有文档,那么我可以使用如下的命令:

GET my-index-000001/_search
{
  "query": {
    "match": {
      "color": "blue"
    }
  }
}

或者:

GET my-index-000001/_search
{
  "query": {
    "term": {
      "color": {
        "value": "blue"
      }
    }
  }
}

上面的命令将会返回如下的结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.21110919,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.21110919,
        "_source" : {
          "color" : [
            "blue",
            "green"
          ]
        }
      },
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.21110919,
        "_source" : {
          "color" : [
            "blue"
          ]
        }
      }
    ]
  }
}

在上面,我们使用了一个固定的 blue 关键字在搜索的命令中。假如有一种情况是,我的这个 blue 不是硬编码,而是需要动态地变化。它可以从另外一个索引中搜索到,那么我们该怎么进行这个搜索呢?我们可以使用 terms lookup query 来实现这个。它的写法是这样的:

GET my-index-000001/_search
{
  "query": {
    "terms": {
      "color": {
        "index": "my-index-000002",
        "id": "1",
        "path": "favorite_color"
      }
    }
  }
}

在上面,我们使用了 my-index-000002 索引来搜索,查询 id 为 “1” 的文档,并使用 favorite_color 来作为 path。我们知道在这个 favorite_color,id 为 "1" 的文档中,它的值是 blue,也即我们使用 blue 来进行查询。上面的查询返回的结果为:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "color" : [
            "blue",
            "green"
          ]
        }
      },
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "color" : [
            "blue"
          ]
        }
      }
    ]
  }
}

这个和我们先前查询的结果是一样的。

接下来,我们使用同样的查询,但是不同的是,在查询之前,我们修改 my-index-000002 索引 id 为 "1" 的内容:

PUT my-index-000002/_doc/1
{
  "favorite_color": "green"
}

我们把这个文档的内容修改为 green。我们做同样的查询:

GET my-index-000001/_search
{
  "query": {
    "terms": {
      "color": {
        "index": "my-index-000002",
        "id": "1",
        "path": "favorite_color"
      }
    }
  }
}

上面的命令显示的结果是:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "color" : [
            "blue",
            "green"
          ]
        }
      }
    ]
  }
}

我们看到只有一个文档匹配,也就是是这个查询正确地从 my-index-000002 读取了更新过的内容,并搜索出我们想要的结果。

 

一个实际的例子

假如有一个网站在用户登录的时候,它会记住该用的登录名字。在这个用户的 profile 里,我们可以看到这个用户的喜欢的书籍类型,比如:

PUT user-profiles/_doc/alex
{
  "preferred_categories" : ["technology"]
}

上面显示这个用户 alex 喜欢科技方面的书。同时,该网站还有如下的一些书的索引:

PUT books/_bulk
{"index":{"_id":"elasticsearch-definitive-guide"}}
{"name":"Elasticsearch - The definitive guide","category":"technology"}
{"index":{"_id":"seven-databases"}}
{"name":"Seven Databases in Seven Weeks","category":"technology"}
{"index":{"_id":"seven-threads"}}
{"name":"Seven Concurrency Models in Seven Weeks","category":"technology"}
{"index":{"_id":"hell-week"}}
{"name":"Seven days to be your best self","category":"motivational"}
{"index":{"_id":"seven-ways"}}
{"name":"Seven Ways: Easy Ideas for Every Day of the Week","category":"cookbooks"}
{"index":{"_id":"seven-book"}}
{"name":"Seven: A journey from seven to seventy-seven","category":"numberphile"}

那么问题来了:

问题一:

搜索出所有书名含有 seven 的书。这个其实非常简单:

GET books/_search
{
  "query": {
    "match": {
      "name": "seven"
    }
  }
}

显示的结果是:

{
  "took" : 661,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 0.36339492,
    "hits" : [
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-book",
        "_score" : 0.36339492,
        "_source" : {
          "name" : "Seven: A journey from seven to seventy-seven",
          "category" : "numberphile"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-databases",
        "_score" : 0.35667667,
        "_source" : {
          "name" : "Seven Databases in Seven Weeks",
          "category" : "technology"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-threads",
        "_score" : 0.3411939,
        "_source" : {
          "name" : "Seven Concurrency Models in Seven Weeks",
          "category" : "technology"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "hell-week",
        "_score" : 0.23632807,
        "_source" : {
          "name" : "Seven days to be your best self",
          "category" : "motivational"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-ways",
        "_score" : 0.20021,
        "_source" : {
          "name" : "Seven Ways: Easy Ideas for Every Day of the Week",
          "category" : "cookbooks"
        }
      }
    ]
  }
}

在上面,我们可以看出来,从相关性的角度来说,technology 类的书的排名不是靠前的。为了使得 technology 类的书名次能够靠前,这是因为用户 alex 喜欢 technology 的书,我们希望他登录以后,他喜欢的书能够排在前面。

问题二

如何把所有 technology 的书排在前面以提高相关性。

我们可以使用如下的命令:

GET books/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "seven"
          }
        }
      ],
      "should": [
        {
          "match": {
            "category": "technology"
          }
        }
      ]
    }
  }
}

上面命令显示的结果为:

  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0498238,
    "hits" : [
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-databases",
        "_score" : 1.0498238,
        "_source" : {
          "name" : "Seven Databases in Seven Weeks",
          "category" : "technology"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-threads",
        "_score" : 1.0343411,
        "_source" : {
          "name" : "Seven Concurrency Models in Seven Weeks",
          "category" : "technology"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-book",
        "_score" : 0.36339492,
        "_source" : {
          "name" : "Seven: A journey from seven to seventy-seven",
          "category" : "numberphile"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "hell-week",
        "_score" : 0.23632807,
        "_source" : {
          "name" : "Seven days to be your best self",
          "category" : "motivational"
        }
      },
      {
        "_index" : "books",
        "_type" : "_doc",
        "_id" : "seven-ways",
        "_score" : 0.20021,
        "_source" : {
          "name" : "Seven Ways: Easy Ideas for Every Day of the Week",
          "category" : "cookbooks"
        }
      }
    ]
  }

从上面的结果我们可以看出来,我们已经达到我们的目的了。所有 technology 的书都排在前面了。

问题三

上面是 alex 喜欢 technology,可能 tom 或者其它的用户喜欢别的 category。在我们的设计中,我们不可能去固定我们的搜索都去对 technology 进行加分。我们必须针对每个用户的档案来进行分别加分。

为了解决这个问题,我们可以使用 terms lookup query:

GET books/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "seven"
          }
        }
      ],
      "should": [
        {
          "terms": {
            "category": {
              "index": "user-profiles",
              "id": "alex",
              "path": "preferred_categories"
            }
          }
        }
      ]
    }
  }
}

这样当每个用户登录的时候,我们只需要在查询时替换上面的 id 中的内容即可。它会自动从 preferred_categories 找到这个用户的喜欢的 category。这样在用户的界面可以展现这个用户喜欢的内容在前面。运行上面的命令,你可以看到和在问题二中一样的结果。

 

总结

Terms lookup query 对很多我们需要使用同时对多个索引进行操作的查询非常有用。这对于搜索大量的词源来说非常有用!

已标记关键词 清除标记