Easy-Es Easy-Es
💋首页
  • v3.0.0(当前版本)
  • What's New

    • What' s New In Easy-Es v3.0.0?
  • 历史版本

    • v1.x.x
    • v2.x.x
  • 升级指南

    • 升级到3.x.x说明
💖赞助
  • 开源社区
  • 周边好物
  • 项目PPT (opens new window)
  • 项目介绍
  • 项目成员
  • 参与贡献
加入社区讨论
谁在使用
  • Doc-Apis (opens new window)
  • 健身计划一键生成系统 (opens new window)
  • 极氪汽车
  • Vuepress-theme-vdoing (opens new window)
  • Gitee (opens new window)
  • GitCode (opens new window)
  • Github (opens new window)
  • 简体中文 (opens new window)
  • English (opens new window)

广告采用随机轮播方式显示 ❤️成为赞助商
💋首页
  • v3.0.0(当前版本)
  • What's New

    • What' s New In Easy-Es v3.0.0?
  • 历史版本

    • v1.x.x
    • v2.x.x
  • 升级指南

    • 升级到3.x.x说明
💖赞助
  • 开源社区
  • 周边好物
  • 项目PPT (opens new window)
  • 项目介绍
  • 项目成员
  • 参与贡献
加入社区讨论
谁在使用
  • Doc-Apis (opens new window)
  • 健身计划一键生成系统 (opens new window)
  • 极氪汽车
  • Vuepress-theme-vdoing (opens new window)
  • Gitee (opens new window)
  • GitCode (opens new window)
  • Github (opens new window)
  • 简体中文 (opens new window)
  • English (opens new window)
  • 快速入门

    • 简介
    • 适用场景
    • 顾虑粉碎
    • 避坑指南
    • 快速开始
    • springboot集成demo
    • spring集成指南
    • solon集成指南
    • 配置
    • 注解
  • 核心功能

    • 条件构造器

      • 条件构造器介绍
      • 索引条件构造器
      • 查询条件构造器
      • 更新条件构造器
    • 索引CRUD

      • 索引托管模式
      • 索引CRUD
    • 数据CRUD

      • 数据同步方案
      • 数据CRUD
    • 多数据源支持
    • 动态索引支持
    • 四大嵌套查询
      • 背景
      • ES四大嵌套查询
      • ES四大拼接查询
      • 嵌套与拼接如何理解和使用
      • 优势对比
    • 链式调用
  • 拓展功能

    • 混合查询
    • 原生查询
    • 分页查询
    • 嵌套类型
    • Join父子类型
    • 获取DSL语句
    • 执行DSL语句
    • 执行SQL语句
    • 自定义RequestOptions
    • 自定义default方法
  • 高阶语法

    • 查询字段过滤
    • 排序
    • 聚合查询
    • 分词&模糊匹配
    • 权重
    • 高亮查询
    • GEO地理位置查询
    • IP查询
  • 插件

    • 插件
    • 领域模型生成插件
  • 其它

    • 问答
    • 与MP差异
    • MySQL和EE语法对比
    • 更新日志
    • 更新计划
    • 版权
    • 鸣谢
  • v2.x文档
  • 核心功能
老汉
2023-03-18
目录

四大嵌套查询

# 背景

MySQL和ES在嵌套查询这块有非常大的差异,所以在2.0版本中特地梳理此章节,帮助各位主公快速了解它们之间的差异并上手

提示

其中MP中已经有的在Easy-Es中用法和功能和它保持一致,降低用户学习及使用成本,只需要学习MP中没有,ES中独有的即可.

# ES四大嵌套查询

MySQL Mybatis-Plus ES Easy-Es
and 嵌套 and(Consumer) must and(Consumer)
or 嵌套 or (Consumer) should or (Consumer)
无 无 filter filter(Consumer)
无 无 must_not not(Consumer)

# ES四大拼接查询

MySQL Mybatis-Plus ES Easy-Es
and 拼接 默认 must 默认
or 拼接 or() should or()
无 无 filter filter()
无 无 must_not not()

提示

如果您有用过MP,理解上面的差异就比较简单,如果您尚未用过MP也没关系,咱只需要搞清楚嵌套类型与拼接类型的差异即可. 另外关于must和filter的选择,它们在功能上类似,都是表示必须满足的条件,不同之处在于filter不计算得分,性能表现更好,但不支持根据得分排序.

# 嵌套与拼接如何理解和使用

简单来说,嵌套就是有括号的,拼接就是无括号的,或者说嵌套就是里面有东西的,拼接是里面没东西的,怎么理解这段话呢?以大家熟悉的MySQL中的一段SQL为例:

  // 这里面的or就是拼接or
  where name = '老汉' or name = '痴汉';
  // 用MP或EE来写就是
  wrapper.eq(name,"老汉").or().eq(name,"老汉");
  
  
  // 这里面的or是嵌套or
  where name = '老汉' or (age = 18 and size = 18)
  // 用MP或EE来写就是
  wrapper.eq(name,"老汉").or(i->i.eq(age,18).eq(size,18));
1
2
3
4
5
6
7
8
9
10

通过上面的例子大家应该很好理解拼接和嵌套的差异了,对应到es中,嵌套就是把嵌套中的所有查询条件封装进一个新创建的boolQuery中,然后拼接至根boolQuery,而拼接则是把查询条件直接拼接进根boolQuery中. 在EE中条件与条件直接默认的拼接方式是以and拼接,由于95%的使用场景都是and拼接,所以直接省略了and这种拼接,这点和MP一样

 // sql 
 where name = '老汉' and size = 18
 // 用EE或者MP可以直接写为
 wrapper.eq(name,"老汉").eq(size,18)
1
2
3
4

如果你需要改写默认的拼接方式只需要加上对应的拼接类型即可,例如:

 // sql
 where name = '老汉' or size = 18
 // 用EE直接写为
 wrapper.eq(name,"老汉").or().eq(size,18);

 // sql
 where name = '老汉' and age != 18
 // 用EE写为
 wrapper.eq(name,"老汉").not().eq(age,18);
  
 // 所有表'非'的条件都可以用not()来拼接
 wrapper.eq(name,"老汉").not().eq(age,18).not().match(desc,'是个纯洁的好男人');
1
2
3
4
5
6
7
8
9
10
11
12

所以,如果你条件中只有一项内容,直接使用拼接就可以解决,如果条件中有多项内容,则可以使用嵌套,关于嵌套和拼接的理解,是不是很简单,你学废了吗?

# 优势对比

相比MySQL只有2中类型嵌套,ES一共有4种,并且在封装方式上差异也非常大,MySQL中的查询条件是以FIFO队列的形式进行封装 但ES中是以树形结构的形式进行封装,在层级比较深的查询中,其难度和复杂程度就算是ES老手也容易搞错,好在有了Easy-Es, 您又可以重回MP时代,大幅减少开发负担和出错的可能,为了保持和MP一样的语法,又要屏蔽ES和MySQL之间巨大的差异,这块 内容我花了近一年的碎片时间才完成,是整个框架中最难啃的一块骨头,不过一切都是值得的,不信咱们接着往下看.

下面我们以一段具体的复杂查询使用案例来对比,优势一目了然:

 // MySQL语法  
 where business_type = 1
 and (state = 9 or (state = 8 and bidding_sign = 1))
 or (business_type = 2 and state in (2,3));

 // Easy-Es及Mybatis-Plus语法 
 wrapper.eq("business_type", 1)
 .and(a -> a.eq("state", 9).or(b -> b.eq("state", 8).eq("bidding_sign", 1)))
 .or(i -> i.eq("business_type", 2).in("state", 2, 3));
        

 // ES原生的RestHighLevel语法
 List<Integer> values = Arrays.asList(2, 3);
 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
 boolQueryBuilder.must(QueryBuilders.termQuery("business_type", 1));
 boolQueryBuilder.must(QueryBuilders.boolQuery()
                 .must(QueryBuilders.termQuery("state", 9))
                      .should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("state", 8))
                              .must(QueryBuilders.termQuery("bidding_sign", 1))));
 boolQueryBuilder.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("business_type", 2))
               	 .must(QueryBuilders.termsQuery("state", values)));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

上面的例子树的最大深度仅为2,如果深度超过2以后ES原生语法就很难看得懂了,更别说正确编码,就算您是ES老手也容易栽坑里,但使用EASY-ES则可以1:1轻松又简单还原复杂SQL.

尽管我们的语法和MP一毛一样,但为了防止很多小白不会用或者没接触过MP,我尽量每种类型的使用都写一个demo供主公们参考:

    /**
     * 场景一: 嵌套and的使用 
     */
    @Test
    public void testNestedAnd() {
        // 下面查询条件等价于MySQL中的 select * from document where star_num in (1, 2) and (title = '老汉' or title = '推*')
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.in(Document::getStarNum, 1, 2)
               .and(w -> w.eq(Document::getTitle, "老汉").or().eq(Document::getTitle, "推*"));
        List<Document> documents = documentMapper.selectList(wrapper);
    }

    /**
     * 场景二: 拼接and的使用 
     */
    @Test
    public void testAnd(){
        // 下面查询条件等价于MySQL中的 select * from document where title = '老汉' and content like '推*'
        // 拼接and比较特殊,因为使用场景最多,所以条件与条件之间默认就是拼接and,所以可以直接省略,这点和MP是一样的
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
               .match(Document::getContent, "推*");
        List<Document> documents = documentMapper.selectList(wrapper);
    }
    
    /**
     * 场景二: 嵌套or的使用 
     */
    @Test
    public void testNestedOr() {
        // 下面查询条件等价于MySQL中的 select * from document where star_num = 1 or (title = '老汉' and creator = '糟老头子')
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getStarNum, 1)
                .or(i -> i.eq(Document::getTitle, "老汉").eq(Document::getCreator, "糟老头子"));
        List<Document> documents = documentMapper.selectList(wrapper);
    }

    /**
     * 场景三: 拼接or的使用 
     */
    @Test
    public void testOr() {
        // 下面查询条件等价于MySQL中的 select * from document where title = '老汉' or title = '痴汉'
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
                .or()
                .eq(Document::getTitle, "痴汉");
        List<Document> documents = documentMapper.selectList(wrapper);
    }

    /**
     * 场景四: 嵌套filter的使用 其实和场景一一样,只不过filter中的条件不计算得分,无法按得分排序,查询性能稍高
     */
    @Test
    public void testNestedFilter() {
        // 下面查询条件等价于MySQL中的 select * from document where star_num in (1, 2) and (title = '老汉' or title = '推*')
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.in(Document::getStarNum, 1, 2)
                .filter(w -> w.eq(Document::getTitle, "老汉").or().eq(Document::getTitle, "推*"));
        List<Document> documents = documentMapper.selectList(wrapper);
    }
    
    /**
     * 场景五: 拼接filter的使用 filter中的条件不计算得分,无法按得分排序,查询性能稍高
     */
    @Test
    public void testFilter() {
        // 下面查询条件等价于MySQL中的 select * from document where title = '老汉'
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.filter().eq(Document::getTitle, "老汉");
        List<Document> documents = documentMapper.selectList(wrapper);
    }
    
    /**
     * 场景六: 嵌套mustNot的使用 
     */
    @Test
    public void testNestedNot() {
        // 下面查询条件等价于MySQL中的 select * from document where title = '老汉' and (size != 18 and age != 18)
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
               .not(i->i.eq(size,18).eq(age,18));
        List<Document> documents = documentMapper.selectList(wrapper);
    }
    
    /**
     * 场景六: 拼接not()的使用
     */
    @Test
    public void testNot() {
        // 下面查询条件等价于MySQL中的 select * from document where title = '老汉' and  size != 18
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
               .not()
               .eq(size,18);
        List<Document> documents = documentMapper.selectList(wrapper);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

以上便是一些高频使用场景中的案例,只需要理解用法,然后今后无论查询条件有多么复杂,嵌套层级有多么的"深",无论您ES水平有多菜,您都可以像ES专家一样轻松拿捏,家人们一起把公屏扣在牛*上!

帮助我们改善此文档 (opens new window)
上次更新: 2025/05/11
动态索引支持
链式调用

← 动态索引支持 链式调用→

Theme by Vdoing | Copyright © 2021-2025 老汉 | 浙ICP备2022020479号 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式