elasticsearch - 深入搜索

526 阅读5分钟

精确查找

当进行精确值查找时,我们会使用过滤器(filters),因为整个过程不会计算相关度,所以它们执行的速度非常快。

term查找数字

如何通过Elasticsearch进行精确值的插叙

SELECT document
FROM products
WHERE price = 20;

在Elasticsearch的查询表达式(query DSL)中,我们可以使用term来达到目的

#方式1

GET /my_store/products/_search
{
	"query":{
		"term":{
			"price":20
		}
	}
}

#方式2
GET /my_store/products/_search
{
	"query":{
		"constant_score":{
			"filter":{
				"term":{
					"price":20
				}
			}
		}
	}
}

方式1和方式2的区别在于当查询一个精确值的时候,我们不希望对查询进行评分计算。只希望对文档进行包括或排除计算,所以使用constant_score查询以非评分模式来执行term查询并以 1 作为统一评分。

term查找文本

如果我们对productID进行查询

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "productID" : "XHDK-A-1293-#fJ3"
                }
            }
        }
    }
}

通过分析命令(分析API)我们可以看到字段会被分解成什么样的数据

GET /my_store/_analyze
{
	"field":"productID",
	"text":"XHDK-A-1293-#fJ3"
}

这是因为默认情况下,string类型的数据在写入是的时候会被分词器分解,因此是无法进行准确值查询,我们可以在创建索引的时候设置mapping,需要准确查询的项不需要分解。

PUT /my_store
{
  "mappings": {
    "products": {
      "properties": {
        "productID": {
          "type": "keyword"
        },
        "price":{
          "type":"integer"
        }
      }
    }
  }
}

组合过滤器

如何使用Elasticsearch实现以下的SQL

SELECT product 
FROM products
WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3")
  AND (price != 30)

需要使用bool(布尔)过滤器

{
  "bool":{
    "must":    [],
    "should":  [],
    "must_not":[]
  }
}
  • must

    所有的语句都 必须(must) 匹配,和 AND 等价

  • must_not

    所有的语句都 不能(must not) 匹配,与 NOT 等价

  • should

    至少有一个语句要匹配,与 OR 等价

GET /my_store/products/_search
{
	"query":{
				"bool":{
					"should":[
						{"term":{"price":20}},
						{"term":{"productID":"XHDK-A-1293-#fJ3"}}
					],
					"must_not":{
						"term":{"price":30}
					}
				}
			}
}

注意事项

[1] 需要使用一个 filtered 查询将所有的东西保存起来

嵌套布尔过滤器

尽管bool是一个复合过滤器,可以接受多个子过滤器,需要注意的是bool也是一个过滤器,这就意味着它也可以嵌套在其他的bool过滤器中。

SELECT document
FROM   products
WHERE  productID      = "KDKE-B-9947-#kL5"
  OR (     productID = "JODL-X-1937-#pV7"
       AND price     = 30 )

现在将该SQL转化成嵌套bool

GET /my_store/products/_search
{
  "query": {
        "bool":{
          "should":[
              {"term":{"productID":"KDKE-B-9947-#kL5"}},
              {
                "bool":{
                  "must":[
                      {"term":{"productID":"JODL-X-1937-#pV7"}},
                      {"term":{"price":30}}
                    ]
                }
              }
            ]
        }
      }
}

查找多个精确值

如何查找多个精确值

{
  "terms":{
    "price":[20,30]
  }
}

与term一样,terms也需要将查询体放置到filter中使用

GET /my_store/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "terms": {
          "price": [
            20,
            30
          ]
        }
      },
      "boost": 1.0
    }
  }
}

包含、而非相等

一定要了解term和terms是包含(contains)操作,而不是相等(equals)操作。比如我们有一个词项term过滤器

{"term":{"tags":"search"},它会和一下两个文档同时匹配

{"tags":["search"]}
{"tags":["search","open_source"]

精确相等

如上,如何做到完全相等,最好的方式增加一个字段。

{"tags":["search"],"tag_count":1}
{"tags":["search","open_source"],"tag_count":2}

则通过以下查询语句达到准确的查询

GET /my_store/products/_search
{
	"query":{
		"constant_score":{
			"filter":{
				"bool":{
					"must":[
						{"term":{"tags":"search"}},
						{"term":{"tag_count":1}}
					]
				}
			}
		}
	}
}

范围查找

现在,我们开始学习如何使用范围查找

SELECT document 
FROM products
WHERE price BETWEEN 20 AND 40;

在Elasticsearch中,使用range来表达范围查询

{
  "range":{
    	"gte":20,
    	"lte":40
  }
}

range提供包含(inclusive)和不包含(exclusive)这两种范围表达式

  • gt : 大于
  • gte : 大于等于
  • lt : 小于
  • lte : 小于等于

举个范围查询的例子

GET /my_store/products/_search
{
	"query":{
		"constant_score":{
			"filter":{
				"range":{
					"price":{
					  "gt":10,
					  "lte":30
					}
				}
			}
		}
	}
}

日期范围查找
"range" : {
    "timestamp" : {
        "gt" : "2014-01-01 00:00:00",
        "lt" : "2014-01-07 00:00:00"
    }
}

计算过去一个小时之内的文档

"range" : {
    "timestamp" : {
        "gt" : "now-1h"
    }
}

也可以通过数学表达式来进行

"range" : {
    "timestamp" : {
        "gt" : "2014-01-01 00:00:00",
        "lt" : "2014-01-01 00:00:00||+1M" 
    }
}

字符串也可以进行范围查找

"range" : {
    "title" : {
        "gte" : "a",
        "lt" :  "b"
    }
}

处理null值

​ 在Elasticsearch中,因为是表字段是动态的,因此可能在一个索引中的数据有的文档存在tags的字段,有的可能不存在或者是空数组,有的可能是多个值。

​ 因为倒排索引只是一个token列表和相关文档信息,如果字段不存在,则不会持有任何token,也就是在倒排索引中是无法体现出来的。

​ 最终,这就意味着 null ,[] 和[null]都是等价的,因为都在倒排索引中无法体现出来。

POST /my_index/posts/_bulk
{ "index": { "_id": "1"              }}
{ "tags" : ["search"]                }  
{ "index": { "_id": "2"              }}
{ "tags" : ["search", "open_source"] }  
{ "index": { "_id": "3"              }}
{ "other_field" : "some data"        }  
{ "index": { "_id": "4"              }}
{ "tags" : null                      }  
{ "index": { "_id": "5"              }}
{ "tags" : ["search", null]          }  

存在字段

现在在这样一个文档中,如何实现以下SQL效果

SELECT tags 
FROM posts
WHERE tags IS NOT NULL 

查询结构体如下

GET /my_index/posts/_search
{
	"query":{
		"constant_score":{
			"filter":{
				"exists":{"field":"tags"}
			}
		}
	}
}

查询缺失字段
GET /my_index/posts/_search
{
	"query":{
  	"constant_score":{
  		"filter":{
  			"bool":{
  				"must_not":[
  					{"exists":{"field":"tags"}}
  				]
  			}
  		}
  	}
	}
}

对象缺失查询

不仅可以过滤核心类型, exists and missing 查询 还可以处理一个对象的内部字段。以下面文档为例:

{
   "name" : {
      "first" : "John",
      "last" :  "Smith"
   }
}

我们不仅可以检查 name.firstname.last 的存在性,也可以检查 name ,不过在 映射 中,如上对象的内部是个扁平的字段与值(field-value)的简单键值结构,类似下面这样:

{
   "name.first" : "John",
   "name.last"  : "Smith"
}

那么我们如何用 existsmissing 查询 name 字段呢? name 字段并不真实存在于倒排索引中。

原因是当我们执行下面这个过滤的时候:

{
    "exists" : { "field" : "name" }
}

实际执行的是:

{
    "bool": {
        "should": [
            { "exists": { "field": "name.first" }},
            { "exists": { "field": "name.last" }}
        ]
    }
}

这也就意味着,如果 firstlast 都是空,那么 name 这个命名空间才会被认为不存在。