搜索 query then fetch
Es 精确查询,term; terms 多个值是精确查询的 或 关系
Es 模糊查询,分词的用 match 、短语的用 match_phrase;
查询任意的,用wildcard通配符,注意查询的内容是否分词,分词的添加keyword,查询非空的情况,用*。
Wildcard 性能会比较慢。如果非必要,尽量避免在开头加通配符 ? 或者 *,这样会明显降低查询性能
{
"wildcard": {
"form_name.keyword": "*very*"
}
}
如果查询的内容非空,怎么处理? 直接用*
{ "wildcard": { "form_name": "*" } }
结构化查询 Query DSL
- 空查询
GET /_search
{ "query": { "match_all": {} } }
- 查询子句
一个查询子句一般使用这种结构:
{ QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } }
举例 GET /_search
{ "query": { "match": { "tweet": "elasticsearch" } } }
- 合并多子句
叶子子句(leaf clauses)(比如match子句)用以在将查询字符串与一个字段(或多字段)进行比较
复合子句(compound)用以合并其他的子句,bool: must, must_not, should
复合子句可以相互嵌套
{ "bool": { "must": { "match": { "email": "business opportunity" }}, "should": [ { "match": { "starred": true }}, { "bool": { "must": { "folder": "inbox" }, "must_not": { "spam": true } } } ], "minimum_should_match": 1 } }
想知道语句非法的具体错误信息,需要加上 explain 参数
GET /index_name/type_name/query?explain
{ "query": { "tweet" : { "match" : "really powerful" } } }
语句错误的详情 "error"
语句正确的解释 "explanation"
最重要的查询过滤语句 filter & query
- 询语句可以包含过滤子句,反之亦然
- 复合查询语句可以加入其他查询子句,复合过滤语句也可以加入其他过滤子句。
GET /_search
{ "query": { "filtered": { "filter": { "bool": { "must": { "term": { "folder": "inbox" }}, "must_not": { "query": { "match": { "email": "urgent business proposal" } } } } } } } }
关键词 | 用途 | 举例 |
---|---|---|
term过滤 | 精确匹配 | 比如数字,日期,布尔值 或 not_analyzed的字符串(未经分析的文本数据类型) |
terms过滤 | 精确匹配 允许指定多个匹配条件 | 同上 |
range过滤 | 按照指定范围过滤 | 类型 integer_range -2的31次 到 2的31次-1 long_range -2的63次 到 2的63次-1 float_range 32位单精度浮点数 double_range 64位双精度浮点数 date_range 时间戳+微秒数 ip_range ipv4和ipv6或者两者的混合 范围操作符包含: gt 大于 gte 大于等于 lt 小于等于 lte 小于等于 |
exists过滤 | 包含指定字段 | 只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用 |
missing过滤 | 不包含指定字段 | 同上 |
bool过滤 | 用来合并多个过滤条件 | 操作符: must: 多个查询条件的完全匹配,相当于 and must_not: 多个查询条件的相反匹配,相当于 not should: 至少有一个查询条件匹配, 相当于 or |
bool 查询 | 合并多个查询子句 | 操作符: must: 查询指定文档一定要被包含 must_not: 查询指定文档一定不要被包含 should: 查询指定文档,有则可以为文档相关性加分 |
match_all查询 | 查询到所有文档 | 是没有查询条件下的默认语句 |
match查询 | 查询是一个标准查询 | 不管你需要全文本查询还是精确查询基本上都要用到它 |
multi_match查询 | 查询允许你做match查询的基础上同时搜索多个字段 | C |
{
"term": {
"age":26
},
"terms": {
"tag": [ "search", "full_text", "nosql" ]
},
"range": {
"age": {
"gte": 20,
"lt": 30
}
},
"exists": {
"field": "title"
}
"bool": {
"must": { "term": { "folder": "inbox" }},
"must_not": { "term": { "tag": "spam" }},
"should": [
{ "term": { "starred": true }},
{ "term": { "unread": true }}
]
},
"match": {
"tweet": "About Search"
},
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}
以下查询将会找到 title 字段中包含 "how to make millions",并且 "tag" 字段没有被标为 spam。 如果有标识为 "starred" 或者发布日期为2014年之前,那么这些匹配的文档将比同类网站等级高:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014-01-01" }}} ] } }
排序 sort
- 过滤语句与 _score 没有关系,但是有隐含的查询条件 match_all 为所有的文档的 _score 设值为 1
- 使用 sort 参数进行排序
{ "query" : { "filtered" : { "filter" : { "term" : { "user_id" : 1 }} } }, "sort": { "date": { "order": "desc" }} }
- 计算 _score 是比较消耗性能的,
- 如果你想强制计算其相关性,可以设置track_scores为 true。
- 字段值默认以顺序排列,而 _score 默认以倒序排列。
多级排序
如果我们想要合并一个查询语句,并且展示所有匹配的结果集使用第一排序是date,第二排序是 _score:
{ "query" : { "filtered" : { "query": { "match": { "tweet": "manage text search" }}, "filter" : { "term" : { "user_id" : 2 }} } }, "sort": [ { "date": { "order": "desc" }}, { "_score": { "order": "desc" }} ] }
- 为多值字段排序
对于数字和日期,你可以从多个值中取出一个来进行排序,你可以使用min, max, avg 或 sum这些模式。
比说你可以在 dates 字段中用最早的日期来进行排序:
{ "sort": { "dates": { "order": "asc", "mode": "min" } } }
- 为了使一个string字段可以进行排序,它必须只包含一个词:即完整的not_analyzed字符串(译者注:未经分析器分词并排序的原字符串)。
- 在 _source 下相同的字符串上排序两次会造成不必要的资源浪费。
同一个字段中同时包含这两种索引方式
对 analyzed 字段进行强制排序会消耗大量内存。
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}
- 我们只需要改变索引(index)的mapping即可,新增的 tweet.raw 子字段索引方式是 not_analyzed
{ "tweet": { "type": "string", "analyzer": "english", "fields": { "raw": { "type": "string", "index": "not_analyzed" } } } }
GET /_search
{ "query": { "match": { "tweet": "elasticsearch" } }, "sort": "tweet.raw" }
相关性
- 每个文档都有相关性评分,用一个相对的浮点数字段 _score 来表示, _score 的评分越高,相关性越高。
- 检索词频率:检索词在该字段出现的频率?出现频率越高,相关性也越高。
- 反向文档频率:每个检索词在索引中出现的频率?频率越高,相关性越低。
- 字段长度准则:字段的长度是多少?长度越长,相关性越低。
- 增加一个 explain 参数会为每个匹配到的文档产生一大堆额外内容,包括评分 _score
返回值中的 _explanation 会包含在每一个入口,告诉你采用了哪种计算方式,并让你知道计算的结果以及其他详情:
{
"query" : { "match" : { "tweet" : "honeymoon" }}
}
JSON形式的explain描述是难以阅读的
但是转成 YAML 会好很多,只需要在参数中加上 format=yaml
{
"_explanation": {
// honeymoon 相关性评分计算的总结
"description": "weight(tweet:honeymoon in 0)[PerFieldSimilarity, result of:",
"value": 0.076713204,
"details": [
{
"description": "fieldWeight in 0, product of:",
"value": 0.076713204,
"details": [
// 检索词频率
{
"description": "tf(freq=1.0), with freq of:",
"value": 1,
"details": [
{
"description": "termFreq=1.0",
"value": 1
}
]
},
// 反向文档频率
{
"description": "idf(docFreq=1, maxDocs=1)",
"value": 0.30685282
},
// 字段长度准则
{
"description": "fieldNorm(doc=0)",
"value": 0.25,
}
]
}
]
}
}
处理Null值
本质上来说,null,[](空数组)和 [null] 是相等的。它们都不存在于倒排索引中!
- SQL 语法中,我们可以用 IS NOT NULL 查询:
SELECT tags FROM posts WHERE tags IS NOT NULL
在 Elasticsearch 中,我们使用 exists 过滤器
{
"query": {
"exists": {
"field": "tags"
}
}
}
{
"query" : {
"filtered" : {
"filter" : {
"exists" : { "field" : "tags" }
}
}
}
}
- 用 missing 过滤器来取代 exists, 获取没有标签的文档
{ "query" : { "filtered" : { "filter": { "missing" : { "field" : "tags" } } } } }
统计
- terms.size有默认值 = 10
不手动设置会导致统计数据缺少xx_status对应数据而展示统计结果为0 且sum_other_doc_count > 0
private function es()
{
$query = [];
$esParams = [
'size' => 0,
'query' => $query,
'aggs' => [
'total_group_by_xx_status' => [
'terms' => [
'field' => 'xx_status',
'size' => '60',
],
'aggs' => [
'count' => [
'value_count' => [
'field' => 'xx_status'
]
]
]
],
],
];
}
按多个字段聚合数据
public function getSummaryByMultiColumns($params, $columns, $sort_by = 'asc')
{
$query = $this->getFobMakeTimeCostEsQuery($params['APP_USER_CODE'], $params);
$aggs_key = 'group_by_multiple_fields';
$sources = [];
foreach ($columns as $column) {
$sources[] = [
$column => [
'terms' => [
'field' => $column,
],
]
];
}
$sort = [];
foreach ($columns as $column) {
$sort[] = [
$column => ['order' => $sort_by],
];
}
$esParams = [
"size" => 0,
'query' => $query,
'aggs' => [
$aggs_key => [
'composite' => [
'sources' => $sources,
'size' => 2000, // 节点 20个,产品类型5个,打版次数≥1
],
'aggs' => [
'sum_amount' => [
'sum' => [
'field' => 'amount',
]
],
'sum_time_use' => [
'sum' => [
'field' => 'time_use',
]
]
]
]
],
'sort' => $sort,
];
$index = config('esindex.ES_INDEX_NAME.' . env('APP_ENV'));
$list = ES::connection()->index($index)->query($esParams);
$aggregations = isset($list['aggregations'][$aggs_key]['buckets']) ? $list['aggregations'][$aggs_key]['buckets'] : [];
$count_list = [];
foreach ($aggregations as $item) {
$summary_item = [
'cnt' => $item['doc_count'],
'amount' => $item['sum_amount']['value'] ?? 0,
'time_use' => $item['sum_time_use']['value'] ?? 0,
];
foreach ($columns as $column) {
$summary_item[$column] = $item['key'][$column];
}
$count_list[] = $summary_item;
}
return $count_list;
}
按单个字段聚合数据
public function countTimeCostByOneColumn($params, $field, $sort_type = 'asc')
{
$query = $this->getFobMakeTimeCostEsQuery($params['APP_USER_CODE'], $params);
$aggs_key = 'group_by_enum';
$esParams = [
"size" => 0,
'query' => $query,
'aggs' => [
$aggs_key => [
'terms' => [
'field' => $field,
'size' => '100',
],
'aggs' => [
'count' => [
'value_count' => [
'field' => $field,
]
]
]
]
],
'sort' =>[
[
$field => ['order' => $sort_type],
]
],
];
$index = config('esindex.mes_fob_time_cost.' . env('APP_ENV'));
$list = ES::connection()->index($index)->query($esParams);
$aggregations = isset($list['aggregations'][$aggs_key]['buckets']) ? $list['aggregations'][$aggs_key]['buckets'] : [];
$count_list = [];
foreach ($aggregations as $item) {
$count_list[] = [
$field => $item['key'],
'cnt' => $item['doc_count'],
];
}
return $count_list;
}
按天统计数据
public function getCountByDay($params)
{
$query = $this->getFobMakeTimeCostEsQuery($params['APP_USER_CODE'], $params);
$aggs_key = 'count_by_TIME_COLUMN';
$esParams = [
"from" => 0,
"size" => 0,
'query' => $query,
'aggs' => [
$aggs_key => [
'date_histogram' => [
'field' => 'TIME_COLUMN',
'interval' => '1d',
],
'aggs' => [
'count' => [
'value_count' => [
'field' =>'TIME_COLUMN',
]
]
]
]
],
'sort' =>[
[
'TIME_COLUMN' => ['order' => 'desc'],
]
],
];
$index = config('esindex.mes_fob_time_cost.' . env('APP_ENV'));
$list = ES::connection()->index($index)->query($esParams);
$aggregations = isset($list['aggregations'][$aggs_key]['buckets']) ? $list['aggregations'][$aggs_key]['buckets'] : [];
$count_list = [];
foreach ($aggregations as $item) {
$count_list[] = [
'TIME_COLUMN' => DatetimeHelper::timestampMsToTimeStr($item['key']),
'cnt' => $item['doc_count'],
];
}
return $count_list;
}
按子文档单一字段维度统计数据
public function getCountByWebsiteCode($params)
{
$query = $this->getFobMakeTimeCostEsQuery($params['APP_USER_CODE'], $params);
$aggs_key = 'group_by_enum';
$field = 'website_code';
$path = 'p_make_sample_order';
$nested_aggs_key = 'group_by_website_code';
$esParams = [
"size" => 0,
'query' => $query,
'aggs' => [
$aggs_key => [
'nested' => [
'path' => 'p_make_sample_order',
],
'aggs' => [
$nested_aggs_key => [
'terms' => [
'field' => $path.'.'.$field,
'size' => '1000', // 站点数量
],
]
]
]
],
'sort' =>[
[
$path .'.'. $field => ['order' => 'asc'],
]
],
];
$index = config('esindex.mes_fob_time_cost.' . env('APP_ENV'));
$list = ES::connection()->index($index)->query($esParams);
$aggregations = !empty($list['aggregations'][$aggs_key][$nested_aggs_key]['buckets']) ? $list['aggregations'][$aggs_key][$nested_aggs_key]['buckets'] : [];
$count_list = [];
foreach ($aggregations as $item) {
$count_list[] = [
$field => $item['key'],
'cnt' => $item['doc_count'],
];
}
return $count_list;
}
子查询and条件(默认OR条件)
$term = [];
$range = [];
$wildcard = [];
$handler_query = [];
$handler_must = [];
if (!empty($params['operator_type_arr'])) {
$handler_query = ES::term($handler_query, 'handler.operator_type',array_values($params['operator_type_arr']));
}
// 节点负责人搜索
if (!empty($params['handler_flag_arr']) && !empty($params['handler_user_name_arr'])) {
$handler_query = ES::term($handler_query, 'handler.flag', array_values($params['handler_flag_arr']));
$handler_query = ES::term($handler_query, 'handler.user_name', array_values($params['handler_user_name_arr']));
}
if (!empty($handler_query)) {
$handler_must = ES::nestedQuery($handler_must, 'handler', ["bool" => ["must" => $handler_query]]);
}
$query = [];
$must = array_merge($term,$range,$wildcard,$handler_must);
$must_not = [];
$query['bool'] = [
'must' => $must,
'must_not' => $must_not,
];
ElasticSearch中的字段数据常被应用到以下场景
- 对一个字段进行排序
- 对一个字段进行聚合
- 某些过滤,比如地理位置过滤
- 某些与字段相关的脚本计算
Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法
- 关闭缓存 swap;
- 堆内存设置为:Min(节点内存/2, 32GB);
- 设置最大文件句柄数;
- 线程池+队列大小根据业务需要做调整;
- 磁盘存储 raid 方式——存储有条件使用 RAID10,增加单节点性能以及避免单节点存储故障。