前言

在学习ES的过程中,数据检索的方式可以是Sql、一些语言客户端(http)、EQL、DSL,其中DSL是ES进行搜索不可或缺的东西,DSL究竟是什么,怎么用,有哪些需要注意的呢,接下来一一进行探索。


DSL定义

DSL全拼 Domain Special Language(领域特定语言),是ES提供的一种以Json体作为请求体的查询语言。

可以把它作为一种语法树,包括两种类型:独立/子查询和组合查询,组合查询可以理解为由其它组合查询和独立查询组合

独立查询包括查询类型如 term、match、range等;
组合查询包括如bool、dix_max等。


Query和Filter Context

默认情况下,ES搜索的结果是按相关性算分进行排序的,算分越高越靠前。

如下,不同的查询类型对算分有不同的影响。

Query context

Query context 决定了文档的匹配度,通过相关性算分进行排序,算分结果会出现在结果的_score字段中。

Filter context

Filter context 决定了文档是否匹配,不会进行相关性算分,答案只有是和否

它比较适合匹配例如时间、数字、Boolean等结构化类型数据。

而且Filter搜索的结果会被ES进行缓存,以加快再次搜索速度。


我们通过定义一下索引文档,索引名 products,以便下面演示:

POST http://127.0.0.1:9200/products/_bulk

{ "index": { "_id": 1 }}
{ "productID" : "XHDK-A-1293-#fJ3","desc":"My iPhone", "createTime":"2023-08-16", "price": 10, "status":true}
{ "index": { "_id": 2 }}
{ "productID" : "KDKE-B-9947-#kL5","desc":"My iPad", "createTime":"2023-08-18", "price": 20, "status":true}
{ "index": { "_id": 3 }}
{ "productID" : "JODL-X-1937-#pV7","desc":"My MBP", "createTime":"2023-08-20", "price": 30.5, "status":false}

注意body体最后记得换行。


进行例如以下搜索:

GET http://127.0.0.1:9200/products/_search

{
  "query": {
    "bool": {
      "must": {
        "match": {"desc": "iphone"}
      },
      "filter": [
        {"term": {"status": true}},
        {"range": {"createTime": {"gte": "2023-08-16"}}}
      ]
    }
  }
}

其中:
1、最外层query表示它是一个query context;
2、must下也是一个query context;
3、filter下是一个filter context,包括了term和range。


分类

组合查询

1、bool查询, 比较常用的一个
它组合了多个查询,包括must、should、filter、must not。其中,
must:必须匹配,会算分
should:选择性匹配,会算分
filter:必须匹配,不算分,使用缓存
must mot:必须不匹配,不算分,使用缓存

可以理解成,bool词查询时,json下一层会接上面的词,如must体,如下:

{
  "query": {
    "bool": {
      "must": {
        "match": {"desc": "iphone"}
      }
    }
}

2、Boosting查询,改变相关性算分
包括positive和negative,positive正常匹配,negative会降低相关性分数,如下所示:

GET http://127.0.0.1:9200/products/_search

{
  "query": {
    "boosting": {
      "positive": {
        "term": {"status": true}
      },
      "negative": {
        "term": {"desc": "iphone"}
      },
	  // 原来的0.5倍
      "negative_boost": 0.5
    }
  }
}

搜索结果,部分如下:

"hits": 
[
	{
		"_index": "products",
		"_id": "2",
		"_score": 0.4700036,
		"_source": {
			"productID": "KDKE-B-9947-#kL5",
			"desc": "My iPad",
			"createTime": "2023-08-18",
			"price": 20,
			"status": true
		}
	},
	{
		"_index": "products",
		"_id": "1",
		"_score": 0.2350018,
		"_source": {
			"productID": "XHDK-A-1293-#fJ3",
			"desc": "My iPhone",
			"createTime": "2023-08-16",
			"price": 10,
			"status": true
		}
	}
]

可以看到 “_id”: “1”,_score分数由于命中negative条件,分值更低了。当然结果都需要满足positive。

3、Constant score query搜索
顾名思义,分数是固定常量,默认是1,也可以根据 "boost"变量进行指定,简化算分的逻辑,有助于搜索性能,如下所示:

{
  "query": {
    "constant_score": {
      "filter": {
        "term": {"status": true}
        },
        "boost":100
    }
  }
}

4、其它,还有查询分数为最大匹配项分数;函数算分 支持包括计算权重等。

可参考地址>


全文检索

Full-text search,针对文本进行分词,分词默认使用文档索引时使用的分词器。

常见的全文检索方式有:文本匹配match、词组匹配match phrase、逻辑与或非多串查询等等

1、match
对于文本会进行分词,再进行搜索;同时也支持数字、boolean、日期等。

查询时需要匹配好类型,如boolean类型字段不能传非boolean字符串。
同时可以指定分词器或者最小匹配数或者逻辑关系等等条件,如下:

{
  "query": {
    "match": {
      "status": {
        "query": true,
        "operator": "and"
        }
    }
  }
}

2、match_phrase,词组搜索

GET API  http://127.0.0.1:9200/products/_search

{
  "query": {
    "match_phrase": {
      "desc": {
        "query": "my iphone"
      }
    }
  }
}

my tel iphone 搜索不到,match_phrase改match可搜索到。

3、Query String VS Simple Query String
Query String:会使用严格的语法来解析查询串,支持例如 AND OR NOT

{
  "query": {
    "query_string": {
      "query": "My AND iphone",
      "default_field": "desc"
    }
  }
}

只会查出id=1的文档,desc为“My iPhone”,AND被解析成逻辑与。


Simple Query String:类似于Query String,容忍语法错误,不会解析AND OR NOT,只支持部分语法如(+ - |等),Term之间默认关系是OR,可以指定default_operator为AND NOT。

{
  "query": {
    "simple_query_string": {
      "query": "My AND iphone",
      "fields": ["desc"],
      "default_operator": "OR"
    }
  }
}

3条商品文档都会被查询出来。 query 中的AND并不会被处理成逻辑关系


精确匹配

这种被称为 Term-level queries,用来查询结构化数据,例如价格、商品id、时间等等

精确查询不会进行分词。

其包括了有 exists、fuzzy、ids、range、prefix、regexp、term、terms等。

1、exists
用来判断某个字段是否有值,返回包含该字段值的文档。
不包含的原因如下所示:
1)、字段值为null或[]
2)、mapping设置了字段 index为false
3)、字段超过了ignore_above的长度

如下:可以查询到数据

{
  "query": {
    "exists": {
      "field": "desc"
    }
  }
}

2、term
精确匹配词项,注意避免使用term进行text文本的搜索。

如下所示,匹配价格字段:

{
  "query": {
    "term": {
      "price": {
        "value": "10"
      }
    }
  }
}

如果进行文本text类型的字段搜索,不会进行分词,所以会导致很可能搜索不到的情况。

如下情况搜索不到,应该使用match,才会分词搜索。

{
  "query": {
    "term": {
      "desc": {
        "value": "My"
      }
    }
  }
}

{
  "query": {
    "term": {
      "desc": {
        "value": "my iphone"
      }
    }
  }
}

如下会搜索到,匹配到my词的倒排索引:

{
  "query": {
    "term": {
      "desc": {
        "value": "my"
      }
    }
  }
}

脚本搜索

ES中脚本查询默认使用的是painless脚本,脚本查询是Filter Context查询的一种。脚本查询可能导致检索速度降低。

painless是ES专门用来在ES中定义脚本的一种特殊语言。

可以通过脚本来创造一个新的字段,它可以用于字段搜索、排序、聚合分析等等场景。

如下,查询价格小于等于10的:

{
  "query": {
    "bool": {
      "filter": {
        "script":{
          "script": "return doc['price'].value <= 10;"
        }
      }
    }
  }
}

总结

ES提供的DSL搜索不止文章中提到的这些,文章列出了大部分常用的,一种是match用于text的文本搜索,会进行分词;一种是term用于结构化字段的精确搜索,不会进行分词。

另外,如果按查询上下文进行搜索的话,一种是按Query Context、另一种是Filter Context,前者会进行算分,按算分高低进行结果的排序,后者不会进行算分,同时对结果进行缓存,提升再次检索的速度。