根据运算符优先级解析SQL规则表达式
正文
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()) { // 空栈返回true return 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; else return false; } case '/': { if (top == '+' || top == '-') // 优先级比+和-高 return true; else return 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转化成String if (!(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的临时结果集 //有序Map LinkedHashMap<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()) { // 空栈返回ture return true; } // 符号优先级说明(从高到低): // 第1级: ( // 第2级: $ // 第3级: | // 第4级: ) char top = (char) symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈 if (top == '(') { return true; } // 比较优先级 switch (symbol) { case '(': // 优先级最高 return true; case '&': { if (top == '|') // 优先级比|高 return true; else return false; } case '|': { return false; } case ')': // 优先级最低 return false; case '#': // 结束符 return false; default: break; } return true; } //List转成String public 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); //移除使用过的Set tempMap.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的value public 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; } @Test public 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=18895358020 String 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的临时结果集 //有序Map HashMap<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()) { // 空栈返回ture return true; } // 符号优先级说明(从高到低): // 第1级: ( // 第2级: $ // 第3级: | // 第4级: ) char top = symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈 if (top == '(') { return true; } // 比较优先级 switch (symbol) { case '(': // 优先级最高 return true; case '&': { if (top == '|') // 优先级比|高 return true; else return false; } case '|': { return false; } case ')': // 优先级最低 return false; case '#': // 结束符 return false; default: break; } return true; } //List转成String public 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<=6 String 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); } }
所需要的依赖包在这里