個人專題目錄](http://www.itdecent.cn/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查詢
前綴查詢,可以使我們找到某個字段以給定前綴開頭的文檔。
最典型的使用場景,一般是在文本框錄入的時候的聯(lián)想功能。
例如想查到公司名以“中國”開頭的文檔:
#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才是實現(xiàn)真正的模糊查詢,我們輸入的字符可以是個大概,他可以根據(jù)我們輸入的文字大概進行匹配查詢。返回包含與搜索詞類似的詞的文檔,該詞由Levenshtein編輯距離度量。
包括以下幾種情況:
更改角色(box→fox)
刪除字符(aple→apple)
插入字符(sick→sic)
調(diào)換兩個相鄰字符(ACT→CAT)
參數(shù)說明:
prefix_length不能被 “模糊化” 的初始字符數(shù)。 大部分的拼寫錯誤發(fā)生在詞的結(jié)尾,而不是詞的開始。 例如通過將
prefix_length設置為3,你可能夠顯著降低匹配的詞項數(shù)量。
例如用戶在查詢過程中把“中國移動”輸為了“中國聯(lián)動”:
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"fuzzy": {
"categoryName": {
"value": "平電視",
"fuzziness": "AUTO",
"prefix_length": 0,
"max_expansions": 50,#控制查詢的數(shù)量,注意不是返回結(jié)果的最大值,而是在查詢過程中最多去匹配到多少條,比如此處 50 條,即便有 1 萬條能匹配到的,當查詢過程中匹配到 50 條的時候就不再繼續(xù)查詢直接返回結(jié)果
"transpositions": true,
"boost": 1
}
}
}
}
@Override
public void fuzzyQuery(String index, String field, String keyword) throws Exception {
builder(index, QueryBuilders.fuzzyQuery(field, keyword));
}
把prefixLength(2)參數(shù)改為prefixLength(3)就查不到結(jié)果了,不能被 “模糊化” 的初始字符變成了中國聯(lián)。
@Test
public void testFuzzyQuery() throws Exception {
baseQuery.fuzzyQuery(Constants.INDEX_NAME, "title", "平電視");
}
1.4 wildcard查詢
wildcard查詢允許我們在要查詢的內(nèi)容中使用通配符*和?,和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查詢
本章到目前為止,對于數(shù)字,只介紹如何處理精確值查詢。 實際上,對數(shù)字范圍進行過濾有時會更有用。例如,我們可能想要查找所有價格大于 40 美元的產(chǎn)品。
在 SQL 中,范圍查詢可以表示為:
SELECT document
FROM products
WHERE price BETWEEN 20 AND 40
Elasticsearch 有 range 查詢, 不出所料地,可以用它來查找處于某個范圍內(nèi)的文檔:
"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)
例如要查詢出短信狀態(tài)報告響應時間在1-10秒之內(nèi)的文檔:
#范圍查詢
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查詢的唯一區(qū)別在于它們能夠支持更加復雜的模式。
這也意味著使用它們存在相同的風險。對一個含有很多不同詞條的字段運行這類查詢是非常消耗資源的。避免使用一個以通配符開頭的模式(比如,*foo)。
盡管對于前綴匹配,可以在索引期間準備你的數(shù)據(jù)讓它更加高效,通配符和正則表達式匹配只能在查詢期間被完成。雖然使用場景有限,但是這些查詢也有它們的用武之地。
<font color=red>注意:prefix,wildcard以及regexp查詢基于詞條進行操作。如果你在一個analyzed字段上使用了它們,它們會檢查字段中的每個詞條,而不是整個字段。</font>
例如查找長號碼(longCode)以1069開頭后面是數(shù)字的文檔:
#模糊查詢: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的個數(shù)是有限制的,二者之和不能超過1w。當所請求的數(shù)據(jù)總量大于1w時,可用scroll來代替from+size。
原理
ES的搜索是分2個階段進行的,即Query階段和Fetch階段。 Query階段比較輕量級,通過查詢倒排索引,獲取滿足查詢結(jié)果的文檔ID列表。 而Fetch階段比較重,需要將每個shard的結(jié)果取回,在協(xié)調(diào)結(jié)點進行全局排序。 通過From+size這種方式分批獲取數(shù)據(jù)的時候,隨著from加大,需要全局排序并丟棄的結(jié)果數(shù)量隨之上升,性能越來越差。
而Scroll查詢,先做輕量級的Query階段以后,免去了繁重的全局排序過程。 它只是將查詢結(jié)果集,也就是doc id列表保留在一個上下文里, 之后每次分批取回的時候,只需根據(jù)設置的size,在每個shard內(nèi)部按照一定順序(默認doc_id續(xù)), 取回這個size數(shù)量的文檔即可。
使用場景
由此也可以看出scroll不適合支持那種實時的和用戶交互的前端分頁工作,其主要用途用于從ES集群分批拉取大量結(jié)果集的情況,一般都是offline的應用場景。 比如需要將非常大的結(jié)果集拉取出來,存放到其他系統(tǒng)處理,或者需要做大索引的reindex等等。
不要把 scroll 用于實時請求,它主要用于大數(shù)據(jù)量的場景。例如:將一個索引的內(nèi)容索引到另一個不同配置的新索引中。
代碼示例
例如滾動查詢所有文檔:
POST /book-index/_search?scroll=1m
{
"query": {
"match_all" : {}
},
"sort": [
"_doc"
]
}
POST /_search/scroll #繼續(xù)使用滾動查詢
{
"scroll": "1m", #設置時間
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTlU3Z1FCT1lWVHdadlhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVlR3WnZYTmFXZw==" #指定在哪個scroll的基礎上繼續(xù)查詢
}
清除scroll
雖然我們在設置開啟scroll時,設置了一個scroll的存活時間,但是如果能夠在使用完順手關閉,可以提早釋放資源,降低ES的負擔.
DELETE /_search/scroll
{
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAdsMqFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbDKRZlZGUwSWpSVlJqeVJiN1dBWHNpUG1RAAAAAABpX2sWclBEekhiRVpSRktHWXFudnVaQ3dIQQAAAAAAaV9qFnJQRHpIYkVaUkZLR1lxbnZ1WkN3SEEAAAAAAGlfaRZyUER6SGJFWlJGS0dZcW52dVpDd0hB"
}
public void scrollQuery(String index) throws Exception {
SearchRequest searchRequest = new SearchRequest(index);
//值不需要足夠長來處理所有數(shù)據(jù)—它只需要足夠長來處理前一批結(jié)果。每個滾動請求(帶有滾動參數(shù))設置一個新的過期時間。
//創(chuàng)建一個有效期為 1 分鐘的滾動
Scroll scroll = new Scroll(TimeValue.timeValueMillis(1L));
//設置滾動查詢
searchRequest.scroll(scroll);
//創(chuàng)建查詢條件的封裝對象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//一般情況下我們的滾動查詢需要查詢所有數(shù)據(jù)
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
//設定每次返回多少條數(shù)據(jù)
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());
}
//遍歷搜索命中的數(shù)據(jù),直到?jīng)]有數(shù)據(jù)
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來刪除一個索引下的全部或者大部分文檔,確實需要的話,可以創(chuàng)建一個新的索引,然后將需要保留的文檔重新索引到新的索引中去,這樣你就可以直接刪掉舊索引。
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("刪除的數(shù)量是{}", deleted);
log.info(response);
}
@Test
public void testDeleteByQuery() throws Exception {
docService.deleteByQuery(Constants.INDEX_NAME, QueryBuilders.termQuery("id", "0"));
}