1.需求

测试数据库使用Greenplum,生产库使用GBase

普通表:存储客户数据,千万级别,结构如下

stat_date代表日期;user_id代表用户id;serial_number代表手机号;A_I72和A_I59是标签字段。

+---------------+--------------+------+-----+---------+-------+
| Field         | Type         | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| stat_date     | varchar(255) | YES  |     | NULL    |       |
| user_id       | varchar(255) | YES  |     | NULL    |       |
| serial_number | varchar(255) | YES  |     | NULL    |       |
| A_I72         | varchar(255) | YES  |     | NULL    |       |
| A_I59         | varchar(255) | YES  |     | NULL    |       |
+---------------+--------------+------+-----+---------+-------+

字典表:记录表的字段的位置。

+-------------+---------+------+-----+---------+-------+
| Field       | Type    | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id          | int(11) | YES  |     | NULL    |       |
| column_name | text    | YES  |     | NULL    |       |
| table_name  | text    | YES  |     | NULL    |       |
+-------------+---------+------+-----+---------+-------+

根据优先级解析如下表达式,返回结果集(String类型手机号的List),解析式中出现的字段不一定在同一个表中,'&'表示求交集,'|'表示求并集。

(A_I72=1|A_I59=0)&serial_number=1369567xxxx

再将结果集放入Redis,给同事使用,千万级的数据预估5GB以上,要注意内存的清理(服务器内存配置64GB)

2.知识准备

我尝试过将形如'(A_I72=1|A_I59=0)&serial_number=1369567xxxx'的表达式放在数据库中查询,我能想到的就是用JOIN,因为字典表的存在,会出现子查询,效率低下;存储过程因为不方便调试和版本迭代,故弃用,最终采用Java来解析规则表达式

我发现这种规则表达式解析类似于运算符优先级的解析,模仿该博主代码,将其改造成业务代码

2.1 运算符优先级解析

先弄懂运算符优先级解析的原理,再继续逻辑

    /** 数字栈:用于存储表达式中的各个数字 */private Stack<Long> numberStack = null;/** 符号栈:用于存储运算符和括号 */private Stack<Character> symbolStack = null;

该博主用栈存储数字和符号,栈的特性众所周知,后进先出,为什么使用栈存储符号,稍后讲到

接下来看判断运算符优先级的方法

/*** 比较符号优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false* ✔*/private boolean comparePri(char symbol) {if (symbolStack.empty()) { // 空栈返回truereturn true;}// 符号优先级说明(从高到低):// 第1级: (// 第2级: * /// 第3级: + -// 第4级: )char top = (char) symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈if (top == '(') {return true;}// 比较优先级switch (symbol) { case '(': // 优先级最高return true;case '*': {if (top == '+' || top == '-') // 优先级比+和-高return true;elsereturn false;}case '/': {if (top == '+' || top == '-') // 优先级比+和-高return true;elsereturn false;}case '+':return false;case '-':return false;case ')': // 优先级最低return false;case '=': // 结束符return false;default:break;}return true;}

comparePri方法比较运算符优先级,优先级降序排序如下:'(','* /','+ -',')',优先级在前的会先return true,结束switch,优先级低的元素会在栈底,优先级高的元素在栈顶参加运算,直到栈底上面的元素运算完毕,才轮到栈底元素

接下来,看判断表达式是否标准的方法

    private boolean isStandard(String numStr) {if (numStr == null || numStr.isEmpty()) // 表达式不能为空return false;//这里不使用栈也可以Stack<Character> stack = new Stack<Character>(); // 用来保存括号,检查左右括号是否匹配boolean b = false; // 用来标记'='符号是否存在多个for (int i = 0; i < numStr.length(); i++) {char n = numStr.charAt(i);// 判断字符是否合法//n+""将char转化成Stringif (!(isNumber(n) || "(".equals(n + "") || ")".equals(n + "")|| "+".equals(n + "") || "-".equals(n + "")|| "*".equals(n + "") || "/".equals(n + "")|| "=".equals(n + ""))) {return false;}// 将左括号压栈,用来给后面的右括号进行匹配if ("(".equals(n + "")) {stack.push(n);}if (")".equals(n + "")) { // 匹配括号//将左括号出栈,说明有右括号匹配到左括号if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配return false;}// 检查是否有多个'='号// 允许末尾出现一个'='号if ("=".equals(n + "")) {if (b)return false;b = true;}}// 可能会有缺少右括号的情况if (!stack.isEmpty())return false;// 检查'='号是否不在末尾if (!("=".equals(numStr.charAt(numStr.length() - 1) + "")))return false;return true;}

isStandard方法匹配左括号,检测表达式末尾是否含有0个或1个'='

现在看核心的计算方法

/*** 解析并计算四则运算表达式(含括号),返回计算结果* ✔* @param numStr*            算术表达式(含括号)*/public long caculate(String numStr) {numStr = removeStrSpace(numStr); // 去除空格// 如果算术表达式尾部没有‘=’号,则在尾部添加‘=’,表示结束符if (numStr.length() > 1 && !"=".equals(numStr.charAt(numStr.length() - 1) + "")) {numStr += "=";}// 检查表达式是否合法if (!isStandard(numStr)) {System.err.println("错误:算术表达式有误!");return 0;}// 初始化栈numberStack = new Stack<Long>();symbolStack = new Stack<Character>();// 用于缓存数字,因为数字可能是多位的StringBuffer temp = new StringBuffer();// 从表达式的第一个字符开始处理for (int i = 0; i < numStr.length(); i++) {char ch = numStr.charAt(i); // 获取一个字符if (isNumber(ch)) { // 若当前字符是数字temp.append(ch); // 加入到数字缓存中} else { // 非数字的情况String tempStr = temp.toString(); // 将数字缓存转为字符串if (!tempStr.isEmpty()) {long num = Long.parseLong(tempStr); // 将数字字符串转为长整型数numberStack.push(num); // 将数字压栈temp = new StringBuffer(); // 重置数字缓存
                }// 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把计算前面计算出来while (!comparePri(ch) && !symbolStack.empty()) {long b = numberStack.pop(); // 出栈,取出数字,后进先出long a = numberStack.pop();// 取出运算符进行相应运算,并把结果压栈进行下一次运算switch ((char) symbolStack.pop()) {case '+':numberStack.push(a + b);break;case '-':numberStack.push(a - b);break;case '*':numberStack.push(a * b);break;case '/':numberStack.push(a / b);break;default:break;}} // while循环结束if (ch != '=') {// 优先级高的符号入栈symbolStack.push(new Character(ch));if (ch == ')') {// 去掉左右括号
                        symbolStack.pop();symbolStack.pop();}}}} // for循环结束return numberStack.pop(); // 返回计算结果}

运算好的结果会放在numStack栈底,后进入的数字在栈顶等待运算。我推荐园友打断点调试此段代码,方便理解

完整代码戳这里

2.2 流程分析

我以‘3*5+2*10’为例分析运算符解析的流程

'*'运算符先入栈

3入栈,5再入栈

此时numStack中5出栈,3出栈,symbolStack中'*'出栈,进入swtich

完成乘法运算,将结果入栈

此时栈的情况如下

现在symbolStack入栈'+',numStack入栈2,此时读取到了'*',根据comparePri方法,'*'在'+'前面,乘号具有更高的优先级,return true,此时入栈'*'。这里我们看到了栈在此处的妙用,低优先级的运算符在栈底等待运算,栈顶存放高优先级运算符;

然后numStack入栈10,栈的情况如下

numStack中10和2出栈,symbolStack中'*'出栈,再进入switch,运算2*10,将结果20入栈numStack

此时栈的情况如下

最后一步,numStack中20,15相继出栈,symbolStack中'+'出栈,将结果35入栈numStack,此时symbolStack为空,我们返回numStack.pop(),numStack出栈,此时numStack也为空

3.解决方案

我尝试过将形如'(A_I72=1|A_I59=0)&serial_number=1369567xxxx'的表达式放在数据库中查询,我能想到的就是用JOIN,因为字典表的存在,会出现子查询,效率低下;存储过程因为不方便调试和版本迭代,故弃用,最终采用Java来解析规则表达式

我发现这种规则表达式解析类似于运算符优先级的解析,模仿该博主代码,将其改造成业务代码

3.1 思路1

优先级排序如下:'(' > '&' > '|' > ')'

优先级的解析我们已经做好,现在解决业务部分

symbolStack存储符号,numStack存储结果集。这里我们不能像刚才数字运算那么做,因为条件(id<=5)和结果集不能合并。我们创建一个LinkedHashMap<String, Set<Map>>存储结果集,并引入UUID,根据UUID前缀来判断是否是结果集;前缀有"UUID"则是结果集,否则是普通条件,调用JDBC工具去数据库查询结果集。总共有四种情况:a和b都含有'UUID_',a和b都不含有"UUID_",a含有"UUID_,b不含",b含有"UUID_,a不含".

运算完毕,清除map,求交集使用retainAll方法,并集使用addAll方法

package com.advance.analysis;
import com.advance.JDBC.Connect_GBase;
import com.advance.Redis.RedisUtil;
import org.apache.log4j.Logger;
import org.testng.annotations.Test;import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;/*** @Author: 谷天乐* @Date: 2019/2/18 19:32* @Description: 修正版*/
public class Analysis {public String UUID_PREFIX = "UUID_";public String UUID_PREFIX_AND = "UUID_And_";public String UUID_PREFIX_OR = "UUID_Or_";private static Logger logger = Logger.getLogger(Analysis.class);/*** 条件栈:用于存储表达式中的各个条件*/private Stack<String> numberStack = null;/*** 符号栈:用于存储运算符和括号*/private Stack<Character> symbolStack = null;/*** 解析并计算规则表达式(含括号),返回计算结果** @param numStr 规则表达式(含括号)*/public LinkedHashMap<String, Set<Map>> caculate(String numStr) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {numStr = removeStrSpace(numStr); // 去除空格// 如果规则表达式尾部没有‘#’号,则在尾部添加‘#’,表示结束符if (numStr.length() > 1 && !"#".equals(numStr.charAt(numStr.length() - 1) + "")) {numStr += "#";}// 检查表达式是否合法if (!isStandard(numStr)) {System.err.println("错误:规则表达式有误!");return null;}// 初始化栈numberStack = new Stack<String>();symbolStack = new Stack<Character>();// 用于缓存条件,因为条件可能是多位的StringBuffer temp = new StringBuffer();//用于缓存执行sql的临时结果集//有序MapLinkedHashMap<String, Set<Map>> tempMap = new LinkedHashMap<>();// 从表达式的第一个字符开始处理for (int i = 0; i < numStr.length(); i++) {char ch = numStr.charAt(i); // 获取一个字符if (!isSign(ch)) { // 若当前字符不是符号temp.append(ch); // 加入到条件缓存中} else { // 非数字的情况String tempStr = temp.toString(); // 将条件缓存转为字符串if (!tempStr.isEmpty()) {numberStack.push(tempStr); // 将条件压栈temp = new StringBuffer(); // 重置条件缓存
                }// 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把前面计算出来while (!comparePri(ch) && !symbolStack.empty()) {String b = numberStack.pop(); // 出栈,取出条件,后进先出String a = numberStack.pop();// 取出运算符进行相应运算,并把结果压栈进行下一次运算switch ((char) symbolStack.pop()) {case '&':if(a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){bothHaveUUID(tempMap,"and");}else if(!a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){optionalUUID(tempMap,a,UUID_PREFIX_AND,"and");}else if(a.contains(UUID_PREFIX)&&!b.contains(UUID_PREFIX)){optionalUUID(tempMap,b,UUID_PREFIX_AND,"and");} else {bothDontHaveUUID(a,b,tempMap,UUID_PREFIX_AND,"and");}break;case '|'://根据列名查表名if(a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){bothHaveUUID(tempMap,"or");}else if(!a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){optionalUUID(tempMap,a,UUID_PREFIX_OR,"or");}else if(a.contains(UUID_PREFIX)&&!b.contains(UUID_PREFIX)){optionalUUID(tempMap,b,UUID_PREFIX_OR,"or");}else {bothDontHaveUUID(a,b,tempMap,UUID_PREFIX_OR,"or");}break;default:break;}} // while循环结束if (ch != '#') {symbolStack.push(new Character(ch)); // 符号入栈if (ch == ')') { // 去括号
                        symbolStack.pop();symbolStack.pop();}}}} // for循环结束//使用entrySet会@throws ConcurrentModificationException//清缓存:去掉Map中无用的Set,计算过程中的Set在a含有'UUID',b不含有'UUID',//b含有'UUID',a不含有'UUID'的情况被清除//清除最终运算结果,即a和b都含有'UUID',a和b都不含有'UUID'的情况Set<Map.Entry<String, Set<Map>>> entries = tempMap.entrySet();Iterator<Map.Entry<String, Set<Map>>> iteratorMap = entries.iterator();while (iteratorMap.hasNext()){Map.Entry<String, Set<Map>> next = iteratorMap.next();if(getTail(tempMap).getKey()!=next.getKey())iteratorMap.remove();}return tempMap; // 返回计算结果
    }/*** 去除字符串中的所有空格*/private String removeStrSpace(String str) {return str != null ? str.replaceAll(" ", "") : "";}/*** 检查算术表达式的基本合法性,符合返回true,否则false*/private boolean isStandard(String numStr) {if (numStr == null || numStr.isEmpty()) // 表达式不能为空return false;Stack<Character> stack = new Stack<Character>(); // 用来保存括号,检查左右括号是否匹配boolean b = false; // 用来标记'#'符号是否存在多个for (int i = 0; i < numStr.length(); i++) {char n = numStr.charAt(i);// 判断字符是否合法if ( !(isChar(n) || isNumber(n) || "(".equals(n + "") || ")".equals(n + "")|| ">".equals(n + "") || "<".equals(n + "")|| "&".equals(n + "") || "|".equals(n + "")|| "=".equals(n + "") || "#".equals(n + "")|| "_".equals(n + "") || "!".equals(n + "")|| "-".equals(n + "") || ".".equals(n + "")|| "'".equals(n + ""))) {return false;}// 将左括号压栈,用来给后面的右括号进行匹配if ("(".equals(n + "")) {stack.push(n);}if (")".equals(n + "")) { // 匹配括号if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配return false;}// 检查是否有多个'#'号if ("#".equals(n + "")) {if (b)return false;b = true;}}// 可能会有缺少右括号的情况if (!stack.isEmpty())return false;// 检查'#'号是否不在末尾if (!("#".equals(numStr.charAt(numStr.length() - 1) + "")))return false;return true;}/*** 判断一个字符是否是 ( ) $ |等符号*/private boolean isSign(char c) {if (c == '(' || c == ')' || c == '&' || c == '|' || c == '#') {return true;} else {return false;}}//提取列名private String extractColomnName(String str){//去掉比较运算符if(str.indexOf("<")!=-1)str = str.substring(0,str.indexOf("<"));if(str.indexOf("=")!=-1)str = str.substring(0,str.indexOf("="));if(str.indexOf(">")!=-1)str = str.substring(0,str.indexOf(">"));return str;}/*** 判断一个字符是否是 a-z  A-Z 之间的字母*/private boolean isChar(char c) {if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {return true;} else {return false;}}/*** 判断字符是否是0-9的数字*/private boolean isNumber(char num) {if (num >= '0' && num <= '9')return true;return false;}/*** 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false*/private boolean comparePri(char symbol) {if (symbolStack.empty()) { // 空栈返回turereturn true;}// 符号优先级说明(从高到低):// 第1级: (// 第2级: $// 第3级: |// 第4级: )char top = (char) symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈if (top == '(') {return true;}// 比较优先级switch (symbol) {case '(': // 优先级最高return true;case '&': {if (top == '|') // 优先级比|高return true;elsereturn false;}case '|': {return false;}case ')': // 优先级最低return false;case '#': // 结束符return false;default:break;}return true;}//List转成Stringpublic String listToString(List list, char separator) {StringBuilder sb = new StringBuilder();for (int i = 0; i < list.size(); i++) {sb.append(list.get(i)).append(separator);}return sb.toString().substring(0, sb.toString().length() - 1);}/*JDK1.8    public static <K, V> Map.Entry<K, V> getTailByReflection(LinkedHashMap<K, V> map)throws NoSuchFieldException, IllegalAccessException {Field tail = map.getClass().getDeclaredField("tail");tail.setAccessible(true);return (Map.Entry<K, V>) tail.get(map);}*/public <K, V> Map.Entry<K, V> getTail(LinkedHashMap<K, V> map) {Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator();Map.Entry<K, V> tail = null;while (iterator.hasNext()) {tail = iterator.next();}return tail;}/*** @Author 谷天乐* @Description a和b都含有'UUID_'* @Date 2019/2/25 11:25* @Param [tempMap, andor]* @return void**/public void bothHaveUUID(LinkedHashMap<String, Set<Map>> tempMap,String andor){int num = 0;List keys = new ArrayList();for (Map.Entry<String, Set<Map>> entry : tempMap.entrySet()) {keys.add(entry.getKey());num++;}Set resultAndSet1 = tempMap.get(keys.get(num-2));Set resultAndSet2 = tempMap.get(keys.get(num-1));//求交集if(andor.equals("and"))resultAndSet1.retainAll(resultAndSet2);if(andor.equals("or"))resultAndSet1.addAll(resultAndSet2);String andkey = String.valueOf(UUID.randomUUID());andkey = "UUID_And_" + andkey.toString().replace("-", "");numberStack.push(andkey);tempMap.put(andkey, resultAndSet1);}/*** @Author 谷天乐* @Description a和b都不含有"UUID_"* @Date 2019/2/25 12:37* @Param [conditionA, conditionB, tempMap, UUID_prefix, andor]* @return void**/public void bothDontHaveUUID(String conditionA,String conditionB,LinkedHashMap<String, Set<Map>> tempMap,String UUID_prefix,String andor) throws SQLException, ClassNotFoundException {String aOrColumnName = extractColomnName(conditionA);String bOrColumnName = extractColomnName(conditionB);StringBuffer getOrTableSqlA = new StringBuffer("select table_name from table_dictionary ");getOrTableSqlA.append("where column_name=");getOrTableSqlA.append("\'");getOrTableSqlA.append(aOrColumnName);getOrTableSqlA.append("\'");ResultSet tableOrNameRSA = Connect_GBase.query(getOrTableSqlA.toString());List tableOrListA = Connect_GBase.resultSetToList(tableOrNameRSA, "table_name");String getOrTableSqlB = "select table_name from table_dictionary " +"where column_name=" + "\'" + bOrColumnName + "\'";ResultSet tableOrNameRSB = Connect_GBase.query(getOrTableSqlB);List tableOrListB = Connect_GBase.resultSetToList(tableOrNameRSB, "table_name");StringBuffer selectOrSql1 = new StringBuffer("select * from ");if(tableOrListA.size()==0)throw new RuntimeException("字段不存在");selectOrSql1.append(listToString(tableOrListA, ' '));selectOrSql1.append(" where ");selectOrSql1.append(conditionA);ResultSet rsOr1 = Connect_GBase.query(selectOrSql1.toString());StringBuffer selectOrSql2 = new StringBuffer("select * from ");if(tableOrListB.size()==0)throw new RuntimeException("字段不存在");selectOrSql2.append(listToString(tableOrListB, ' '));selectOrSql2.append(" where ");selectOrSql2.append(conditionB);ResultSet rsOr2 = Connect_GBase.query(selectOrSql2.toString());List resultOrList1 = Connect_GBase.resultSetToList(rsOr1, null);List resultOrList2 = Connect_GBase.resultSetToList(rsOr2, null);Set resultOrSet1 = new HashSet(resultOrList1);Set resultOrSet2 = new HashSet(resultOrList2);//求并集if(andor.equals("or"))resultOrSet1.addAll(resultOrSet2);if(andor.equals("and"))resultOrSet1.retainAll(resultOrSet2);//对每个条件进行执行,把执行出来的结果进行求并集操作,把结果放到栈中String orkey = String.valueOf(UUID.randomUUID());orkey = UUID_prefix + orkey.toString().replace("-", "");numberStack.push(orkey);tempMap.put(orkey, resultOrSet1);}/** @Author 谷天乐* @Description Or 运算如下情况:* a含有'UU_ID',b不含有'UUID';b含有'UU_ID',a不含有'UUID'* @Date 2019/2/20 9:34* @Param [tempMap, condition]* @return void**/public void optionalUUID(LinkedHashMap<String, Set<Map>> tempMap,String condition,String UUID_prefix,String andor) throws SQLException, ClassNotFoundException {int num = 0;List keys = new ArrayList();for (Map.Entry<String, Set<Map>> entry : tempMap.entrySet()) {keys.add(entry.getKey());num++;}Set resultOrSet1 = tempMap.get(keys.get(num-1));//根据列名查表名String aOrColumnName = extractColomnName(condition);StringBuffer getOrTableSqlA = new StringBuffer("select table_name from table_dictionary ");getOrTableSqlA.append("where column_name=");getOrTableSqlA.append("\'");getOrTableSqlA.append(aOrColumnName);getOrTableSqlA.append("\'");ResultSet tableOrNameRSA = Connect_GBase.query(getOrTableSqlA.toString());List tableOrListA = Connect_GBase.resultSetToList(tableOrNameRSA, "table_name");if(tableOrListA.size()==0)throw new RuntimeException("字段不存在");StringBuffer selectOrSql2 = new StringBuffer("select * from ");selectOrSql2.append(listToString(tableOrListA, ' '));selectOrSql2.append(" where ");selectOrSql2.append(condition);ResultSet rsOr2 = Connect_GBase.query(selectOrSql2.toString());List resultOrList2 = Connect_GBase.resultSetToList(rsOr2, null);Set resultOrSet2 = new HashSet(resultOrList2);//求并集if (andor.equals("or"))resultOrSet1.addAll(resultOrSet2);if  (andor.equals("and"))resultOrSet1.retainAll(resultOrSet2);//移除使用过的SettempMap.remove(keys.get(num-1));String orkey = String.valueOf(UUID.randomUUID());orkey = UUID_prefix + orkey.toString().replace("-", "");//对每个条件进行执行,把执行出来的结果进行求交集操作,把结果放到栈中
        numberStack.push(orkey);//--1、把a和b解析成sql并执行获取结果集,使用UUID生成key放入tempMap中,
        tempMap.put(orkey, resultOrSet1);}//根据Map获取指定key的valuepublic Set getSpecifiedListFromMap(LinkedHashMap<String, Set<Map>> resultMap, String key,String id) throws Exception{Set<Object> set = new HashSet<>();for (Map.Entry<String, Set<Map>> entry : resultMap.entrySet()) {new RedisUtil().del(id);for(Map maplist : entry.getValue()){set.add(maplist.get(key));new RedisUtil().lpush(id,String.valueOf(maplist.get(key)));}}logger.debug(Analysis.class+"Jedis插入数据成功");return set;}@Testpublic void test() throws Exception {//a>=20 & (b<=40|c!=1) | d//id<=5&(id<=3|id<=5)|(amt=45.00|id<=6)//id<=5|id<=5&amt=45.00|id<=6//(date='2013-07-22'|date='2013-02-22')&id<=7&phone_number=18895358020String num = "(A_I72=1&A_I59=0)"; // 默认的算式Analysis analysis = new Analysis();LinkedHashMap<String, Set<Map>> resultMap = analysis.caculate(num);getSpecifiedListFromMap(resultMap,"serial_number","ssss1111");logger.debug(resultMap);}
}

3.2 思路2

先算结果集,再判断运算条件是否含有“UUID_”前缀,运算过程中清理map

这个是同事写的版本,代码少139行

package com.advance.analysis;import com.advance.Engine_and_Message.util.NonUtil;
import com.advance.JDBC.Connect_Greenplum;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;import java.sql.ResultSet;
import java.util.*;/*** @Title:  Analysis_zk.java* @Description: 规则解析工具* @author: zhangkuo* @date:   2019-2-18 上午9:03:28*/
public class Analysis_zk {private static Logger logger = LogManager.getLogger(Analysis.class);/*** 条件栈:用于存储表达式中的各个条件*/private Stack<String> numberStack = null;/*** 符号栈:用于存储运算符和括号*/private Stack<Character> symbolStack = null;/**** @Title: caculate* @Description: 解析并计算规则表达式(含括号),返回计算结果* @Author: zhangkuo* @param: @param numStr 规则表达式(含括号)* @param: @return* @param: @throws Exception* @return: LinkedHashMap<String,Set<String>>* @throws*/public Set<String> caculate(String numStr) throws Exception {numStr = removeStrSpace(numStr); // 去除空格// 如果规则表达式尾部没有‘#’号,则在尾部添加‘#’,表示结束符if (numStr.length() > 1 && !"#".equals(numStr.charAt(numStr.length() - 1) + "")) {numStr += "#";}// 检查表达式是否合法if (!isStandard(numStr)) {logger.debug("错误:规则表达式有误!");return null;}// 初始化栈numberStack = new Stack<String>();symbolStack = new Stack<Character>();// 用于缓存条件,因为条件可能是多位的StringBuffer temp = new StringBuffer();//用于缓存执行sql的临时结果集//有序MapHashMap<String, Set<String>> tempMap = new HashMap<>();// 从表达式的第一个字符开始处理for (int i = 0; i < numStr.length(); i++) {char ch = numStr.charAt(i); // 获取一个字符if (!isSign(ch)) { // 若当前字符不是符号temp.append(ch); // 加入到条件缓存中} else { // 非数字的情况String tempStr = temp.toString(); // 将条件缓存转为字符串if (!tempStr.isEmpty()) {numberStack.push(tempStr); // 将条件压栈temp = new StringBuffer(); // 重置条件缓存
                }// 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把前面计算出来while (!comparePri(ch) && !symbolStack.empty()) {String b = numberStack.pop(); // 出栈,取出条件,后进先出String a = numberStack.pop();// 取出运算符进行相应运算,并把结果压栈进行下一次运算switch ((char) symbolStack.pop()) {case '&':Set<String> set1 = this.calculator(b,tempMap);Set<String> set2 = this.calculator(a,tempMap);String key = "UUID_"+UUID.randomUUID();set1.retainAll(set2);tempMap.put(key, set1);numberStack.push(key);if(a.contains("UUID_")){tempMap.remove(a);}if(b.contains("UUID_")){tempMap.remove(b);}break;case '|':Set<String> set3 = this.calculator(b,tempMap);Set<String> set4 = this.calculator(a,tempMap);String keyOr = "UUID_"+ UUID.randomUUID();set3.addAll(set4);tempMap.put(keyOr, set3);numberStack.push(keyOr);if(a.contains("UUID_")){tempMap.remove(a);}if(b.contains("UUID_")){tempMap.remove(b);}break;default:break;}} // while循环结束if (ch != '#') {symbolStack.push(new Character(ch)); // 符号入栈if (ch == ')') { // 去括号
                        symbolStack.pop();symbolStack.pop();}}}} // for循环结束return tempMap.get(numberStack.pop().toString()); // 返回计算结果
    }//根据传入的字符串进行运算并返回结果集public Set<String> calculator(String resultset,Map<String,Set<String>> tempMap) throws Exception{Set<String> tempSet;if(resultset.contains("UUID_")){tempSet =  tempMap.get(resultset);}else{//根据列名查表名String columnName = extractColomnName(resultset);StringBuffer getTableName = new StringBuffer("select table_name from table_dictionary ");getTableName.append("where column_name=");getTableName.append("\'");getTableName.append(columnName);getTableName.append("\'");ResultSet tableName = Connect_Greenplum.query(getTableName.toString());List tableList = Connect_Greenplum.resultSetToList(tableName, "table_name");if(NonUtil.isNon(tableList)){throw new RuntimeException("字段不存在");}StringBuffer queryResult = new StringBuffer("select * from ");queryResult.append(listToString(tableList, ' '));queryResult.append(" where ");queryResult.append(resultset);ResultSet result = Connect_Greenplum.query(queryResult.toString());List resultList = Connect_Greenplum.resultSetToList(result, null);tempSet = new HashSet(resultList);}return tempSet;}/*** 去除字符串中的所有空格*/private String removeStrSpace(String str) {return str != null ? str.replaceAll(" ", "") : "";}/*** 检查算术表达式的基本合法性,符合返回true,否则false*/private boolean isStandard(String numStr) {if (numStr == null || numStr.isEmpty()) // 表达式不能为空return false;Stack<Character> stack = new Stack<Character>(); // 用来保存括号,检查左右括号是否匹配boolean b = false; // 用来标记'#'符号是否存在多个for (int i = 0; i < numStr.length(); i++) {char n = numStr.charAt(i);// 判断字符是否合法if ( !(isChar(n) || isNumber(n) || "(".equals(n + "") || ")".equals(n + "")|| ">".equals(n + "") || "<".equals(n + "")|| "&".equals(n + "") || "|".equals(n + "")|| "=".equals(n + "") || "#".equals(n + "")|| "_".equals(n + "") || "!".equals(n + "")|| "-".equals(n + "") || ".".equals(n + "")|| "'".equals(n + ""))) {return false;}// 将左括号压栈,用来给后面的右括号进行匹配if ("(".equals(n + "")) {stack.push(n);}if (")".equals(n + "")) { // 匹配括号if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配return false;}// 检查是否有多个'#'号if ("#".equals(n + "")) {if (b)return false;b = true;}}// 可能会有缺少右括号的情况if (!stack.isEmpty())return false;// 检查'#'号是否不在末尾if (!("#".equals(numStr.charAt(numStr.length() - 1) + "")))return false;return true;}/*** 判断一个字符是否是 ( ) $ |等符号*/private boolean isSign(char c) {if (c == '(' || c == ')' || c == '&' || c == '|' || c == '#') {return true;} else {return false;}}//提取列名private String extractColomnName(String str){//去掉比较运算符if(str.indexOf("<")!=-1)str = str.substring(0,str.indexOf("<"));if(str.indexOf("=")!=-1)str = str.substring(0,str.indexOf("="));if(str.indexOf(">")!=-1)str = str.substring(0,str.indexOf(">"));return str;}/*** 判断一个字符是否是 a-z  A-Z 之间的字母*/private boolean isChar(char c) {if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {return true;} else {return false;}}/*** 判断字符是否是0-9的数字*/private boolean isNumber(char num) {if (num >= '0' && num <= '9')return true;return false;}/*** 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false*/private boolean comparePri(char symbol) {if (symbolStack.empty()) { // 空栈返回turereturn true;}// 符号优先级说明(从高到低):// 第1级: (// 第2级: $// 第3级: |// 第4级: )char top = symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈if (top == '(') {return true;}// 比较优先级switch (symbol) {case '(': // 优先级最高return true;case '&': {if (top == '|') // 优先级比|高return true;elsereturn false;}case '|': {return false;}case ')': // 优先级最低return false;case '#': // 结束符return false;default:break;}return true;}//List转成Stringpublic String listToString(List list, char separator) {StringBuilder sb = new StringBuilder();for (int i = 0; i < list.size(); i++) {sb.append(list.get(i)).append(separator);}return sb.toString().substring(0, sb.toString().length() - 1);}// 测试public static void main(String args[]) throws Exception {//a>=20 & (b<=40|c!=1) | d//id<=5&(id<=3|id<=5)|(amt=45.00|id<=6)//id<=5|id<=5&amt=45.00|id<=6String num = "(date='2013-02-22'&date='2013-03-01')|id<=3&amt>=500.00"; // 默认的算式Analysis_zk analysis = new Analysis_zk();Set<String> resultMap = analysis.caculate(num);logger.debug(resultMap);}
}

所需要的依赖包在这里

转载于:https://www.cnblogs.com/Java-Starter/p/10438807.html

根据运算符优先级解析SQL规则表达式相关推荐

  1. 万物之始正则表达式全解析三部曲(中篇)-正则表达式运算符优先级及匹配规则

    前言 各位小伙伴大家好,接下来几天时间,我会从多个角度对正则表达式进行系统阐述,让你了解正则表达式的前世今生. 该系列文章上篇 万物之始正则表达式全解析三部曲(上篇)-正则表达式基础知识及语法 以下是 ...

  2. php中的逻辑运算符优先级,PHP运算符优先级 运算符分类

    运算符 运算符是可以通过给出的一或多个值(用编程行话来说,表达式)来产生另一个值(因而整个结构成为一个表达式)的东西. 运算符可按照其能接受几个值来分组.一元运算符只能接受一个值,例如 !(逻辑取反运 ...

  3. mysql逻辑运算符的优先顺序_PL/SQL运算符优先级

    运算符优先级决定表达式中术语的分组.这会影响表达式的评估求值顺序.某些运算符的优先级高于其他运算符; 例如,乘法运算符的优先级高于加法运算符. 例如,x = 7 + 3 * 2; 这里,求值结果x的值 ...

  4. JS运算符优先级规则

    JScript 中的运算符优先级是一套规则.该规则在计算表达式时控制运算符执行的顺序.具有较高优先级的运算符先于较低优先级的运算符执行.例如,乘法的执行先于加法. 下表按从最高到最低的优先级列出 JS ...

  5. Java解析SQL生成语法树_Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python...

    Atitit.sql ast 表达式 语法树 语法 解析原理与实现java php c#.net js python 1.1.Sql语法树ast如下图锁死 2.SQL语句解析的思路和过程 2.1.le ...

  6. oracle 计算 符号优先级,oracle 表达式运算符优先级

    oracle 有以下几种运算符 算数运算符 连接运算符 比较(关系)运算符 逻辑运算符 1.算数运算符 算数运算符有四个, + , - ,* ,/. SELECT sal,sal*12 from em ...

  7. c语言表达式的计算优先级,C语言运算符优先级(超详细)

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 C语言运算符优先级(超详细) 当想找哪个运算符优先级高时,很多时候总是想找的就没有,真让人气愤!现在,终于有个我个人觉得非常全的,分享给大家,欢迎拍砖! ...

  8. Like Sunday, Like Rain - JavaScript运算符优先级

    "JavaScript⾥的很多奇技淫巧,都来⾃于对运算符的灵活使⽤." 说到运算符的优先级,我们往往会想到一张见过无数次却从来没背下来的表.因为没背下来, 所以往往会认为它很简单, ...

  9. 淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树

    http://blog.csdn.net/qq910894904/article/details/28658421 OceanBase是阿里巴巴集团自主研发的可扩展的关系型数据库,实现了跨行跨表的事务 ...

最新文章

  1. Android技术点增长 - 收藏集 - 掘金
  2. SSL_TLS快速扫描器SSLScan常用命令集合大学霸IT达人
  3. 本地代码替换到某个提交点_github教程 -- Windows平台下如何将项目华丽的提交到Github上...
  4. PFH和FPFH详解
  5. 看了这一篇,就不用看别的——Java中Object关于锁的的三个方法:wait,notify,notifyAll的作用
  6. JSON.parse()和JSON.stringify()的区别
  7. 使用SAP CRM中间件XIF(External Interface)一步步创建服务订单
  8. 配置CentOS6.3 NFS
  9. linux添加网络节点,Pi网络节点配置教程(windows10专业版)
  10. DataSet用法详细 转
  11. 超详细使用VirtualBox安装虚拟机
  12. 电脑怎么录制玩王者荣耀的过程
  13. linux polkitd 漏洞,Ubuntu Linux中的特权提升漏洞Dirty Sock分析(含PoC)
  14. 自学Python第二十天- MongoDB 库
  15. Nginx学习八:虚拟主机
  16. tcp 如何维护长连接
  17. 江淮汽车回应为小米代工;腾讯起诉腾迅获赔 30 万;鸿星尔克因公司系统崩溃、恳请顾客退款 | EA周报...
  18. 计算机十一月份成绩,CPU天梯图2018年11月最新版 十一月台式电脑CPU性能排行
  19. 创联群控系统实时投屏一人操作N台手机
  20. 中国经济增长预期上调至8.5%;亚马逊药店提供常见处方药;中国外汇储备激增236亿美元…| 洞悉跨境

热门文章

  1. spdep | 最小生成树
  2. react在组件内插入标签_javascript – 如何将css类添加到react中的组件?
  3. 24岁大专非科班转行前端开发可行性有多少?
  4. 二维数组初始化为0的方式
  5. 面试问php学得怎么样,php面试常问的问题及回答
  6. 华为魔术手机拆机图解_华为荣耀20进水不开机
  7. 12306一直提示网络有问题_春运攻略:西安网友买票遇到的这些问题,这里有答案...
  8. yocto添加登录用户和密码
  9. 3Dslicer +VS2013编译介绍
  10. 诺基亚挥别Qt,转手给Digia