高级查询是前面查询功能的进阶篇,这里将会介绍一些更为复杂的查询方式,按照复杂性依次为排序,分组,函数,视图,以及插件特殊的指向性操作等,接下来老规矩上一个比较复杂的json,如下所示
{
"rule": {
"name": "test_group",
"actions": [
{
"relations": {"classNames":[
"view.ZsTestView","view.neibu.ZsTestPOView"
],"type":"left","main":"view.neibu.ZsTestPOView"},
"name": "group",
"params":{"viewParams":{"view.ZsTestView":["kkk","%3%"]}},
"get": {
"function":{
"sum":["sumSalary"],"substr":{"view.neibu.ZsTestPOView.test_field":["#","1","2"],"view.ZsTestView.test_field":["#","1","3"],
"view.neibu.ZsTestPOView.test_field@qweewq":["#","1","8"],"view.ZsTestView.test_field@asddsa":["#","1","5"]},
"decode":{"view.neibu.ZsTestPOView.name":["#","xxx","!!!!!!"]},"avg":["view.neibu.ZsTestPOView.qian"]
},
"fields": ["view.ZsTestView.test_field","view.neibu.ZsTestPOView.test_field","substr->view.neibu.ZsTestPOView.test_field@qweewq","sum->sumSalary","avg->view.neibu.ZsTestPOView.qian"],
"order":{"substr->view.neibu.ZsTestPOView.test_field":"desc"},
"group":{
"by":["substr->view.neibu.ZsTestPOView.test_field@qweewq","view.neibu.ZsTestPOView.test_field","view.ZsTestView.test_field"],
"having":{"in":{"view.neibu.ZsTestPOView.test_field":["tyu"]},"bracket":[{"equal":{"substr->view.ZsTestView.test_field@asddsa":["zzz","sss"]}}]},
"relation":"and"
}
}
}
]
}
}
排序
这里的排序是最简单的,使用order属性,key为字段,value为排序方式,这里的key使用了函数,substr->view.neibu.ZsTestPOView.test_field,如果不使用函数则直接写上对应类名+字段就可以了,如:{view.neibu.ZsTestPOView.test_field":"desc"},这里的view.neibu是test.po.view.neibu,关于这个说明已经在介绍以及查询功能篇有过说明,关于函数将会在下面进行具体说明,生成的sql在分组说明后附上。
分组分组对应JSRuleGroups这个对象,目前只有3个属性,by、having和relation属性
- by:顾名思义就是group by,按照哪些字段进行分组查询,这里不仅可以写字段,而且同样也是可以使用函数的,例如按照时间进行分组,每年每月每周,可以substr截取时间的字符串进行分组
- having:这个也不用多说了,需要注意的是having这个属性的类型其实是JSRuleMatches,也就是前面查询功能篇所提到的查询条件这个对象模型,这里是复用了查询条件这个对象,因此里面的写法规则一样可以参照前面所讲的,同样在这里依然是可以使用函数的,参考上面的json即可
- relation:表示逻辑运算符and或or,这个也可以参考先前所写的查询功能篇里的relation,这里是同一个东西。
生成的sql:
select suibian.test_field as "view_ZsTestView-test_field",zs_test_view_po.test_field as "view_neibu_ZsTestPOView-asddsa",substr(zs_test_view_po.test_field,'1','8') as "substr$view_neibu_ZsTestPOView-asddsa@qweewq",sum(sum1_salary) as "sum$lbv_salary",avg(zs_test_view_po.qian) as "avg$view_neibu_ZsTestPOView-qian" from (select * from zs_test) zs_test_view_po left join (select * from zs_test_son1 where oh_no='kkk' and id like '%3%') suibian on zs_test_view_po.id=suibian.zs_test_id group by substr(zs_test_view_po.test_field,'1','8'),zs_test_view_po.test_field,suibian.test_field having zs_test_view_po.test_field in ('tyu') and ( substr(suibian.test_field,'1','5')='zzz' or substr(suibian.test_field,'1','5')='sss') order by substr(zs_test_view_po.test_field,'1','2') desc
对比json和sql可以更为有效的理解语法的含义
这里需要注意的是:不同的数据库有不同的语法,上面的json适用于除mysql外的数据库,因为mysql的group by 后面的having里如果要使用字段,譬如上面条件里的view.ZsTestView.test_field
"bracket":[{"equal":{"substr->view.ZsTestView.test_field":["zzz","sss"]}}]
如果要使用这个字段,就必须要在select中存在这个字段(其它数据库没有这个问题),因此上面的json中的fields属性应该将view.ZsTestView.test_field加进去,如下
"fields": ["view.ZsTestView.test_field","view.neibu.ZsTestPOView.test_field","substr->view.neibu.ZsTestPOView.test_field","sum->sumSalary","avg->view.neibu.ZsTestPOView.qian"]
除此之外,一些数据库的差异简单的罗列几个
- 例如postgresql的substr函数是从0开始计算的,而mysql是从1开始计算的
- 同样的更新语句,在postgresql执行不通过,在其它数据库都可以
update zs_test set zs_test.name='lkjhg' where name='ccc' or name='mmm'
- postgresql插入时会校验字段类型,因此在插入时,calculate规则不适用于postgresql
- oracle数据库在使用pagehelper插件时会额外多返回PAGEHELPER_ROW_ID或ROW_ID字段
因此综上所述,不同的数据库需要不同的对待,可将sql先行打印出来再在数据库里执行一下,对比结果便不难得出结论,究竟是json语法错了还是sql语句错了(或许是插件的bug也说不定)
函数这里再贴上一个新的示例,json如下:
{
"rule": {
"name": "zssz",
"actions": [
{
"relations": {"classNames":[
"ZsTestPO","view.ZsTestView"
]},
"name": "test_function",
"params":{"viewParams":{"view.ZsTestView":["kkk","%1%"]}},
"get": {
"relation":"or",
"matches":{
"bracket":[
{"bracket":[{"in":{"substr->ZsTestPO.name":["qqq"]},"like":{"substr->ZsTestPO.name@2asd":["%x%","%p%"]}}]},
{"bracket":[{"equal":{"substr->view.ZsTestView.test_field":["zzz"],"view.ZsTestView.test_field":["sss"]}}]}
]
},
"fields": ["length->name","view.ZsTestView.test_field","substr->ZsTestPO.name","substr->ZsTestPO.name@2asd","substr->ZsTestPO.test_field"],
"function":{
"length":["name"],"concat":{"ZsTestPO.name":["#","vnvn","kook"]},
"substr":{"ZsTestPO.name":["#","1","3"],"ZsTestPO.name@2asd":["#","1","2"],"view.ZsTestView.test_field":["#","1","3"],"ZsTestPO.test_field":["#","1","4"]}
}
}
}
]
}
}
首先函数对应的类为JSRuleFunctions,代码如下:
@Data
@JsonIgnoreProperties(ignoreUnknown=true)
public class JSRuleFunctions {
public String[] length;
public String[] reverse;
public Map decode;
public Map concat;
public Map substr;
public String[] sum;
public String[] min;
public String[] max;
public String[] avg;
public String[] count;
public List getFunctions() {
List fields = new ArrayList();
for (Field f:ZSReflect.getFields(this.getClass(),false)) {
try {
if (ZSObject.sizeIsNotEmpty(f.get(this))) {
fields.add(f);
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new JSRuleException(e);
}
}
return fields;
}
}
- 首先需要在类中定义函数类型,目前对象之中只定义了类中所写的上面的这几个函数,后续会开放更多的函数(其实就是在这里多定义几列的问题)
提示:属性中只存在String[](单体参数)和Map
- 接下来需要在json主体中定义函数变量,如上面的json中的
"function":{
"length":["name"],"concat":{"ZsTestPO.name":["#","vnvn","kook"]},
"substr":{"ZsTestPO.name":["#","1","3"],"ZsTestPO.name@2asd":["#","1","2"],"view.ZsTestView.test_field":["#","1","3"],"ZsTestPO.test_field":["#","1","4"]}
}
这里是定义本次action请求所可能用到的函数,也可能没有用到,所以多写几个也没关系,其中"length":["name"]对应的sql含义是length(name),多体参数"decode":{"ZsTestPO.name":["#","xxx","!!!!!!"]},表示sql语法decode(ZsTestPO.name对应的表字段名,'xxx','!!!!!!'),这里的"#"表示的就是前面key所对应的表的字段名,后面则是多个参数,有几个写几个。
提示:decode函数是oracle的函数,如果你用的是mysql等数据库则无法使用这个函数
- 最后在json主体中引用函数的定义,截取上面的json片段
"matches":{
"bracket":[
{"bracket":[{"in":{"substr->ZsTestPO.name":["qqq"]},"like":{"substr->ZsTestPO.name@2asd":["%x%","%p%"]}}]},
{"bracket":[{"equal":{"substr->view.ZsTestView.test_field":["zzz"],"view.ZsTestView.test_field":["sss"]}}]}
]
},
"fields": ["length->name","view.ZsTestView.test_field","substr->ZsTestPO.name","substr->ZsTestPO.name@2asd","substr->ZsTestPO.test_field"]
这里列举了两个示例,一个是查询条件里使用函数,一个是在查询字段上使用函数,它们的语法都是以函数名开头+指向箭头符号(->)+字段名,这里面的in所使用的函数表示sql语法substr(ZsTestPO.name对应的表字段名,'0','3') in ('qqq'),注意,substr所指向的ZsTestPO.name必须是在上面所示的json主体的function中定义的,如果没有定义则会提示报错
{
"code": "6000",
"msg": "操作失败",
"result": null,
"log": "未定义的表达式:substr->ZsTestPO.name@2asd"
}
再来看看"like":{"substr->ZsTestPO.name@2asd":["%x%","%p%"]}这个语句,前面的都说过了,后面的@2asd代表的是别名的意思,当使用函数对同一个字段进行多次计算时需要加个别名以区分不同的函数指向,这里function中的substr函数定义了ZsTestPO.name以及ZsTestPO.name@2asd两个函数表达式,这两个函数表达式对应的参数不同,第一个是截取3长度的,第二个是截取2长度的,在使用时指向所需的表达式即可转化对应的函数sql,此表达式只针对多体参数的函数,因为单体用不到
最后附上此json最后生成的sql语句:
select length(name) as "length$name",suibian.test_field as "view_ZsTestView-test_field",substr(zs_test.name,'1','3') as "substr$ZsTestPO-name",substr(zs_test.name,'1','2') as "substr$ZsTestPO-name@2asd",substr(zs_test.test_field,'1','4') as "substr$ZsTestPO-sum_test_field" from zs_test,(select * from zs_test_son1 where oh_no='kkk' and id like '%1%') suibian where zs_test.id=suibian.zs_test_id and ((( substr(zs_test.name,'1','3') in ('qqq') or substr(zs_test.name,'1','2') like '%x%' or substr(zs_test.name,'1','2') like '%p%' ) and ( substr(suibian.test_field,'1','3')='zzz' or suibian.test_field='sss')))
总结:函数表达式可作用于查询字段、查询条件、分组、排序等。
视图视图说通俗一点就是一个sql语句而并非是一个表,它可能是由一个表组成也可能是由多个表组成,总之就是一个查询结果集,如果这里不明白的可以去查找一些视图资料
说明:
早在第一篇介绍的时候就提到过使用引擎必须要配置一些类似mybatis映射数据库表的po类,这里贴上测试类代码加以说明
@JSRuleRelationTable(tableName="suibian",view="select # from zs_test_son1 where oh_no=? and id like ?")
public class ZsTestView {
@JSRuleRelationField(pk=true)
public String id;
@JSRuleRelationField(fieldName= "zs_test_id",fk="ZsTestPO")
public String zs_test_id;
@JSRuleRelationField(fieldName= "zs_test_id",fk="view.neibu.ZsTestPOView")
public String zs_test_id2;
@JSRuleRelationField(fieldName= "oh_no")
public String ohNo;
public double qian;
public String test_field;
@JSRuleRelationField(fieldName= "zs_test_son2_id")
public String zs_test_son2_id;
}
这里我们只看上面的@JSRuleRelationTable注解,这里的tableName是为视图起的别名(如果不是视图这里需要写表的名字),后面的view是视图的查询sql,可以单表也可以多个表,里面的#符号表示字段动态传参,参数取决于json主体配置(postman中的json),如果你不需要动态传参,这里直接写sql原语法select * from table就可以了,系统会把这个sql作为这个对象ZsTestView进行关联处理,对应的sql含义进行对比,如下:
表:select * from tableName
视图:select * from (select * from xx) suibian
后面的?符号表示的是查询参数动态传参,参数取决于json主体配置,这里也可以直接写死参数是什么。
先附上po结构图:
视图.png
接下来是一个足够复杂的json主体
{
"rule": {
"name": "test_view_left",
"actions": [
{
"name": "poAction",
"relations": {"classNames":["ZsTestPO"]},
"get": {
"isExecute":false,
"fields":["test_field"]
}
},
{
"relations": {"classNames":[
"ZsTestPO","view.ZSTestUpdateSon","view.ZsTestView"
],"type":"left","main":"ZsTestPO"},
"params":{
"viewParams":{"view.ZsTestView":["kkk","%1%"]},
"viewFields":{"view.ZsTestView":[],"view.ZSTestUpdateSon":[],"view.neibu.ZsTestPOView":["name"]}
},
"name": "view_left",
"get": {
"relation":"or",
"fields":[],
"matches":{
"in":{"ZsTestPO.name":"view->view.neibu.ZsTestPOView",
"ZsTestPO.test_field":"action->poAction"}
},
"order":{"ZsTestPO.id":"asc"},
"page":{"pageNum":"1","pageSize":"10"}
}
}
]
}
}
上面提到的动态传参,这里对应的属性是
"params":{
"viewParams":{"view.ZsTestView":["kkk","%1%"]},
"viewFields":{"view.ZsTestView":[],"view.ZSTestUpdateSon":[],"view.neibu.ZsTestPOView":["name"]}
}
viewParams对应的是?符号,里面的参数个数应该与视图中?符号的个数相同,按照顺序进行赋值
viewFields对应的是#符号,如果没有定义对应视图的动态字段数组,或字段数组是个空[]数组,则表示的动态字段是所有字段
这里值得注意的是:view.neibu.ZsTestPOView并没有出现在classNames中,但在in查询的时候却用到了这个动态视图,因此说明params属性中定义的并非仅仅是classNames中的类,而是所有通过po类配置过的视图类,这个in所用到的->指向符号表达的含义是指向性操作,也就是sql语法 in (view.neibu.ZsTestPOView对应的视图sql),这样一个in (子查询)就完成了。
生成的sql:
select zs_test.remark as "ZsTestPO-remark",zs_test.test_field as "ZsTestPO-sum_test_field",zs_test.birth_day as "ZsTestPO-birthDay",zs_test.id as "ZsTestPO-id",zs_test.bonus as "ZsTestPO-bonus",zs_test.name as "ZsTestPO-name",zs_test.salary as "ZsTestPO-salary",zs_test.qian as "ZsTestPO-qian",zs_test.create_date as "ZsTestPO-create_date",zs_test.sum1_salary as "ZsTestPO-lbv_salary",zs_test_son1_son1.zs_test_id as "view_ZSTestUpdateSon-zs_test_id",zs_test_son1_son1.zs_test_son1_id as "view_ZSTestUpdateSon-zs_test_son1_id2",zs_test_son1_son1.zs_test_son1_id as "view_ZSTestUpdateSon-zs_test_son1_id",zs_test_son1_son1.oh_oh_no as "view_ZSTestUpdateSon-wolegequ",zs_test_son1_son1.id as "view_ZSTestUpdateSon-id",zs_test_son1_son1.zs_test_id as "view_ZSTestUpdateSon-zs_test_id2",suibian.zs_test_id as "view_ZsTestView-zs_test_id",suibian.oh_no as "view_ZsTestView-ohNo",suibian.zs_test_id as "view_ZsTestView-zs_test_id2",suibian.id as "view_ZsTestView-id",suibian.qian as "view_ZsTestView-qian",suibian.test_field as "view_ZsTestView-test_field",suibian.zs_test_son2_id as "view_ZsTestView-zs_test_son2_id" from zs_test left join (select * from zs_test_son1_son1) zs_test_son1_son1 on zs_test.id=zs_test_son1_son1.zs_test_id left join (select * from zs_test_son1 where oh_no='kkk' and id like '%1%') suibian on zs_test.id=suibian.zs_test_id where zs_test.name in (select name from zs_test) or zs_test.test_field in (select test_field as "sum_test_field" from zs_test) order by zs_test.id asc
生成的sql有点长,因为没有指定查询字段fields属性的缘故,默认就是查询所有字段
指向性查询还是上面的json,我们先来看看第一个action,这个action非常简单就是select id as id from zs_test,查询ZsTestPO对应的表id字段的所有数据,不过这里的属性"isExecute":false,表示这个action并不执行,它只为第二个action做指向性子查询工作。
我们再看下面的json片段
"matches":{
"in":{"ZsTestPO.name":"view->view.neibu.ZsTestPOView",
"ZsTestPO.test_field":"action->poAction"}
}
这里第二行表达式action->poAction指向了上面第一个action的名字,这行表达式的意思对应的sql为zs_test_son1_son1.zs_test_id in (select id as id from zs_test),也就是将第一个action查询的sql作为子查询放进了in的参数里,这与先前的in (视图) 子查询是一个意思,指向性操作目前只有view以及action两种方式
注意:作为子查询的action一定要写在前面
总结:高级查询其实可以满足绝大部分应用场景了,如果有极个别的特殊需求也可以通过扩展接口来变相实现,这些自定义开发规则将在后面的介绍中详细说明



