https://www.cnblogs.com/jajian/p/10053504.html

前言#

这篇博文本来是想放在全系列的大概第五、六篇的时候再讲的,毕竟查询是在索引创建、索引文档数据生成和一些基本概念介绍完之后才需要的。当前面的一些知识概念全都讲解完之后再讲解查询是最好的,但是最近公司项目忙经常加班,毕竟年底了。但是不写的话我怕会越拖越久,最后会不了了之了,所以刚好上海周末下雪,天冷无法出门,就坐在电脑前敲下了这篇博文。因为公司的查询这块是我负责的所以我研究了比较多点,写起来也顺手些。那么进入正文。

为什么用SQL查询#

前面的文章介绍过,Elasticsearch 的官方查询语言是 Query DSL,既然是官方指定的,说明最吻合 ES 的强大功能,为ES做支撑。那么我们为什么还用 SQL 查询?这是否是多此一举了呢?

其实,存在毕竟有存在的道理,存在即合理。SQL 作为一个数据库查询语言,它语法简洁,书写方便而且大部分服务端程序员都清楚了解和熟知它的写法。但是作为一个 ES 萌新来说,就算他已经是一位编程界的老江湖,但是如果他不熟悉 ES ,那么他如果要使用公司已经搭好的 ES 服务,他必须要先学习 Query DSL,学习成本也是一项影响技术开发进度的因素而且不稳定性高。但是如果 ES 查询支持 SQL的话,那么也许就算他是工作一两年的同学,他虽然不懂 ES的复杂概念,他也能很好的使用 ES 而且顺利的参加到开发的队伍中,毕竟SQL 谁不会写呢?

Elasticsearch-SQL#

我们正式介绍下我们的主角 - Elasticsearch-SQL,Elasticsearch-SQL不属于 Elasticsearch 官方的,它是 NLPChina(中国自然语言处理开源组织)开源的一个 ES 插件,主要功能是通过 SQL 来查询 ES,其实它的底层是通过解释 SQL,将SQL 转换为 DSL 语法,再通过DSL 查询。

Elasticsearch-SQL目前已经支持大概所有版本的 ES,而且最近的6.5.x的也在支持的范围了,所以可以看得出来维护的还是蛮频繁的。

安装插件#

由于 ES 2.x 和 5.x 的版本区别(详细参考:版本选择),我们安装 ES 插件是有点区别的,

在 5.0之前的安装方式为:plugin install

./bin/plugin install https://github.com/NLPchina/elasticsearch-sql/releases/download/2.4.6.0/elasticsearch-sql-2.4.6.0.zip

在5.0之后(包括6.x)的安装方式为:elasticsearch-plugin install

./bin/elasticsearch-plugin install https://github.com/NLPchina/elasticsearch-sql/releases/download/5.0.1/elasticsearch-sql-5.0.1.0.zip

如果我们安装不成功,我们可以直接下载 Elasticsearch-SQL 插件的压缩包,然后解压,完成之后重命名文件夹为 sql ,放到 ES 的安装路径的 plugins目录中,例如:..\elasticsearch-6.4.0\plugins\sql

完成此操作后,需要重新启动Elasticsearch服务器,否则会报错:Invalid index name [sql], must not start with '']; ","status":400}

前端可视化界面#

Elasticsearch-SQL 插件提供了可视化的界面,方便你执行SQL查询,界面如下:

在 elasticsearch 1.x / 2.x,你可以直接访问如下地址:

http://localhost:9200/_plugin/sql/

而在 elasticsearch 5.x/6.x,这需要安装 node.js 和下载及解压site,然后像这样启动web前端:

cd site-server
npm install express --save
node node-server.js

查询语法#

经过以上的操作之后,如果没出问题,现在就可以使用 SQL 查询 ES 了,其中有些是正常的 SQL 语法,还有些是超越SQL 语法的,相当于是对 SQL 语法的增强,ES 的查询格式是:

http://localhost:9200/_sql?sql=select * from indexName limit 10

简单查询#

先上个简单的查询语法:

SELECT fields from indexName WHERE conditions

可以看到,我们以前的查询语句中,表名 tableName 的地方现在改为了索引名 indexName,如果有索引Type ,还可以这样写:

SELECT fields from indexName/type WHERE conditions

也可以同时查询索引的多个类型,语法如下:

SELECT fields from indexName/type1,indexName/type2 WHERE conditions

如果想知道当前SQL是如何将SQL解释为Elasticsearch 的Query DSL,可以这样通过关键字explain

http://localhost:9200/_sql/_explain?sql=select * from indexName limit 10

聚合类函数查询

select COUNT(*),SUM(age),MIN(age) as m, MAX(age),AVG(age)
FROM bank GROUP BY gender ORDER BY SUM(age), m DESC

额外增强查询#

Search

 SELECT address FROM bank WHERE address = matchQuery('880 Holmes Lane') ORDER BY _score DESC LIMIT 3

Aggregations

  • range age group 20-25,25-30,30-35,35-40
SELECT COUNT(age) FROM bank GROUP BY range(age, 20,25,30,35,40)
  • range date group by day
SELECT online FROM online GROUP BY date_histogram(field='insert_time','interval'='1d')
  • range date group by your config
  SELECT online FROM online GROUP BY date_range(field='insert_time','format'='yyyy-MM-dd' ,'2014-08-18','2014-08-17','now-8d','now-7d','now-6d','now')

地理查询#

Elasticsearch 可以把地理位置、全文搜索、结构化搜索和分析结合到一起。而Elasticsearch-sql 也基本支持所有地理位置相关的查询,对应 Elasticsearch的章节内容为Geolocation

1、地理坐标盒模型过滤器

地理坐标盒模型过滤器(Geo Bounding Box Filter),指定一个矩形的顶部,底部,左边界和右边界,然后过滤器只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间。

语法:

GEO_BOUNDING_BOX(fieldName,topLeftLongitude,topLeftLatitude,bottomRightLongitude,bottomRightLatitude)

示例:

SELECT * FROM location WHERE GEO_BOUNDING_BOX(center,100.0,1.0,101,0.0)

2、地理距离过滤器

地理距离过滤器( geo_distance ),以给定位置为圆心画一个圆,来找出那些地理坐标落在指定距离范围的文档。

语法:

GEO_DISTANCE(fieldName,distance,fromLongitude,fromLatitude)

示例:

SELECT * FROM location WHERE GEO_DISTANCE(center,'1km',100.5,0.5)

3、地理距离区间过滤器

范围距离过滤器(Range Distance filter),以给定位置为圆心,分别以两个给定的距离画圆,找出与指定点距离在给定最小距离和最大距离之间的点,和geo_distance filter的唯一差别在于Range Distance filter是一个环状的,它会排除掉落在内圈中的那部分文档。

语法:

GEO_DISTANCE_RANGE(fieldName,distanceFrom,distanceTo,fromLongitude,fromLatitude)

示例:

SELECT * FROM location WHERE GEO_DISTANCE_RANGE(center,'1m','1km',100.5,0.50001)

4、Polygon filter (works on points)

找出落在多边形中的点。 这个过滤器使用代价很大 。当你觉得自己需要使用它,最好先看看 geo-shapes 。

语法:

GEO_POLYGON(fieldName,lon1,lat1,lon2,lat2,lon3,lat3,...)

示例:

SELECT * FROM location WHERE GEO_POLYGON(center,100,0,100.5,2,101.0,0)

5、GeoShape Intersects filter (works on geoshapes)

这里需要使用WKT表示查询时的形状。
语法:

GEO_INTERSECTS(fieldName,'WKT')

示例:

SELECT * FROM location WHERE GEO_INTERSECTS(place,'POLYGON ((102 2, 103 2, 103 3, 102 3, 102 2))

更多关于地理的查询可以参考这里

实战用法#

我们以本系列的第一篇教程中我们创建的索引 nba来作示例,如下:

1、查询 nba 所有球队信息

http://localhost:9200/_sql?sql=select * from nba limit 10

查询结果:

2、查询当家球星是詹姆斯的球队信息

http://localhost:9200/_sql?sql=select * from nba where topStar  = "勒布朗·詹姆斯"

查询结果:

3、根据建队时间降序排列

http://localhost:9200/_sql?sql=select * from nba order by date desc

查询结果:

4、查询拥有总冠军超过5个的球队信息

http://localhost:9200/_sql?sql=select * from nba where championship  >= 5

查询结果:

5、查询总冠军数量分别在1-5,5-10,10-15,15-20范围之间球队的数量

http://localhost:9200/_sql?sql=SELECT COUNT(championship) FROM nba GROUP BY range(championship, 1,5,10,15,20)

查询结果:

当然还有更多的写法,具体实现在这里就不多诉了,感兴趣的读者可以自己搭建个项目然后尝试下,更多特色SQL写法可以参考这里:

Java实现#

上面已经介绍了 Elasticsearch-SQL的安装和使用,那么我们如何在项目中使用它,Elasticsearch-SQL底层是使用Java语言开发的,通过解析SQL 转换为 DSL 语言,然后得出查询结果,解析结果成key-value的固定格式返回。

引入依赖#

使用前我们需要先引入maven依赖

<dependency>
<groupId>org.nlpcn</groupId>
<artifactId>elasticsearch-sql</artifactId>
<version>x.x.x.0</version>
</dependency>

版本号(x.x.x)需要和 Elasticsearch的版本对应上,具体的对应关系大致可以参考下图:

但是不是所有的版本,我们都可以从Maven Repository里获取到,我们如果直接从Maven 仓库里面只能获取如下几个版本的依赖,其中缺少很多版本:

那如果我们使用的是其他版本的 ES 如何解决依赖 jar包问题呢?还记得我们开始下载插件解压后的sql文件夹吗?例如6.5.0版本的插件的解压后文件夹内容如下:

这里面就有我们需要的 jar包,有了 jar包就好办了,我们可以直接加入到项目中,当然最好的方式是上传到公司的私有仓库里面,然后通过pom文件依赖进来。

搭建项目#

jar包问题解决之后就可以正式进入开发阶段了,新建一个springboot项目,引入各项依赖,一切准备就寻后,如何连接ES呢?

这里有两种方式可以实现我们的功能,一个是通过JDBC的方式,连接数据库一样连接ES。还有一种就是通过 tansport client 方式。

JDBC的方式

代码示例

public void testJDBC() throws Exception {
Properties properties = new Properties();
properties.put("url", "jdbc:elasticsearch://192.168.3.31:9300,192.168.3.32:9300/" + TestsConstants.TEST_INDEX);
DruidDataSource dds = (DruidDataSource) ElasticSearchDruidDataSourceFactory.createDataSource(properties);
Connection connection = dds.getConnection();
PreparedStatement ps = connection.prepareStatement("SELECT gender,lastname,age from " + TestsConstants.TEST_INDEX + " where lastname='Heath'");
ResultSet resultSet = ps.executeQuery();
List<String> result = new ArrayList<String>();
while (resultSet.next()) {
System.out.println(resultSet.getString("lastname") + "," + resultSet.getInt("age") + "," + resultSet.getString("gender"))
}
ps.close();
connection.close();
dds.close();
}

这种方式是最直观的,用到了Druid连接池,所以我们还需要在项目中引入druid依赖,而且需要注意依赖的版本,否则会报错。

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.15</version>
</dependency>

这种方式很好理解,而且开发也方便,但是我在项目中应用了发现它有很多不足,所以我最后还是自己看了下源码,通过API的方式重新封装调用。

API方式

其实 elasticsearch-sql 没有提供开发的 文档,并没有介绍如何通过调用 Java API方式开发,我们需要阅读 elasticsearch-sql 的源代码来发现它的service,然后包装成我们需要的,通过阅读源码我们发现了如下一个功能明显的Service类。

public class SearchDao {

	private static final Set<String> END_TABLE_MAP = new HashSet<>();

	static {
END_TABLE_MAP.add("limit");
END_TABLE_MAP.add("order");
END_TABLE_MAP.add("where");
END_TABLE_MAP.add("group"); } private Client client = null; public SearchDao(Client client) {
this.client = client;
} public Client getClient() {
return client;
} /**
* Prepare action And transform sql
* into ES ActionRequest
* @param sql SQL query to execute.
* @return ES request
* @throws SqlParseException
*/
public QueryAction explain(String sql) throws SqlParseException, SQLFeatureNotSupportedException {
return ESActionFactory.create(client, sql);
}
}

SearchDao 类中有一个explain方法,接收的参数就是一个字符串sql ,返回结果是 QueryAction ,QueryAction 是一个抽象类,它又有如下子类

可以看出,每个子类对应的就是一个查询的功能,聚合查询,默认查询,删除,哈希连接查询,连接查询,嵌套查询等等。

获得的 QueryAction 我们可以通过 QueryActionElasticExecutor类的executeAnyAction方法来接受,并内部处理,然后就能获得相应的执行结果。

 public static Object executeAnyAction(Client client , QueryAction queryAction) throws SqlParseException, IOException {
if(queryAction instanceof DefaultQueryAction)
return executeSearchAction((DefaultQueryAction) queryAction);
if(queryAction instanceof AggregationQueryAction)
return executeAggregationAction((AggregationQueryAction) queryAction);
if(queryAction instanceof ESJoinQueryAction)
return executeJoinSearchAction(client, (ESJoinQueryAction) queryAction);
if(queryAction instanceof MultiQueryAction)
return executeMultiQueryAction(client, (MultiQueryAction) queryAction);
if(queryAction instanceof DeleteQueryAction )
return executeDeleteAction((DeleteQueryAction) queryAction);
return null;
}

虽然得到了查询结果,但是它是一个Object类型,我们还需要定制化一下,注意到了一个类:ObjectResultsExtractor,它的构造函数如下,构造函数包含三个布尔类型的参数。它们的作用是在结果集中是否包含score,是否包含type,是否包含ID,我们可以都设置为 false。

public ObjectResultsExtractor(boolean includeScore, boolean includeType, boolean includeId) {
this.includeScore = includeScore;
this.includeType = includeType;
this.includeId = includeId;
this.currentLineIndex = 0;
}

ObjectResultsExtractor它仅有一个对外的 pulic 修饰的方法extractResults

public ObjectResult extractResults(Object queryResult, boolean flat) throws ObjectResultsExtractException {
if (queryResult instanceof SearchHits) {
SearchHit[] hits = ((SearchHits) queryResult).getHits();
List<Map<String, Object>> docsAsMap = new ArrayList<>();
List<String> headers = createHeadersAndFillDocsMap(flat, hits, docsAsMap);
List<List<Object>> lines = createLinesFromDocs(flat, docsAsMap, headers);
return new ObjectResult(headers, lines);
}
if (queryResult instanceof Aggregations) {
List<String> headers = new ArrayList<>();
List<List<Object>> lines = new ArrayList<>();
lines.add(new ArrayList<Object>());
handleAggregations((Aggregations) queryResult, headers, lines); // remove empty line。
if(lines.get(0).size() == 0) {
lines.remove(0);
}
//todo: need to handle more options for aggregations:
//Aggregations that inhrit from base
//ScriptedMetric return new ObjectResult(headers, lines); }
return null;
}

至此我们就大致了解了它的查询API ,然后我们只需要在我们项目中做如下的代码调用就可以完成我们的查询功能了,最后得到的ObjectResult就是我们的最终查询结果集了。

//1.解释SQL
SearchDao searchDao = new SearchDao(transportClient);
QueryAction queryAction = searchDao.explain(sql);
//2.执行
Object execution = QueryActionElasticExecutor.executeAnyAction(searchDao.getClient(), queryAction);
//3.格式化查询结果
ObjectResult result = (new ObjectResultsExtractor(true, false, false)).extractResults(execution, true);

至此,代码开发完成,我们来测试下运行结果,我对外提供了三个接口,一个是 API方式查询,一个是JDBC方式查询,还有一个解释SQL。

@RestController
@RequestMapping("/es/data")
public class ElasticSearchController { @Autowired
private ElasticSearchSqlService elasticSearchSqlService; @PostMapping(value = "/search")
public CommonResult search(@RequestBody QueryDto queryDto) {
SearchResultDTO resultDTO = elasticSearchSqlService.search(queryDto.getSql());
return CommonResult.success(resultDTO.getResult());
} @PostMapping(value = "/query")
public CommonResult query(@RequestBody QueryDto queryDto) {
SearchResultDTO resultDTO = elasticSearchSqlService.query(queryDto.getSql(), queryDto.getIndex());
return CommonResult.success(resultDTO.getResult());
} @PostMapping(value = "/explain")
public CommonResult explain(@RequestBody QueryDto queryDto) {
return CommonResult.success(elasticSearchSqlService.explain(queryDto.getSql()));
} }

请求示例:

查询结果示例:

总结#

SQL 虽然不是 ES 官方推荐的查询语言,但是由于他的便捷性,ES 官方也开始意识到这块。ES 在 6.3.0版本后也开始支持 SQL了,但是他是通过引入 x-pack 的方式,如果我们可以通过 REST 方式使用,但是我们引入到开发中还是有点问题,需要铂金会员才行,不知道以后会不会放开。

另外,SQL 虽然使用起来比较方便,但是毕竟不是官方指定的,所以难免在功能上有缺陷,没有 DSL 功能强大,而且里面的坑比较多,但是基本的查询都支持。所以如果不是迫不得已,我还是建议使用 DSL,而一些简单的操作可以用SQL来辅助,本篇文章源码都已上传到本人的 Github ,如果感兴趣的读者可以关注我的 Github

[转帖]Elasticsearch 技术分析(五):如何通过SQL查询Elasticsearch的更多相关文章

  1. Elasticsearch 技术分析(八):剖析 Elasticsearch 的索引原理

    前言 创建索引的时候,我们通过Mapping 映射定义好索引的基本结构信息,接下来我们肯定需要往 ES 里面新增业务文档数据了,例如用户,日志等业务数据.新增的业务数据,我们根据 Mapping 来生 ...

  2. ElasticSearch评分分析 explian 解释和一些查询理解

    ElasticSearch评分分析 explian 解释和一些查询理解 按照es-ik分析器安装了ik分词器.创建索引:PUT /index_ik_test.索引包含2个字段:content和nick ...

  3. Elasticsearch-sql 用SQL查询Elasticsearch

    Elasticsearch的查询语言(DSL)真是不好写,偏偏查询的功能千奇百怪,filter/query/match/agg/geo各种各样,不管你是通过封装JSON还是通过python/java的 ...

  4. Elasticsearch通关教程(五):如何通过SQL查询Elasticsearch

    前言 这篇博文本来是想放在全系列的大概第五.六篇的时候再讲的,毕竟查询是在索引创建.索引文档数据生成和一些基本概念介绍完之后才需要的.当前面的一些知识概念全都讲解完之后再讲解查询是最好的,但是最近公司 ...

  5. Elasticsearch 技术分析(九):Elasticsearch的使用和原理总结

    前言 之前已经分享过Elasticsearch的使用和原理的知识,由于近期在公司内部做了一次内部分享,所以本篇主要是基于之前的博文的一个总结,希望通过这篇文章能让读者大致了解Elasticsearch ...

  6. Elasticsearch技术解析与实战(六)Elasticsearch并发

    乐观锁与悲观锁 图示的冲突过程,其实就是es的并发冲突问题,会导致数据不准确 当并发操作es的线程越多,或者读取一份数据,供用户查询和操作的时间越长,在这段时间里,如果数据被其他用户修改,那么我们拿到 ...

  7. Elasticsearch技术解析与实战(七)Elasticsearch批量操作

    批量查询 1.如果查询的document是不同index下的不同type种的话 GET /_mget { "docs" : [ { "_index" : &qu ...

  8. Elasticsearch技术解析与实战(七)Elasticsearch partial update

    普通的partial update 1.插入测试数据 PUT /test_index/test_type/10 { "test_field1": "test1" ...

  9. Elasticsearch日志分析系统

    Elasticsearch日志分析系统 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是Elasticsearch 一个采用Restful API标准的高扩展性的和高可用性 ...

  10. 手机自动化测试:Appium源码分析之跟踪代码分析五

    手机自动化测试:Appium源码分析之跟踪代码分析五   手机自动化测试是未来很重要的测试技术,作为一名测试人员应该熟练掌握,POPTEST举行手机自动化测试的课程,希望可以训练出优秀的手机测试开发工 ...

随机推荐

  1. Java 如何在PPT中设置形状组合、取消组合、编辑组合形状

    PPT中支持插入多达9种不同类型的形状,每种类型下又包含数十种形状样式.针对如此多种类型的形状,为页面元素设计需要.方便形状操作管理的目的,可通过形状"组合"的方式来固定多个形状的 ...

  2. 如何去学好JS的8条小建议

    摘要:如何才能学好JS?在这里给大家总结一些学习Js的经验,希望能对你们有所帮助. 在我们第一阶段完成HTML+CSS的学习之后,很多同学都会被第二阶段JS难倒--JS语法.JS数据类型.JS对象,J ...

  3. 二进制SCA指纹提取黑科技:Go语言逆向技术

    摘要:SCA(Software Composition Analysis)软件成分分析,指通过对软件源码.二进制软件包等的静态分析,挖掘其所存在的开源合规.已知漏洞等安全合规风险,是一种业界常见的安全 ...

  4. U2Net基于ModelArts Notbook的仿真实验

    摘要:U2Net是一个优秀的显著性目标检测算法,由Qin Xuebin等人发表在Pattern Recognition 2020期刊[Arxiv].U2Net名称的来源在于其网络结构由两层嵌套的Une ...

  5. 云图说:云数据库 RDS for MySQL一键开通读写分离,轻松应对业务高峰期

    摘要:华为云数据库 RDS for MySQL提供一键开通读写分离功能,只需要一个连接地址,让您在业务高峰期不再迷茫,不再慌乱,so easy 的应对业务. 本文分享自华为云社区<云图说 | 第 ...

  6. 火山引擎DataLeap的Data Catalog系统搜索实践 (上)

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 摘要 火山引擎大数据研发治理套件 DataLeap的Data Catalog系统通过汇总和组织各种元数据,解决了数 ...

  7. PPT 如何将大段WORD制作成PPT

    PPT 如何将大段WORD制作成PPT 标记等级 发送到PPT 图片.表格,无法发送

  8. Xml Entity 实体互转(JAXB)

    感觉比 xStream 操作起来复杂些 Xml Entity 实体互转(XStream).但学习成本低些,不需要引用第三方依赖包 需要注意的是 实体中如果加了 getXX 需要在上面加上 @XmlTr ...

  9. Java 剑指offer(16) 打印1到最大的n位数

    题目 输入数字n,按顺序打印出从1最大的n位十进制数.比如输入3,则打印出1.2.3一直到最大的3位数即999. 思路 陷阱: n过大时是大数问题,不能简单用int或者long数据输出,需要采用字符串 ...

  10. 基于分发与计算的GRTN全球实时传输网络

    一张能同时满足「分发」与「计算」需求的网. 从直播趋势看「分发」与「计算」 阿里云直播产品架构图中,主要分为端和云两个部分:在端侧,主要包含推流端和播放端:在云侧,一是基于分布式节点构建的传输网,二是 ...