elasticsearch之十四springboot测试文档基本查询

个人专题目录](//www.greatytc.com/p/140e2a59db2c)


1. elasticsearch文档基本查询

1.1 ids查询

ids查询是一类简单的查询,它过滤返回的文档只包含其中指定标识符的文档,该查询默认指定作用在“_id”上面。

POST /book-index/_search
{
  "from": 0,
  "size": 100,
  "query": {
    "ids": {
      "values": [
        "1145177",
        "1170772"
      ],
      "boost": 1
    }
  }
}
@Override
public void idsQuery(String index, String... ids) throws Exception {
    builder(index, QueryBuilders.idsQuery().addIds(ids));
}
@Test
public void testIdsQuery() throws Exception {
    baseQuery.idsQuery(Constants.INDEX_NAME, "1145177", "1170772");
}

1.2 prefix查询

前缀查询,可以使我们找到某个字段以给定前缀开头的文档。

最典型的使用场景,一般是在文本框录入的时候的联想功能。

例如想查到公司名以“中国”开头的文档:

#prefix 查询
POST /book-index/_search
{
  "from": 0,
  "size": 100,
  "query": {
    "prefix": {
      "title": {
        "value": "三",
        "boost": 1
      }
    }
  }
}
/**
 * 模糊查询:prefixQuery
 */
@Override
public void prefixQuery(String index, String field, String prefixValue) throws Exception {
    builder(index, QueryBuilders.prefixQuery(field, prefixValue));
}
@Test
public void testPrefixQuery() throws Exception {
    baseQuery.prefixQuery(Constants.INDEX_NAME, "title", "三");
}

1.3 fuzzy查询

fuzzy才是实现真正的模糊查询,我们输入的字符可以是个大概,他可以根据我们输入的文字大概进行匹配查询。返回包含与搜索词类似的词的文档,该词由Levenshtein编辑距离度量。

包括以下几种情况:

  • 更改角色(box→fox)

  • 删除字符(aple→apple)

  • 插入字符(sick→sic)

  • 调换两个相邻字符(ACT→CAT)

参数说明:

prefix_length

不能被 “模糊化” 的初始字符数。 大部分的拼写错误发生在词的结尾,而不是词的开始。 例如通过将 prefix_length 设置为 3 ,你可能够显著降低匹配的词项数量。

例如用户在查询过程中把“中国移动”输为了“中国联动”:

POST /book-index/_search
{
  "from": 0,
  "size": 100,
  "query": {
    "fuzzy": {
      "categoryName": {
        "value": "平电视",
        "fuzziness": "AUTO",
        "prefix_length": 0,
        "max_expansions": 50,#控制查询的数量,注意不是返回结果的最大值,而是在查询过程中最多去匹配到多少条,比如此处 50 条,即便有 1 万条能匹配到的,当查询过程中匹配到 50 条的时候就不再继续查询直接返回结果
        "transpositions": true,
        "boost": 1
      }
    }
  }
}
@Override
    public void fuzzyQuery(String index, String field, String keyword) throws Exception {
        builder(index, QueryBuilders.fuzzyQuery(field, keyword));
    }

把prefixLength(2)参数改为prefixLength(3)就查不到结果了,不能被 “模糊化” 的初始字符变成了中国联

@Test
public void testFuzzyQuery() throws Exception {
    baseQuery.fuzzyQuery(Constants.INDEX_NAME, "title", "平电视");
}

1.4 wildcard查询

wildcard查询允许我们在要查询的内容中使用通配符*和?,和SQL语句。

<font color=red>注意:wildcard查询不注意查询性能,应尽可能避免使用。</font>

例如用户在查询公司名以“中国”开头的文档:

#wildcard 查询。查询条件分词,模糊查询
POST /book-index/_search
{
  "from": 0,
  "size": 100,
  "query": {
    "wildcard": {
      "title": {
        "wildcard": "三*",
        "boost": 1
      }
    }
  }
}
/**
 * 模糊查询:WildcardQuery
 */
@Override
public void wildCardQuery(String index, String field, String wildcardValue) throws Exception {
    builder(index, QueryBuilders.wildcardQuery(field, wildcardValue));
}
@Test
public void testWildCardQuery() throws Exception {
    baseQuery.wildCardQuery(Constants.INDEX_NAME, "title", "三*");
}

1.5 range查询

本章到目前为止,对于数字,只介绍如何处理精确值查询。 实际上,对数字范围进行过滤有时会更有用。例如,我们可能想要查找所有价格大于 20 且小于40 美元的产品。

在 SQL 中,范围查询可以表示为:

SELECT document
FROM   products
WHERE  price BETWEEN 20 AND 40

Elasticsearch 有 range 查询, 不出所料地,可以用它来查找处于某个范围内的文档:

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

range 查询可同时提供包含(inclusive)和不包含(exclusive)这两种范围表达式,可供组合的选项如下:

  • gt: > 大于(greater than)

  • lt: < 小于(less than)

  • gte: >= 大于或等于(greater than or equal to)

  • lte: <= 小于或等于(less than or equal to)

例如要查询出短信状态报告响应时间在1-10秒之内的文档:

#范围查询
POST /book-index/_search
{
  "from": 0,
  "size": 100,
  "query": {
    "range": {
      "price": {
        "from": 2000,
        "to": 3000,
        "include_lower": true,
        "include_upper": true,
        "boost": 1
      }
    }
  }
}
/**
 * 范围查询:rangeQuery
 */
@Override
public void rangeQuery(String index, String field, int from, int to) throws Exception {
    //builder(index, QueryBuilders.rangeQuery(field).from(from).to(to));
    builder(index, QueryBuilders.rangeQuery(field).gte(2000).lte(3000));
}
@Test
public void testRangeQuery() throws Exception {
    baseQuery.rangeQuery(Constants.INDEX_NAME, "price", 2000, 3000);
}

1.6 regexp查询

正则表达式查询,wildcard和regexp查询的工作方式和prefix查询完全一样。它们也需要遍历倒排索引中的词条列表来找到所有的匹配词条,然后逐个词条地收集对应的文档ID。它们和prefix查询的唯一区别在于它们能够支持更加复杂的模式。

这也意味着使用它们存在相同的风险。对一个含有很多不同词条的字段运行这类查询是非常消耗资源的。避免使用一个以通配符开头的模式(比如,*foo)。

尽管对于前缀匹配,可以在索引期间准备你的数据让它更加高效,通配符和正则表达式匹配只能在查询期间被完成。虽然使用场景有限,但是这些查询也有它们的用武之地。

<font color=red>注意:prefix,wildcard以及regexp查询基于词条进行操作。如果你在一个analyzed字段上使用了它们,它们会检查字段中的每个词条,而不是整个字段。</font>

例如查找长号码(longCode)以1069开头后面是数字的文档:

#模糊查询:regexpQuery
POST /book-index/_search
{
  "from": 0,
  "size": 100,
  "query": {
    "regexp": {
      "title": {
        "value": """\\w+(.)*""",
        "flags_value": 65535,
        "max_determinized_states": 10000,
        "boost": 1
      }
    }
  }
}
/**
 * 模糊查询:regexpQuery
 */
@Override
public void regexQuery(String index, String field, String regex) throws Exception {
    QueryBuilders.regexpQuery("title", "\\w+(.)*");
    builder(index, QueryBuilders.regexpQuery(field, regex));
}
@Test
public void testRegexQuery() throws Exception {
    baseQuery.regexQuery(Constants.INDEX_NAME, "title", "\\\\w+(.)*");
}

1.7 scroll查询

ES对于from+size的个数是有限制的,二者之和不能超过1w。当所请求的数据总量大于1w时,可用scroll来代替from+size。

原理

ES的搜索是分2个阶段进行的,即Query阶段和Fetch阶段。 Query阶段比较轻量级,通过查询倒排索引,获取满足查询结果的文档ID列表。 而Fetch阶段比较重,需要将每个shard的结果取回,在协调结点进行全局排序。 通过From+size这种方式分批获取数据的时候,随着from加大,需要全局排序并丢弃的结果数量随之上升,性能越来越差。

而Scroll查询,先做轻量级的Query阶段以后,免去了繁重的全局排序过程。 它只是将查询结果集,也就是doc id列表保留在一个上下文里, 之后每次分批取回的时候,只需根据设置的size,在每个shard内部按照一定顺序(默认doc_id续), 取回这个size数量的文档即可。

使用场景

由此也可以看出scroll不适合支持那种实时的和用户交互的前端分页工作,其主要用途用于从ES集群分批拉取大量结果集的情况,一般都是offline的应用场景。 比如需要将非常大的结果集拉取出来,存放到其他系统处理,或者需要做大索引的reindex等等。
不要把 scroll 用于实时请求,它主要用于大数据量的场景。例如:将一个索引的内容索引到另一个不同配置的新索引中。

代码示例

例如滚动查询所有文档:

POST  /book-index/_search?scroll=1m
{
    "query": {
        "match_all" : {}
    },
    "sort": [
        "_doc"
        ]
}

POST /_search/scroll #继续使用滚动查询
{
   "scroll": "1m", #设置时间
   "scroll_id": "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTlU3Z1FCT1lWVHdadlhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVlR3WnZYTmFXZw==" #指定在哪个scroll的基础上继续查询
}

清除scroll

虽然我们在设置开启scroll时,设置了一个scroll的存活时间,但是如果能够在使用完顺手关闭,可以提早释放资源,降低ES的负担.

DELETE /_search/scroll
{
    "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAdsMqFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbDKRZlZGUwSWpSVlJqeVJiN1dBWHNpUG1RAAAAAABpX2sWclBEekhiRVpSRktHWXFudnVaQ3dIQQAAAAAAaV9qFnJQRHpIYkVaUkZLR1lxbnZ1WkN3SEEAAAAAAGlfaRZyUER6SGJFWlJGS0dZcW52dVpDd0hB"
}
public void scrollQuery(String index) throws Exception {
    SearchRequest searchRequest = new SearchRequest(index);
    //值不需要足够长来处理所有数据—它只需要足够长来处理前一批结果。每个滚动请求(带有滚动参数)设置一个新的过期时间。
    //创建一个有效期为 1 分钟的滚动
    Scroll scroll = new Scroll(TimeValue.timeValueMillis(1L));
    //设置滚动查询
    searchRequest.scroll(scroll);
    //创建查询条件的封装对象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //一般情况下我们的滚动查询需要查询所有数据
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    //设定每次返回多少条数据
    searchSourceBuilder.size(5);
    //对我们的请求指定查询条件
    searchRequest.source(searchSourceBuilder);
    log.info("string:" + searchRequest.source());
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

    String scrollId = searchResponse.getScrollId();
    SearchHit[] searchHits = searchResponse.getHits().getHits();
    log.info("-----首页-----");
    for (SearchHit searchHit : searchHits) {
        log.info(searchHit.getSourceAsString());
    }
    //遍历搜索命中的数据,直到没有数据
    while (searchHits != null && searchHits.length > 0) {
        SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
        scrollRequest.scroll(scroll);
        log.info("string:" + scrollRequest.toString());
        try {
            searchResponse = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        scrollId = searchResponse.getScrollId();
        searchHits = searchResponse.getHits().getHits();
        if (searchHits != null && searchHits.length > 0) {
            log.info("-----下一页-----");
            for (SearchHit searchHit : searchHits) {
                log.info(searchHit.getSourceAsString());
            }
        }

    }

    //清除滚屏
    ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
    //也可以选择setScrollIds()将多个scrollId一起使用
    clearScrollRequest.addScrollId(scrollId);
    ClearScrollResponse clearScrollResponse = null;
    try {
        clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
    }
    boolean succeeded = clearScrollResponse != null && clearScrollResponse.isSucceeded();
    log.info("succeeded:" + succeeded);
}
@Test
public void testScrollQuery() throws Exception {
    baseQuery.scrollQuery(indexName,type);
}

2. delete-by-query

删除查询的文档,由于每个文档都需要单独被删除,查询大量文档可能需要很长的时间。

注意:

不要使用delete-by-query来删除一个索引下的全部或者大部分文档,确实需要的话,可以创建一个新的索引,然后将需要保留的文档重新索引到新的索引中去,这样你就可以直接删掉旧索引。
POST /book-index/_delete_by_query
{
   "query": { #此处使用之前的查询条件
    "match_all": {}
  }
}
@Override
public void deleteByQuery(String index, QueryBuilder queryBuilder) throws Exception {
    DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(index).setQuery(queryBuilder);

    BulkByScrollResponse response = restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
    long deleted = response.getDeleted();
    log.info("删除的数量是{}", deleted);
    log.info(response);
}
@Test
public void testDeleteByQuery() throws Exception {
    docService.deleteByQuery(Constants.INDEX_NAME, QueryBuilders.termQuery("id", "0"));
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容