easy-query隐式Group革命性OLAP优化JAVA下最强查询ORM没有之一子查询合并
easy-query JAVA下最强查询ORM没有之一的任意子查询合并革命性OLAP优化
前言
对于大部分OLTP而言的查询市面上常见的orm已经能很好的处理,只要建立好对象关系那么就可以非常简单的实现隐式join或者隐式子查询
easy-query一款将ORM的Relational在查询中表现得淋漓尽致,支持手动join、手动子查询也支持隐式join、隐式子查询并且支持手动和隐式混用,经过2年半时间的打磨努力从追赶.net orm到超越我敢说里面有着许多人的努力和众多框架作者的思想借鉴才能让easy-query在短短2年变得越来越完善功能也是越来越强大
- 2023年如何实现更多的sql让orm在项目开发中变得可用做到零sql
- 2024实现如何不以翻译sql为目标实现用户心中无sql编写表达式
- 2025至今优化复杂sql在olap下的优化处理
当你在想怎么连表的时候、当你在想这个sql怎么翻译成表达式的时候其实你已经陷入了“自证”的环节,我们应该要考虑的是你希望实现的功能比如查询用户条件是银行卡有2张的,而不是写好一堆sql然后问orm作者这个怎么写,从面向sql结果向面向需求编写表达式的转变。
介绍
easy-query
文档地址 https://www.easy-query.com/easy-query-doc/
GITHUB地址 https://github.com/dromara/easy-query
GITEE地址 https://gitee.com/dromara/easy-query
对象关系
隐式join
常见的隐式join如下
查询银行卡要求是筛选条件是银行卡所属用户手机号包含1234并且所属银行是工商银行的
List<SysBankCard> bankCards = easyEntityQuery.queryable(SysBankCard.class)
.where(bank_card -> {
bank_card.user().phone().like("1234");
bank_card.bank().name().eq("工商银行");
}).toList();
生成的sql为
SELECT
t.`id`,
t.`uid`,
t.`code`,
t.`type`,
t.`bank_id`,
t.`open_time`
FROM
`t_bank_card` t
LEFT JOIN
`t_sys_user` t1
ON t1.`id` = t.`uid`
INNER JOIN
`t_bank` t2
ON t2.`id` = t.`bank_id`
WHERE
t1.`phone` LIKE '%1234%'
AND t2.`name` = '工商银行'
有些小伙伴就很奇怪为什么对于user而言是left join对于bank而言缺是inner join原因就是在创建对象关系的时候我们指定了SysBankCard和SysBank的关系是外键关系所以不可能存在有银行卡没有银行的情况
具体对象如下
@Table("t_bank")
@EntityProxy
@Data
@FieldNameConstants
@EasyAlias("bank")
public class SysBank implements ProxyEntityAvailable<SysBank, SysBankProxy> {
@Column(primaryKey = true)
private String id;
/**
* 银行名称
*/
private String name;
/**
* 成立时间
*/
private LocalDateTime createTime;
/**
* 拥有的银行卡
*/
@Navigate(value = RelationTypeEnum.OneToMany,
selfProperty = {"id"},
targetProperty = {"bankId"})
private List<SysBankCard> bankCards;
}
@Table("t_bank_card")
@EntityProxy
@Data
@FieldNameConstants
@EasyAlias("bank_card")
public class SysBankCard implements ProxyEntityAvailable<SysBankCard , SysBankCardProxy> {
@Column(primaryKey = true)
private String id;
private String uid;
/**
* 银行卡号
*/
private String code;
/**
* 银行卡类型借记卡 储蓄卡
*/
private String type;
/**
* 所属银行
*/
private String bankId;
/**
* 用户开户时间
*/
private LocalDateTime openTime;
/**
* 所属银行
*/
@Navigate(value = RelationTypeEnum.ManyToOne, selfProperty = {"bankId"}, targetProperty = {"id"})
@ForeignKey//可以不加 加了就是InnerJoin处理更多细节查看注解篇章
private SysBank bank;
/**
* 所属用户
*/
@Navigate(value = RelationTypeEnum.ManyToOne, selfProperty = {"uid"}, targetProperty = {"id"})
private SysUser user;
}
@Table("t_sys_user")
@EntityProxy
@Data
@FieldNameConstants
@EasyAlias("user")
public class SysUser implements ProxyEntityAvailable<SysUser , SysUserProxy> {
@Column(primaryKey = true)
private String id;
private String name;
private String phone;
private Integer age;
private LocalDateTime createTime;
/**
* 用户拥有的银行卡数
*/
@Navigate(value = RelationTypeEnum.OneToMany, selfProperty = {"id"}, targetProperty = {"uid"})
private List<SysBankCard> bankCards;
/**
* 用户拥有的书本
*/
@Navigate(value = RelationTypeEnum.OneToMany, selfProperty = {"id"}, targetProperty = {"uid"})
private List<SysUserBook> userBooks;
}
@Table("t_sys_user_book")
@EntityProxy
@Data
@FieldNameConstants
@EasyAlias("user_book")
public class SysUserBook implements ProxyEntityAvailable<SysUserBook , SysUserBookProxy> {
private String id;
private String name;
private String uid;
private BigDecimal price;
}
演示了隐式join后我在演示对应的隐式子查询
隐式子查询
筛选出用户拥有至少2张工商银行卡且还未在建设银行开户的用户
List<SysUser> list = easyEntityQuery.queryable(SysUser.class)
.where(user -> {
user.bankCards().where(card -> {
card.bank().name().eq("工商银行");
}).count().ge(2L);
user.bankCards().none(card -> {
card.bank().name().eq("建设银行");
});
}).toList();
生成的sql为
SELECT
t.`id`,
t.`name`,
t.`phone`,
t.`age`,
t.`create_time`
FROM
`t_sys_user` t
WHERE
(
SELECT
COUNT(*)
FROM
`t_bank_card` t1
INNER JOIN
`t_bank` t2
ON t2.`id` = t1.`bank_id`
WHERE
t1.`uid` = t.`id`
AND t2.`name` = '工商银行'
) >= 2
AND NOT ( EXISTS (SELECT
1
FROM
`t_bank_card` t3
INNER JOIN
`t_bank` t4
ON t4.`id` = t3.`bank_id`
WHERE
t3.`uid` = t.`id`
AND t4.`name` = '建设银行' LIMIT 1))
这里出现了两个子查询分别是[至少2张工商银行卡]和[还未在建设银行开户的用户]两个条件
那么到这里就是大部分ORM能做的事情了,甚至大部分ORM连这点都做不到,所以说如果你的ORM支持子查询那么你的ORM在面对复杂查询如果只能做到这么一点那么只能算是一个刚到及格线的ORM,因为如果出现多个子查询或者在多对多下这种处理是非常致命的,尤其是查询用户条件是菜单为/admin这种
正片开始
所谓鱼(可维护性)和熊掌(性能)不可兼得,那么easy-query是如何处理像这种情况下的呢,又是如何让你兼得鱼和熊掌的有请我们的本期主角【隐式Group】
隐式Group
筛选出用户拥有至少2张工商银行卡且还未在建设银行开户的用户
对于相同的条件easy-query只需要一个配置就可以让你兼得鱼(可维护性)和熊掌(性能)
List<SysUser> list = easyEntityQuery.queryable(SysUser.class)
//启用隐式group,无论实在where还是select里面用到的bankCards子查询都会复用同一个隐式Group
.subQueryToGroupJoin(u->u.bankCards())
.where(user -> {
//至少2张工商银行
user.bankCards().where(card -> {
card.bank().name().eq("工商银行");
}).count().ge(2L);
//没有建行卡
user.bankCards().none(card -> {
card.bank().name().eq("建设银行");
});
}).toList();
我们再来看生成的sql
SELECT
t.`id`,
t.`name`,
t.`phone`,
t.`age`,
t.`create_time`
FROM
`t_sys_user` t
LEFT JOIN
(
SELECT
t1.`uid` AS `uid`,
COUNT((CASE WHEN t3.`name` = '工商银行' THEN 1 ELSE NULL END)) AS `__count2__`,
(CASE WHEN COUNT((CASE WHEN t3.`name` = '建设银行' THEN 1 ELSE NULL END)) > 0 THEN false ELSE true END) AS `__none3__`
FROM
`t_bank_card` t1
INNER JOIN
`t_bank` t3
ON t3.`id` = t1.`bank_id`
GROUP BY
t1.`uid`
) t2
ON t2.`uid` = t.`id`
WHERE
IFNULL(t2.`__count2__`,0) >= 2
AND IFNULL(t2.`__none3__`,true) = true
有没有一种眼前一亮的感觉,我敢说目前来讲并没有多少ORM实现了隐式Group,当然对于OLAP而言easy-query的提供的功能远不止此
partition by
筛选用户条件为喜欢工商银行的(第一张开户的银行卡是工商银行的)
List<SysUser> list = easyEntityQuery.queryable(SysUser.class)
.where(user -> {
//用户的银行卡中第一个开户银行卡是工商银行的
user.bankCards().orderBy(x->x.openTime().asc()).firstElement().bank().name().eq("工商银行");
}).toList();
生成的sql
SELECT
t.`id`,
t.`name`,
t.`phone`,
t.`age`,
t.`create_time`
FROM
`t_sys_user` t
LEFT JOIN
(
SELECT
t2.`id` AS `id`,
t2.`uid` AS `uid`,
t2.`code` AS `code`,
t2.`type` AS `type`,
t2.`bank_id` AS `bank_id`,
t2.`open_time` AS `open_time`
FROM
(SELECT
t1.`id`,
t1.`uid`,
t1.`code`,
t1.`type`,
t1.`bank_id`,
t1.`open_time`,
(ROW_NUMBER() OVER (PARTITION BY t1.`uid` ORDER BY t1.`open_time` ASC)) AS `__row__`
FROM
`t_bank_card` t1) t2
WHERE
t2.`__row__` = 1
) t4
ON t4.`uid` = t.`id`
INNER JOIN
`t_bank` t5
ON t5.`id` = t4.`bank_id`
WHERE
t5.`name` = '工商银行'
是的你没看错就是这么简单如果你需要动态那么只需要用where(true/false,条件)或者更加直白的方式即可
筛选用户条件为喜欢工商银行的(第一张开户的银行卡是工商银行的)和用户姓名叫小明的,其中喜欢工商银行这个条件可以是动态的
boolean likeICBC = false;
String name = "小明";
List<SysUser> list = easyEntityQuery.queryable(SysUser.class)
.where(user -> {
if(EasyStringUtil.isNotBlank(name)){
user.name().like(name);
}
if(likeICBC){
//用户的银行卡中第一个开户银行卡是工商银行的
user.bankCards().orderBy(x -> x.openTime().asc()).firstElement().bank().name().eq("工商银行");
}
}).toList();
生成的sql
SELECT
`id`,
`name`,
`phone`,
`age`,
`create_time`
FROM
`t_sys_user`
WHERE
`name` LIKE '%小明%'
是的你没看错动态partition就是这么简单就是这么容易阅读和维护
select子查询
写了这么多where子查询的隐式Group那么再来写点select子查询相关的吧
查询用户返回用户姓名和用户开户的前两张银行卡类型逗号分割
List<Draft2<String, String>> list = easyEntityQuery.queryable(SysUser.class)
.where(user -> {
user.name().like("小明");
}).select(user -> Select.DRAFT.of(
user.name(),
//用户的银行卡中前两个开户银行卡类型
user.bankCards().orderBy(x -> x.openTime().asc()).elements(0, 1).joining(x -> x.type(),",")
)).toList();
生成的sql
SELECT
t.`name` AS `value1`,
(SELECT
GROUP_CONCAT(t1.`type` SEPARATOR ',')
FROM
`t_bank_card` t1
WHERE
t1.`uid` = t.`id`
ORDER BY
t1.`open_time` ASC LIMIT 2) AS `value2`
FROM
`t_sys_user` t
WHERE
t.`name` LIKE '%小明%'
超级无敌究极子查询转group
筛选用户条件为姓名包含小明,并且用户的所有储蓄卡中前三张银行卡都不是在2000年前的银行中开户的,并且返回用户姓名和储蓄卡的所属银行名称逗号分割
List<Draft2<String, String>> list = easyEntityQuery.queryable(SysUser.class)
.subQueryToGroupJoin(x -> x.bankCards())
.where(user -> {
user.name().like("小明");
user.bankCards()
.where(x -> x.type().eq("储蓄卡"))
.orderBy(x -> x.openTime().asc()).elements(0, 2)
.none(x -> x.bank().createTime().ge(LocalDateTime.of(2000,1,1,0,0)));
}).select(user -> Select.DRAFT.of(
user.name(),
user.bankCards()
.where(x -> x.type().eq("储蓄卡"))
.orderBy(x -> x.openTime().asc())
.elements(0, 2).joining(x -> x.bank().name(),",")
)).toList();
生成的sql
SELECT
t.`name` AS `value1`,
t3.`__joining3__` AS `value2`
FROM
`t_sys_user` t
LEFT JOIN
(
SELECT
t2.`uid` AS `uid`,
(CASE
WHEN COUNT((CASE WHEN t4.`create_time` >= '2000-01-01 00:00' THEN 1 ELSE NULL END)) > 0 THEN false ELSE true
END) AS `__none2__`,
GROUP_CONCAT(t4.`name` SEPARATOR ',') AS `__joining3__`
FROM
(SELECT
t1.`id`,
t1.`uid`,
t1.`code`,
t1.`type`,
t1.`bank_id`,
t1.`open_time`
FROM
`t_bank_card` t1
WHERE
t1.`type` = '储蓄卡'
ORDER BY
t1.`open_time` ASC LIMIT 3) t2
INNER JOIN
`t_bank` t4
ON t4.`id` = t2.`bank_id`
GROUP BY
t2.`uid`) t3
ON t3.`uid` = t.`id`
WHERE
t.`name` LIKE '%小明%'
AND IFNULL(t3.`__none2__`,true) = true
是的你没看错这就是easy-query在2023年诞生到现在2年时间的努力,是否有一种惊艳到你的冲动
最后
可能有很多小伙伴会推荐我jpa或者jooq我想说如果我没能力那么我可能会选择他们,如果他们支持国产数据库我可能会选择他们,但是你我更愿意推荐easy-query因为我会聆听开发者的声音起码你叫的动我,我是一个在crud混的菜鸟开发,crud的困难,orm的困难必须是一个混迹在业务开发的程序员才能开发出来的好框架,在没开发出这个api的时候已经有很多小伙伴使用lambda的api进行了开发反向非常不错,期待您的使用。
easy-query隐式Group革命性OLAP优化JAVA下最强查询ORM没有之一子查询合并的更多相关文章
- MySQL 子查询(四)子查询的优化、将子查询重写为连接
MySQL 5.7 ref ——13.2.10.10优化子查询 十.子查询的优化 开发正在进行中,因此从长远来看,没有什么优化建议是可靠的.以下列表提供了一些您可能想要使用的有趣技巧.See also ...
- Qt C++中的关键字explicit——防止隐式转换(也就是Java里的装箱),必须写清楚
最近在复习QT,准备做项目了,QT Creator 默认生成的代码 explicit Dialog(QWidget *parent = 0)中,有这么一个关键字explicit,用来修饰构造函数.以前 ...
- mysql优化---in型子查询,exists子查询,from 型子查询
in型子查询引出的陷阱:(扫更少的行,不要临时表,不要文件排序就快) 题: 在ecshop商城表中,查询6号栏目的商品, (注,6号是一个大栏目) 最直观的: mysql); 误区: 给我们的感觉是, ...
- MySQL解惑——GROUP BY隐式排序
MySQL中GROUP BY隐式排序是什么概念呢? 主要是其它RDBMS没有这样的概念,如果没有认真了解过概念,对这个概念会感觉有点困惑,我们先来看看官方文档的介绍: 官方文档MySQL 5.7 Re ...
- MySQL解惑——GROUP BY隐式排序
原文:MySQL解惑--GROUP BY隐式排序 MySQL中GROUP BY隐式排序是什么概念呢? 主要是其它RDBMS没有这样的概念,如果没有认真了解过概念,对这个概念会感觉有点困惑,我们先来看看 ...
- 显示转换explicit和隐式转换implicit
用户自定义的显示转换和隐式转换 显式转换implicit关键字告诉编译器,在源代码中不必做显示的转型就可以产生调用转换操作符方法的代码. 隐式转换implicit关键字告诉编译器只有当源代码中指定了显 ...
- JSP的学习(6)——九大隐式对象及其out对象
本篇将介绍JSP中的九大隐式对象,并重点介绍其中的out对象. 我们在之前的博客<JSP的学习(1)——基础知识与底层原理>一文中已经知道,JSP最终要被翻译和转换成Servlet,在转换 ...
- Java :构造器中的显式参数和this隐式参数
1.构造器 写一个Java类,首先要先从构造器开始,构造器与类同名,在构造类的对象时会先从构造器开始. 构造器总是伴随着new操作符的执行而被调用. 构造器主要是用来初始化类的实例域. 构造器的特点: ...
- Scala学习之路 (八)Scala的隐式转换和隐式参数
一.概念 Scala 2.10引入了一种叫做隐式类的新特性.隐式类指的是用implicit关键字修饰的类.在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换. 隐式转换和隐式参数是Scal ...
- C++转换构造函数和隐式转换函数 ~ 转载
原文地址: C++转换构造函数和隐式转换函数 用转换构造函数可以将一个指定类型的数据转换为类的对象.但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成doubl ...
随机推荐
- Linux安装Python 3.11
Linux安装python 在Linux上安装Python 3.11,你可以按照以下步骤进行.这些步骤以CentOS为例,但其他Linux发行版的过程大同小异,可能只需稍作调整. 1. 检查Pytho ...
- 欧拉积分(Genshin)
\(\Gamma\) 函数 引入.定义 在计算组合数式子的时候,我们时常会看到这样的式子: \[\frac{(-2n)!((-n/2)!)^2}{((-n)!)^3} \] 然而,我们不知道什么是负数 ...
- AI-接入
前言 前面已经申请了模型,并且通过测试已经可以访问使用了,本篇的接入还是使用Ollama,前面我们已经可以在命令行终端能够进行交互了,现在将AI接入到代码中: 准备 作为一名Neter这里使用的是.n ...
- 在python中通过模型api
前言 首先我选择的siliconflow(硅基流动)平台来调用它的api,无为啥,就是因为我点击了别人的邀请链接它送了我14块余额,那个人同样也获得14块余额 这时候,就不得不说一下我的邀请链接了ht ...
- 大型语言模型(LLM)为什么处理日语这么“头大”?
引言 你有没有想过,为什么 AI 大神们处理日语时,总是会挠头?其实,这都要从"token"这个神奇的小东西说起. 在大型语言模型(LLM)中,token 就是文本的基本处理单位. ...
- FANUC发那科工业机器人减速器维修小细节
在现代工业生产中,FANUC发那科机器人已成为不可或缺的一部分.然而,随着时间的推移,发那科机械手减速器可能会出现故障,影响机器人的正常工作. 一.了解减速器的结构与工作原理 在开始FANUC发那科机 ...
- C# List LinQ Lambda 表达式
------------恢复内容开始------------ # 参考链接 : https://blog.csdn.net/wori/article/details/113144580 首先 => ...
- 第二课 - 输入(按键)控制输出(LED)-设备树
在第一课中学习了如何安装NCS开发环境,以及如何新建一个工程,还有如何构建和下载到开发板.并运行了官方的LED闪烁例程. 设备树 我们继续跟着官方开发者学院的教程来学习第二课的课程.官方课程包含了以下 ...
- php查询结果汉字乱码解决方法
问题描述:使用php查询数据显示,显示的结果中所有汉字乱码 问题及解决:这种情况是编码造成的,检查数据库及页面编码是否一致,也可在页面增加: header('Content-Type:text/htm ...
- [评测/调研/AIGC/流媒体] 视频内容自动生成摘要工具
概述:视频内容自动生成摘要工具 SolidPoint | 仅支持 简介 SolidPoint 是一款AI驱动的在线视频摘要工具,专注于自动生成YouTube视频的简洁摘要. 通过分析视频内容提取关键点 ...